Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
emoji reaction
Browse files- prisma/dev.db +0 -0
- src/lib/components/community/Card.svelte +1 -5
- src/lib/components/community/reactions/Add.svelte +29 -2
- src/lib/components/community/reactions/Reaction.svelte +21 -2
- src/lib/components/community/reactions/Reactions.svelte +28 -3
- src/lib/utils/index.ts +1 -1
- src/routes/+layout.svelte +1 -1
- src/routes/api/community/reaction/+server.ts +94 -0
prisma/dev.db
CHANGED
Binary files a/prisma/dev.db and b/prisma/dev.db differ
|
|
src/lib/components/community/Card.svelte
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
<script lang="ts">
|
2 |
-
import Add from "$lib/components/community/reactions/Add.svelte";
|
3 |
import type { CommunityCard } from "$lib/type";
|
4 |
import Reactions from "./reactions/Reactions.svelte";
|
5 |
|
@@ -17,9 +16,6 @@
|
|
17 |
<p class="text-white/75 font-regular text-sm">{card.model.id}</p>
|
18 |
</div>
|
19 |
<div class="flex items-center justify-start gap-2">
|
20 |
-
{
|
21 |
-
<Reactions reactions={card.reactions} />
|
22 |
-
{/if}
|
23 |
-
<Add count={card?.reactions?.length} />
|
24 |
</div>
|
25 |
</div>
|
|
|
1 |
<script lang="ts">
|
|
|
2 |
import type { CommunityCard } from "$lib/type";
|
3 |
import Reactions from "./reactions/Reactions.svelte";
|
4 |
|
|
|
16 |
<p class="text-white/75 font-regular text-sm">{card.model.id}</p>
|
17 |
</div>
|
18 |
<div class="flex items-center justify-start gap-2">
|
19 |
+
<Reactions reactions={card.reactions} gallery_id={card.id} />
|
|
|
|
|
|
|
20 |
</div>
|
21 |
</div>
|
src/lib/components/community/reactions/Add.svelte
CHANGED
@@ -4,6 +4,9 @@
|
|
4 |
import { REACTION_EMOJIS } from "$lib/utils";
|
5 |
|
6 |
export let count: number;
|
|
|
|
|
|
|
7 |
|
8 |
let isOpen: boolean = false;
|
9 |
$: uuid = Math.random().toString(36).substring(7);
|
@@ -20,6 +23,24 @@
|
|
20 |
if (browser) {
|
21 |
window.addEventListener("click", handleClick);
|
22 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
</script>
|
24 |
|
25 |
<div
|
@@ -27,6 +48,7 @@
|
|
27 |
class="rounded-full text-sm text-white/80 hover:text-white font-bold flex items-center justify-start gap-1.5 px-1.5 py-1 border border-dashed border-white/80 hover:border-white relative z-[2]"
|
28 |
class:!border-white={isOpen}
|
29 |
class:!text-white={isOpen}
|
|
|
30 |
>
|
31 |
<button on:click={() => isOpen = !isOpen}>
|
32 |
<Icon icon="fluent:emoji-add-16-regular" class="w-5 h-5" />
|
@@ -36,8 +58,13 @@
|
|
36 |
class:opacity-100={isOpen}
|
37 |
class:pointer-events-auto={isOpen}
|
38 |
>
|
39 |
-
{#each
|
40 |
-
<
|
|
|
|
|
|
|
|
|
|
|
41 |
{/each}
|
42 |
</div>
|
43 |
</div>
|
|
|
4 |
import { REACTION_EMOJIS } from "$lib/utils";
|
5 |
|
6 |
export let count: number;
|
7 |
+
export let reactions: Array<{ emoji: string, count: number }> = [];
|
8 |
+
export let gallery_id: string;
|
9 |
+
export let onAdd: (emoji: string, id: string) => void;
|
10 |
|
11 |
let isOpen: boolean = false;
|
12 |
$: uuid = Math.random().toString(36).substring(7);
|
|
|
23 |
if (browser) {
|
24 |
window.addEventListener("click", handleClick);
|
25 |
}
|
26 |
+
|
27 |
+
const handleReaction = async (emoji: string) => {
|
28 |
+
await fetch(`/api/community/reaction`, {
|
29 |
+
method: "POST",
|
30 |
+
body: JSON.stringify({ emoji, gallery_id }),
|
31 |
+
headers: {
|
32 |
+
"Content-Type": "application/json",
|
33 |
+
},
|
34 |
+
})
|
35 |
+
.then(res => res.json())
|
36 |
+
.then(data => {
|
37 |
+
if (!data.delete) {
|
38 |
+
onAdd(emoji, data.id)
|
39 |
+
}
|
40 |
+
})
|
41 |
+
}
|
42 |
+
|
43 |
+
$: AVAILABLE_EMOJIS = REACTION_EMOJIS.filter(e => !reactions.find(emj => emj.emoji === e));
|
44 |
</script>
|
45 |
|
46 |
<div
|
|
|
48 |
class="rounded-full text-sm text-white/80 hover:text-white font-bold flex items-center justify-start gap-1.5 px-1.5 py-1 border border-dashed border-white/80 hover:border-white relative z-[2]"
|
49 |
class:!border-white={isOpen}
|
50 |
class:!text-white={isOpen}
|
51 |
+
class:opacity-0={count >= 4}
|
52 |
>
|
53 |
<button on:click={() => isOpen = !isOpen}>
|
54 |
<Icon icon="fluent:emoji-add-16-regular" class="w-5 h-5" />
|
|
|
58 |
class:opacity-100={isOpen}
|
59 |
class:pointer-events-auto={isOpen}
|
60 |
>
|
61 |
+
{#each AVAILABLE_EMOJIS as emoji}
|
62 |
+
<button
|
63 |
+
class="w-8 h-8 hover:bg-neutral-200 rounded-full text-center flex items-center justify-center"
|
64 |
+
on:click={() => handleReaction(emoji)}
|
65 |
+
>
|
66 |
+
{emoji}
|
67 |
+
</button>
|
68 |
{/each}
|
69 |
</div>
|
70 |
</div>
|
src/lib/components/community/reactions/Reaction.svelte
CHANGED
@@ -1,9 +1,28 @@
|
|
1 |
<script lang="ts">
|
2 |
export let emoji: string;
|
3 |
export let count: number;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
</script>
|
5 |
|
6 |
-
<
|
|
|
|
|
|
|
7 |
<span>{emoji}</span>
|
8 |
{count}
|
9 |
-
</
|
|
|
1 |
<script lang="ts">
|
2 |
export let emoji: string;
|
3 |
export let count: number;
|
4 |
+
export let gallery_id: string;
|
5 |
+
export let onReact: (emoji: string, id: string, deleted: boolean) => void;
|
6 |
+
|
7 |
+
const handleReaction = async (emoji: string) => {
|
8 |
+
await fetch(`/api/community/reaction`, {
|
9 |
+
method: "POST",
|
10 |
+
body: JSON.stringify({ emoji, gallery_id }),
|
11 |
+
headers: {
|
12 |
+
"Content-Type": "application/json",
|
13 |
+
},
|
14 |
+
})
|
15 |
+
.then(res => res.json())
|
16 |
+
.then(data => {
|
17 |
+
onReact(emoji, data.id, data.delete);
|
18 |
+
})
|
19 |
+
}
|
20 |
</script>
|
21 |
|
22 |
+
<button
|
23 |
+
class="rounded-full bg-white text-sm text-neutral-800 font-bold flex items-center justify-start gap-1.5 px-2.5 py-1 border border-white hover:bg-neutral-200"
|
24 |
+
on:click={() => handleReaction(emoji)}
|
25 |
+
>
|
26 |
<span>{emoji}</span>
|
27 |
{count}
|
28 |
+
</button>
|
src/lib/components/community/reactions/Reactions.svelte
CHANGED
@@ -1,8 +1,14 @@
|
|
1 |
<script lang="ts">
|
2 |
import type { ReactionType } from "$lib/type";
|
|
|
3 |
import Reaction from "$lib/components/community/reactions/Reaction.svelte";
|
|
|
|
|
|
|
|
|
4 |
|
5 |
export let reactions: ReactionType[] = [];
|
|
|
6 |
|
7 |
const groupReactionsByEmoji = (reactions: ReactionType[]) => {
|
8 |
const grouped = new Set(reactions.map((reaction) => reaction.emoji));
|
@@ -14,9 +20,28 @@
|
|
14 |
});
|
15 |
};
|
16 |
|
17 |
-
|
18 |
</script>
|
19 |
|
20 |
{#each groupedReactions as reaction}
|
21 |
-
<Reaction
|
22 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
<script lang="ts">
|
2 |
import type { ReactionType } from "$lib/type";
|
3 |
+
import Add from "$lib/components/community/reactions/Add.svelte";
|
4 |
import Reaction from "$lib/components/community/reactions/Reaction.svelte";
|
5 |
+
import { get } from 'svelte/store';
|
6 |
+
import { userStore } from "$lib/stores/use-user";
|
7 |
+
|
8 |
+
let user = get(userStore);
|
9 |
|
10 |
export let reactions: ReactionType[] = [];
|
11 |
+
export let gallery_id: string;
|
12 |
|
13 |
const groupReactionsByEmoji = (reactions: ReactionType[]) => {
|
14 |
const grouped = new Set(reactions.map((reaction) => reaction.emoji));
|
|
|
20 |
});
|
21 |
};
|
22 |
|
23 |
+
$: groupedReactions = groupReactionsByEmoji(reactions);
|
24 |
</script>
|
25 |
|
26 |
{#each groupedReactions as reaction}
|
27 |
+
<Reaction
|
28 |
+
emoji={reaction.emoji}
|
29 |
+
count={reaction?.count}
|
30 |
+
{gallery_id}
|
31 |
+
onReact={(emoji, id, deleted) => {
|
32 |
+
if (deleted) {
|
33 |
+
reactions = reactions.filter((reaction) => reaction.id !== id);
|
34 |
+
} else {
|
35 |
+
reactions = [...reactions, { emoji, hf_user_id: user?.sub, galleryId: gallery_id, id }];
|
36 |
+
}
|
37 |
+
}}
|
38 |
+
/>
|
39 |
+
{/each}
|
40 |
+
<Add
|
41 |
+
count={groupedReactions?.length}
|
42 |
+
reactions={groupedReactions}
|
43 |
+
{gallery_id}
|
44 |
+
onAdd={(emoji, id) => {
|
45 |
+
reactions = [...reactions, { emoji, hf_user_id: user?.sub, galleryId: gallery_id, id }];
|
46 |
+
}}
|
47 |
+
/>
|
src/lib/utils/index.ts
CHANGED
@@ -53,5 +53,5 @@ export const tokenIsAvailable = async (token: string) => {
|
|
53 |
})
|
54 |
|
55 |
const user = await userRequest.clone().json().catch(() => ({}));
|
56 |
-
return
|
57 |
}
|
|
|
53 |
})
|
54 |
|
55 |
const user = await userRequest.clone().json().catch(() => ({}));
|
56 |
+
return user?.sub ? user : null;
|
57 |
}
|
src/routes/+layout.svelte
CHANGED
@@ -4,7 +4,7 @@
|
|
4 |
import { userStore } from "$lib/stores/use-user";
|
5 |
|
6 |
export let data;
|
7 |
-
userStore.set(data
|
8 |
</script>
|
9 |
|
10 |
<div class="flex items-start">
|
|
|
4 |
import { userStore } from "$lib/stores/use-user";
|
5 |
|
6 |
export let data;
|
7 |
+
userStore.set(data?.user?.user);
|
8 |
</script>
|
9 |
|
10 |
<div class="flex items-start">
|
src/routes/api/community/reaction/+server.ts
ADDED
@@ -0,0 +1,94 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { json, type RequestEvent } from '@sveltejs/kit';
|
2 |
+
import { tokenIsAvailable } from '$lib/utils';
|
3 |
+
import { REACTION_EMOJIS } from "$lib/utils";
|
4 |
+
import prisma from '$lib/prisma';
|
5 |
+
|
6 |
+
/** @type {import('./$types').RequestHandler} */
|
7 |
+
|
8 |
+
export async function POST({ cookies, request } : RequestEvent) {
|
9 |
+
const token = cookies.get('hf_access_token')
|
10 |
+
if (!token) {
|
11 |
+
return json({
|
12 |
+
error: {
|
13 |
+
token: "You must be logged"
|
14 |
+
}
|
15 |
+
}, { status: 401 })
|
16 |
+
}
|
17 |
+
|
18 |
+
const is_token_available = await tokenIsAvailable(token)
|
19 |
+
if (!is_token_available) {
|
20 |
+
return json({
|
21 |
+
error: {
|
22 |
+
token: "Invalid token"
|
23 |
+
}
|
24 |
+
}, { status: 401 })
|
25 |
+
}
|
26 |
+
|
27 |
+
const { emoji, gallery_id } = await request.json()
|
28 |
+
|
29 |
+
if (!REACTION_EMOJIS.includes(emoji)) {
|
30 |
+
return json({
|
31 |
+
error: {
|
32 |
+
emoji: "Invalid emoji"
|
33 |
+
}
|
34 |
+
}, { status: 400 })
|
35 |
+
}
|
36 |
+
|
37 |
+
if (!gallery_id ) {
|
38 |
+
return json({
|
39 |
+
error: {
|
40 |
+
gallery_id: "Invalid gallery_id"
|
41 |
+
}
|
42 |
+
}, { status: 400 })
|
43 |
+
}
|
44 |
+
|
45 |
+
const gallery = await prisma.gallery.findUnique({
|
46 |
+
where: {
|
47 |
+
id: gallery_id
|
48 |
+
}
|
49 |
+
})
|
50 |
+
|
51 |
+
if (!gallery) {
|
52 |
+
return json({
|
53 |
+
error: {
|
54 |
+
gallery_id: "Gallery not found"
|
55 |
+
}
|
56 |
+
}, { status: 404 })
|
57 |
+
}
|
58 |
+
|
59 |
+
const reaction_exist = await prisma.reaction.findFirst({
|
60 |
+
where: {
|
61 |
+
galleryId: gallery_id,
|
62 |
+
hf_user_id: is_token_available.sub,
|
63 |
+
emoji
|
64 |
+
}
|
65 |
+
})
|
66 |
+
|
67 |
+
if (reaction_exist) {
|
68 |
+
await prisma.reaction.delete({
|
69 |
+
where: {
|
70 |
+
id: reaction_exist.id
|
71 |
+
}
|
72 |
+
})
|
73 |
+
|
74 |
+
return json({
|
75 |
+
success: true,
|
76 |
+
delete: true,
|
77 |
+
id: reaction_exist.id
|
78 |
+
})
|
79 |
+
}
|
80 |
+
|
81 |
+
const new_reaction = await prisma.reaction.create({
|
82 |
+
data: {
|
83 |
+
emoji,
|
84 |
+
galleryId: gallery_id,
|
85 |
+
hf_user_id: is_token_available.sub
|
86 |
+
}
|
87 |
+
})
|
88 |
+
|
89 |
+
return json({
|
90 |
+
success: true,
|
91 |
+
delete: false,
|
92 |
+
id: new_reaction.id
|
93 |
+
})
|
94 |
+
}
|