Skip to content

Commit

Permalink
Implement gallery
Browse files Browse the repository at this point in the history
  • Loading branch information
pbzweihander committed May 15, 2024
1 parent 5758be8 commit 2310c61
Show file tree
Hide file tree
Showing 18 changed files with 178 additions and 30 deletions.
8 changes: 6 additions & 2 deletions backend/src/ap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ pub mod tag;
pub mod undo;

pub fn generate_object_id() -> Result<Url, Error> {
Url::parse(&format!("https://{}/object/{}", CONFIG.domain, Ulid::new(),))
.context_internal_server_error("failed to construct object URL")
Url::parse(&format!(
"https://{}/object/{}",
CONFIG.public_domain,
Ulid::new(),
))
.context_internal_server_error("failed to construct object URL")
}

#[derive(Debug, Clone, Deserialize, Serialize)]
Expand Down
7 changes: 5 additions & 2 deletions backend/src/ap/follow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,11 @@ impl FollowReject {
object: Follow {
ty: Default::default(),
id: Some(
Url::parse(&format!("https://{}/follower/{}", CONFIG.domain, user_id))
.context_internal_server_error("failed to construct URL")?,
Url::parse(&format!(
"https://{}/follower/{}",
CONFIG.public_domain, user_id
))
.context_internal_server_error("failed to construct URL")?,
),
actor: user_uri,
object: LocalPerson::id(),
Expand Down
4 changes: 2 additions & 2 deletions backend/src/ap/person.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,15 @@ impl LocalPerson {

pub fn id() -> Url {
static ID: Lazy<Url> = Lazy::new(|| {
Url::parse(&format!("https://{}/person", CONFIG.domain))
Url::parse(&format!("https://{}/person", CONFIG.public_domain))
.expect("failed to construct ID URL")
});
ID.clone()
}

pub fn inbox() -> Url {
static INBOX: Lazy<Url> = Lazy::new(|| {
Url::parse(&format!("https://{}/inbox", CONFIG.domain))
Url::parse(&format!("https://{}/inbox", CONFIG.public_domain))
.expect("failed to construct inbox URL")
});
INBOX.clone()
Expand Down
9 changes: 7 additions & 2 deletions backend/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ fn default_debug() -> bool {
false
}

fn default_public_domain() -> String {
"localhost".to_string()
}

fn default_listen_addr() -> String {
"0.0.0.0:3000".to_string()
}
Expand Down Expand Up @@ -65,10 +69,11 @@ pub struct Config {
#[serde(default = "default_debug")]
pub debug: bool,

/// Domain of the instance.
/// Public domain of the instance.
/// DO NOT CHANGE!
/// e.g. `example.com`
pub domain: String,
#[serde(default = "default_public_domain")]
pub public_domain: String,

#[serde(default = "default_listen_addr")]
pub listen_addr: String,
Expand Down
9 changes: 6 additions & 3 deletions backend/src/entity_impl/emoji.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ use crate::{

impl emoji::Model {
pub fn ap_id(&self) -> Result<Url, Error> {
Url::parse(&format!("https://{}/emoji/{}", CONFIG.domain, self.name))
.context_internal_server_error("failed to construct follow URL ID")
Url::parse(&format!(
"https://{}/emoji/{}",
CONFIG.public_domain, self.name
))
.context_internal_server_error("failed to construct follow URL ID")
}

pub fn parse_ap_id(url: &str) -> Option<String> {
url.strip_prefix(&format!("https://{}/emoji/", CONFIG.domain))
url.strip_prefix(&format!("https://{}/emoji/", CONFIG.public_domain))
.map(str::to_string)
}
}
Expand Down
4 changes: 2 additions & 2 deletions backend/src/entity_impl/follow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,12 @@ impl follow::Model {
}

pub fn ap_id_from_id(id: Ulid) -> Result<Url, Error> {
Url::parse(&format!("https://{}/follow/{}", CONFIG.domain, id))
Url::parse(&format!("https://{}/follow/{}", CONFIG.public_domain, id))
.context_internal_server_error("failed to construct follow URL ID")
}

pub fn parse_ap_id(url: &str) -> Option<Ulid> {
url.strip_prefix(&format!("https://{}/follow/", CONFIG.domain))
url.strip_prefix(&format!("https://{}/follow/", CONFIG.public_domain))
.and_then(|id| Ulid::from_string(id).ok())
}
}
Expand Down
2 changes: 1 addition & 1 deletion backend/src/entity_impl/post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ fn calculate_visibility(to: &[Url], cc: &[Url]) -> sea_orm_active_enums::Visibil

impl post::Model {
pub fn ap_id_from_id(id: Ulid) -> Result<Url, Error> {
Url::parse(&format!("https://{}/note/{}", CONFIG.domain, id))
Url::parse(&format!("https://{}/note/{}", CONFIG.public_domain, id))
.context_internal_server_error("failed to construct follow URL ID")
}

Expand Down
2 changes: 1 addition & 1 deletion backend/src/entity_impl/reaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use crate::{

impl reaction::Model {
pub fn ap_id_from_id(id: Ulid) -> Result<Url, Error> {
Url::parse(&format!("https://{}/like/{}", CONFIG.domain, id))
Url::parse(&format!("https://{}/like/{}", CONFIG.public_domain, id))
.context_internal_server_error("failed to construct follow URL ID")
}

Expand Down
2 changes: 1 addition & 1 deletion backend/src/handler/well_known.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async fn get_nodeinfo() -> Result<Json<NodeInfoWellKnown>> {
links: vec![NodeInfoWellKnownLinks {
rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")
.context_internal_server_error("failed to construct URL")?,
href: Url::parse(&format!("https://{}/nodeinfo/2.0", CONFIG.domain,))
href: Url::parse(&format!("https://{}/nodeinfo/2.0", CONFIG.public_domain,))
.context_internal_server_error("failed to construct URL")?,
}],
}))
Expand Down
3 changes: 2 additions & 1 deletion backend/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ async fn main() -> anyhow::Result<()> {
if crate::config::CONFIG.debug {
tracing::warn!("Enabling debug mode... DO NOT USE IN PRODUCTION!");
}
tracing::info!("Using domain `{}`", crate::config::CONFIG.public_domain);

let db = Database::connect(crate::config::CONFIG.database_url.as_str())
.await
Expand All @@ -78,7 +79,7 @@ async fn main() -> anyhow::Result<()> {
.await
.context("failed to construct app state")?;
let federation_config = FederationConfig::builder()
.domain(&crate::config::CONFIG.domain)
.domain(&crate::config::CONFIG.public_domain)
.app_data(state.clone())
.debug(crate::config::CONFIG.debug)
.build()
Expand Down
2 changes: 1 addition & 1 deletion backend/src/object_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ impl ObjectStore {
.to_string_lossy()
.to_string(),
sea_orm_active_enums::ObjectStoreType::LocalFileSystem,
Url::parse(&format!("https://{}/file/{}", CONFIG.domain, key))
Url::parse(&format!("https://{}/file/{}", CONFIG.public_domain, key))
.context_internal_server_error("failed to construct public URL")?,
),
})
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/RightNav.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useIsAuthed } from "../queries/auth";
import { useSetting } from "../queries/setting";
import RightNavGallery from "./RightNavGallery";
import RightNavSetting from "./RightNavSetting";
import RightNavUpload from "./RightNavUpload";

Expand All @@ -14,9 +15,12 @@ export default function RightNav() {
<div className="mb-4">
{setting && <RightNavSetting setting={setting} />}
</div>
<div>
<div className="mb-4">
<RightNavUpload />
</div>
<div>
<RightNavGallery />
</div>
</>
)}
</div>
Expand Down
79 changes: 79 additions & 0 deletions frontend/src/components/RightNavGallery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { PhotoIcon, PlusIcon, TrashIcon } from "@heroicons/react/24/outline";
import { Fragment, useRef } from "react";

import { useLocalFileDeleteMutation, useLocalFiles } from "../queries/file";

export default function RightNavGallery() {
const modalRef = useRef<HTMLDialogElement>(null);

const { data, fetchNextPage, hasNextPage, isFetchingNextPage, remove } =
useLocalFiles();
const { mutate: deleteFile, isLoading: isDeleteLoading } =
useLocalFileDeleteMutation(() => {
remove();
});

return (
<>
<button
className="flex items-center"
onClick={() => {
modalRef?.current?.showModal();
}}
>
<PhotoIcon className="mr-3 inline h-10 w-10" />
<span className="text-lg">Gallery</span>
</button>
<dialog ref={modalRef} className="modal">
<div className="modal-box max-w-screen-xl">
<h2 className="mb-4 text-lg font-bold">Gallery</h2>
<div className="grid grid-cols-3 gap-4">
{(data?.pages ?? []).map((page, i) => (
<Fragment key={i}>
{page.map((file) => (
<div
key={file.id}
className="relative w-full overflow-y-hidden shadow-lg after:block after:pb-[100%] after:content-['']"
>
<img
className="absolute w-full"
src={file.url}
alt={file.alt ?? undefined}
/>
<button
className="btn btn-circle btn-error btn-sm absolute right-4 top-4"
disabled={isDeleteLoading}
onClick={() => {
deleteFile(file.id);
}}
>
<TrashIcon className="h-5 w-5" />
</button>
</div>
))}
</Fragment>
))}
</div>
<div className="mt-4 flex w-full justify-center">
{isFetchingNextPage ? (
<span className="loading loading-spinner loading-lg" />
) : (
hasNextPage && (
<button
onClick={() => {
fetchNextPage();
}}
>
<PlusIcon className="h-10 w-10" />
</button>
)
)}
</div>
</div>
<form method="dialog" className="modal-backdrop">
<button>close</button>
</form>
</dialog>
</>
);
}
1 change: 1 addition & 0 deletions frontend/src/components/RightNavSetting.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export default function RightNavSetting({
</button>
<dialog ref={modalRef} className="modal">
<div className="modal-box">
<h2 className="mb-4 text-lg font-bold">Settings</h2>
<form className="form-control" onSubmit={handleSubmit(onSubmit)}>
<label className="label label-text">User name</label>
<input
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/RightNavUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { PlusIcon } from "@heroicons/react/24/outline";
import { useRef } from "react";
import { SubmitHandler, useForm } from "react-hook-form";

import { useFileUploadMutation } from "../queries/file";
import { useLocalFileUploadMutation } from "../queries/file";

// import { PostFileQuery } from "../queries/file";

Expand All @@ -13,12 +13,13 @@ interface UploadForm {

export default function RightNavUpload() {
const modalRef = useRef<HTMLDialogElement>(null);
const { register, handleSubmit } = useForm<UploadForm>();
const { register, handleSubmit, reset } = useForm<UploadForm>();
const {
mutate: upload,
isLoading,
error,
} = useFileUploadMutation(() => {
} = useLocalFileUploadMutation(() => {
reset();
modalRef.current?.close();
});

Expand All @@ -44,6 +45,7 @@ export default function RightNavUpload() {
</button>
<dialog ref={modalRef} className="modal">
<div className="modal-box">
<h2 className="mb-4 text-lg font-bold">Upload</h2>
<form className="form-control" onSubmit={handleSubmit(onSubmit)}>
<input
type="file"
Expand Down
1 change: 1 addition & 0 deletions frontend/src/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const Mention = z.object({
});

export const File = z.object({
id: Id,
mediaType: z.string(),
url: z.string().url(),
alt: z.string().nullish(),
Expand Down
57 changes: 51 additions & 6 deletions frontend/src/queries/file.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
import { useContext } from "react";
import { useMutation } from "react-query";
import { useMutation, useQueryClient } from "react-query";
import * as z from "zod";

import { MutationRet } from ".";
import { MutationRet, useInfiniteJsonQuery } from ".";
import { AccessKeyContext } from "../contexts/auth";
import { throwError } from "../dto";
import { throwError, LocalFile, Id } from "../dto";

export interface PostFileQuery {
const FILES_KEY = ["notes"];

export function useLocalFiles() {
const params = new URLSearchParams();
params.set("size", "9");
return useInfiniteJsonQuery(
z.array(LocalFile),
FILES_KEY,
"/api/file",
params,
);
}

export interface PostLocalFileQuery {
file: File;
mediaType: string;
alt?: string;
}

export function useFileUploadMutation(
export function useLocalFileUploadMutation(
onSuccess: () => void,
): MutationRet<PostFileQuery> {
): MutationRet<PostLocalFileQuery> {
const queryClient = useQueryClient();
const [accessKey] = useContext(AccessKeyContext);

return useMutation(
Expand All @@ -40,6 +55,36 @@ export function useFileUploadMutation(
},
{
onSuccess: () => {
queryClient.invalidateQueries(FILES_KEY);
onSuccess();
},
},
);
}

export function useLocalFileDeleteMutation(
onSuccess: () => void,
): MutationRet<z.infer<typeof Id>> {
const queryClient = useQueryClient();
const [accessKey] = useContext(AccessKeyContext);

return useMutation(
async (id) => {
const headers: HeadersInit = {};
if (accessKey.length > 0) {
headers["authorization"] = `Bearer ${accessKey}`;
}
const resp = await fetch(`/api/file/${id}`, {
method: "DELETE",
headers,
});
if (!resp.ok) {
await throwError(resp);
}
},
{
onSuccess: () => {
queryClient.invalidateQueries(FILES_KEY);
onSuccess();
},
},
Expand Down
Loading

0 comments on commit 2310c61

Please sign in to comment.