Skip to content

Commit

Permalink
feat: ✨ implement subscription to show new suggested tags and popover…
Browse files Browse the repository at this point in the history
… when generated by tagger
  • Loading branch information
Dan6erbond committed May 13, 2023
1 parent 37c9593 commit 6bf5157
Show file tree
Hide file tree
Showing 2 changed files with 193 additions and 114 deletions.
16 changes: 16 additions & 0 deletions src/hooks/useRefCallback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { DependencyList, useCallback, useEffect, useRef } from "react";

export const useRefCallback = <T extends Function>(
callback: T,
deps: DependencyList
) => {
// eslint-disable-next-line react-hooks/exhaustive-deps
const func = useCallback(callback, deps);
const ref = useRef(func);

useEffect(() => {
ref.current = func;
}, [func]);

return ref;
};
291 changes: 177 additions & 114 deletions src/pages/posts/[id].tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CommaMultiSelect from "@/components/commaMultiSelect";
import Dropzone from "@/components/dropzone";
import Head from "@/components/head";
import Layout from "@/components/layout";
Expand All @@ -21,8 +22,8 @@ import {
Image,
Loader,
MediaQuery,
MultiSelect,
Paper,
Popover,
Stack,
Switch,
Text,
Expand All @@ -40,9 +41,9 @@ import {
import { GetServerSideProps } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import { Record } from "pocketbase";
import { Record, RecordSubscription } from "pocketbase";
import { useCallback, useEffect, useState } from "react";
import CommaMultiSelect from "../../components/commaMultiSelect";
import { useRefCallback } from "../../hooks/useRefCallback";

interface PostProps extends ShareMeEnv {
title: string;
Expand Down Expand Up @@ -72,6 +73,8 @@ export default function Post(props: PostProps) {

const [blurred, setBlurred] = useState<boolean[]>([]);
const [tagsSuggestions, setTagsSuggestions] = useState<string[]>([]);
const [showTagsPopover, setShowTagsPopover] = useState(false);
const [showedTagsPopover, setShowedTagsPopover] = useState(false);

const [debouncedTitle] = useDebouncedValue(title, 200, { leading: true });

Expand All @@ -88,7 +91,6 @@ export default function Post(props: PostProps) {
} else {
f = setter;
}

_setFiles(f);

setTagsSuggestions(
Expand Down Expand Up @@ -159,7 +161,7 @@ export default function Post(props: PostProps) {
const setValues = useCallback(
(record: Post) => {
setPost(record);
setFiles((files) => (record.expand.files as ShareMeFile[]) || files);
record.expand.files && setFiles(record.expand.files as ShareMeFile[]);
setTitle(record.title);
setIsPublic(record.public);
setNsfw(record.nsfw);
Expand All @@ -178,22 +180,58 @@ export default function Post(props: PostProps) {
]
);

const fetchPost = useCallback(async () => {
if (id !== post?.id) {
try {
const record = await pb
.collection("posts")
.getOne<Post>(Array.isArray(id) ? id[0] : id!, {
const fileSubscription = useRefCallback(
(e: RecordSubscription<ShareMeFile>) => {
if (e.action === "create") return;

setFiles((files) => {
if (
(e.record.tagsSuggestions ?? []).length > 0 &&
!e.record.tagsSuggestions.every((s: string) =>
tagsSuggestions.includes(s)
)
) {
if (!showedTagsPopover) {
setShowTagsPopover(true);
setTimeout(() => setShowTagsPopover(false), 3 * 1000);
setShowedTagsPopover(true);
}
}

return files.map((f) => (f.id === e.record.id ? e.record : f));
});
},
[
setFiles,
setShowTagsPopover,
tagsSuggestions,
showedTagsPopover,
setShowedTagsPopover,
]
);

useEffect(() => {
id &&
id !== post?.id &&
(async () => {
try {
const _id = Array.isArray(id) ? id[0] : id!;
const record = await pb.collection("posts").getOne<Post>(_id, {
expand: "files,author",
});
setValues(record);
} catch {}
}
}, [setValues, pb, post, id]);
setValues(record);

useEffect(() => {
id && fetchPost();
}, [id, fetchPost]);
pb.collection("files").subscribe<ShareMeFile>("*", (e) =>
fileSubscription.current(e)
);
} catch {}
})();

return () => {
post?.id &&
pb.collection("posts").unsubscribe(post?.id).catch(console.error);
};
}, [id, setValues, pb, post, tags, files, setFiles, fileSubscription]);

const deleteFile = async (id: string) => {
const record = await pb.collection("posts").update<Post>(
Expand Down Expand Up @@ -286,7 +324,7 @@ export default function Post(props: PostProps) {
: user?.id && createPost(files)
}
>
<Group sx={{ justifyContent: "center" }} align="start">
<Group sx={{ justifyContent: "center" }} align="start" px="md" grow>
<Stack maw="650px" miw="350px" sx={{ flex: 1, flexGrow: 1 }} px="md">
{userIsAuthor ||
(post?.expand.author && (
Expand Down Expand Up @@ -448,108 +486,133 @@ export default function Post(props: PostProps) {
)}
{userIsAuthor && <Dropzone onDrop={uploadFiles} />}
</Stack>
<Stack h="100%">
<Paper bg="dark.6" p="lg" withBorder miw="200px">
<Stack>
{userIsAuthor && (
<Switch
label="Public"
labelPosition="left"
checked={isPublic}
onChange={(e) => setIsPublic(e.target.checked)}
/>
<Paper
bg="dark.6"
p="lg"
withBorder
sx={{ flex: 1 }}
maw={320}
pos="sticky"
top={92}
>
<Stack>
<CopyButton
value={
typeof window !== "undefined" ? window.location.href : ""
}
>
{({ copy, copied }) => (
<Button
variant="gradient"
onClick={copy}
color={copied ? "teal" : "blue"}
>
{copied ? "Copied" : "Copy Link"}
</Button>
)}
<CopyButton
value={
typeof window !== "undefined" ? window.location.href : ""
}
>
{({ copy, copied }) => (
<Button
variant="gradient"
onClick={copy}
color={copied ? "teal" : "blue"}
>
{copied ? "Copied" : "Copy Link"}
</Button>
)}
</CopyButton>
{userIsAuthor && (
<>
</CopyButton>
{userIsAuthor && (
<>
<Group>
<Switch
label="Public"
checked={isPublic}
onChange={(e) => setIsPublic(e.target.checked)}
/>
<Checkbox
label="NSFW"
checked={nsfw}
onChange={(e) => setNsfw(e.target.checked)}
/>
<CommaMultiSelect
data={tagsMultiSelectData}
label="Tags"
placeholder="Select or add your own"
maw={350}
value={tags}
onChange={setTags}
setData={setTagsMultiSelectData}
/>
{tagsSuggestions.filter((t) => !tags.includes(t)).length >
0 && (
<>
<Text color="dimmed">Suggestions</Text>
<Group maw={350}>
{tagsSuggestions
.filter((t) => !tags.includes(t))
.map((t) => (
<Badge
key={t}
rightSection={<IconPlus size={14} />}
onClick={() => setTags((tags) => [...tags, t])}
styles={(theme) => ({
rightSection: {
display: "flex",
alignItems: "center",
},
root: {
cursor: "pointer",
":hover": {
background: theme.fn.rgba(
theme.colors.blue[4],
0.3
),
},
</Group>
<CommaMultiSelect
data={tagsMultiSelectData}
label="Tags"
placeholder="Select or add your own"
value={tags}
onChange={setTags}
setData={setTagsMultiSelectData}
/>
{tagsSuggestions.filter((t) => !tags.includes(t)).length >
0 && (
<Box>
<Popover
width={250}
position="right"
withArrow
shadow="md"
opened={showTagsPopover}
>
<Popover.Target>
<Text
color="dimmed"
display="inline-block"
mb="xs"
w="auto"
>
Suggestions
</Text>
</Popover.Target>
<Popover.Dropdown>
✨ You can add suggested tags based on AI
</Popover.Dropdown>
</Popover>
<Group>
{tagsSuggestions
.filter((t) => !tags.includes(t))
.map((t) => (
<Badge
key={t}
rightSection={<IconPlus size={14} />}
onClick={() => setTags((tags) => [...tags, t])}
styles={(theme) => ({
rightSection: {
display: "flex",
alignItems: "center",
},
root: {
cursor: "pointer",
":hover": {
background: theme.fn.rgba(
theme.colors.blue[4],
0.3
),
},
})}
>
{t}
</Badge>
))}
</Group>
</>
)}
<Button
variant="gradient"
color="red"
sx={(theme) => ({
background: theme.fn.linearGradient(
45,
theme.colors.red[6],
theme.colors.pink[5]
),
})}
onClick={() => {
post &&
pb
.collection("posts")
.delete(post.id)
.then(() => router.push("/"))
.catch((ex) => console.error(ex));
}}
>
Delete
</Button>
</>
)}
</Stack>
</Paper>
</Stack>
},
})}
>
{t}
</Badge>
))}
</Group>
</Box>
)}

<Button
variant="gradient"
color="red"
sx={(theme) => ({
background: theme.fn.linearGradient(
45,
theme.colors.red[6],
theme.colors.pink[5]
),
})}
onClick={() => {
post &&
pb
.collection("posts")
.delete(post.id)
.then(() => router.push("/"))
.catch((ex) => console.error(ex));
}}
>
Delete
</Button>
</>
)}
</Stack>
</Paper>
</Group>
</Layout>
</>
Expand Down

0 comments on commit 6bf5157

Please sign in to comment.