Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
gallery comment
Browse files- prisma/schema.prisma +1 -1
- src/lib/components/{models/drawer/comments β comments}/Comment.svelte +0 -0
- src/lib/components/community/drawer/Comments.svelte +65 -0
- src/lib/components/community/drawer/Drawer.svelte +112 -0
- src/lib/components/models/drawer/Drawer.svelte +1 -1
- src/lib/components/models/drawer/comments/Comments.svelte +27 -29
- src/lib/type.ts +1 -0
- src/routes/api/community/[id]/+server.ts +16 -0
- src/routes/api/community/[id]/comments/+server.ts +89 -0
- src/routes/api/models/[id]/comments/+server.ts +1 -0
- src/routes/gallery/+page.svelte +4 -2
prisma/schema.prisma
CHANGED
@@ -35,7 +35,7 @@ model Gallery {
|
|
35 |
modelId String
|
36 |
user User? @relation(fields: [userId], references: [sub])
|
37 |
userId String?
|
38 |
-
Comment
|
39 |
}
|
40 |
|
41 |
model Reaction {
|
|
|
35 |
modelId String
|
36 |
user User? @relation(fields: [userId], references: [sub])
|
37 |
userId String?
|
38 |
+
comments Comment[]
|
39 |
}
|
40 |
|
41 |
model Reaction {
|
src/lib/components/{models/drawer/comments β comments}/Comment.svelte
RENAMED
File without changes
|
src/lib/components/community/drawer/Comments.svelte
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { error } from "$lib/utils/toaster";
|
3 |
+
|
4 |
+
import Button from "$lib/components/Button.svelte";
|
5 |
+
import type { CommentType, CommunityCard } from "$lib/type";
|
6 |
+
import Comment from "$lib/components/comments/Comment.svelte";
|
7 |
+
|
8 |
+
export let comments: CommentType[] = [];
|
9 |
+
export let gallery: CommunityCard;
|
10 |
+
|
11 |
+
let text = "";
|
12 |
+
let loading = false;
|
13 |
+
|
14 |
+
const handleSubmit = async () => {
|
15 |
+
loading = true;
|
16 |
+
const comment_request = await fetch(`/api/community/${gallery?.id}/comments`, {
|
17 |
+
method: "POST",
|
18 |
+
headers: {
|
19 |
+
"Content-Type": "application/json",
|
20 |
+
},
|
21 |
+
body: JSON.stringify({ text }),
|
22 |
+
});
|
23 |
+
|
24 |
+
const comment_response = await comment_request.json();
|
25 |
+
if (comment_response.error) {
|
26 |
+
error(comment_response.error)
|
27 |
+
} else {
|
28 |
+
comments = [comment_response.comment, ...comments];
|
29 |
+
text = "";
|
30 |
+
}
|
31 |
+
loading = false;
|
32 |
+
}
|
33 |
+
|
34 |
+
const handleChange = async (event: any) => {
|
35 |
+
text = event.target.value;
|
36 |
+
}
|
37 |
+
</script>
|
38 |
+
|
39 |
+
<div class="grid grid-cols-1 gap-3 overflow-auto h-full max-h-[300px]">
|
40 |
+
{#if comments?.length === 0}
|
41 |
+
<p class="text-neutral-500 text-sm">No comments yet! Be the first one!</p>
|
42 |
+
{/if}
|
43 |
+
{#each comments as comment}
|
44 |
+
<Comment comment={comment} />
|
45 |
+
{/each}
|
46 |
+
</div>
|
47 |
+
<div class="flex gap-4 items-start justify-between flex-col lg:flex-row mt-7">
|
48 |
+
<textarea
|
49 |
+
value={text}
|
50 |
+
class="rounded-xl bg-neutral-900 text-neutral-200 text-base placeholder:text-neutral-500 outline-none resize-none p-4 w-full"
|
51 |
+
placeholder="Write a comment..."
|
52 |
+
on:input={handleChange}
|
53 |
+
/>
|
54 |
+
<Button
|
55 |
+
theme="blue"
|
56 |
+
size="md"
|
57 |
+
icon="carbon:send-alt-filled"
|
58 |
+
iconPosition="right"
|
59 |
+
loading={loading}
|
60 |
+
disabled={text.length < 3}
|
61 |
+
onClick={handleSubmit}
|
62 |
+
>
|
63 |
+
Post
|
64 |
+
</Button>
|
65 |
+
</div>
|
src/lib/components/community/drawer/Drawer.svelte
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
import { clickoutside } from '@svelte-put/clickoutside';
|
3 |
+
import { goto, invalidate } from "$app/navigation";
|
4 |
+
import { page } from "$app/stores";
|
5 |
+
import { get } from "svelte/store";
|
6 |
+
import Icon from "@iconify/svelte";
|
7 |
+
|
8 |
+
import { galleryStore } from "$lib/stores/use-gallery";
|
9 |
+
import UserIsLogged from '$lib/components/UserIsLogged.svelte';
|
10 |
+
import Comments from '$lib/components/community/drawer/Comments.svelte';
|
11 |
+
import Reactions from '$lib/components/community/reactions/Reactions.svelte';
|
12 |
+
|
13 |
+
export let form: Record<string, string> | undefined = undefined;
|
14 |
+
let { open, gallery, next, previous } = get(galleryStore);
|
15 |
+
|
16 |
+
galleryStore.subscribe((value) => {
|
17 |
+
open = value?.open;
|
18 |
+
gallery = value?.gallery;
|
19 |
+
next = value?.next;
|
20 |
+
previous = value?.previous;
|
21 |
+
});
|
22 |
+
|
23 |
+
const handleClose = async () => {
|
24 |
+
galleryStore.update((value) => {
|
25 |
+
return {
|
26 |
+
...value,
|
27 |
+
open: false,
|
28 |
+
};
|
29 |
+
});
|
30 |
+
|
31 |
+
$page.url.searchParams.delete('model');
|
32 |
+
await goto(`?${$page.url.searchParams.toString()}`);
|
33 |
+
await invalidate(url => url.pathname.includes("/api/models"));
|
34 |
+
};
|
35 |
+
|
36 |
+
const handleClickNext = () => {
|
37 |
+
const element = document.getElementById('gallery_examples');
|
38 |
+
element?.scrollBy({
|
39 |
+
left: 300,
|
40 |
+
behavior: 'smooth'
|
41 |
+
});
|
42 |
+
}
|
43 |
+
const handlePressEscape = (event: KeyboardEvent) => {
|
44 |
+
if (event.key === 'Escape') {
|
45 |
+
handleClose();
|
46 |
+
}
|
47 |
+
};
|
48 |
+
</script>
|
49 |
+
|
50 |
+
<div
|
51 |
+
class="w-full fixed top-0 left-0 h-full bg-black bg-opacity-50 z-0 backdrop-blur transition-all duration-100"
|
52 |
+
class:opacity-0={!open}
|
53 |
+
class:pointer-events-none={!open}
|
54 |
+
class:!z-40={open}
|
55 |
+
>
|
56 |
+
{#if open}
|
57 |
+
<div
|
58 |
+
class="ml-auto w-full max-w-3xl bg-neutral-950 h-full border-l border-neutral-800 transition-all duration-200 flex flex-col justify-between"
|
59 |
+
use:clickoutside on:clickoutside={handleClose}
|
60 |
+
>
|
61 |
+
{#if gallery?.id}
|
62 |
+
<div class="overflow-auto p-8">
|
63 |
+
<header class="mb-6 justify-between items-start flex">
|
64 |
+
<div class="flex items-center justify-start gap-4">
|
65 |
+
<img src={gallery?.user?.picture} class="w-12 h-12 rounded-full object-cover" alt={gallery?.user?.name} />
|
66 |
+
<div>
|
67 |
+
<p class="text-neutral-100 font-bold text-lg">
|
68 |
+
{gallery?.user?.name}
|
69 |
+
</p>
|
70 |
+
<p class="text-neutral-400 text-sm">
|
71 |
+
@{gallery?.user?.preferred_username}
|
72 |
+
</p>
|
73 |
+
</div>
|
74 |
+
</div>
|
75 |
+
<button on:click={handleClose}>
|
76 |
+
<Icon icon="carbon:close" class="w-6 h-6 text-white cursor-pointer" />
|
77 |
+
</button>
|
78 |
+
</header>
|
79 |
+
<main class="w-full grid grid-cols-1 gap-5">
|
80 |
+
<div class="w-full">
|
81 |
+
<Reactions reactions={gallery?.reactions} gallery_id={gallery.id} />
|
82 |
+
</div>
|
83 |
+
<img src="/api/images/{gallery?.image}" class="max-w-[450px] w-full h-[450px] bg-neutral-800 object-cover rounded-xl" alt={gallery?.prompt} />
|
84 |
+
<div class="px-2">
|
85 |
+
<p class="text-neutral-400 font-semibold text-xs uppercase">
|
86 |
+
Prompt
|
87 |
+
</p>
|
88 |
+
<p class="text-neutral-200 text-base font-medium mt-2">"{gallery?.prompt}"</p>
|
89 |
+
</div>
|
90 |
+
<div class="px-2">
|
91 |
+
<p class="text-neutral-400 font-semibold text-xs uppercase">
|
92 |
+
Dimension
|
93 |
+
</p>
|
94 |
+
<p class="text-neutral-200 text-base font-medium mt-2">1024x1024</p>
|
95 |
+
</div>
|
96 |
+
</main>
|
97 |
+
</div>
|
98 |
+
<footer class="p-8 border-t border-neutral-900 bg-neutral-900/30 flex flex-col justify-between">
|
99 |
+
<p class="font-semibold text-neutral-100 text-base lg:text-lg mb-6">
|
100 |
+
Commentaire{(gallery?.comments?.length ?? 0) > 1 ? 's' : ''} ({gallery?.comments?.length ?? 0})
|
101 |
+
</p>
|
102 |
+
{#if gallery?.id}
|
103 |
+
<UserIsLogged>
|
104 |
+
<Comments comments={gallery?.comments} gallery={gallery} />
|
105 |
+
</UserIsLogged>
|
106 |
+
{/if}
|
107 |
+
</footer>
|
108 |
+
{/if}
|
109 |
+
</div>
|
110 |
+
{/if}
|
111 |
+
</div>
|
112 |
+
<svelte:window on:keydown={handlePressEscape} />
|
src/lib/components/models/drawer/Drawer.svelte
CHANGED
@@ -148,7 +148,7 @@
|
|
148 |
{/if}
|
149 |
</main>
|
150 |
</div>
|
151 |
-
<footer class="p-8 border-t border-neutral-900 bg-neutral-900/30">
|
152 |
<p class="font-semibold text-neutral-100 text-base lg:text-lg mb-6">
|
153 |
Commentaire{(model?.comments?.length ?? 0) > 1 ? 's' : ''} ({model?.comments?.length ?? 0})
|
154 |
</p>
|
|
|
148 |
{/if}
|
149 |
</main>
|
150 |
</div>
|
151 |
+
<footer class="p-8 border-t border-neutral-900 bg-neutral-900/30 flex flex-col justify-between">
|
152 |
<p class="font-semibold text-neutral-100 text-base lg:text-lg mb-6">
|
153 |
Commentaire{(model?.comments?.length ?? 0) > 1 ? 's' : ''} ({model?.comments?.length ?? 0})
|
154 |
</p>
|
src/lib/components/models/drawer/comments/Comments.svelte
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
|
4 |
import Button from "$lib/components/Button.svelte";
|
5 |
import type { ModelCard, CommentType } from "$lib/type";
|
6 |
-
import Comment from "
|
7 |
|
8 |
export let comments: CommentType[] = [];
|
9 |
export let model: ModelCard;
|
@@ -36,32 +36,30 @@
|
|
36 |
}
|
37 |
</script>
|
38 |
|
39 |
-
<div>
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
<
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
</Button>
|
66 |
-
</div>
|
67 |
</div>
|
|
|
3 |
|
4 |
import Button from "$lib/components/Button.svelte";
|
5 |
import type { ModelCard, CommentType } from "$lib/type";
|
6 |
+
import Comment from "$lib/components/comments/Comment.svelte";
|
7 |
|
8 |
export let comments: CommentType[] = [];
|
9 |
export let model: ModelCard;
|
|
|
36 |
}
|
37 |
</script>
|
38 |
|
39 |
+
<div class="grid grid-cols-1 gap-3 overflow-auto h-full max-h-[300px]">
|
40 |
+
{#if comments?.length === 0}
|
41 |
+
<p class="text-neutral-500 text-sm">No comments yet! Be the first one!</p>
|
42 |
+
{/if}
|
43 |
+
{#each comments as comment}
|
44 |
+
<Comment comment={comment} />
|
45 |
+
{/each}
|
46 |
+
</div>
|
47 |
+
<div class="flex gap-4 items-start justify-between flex-col lg:flex-row mt-7">
|
48 |
+
<textarea
|
49 |
+
value={text}
|
50 |
+
class="rounded-xl bg-neutral-900 text-neutral-200 text-base placeholder:text-neutral-500 outline-none resize-none p-4 w-full"
|
51 |
+
placeholder="Write a comment..."
|
52 |
+
on:input={handleChange}
|
53 |
+
/>
|
54 |
+
<Button
|
55 |
+
theme="blue"
|
56 |
+
size="md"
|
57 |
+
icon="carbon:send-alt-filled"
|
58 |
+
iconPosition="right"
|
59 |
+
loading={loading}
|
60 |
+
disabled={text.length < 3}
|
61 |
+
onClick={handleSubmit}
|
62 |
+
>
|
63 |
+
Post
|
64 |
+
</Button>
|
|
|
|
|
65 |
</div>
|
src/lib/type.ts
CHANGED
@@ -13,6 +13,7 @@ export interface CommunityCard {
|
|
13 |
createdAt: Date,
|
14 |
user: UserType,
|
15 |
image: string,
|
|
|
16 |
}
|
17 |
|
18 |
export interface ModelCard {
|
|
|
13 |
createdAt: Date,
|
14 |
user: UserType,
|
15 |
image: string,
|
16 |
+
comments?: CommentType[],
|
17 |
}
|
18 |
|
19 |
export interface ModelCard {
|
src/routes/api/community/[id]/+server.ts
CHANGED
@@ -29,6 +29,22 @@ export async function GET({ params, url } : RequestEvent) {
|
|
29 |
preferred_username: true,
|
30 |
}
|
31 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
model: {
|
33 |
select: {
|
34 |
image: true,
|
|
|
29 |
preferred_username: true,
|
30 |
}
|
31 |
},
|
32 |
+
comments: {
|
33 |
+
select: {
|
34 |
+
id: true,
|
35 |
+
createdAt: true,
|
36 |
+
text: true,
|
37 |
+
user: {
|
38 |
+
select: {
|
39 |
+
id: true,
|
40 |
+
name: true,
|
41 |
+
sub: true,
|
42 |
+
picture: true,
|
43 |
+
preferred_username: true,
|
44 |
+
}
|
45 |
+
}
|
46 |
+
}
|
47 |
+
},
|
48 |
model: {
|
49 |
select: {
|
50 |
image: true,
|
src/routes/api/community/[id]/comments/+server.ts
ADDED
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { json, type RequestEvent } from '@sveltejs/kit';
|
2 |
+
import { tokenIsAvailable } from '$lib/utils';
|
3 |
+
import prisma from '$lib/prisma';
|
4 |
+
|
5 |
+
/** @type {import('./$types').RequestHandler} */
|
6 |
+
|
7 |
+
export async function POST({ cookies, request, params } : RequestEvent) {
|
8 |
+
const id = params.id;
|
9 |
+
|
10 |
+
const gallery = await prisma.gallery.findFirst({
|
11 |
+
where: {
|
12 |
+
id,
|
13 |
+
},
|
14 |
+
})
|
15 |
+
if (!gallery) {
|
16 |
+
return json({
|
17 |
+
error: "Gallery not found",
|
18 |
+
}, { status: 404 })
|
19 |
+
}
|
20 |
+
|
21 |
+
const token = cookies.get('hf_access_token')
|
22 |
+
if (!token) {
|
23 |
+
return json({
|
24 |
+
error: "You must be logged",
|
25 |
+
}, { status: 401 })
|
26 |
+
}
|
27 |
+
|
28 |
+
const is_token_available = await tokenIsAvailable(token)
|
29 |
+
if (!is_token_available) {
|
30 |
+
return json({
|
31 |
+
error: "Invalid token",
|
32 |
+
}, { status: 401 })
|
33 |
+
}
|
34 |
+
|
35 |
+
// check if user has already comment this model twice
|
36 |
+
const total_comments = await prisma.comment.count({
|
37 |
+
where: {
|
38 |
+
galleryId: gallery.id,
|
39 |
+
userId: is_token_available.sub,
|
40 |
+
}
|
41 |
+
})
|
42 |
+
|
43 |
+
if (total_comments >= 2) {
|
44 |
+
return json({
|
45 |
+
error: "You have already comment this image twice",
|
46 |
+
}, { status: 401 })
|
47 |
+
}
|
48 |
+
|
49 |
+
const { text } = await request.json()
|
50 |
+
if (!text || text.length < 3) {
|
51 |
+
return json({
|
52 |
+
error: "Text must be at least 10 characters",
|
53 |
+
}, { status: 400 })
|
54 |
+
}
|
55 |
+
|
56 |
+
const comment = await prisma.comment.create({
|
57 |
+
data: {
|
58 |
+
text,
|
59 |
+
user: {
|
60 |
+
connect: {
|
61 |
+
sub: is_token_available.sub
|
62 |
+
}
|
63 |
+
},
|
64 |
+
gallery: {
|
65 |
+
connect: {
|
66 |
+
id
|
67 |
+
}
|
68 |
+
}
|
69 |
+
},
|
70 |
+
select: {
|
71 |
+
id: true,
|
72 |
+
text: true,
|
73 |
+
createdAt: true,
|
74 |
+
user: {
|
75 |
+
select: {
|
76 |
+
name: true,
|
77 |
+
picture: true,
|
78 |
+
preferred_username: true,
|
79 |
+
sub: true,
|
80 |
+
}
|
81 |
+
}
|
82 |
+
}
|
83 |
+
})
|
84 |
+
|
85 |
+
return json({
|
86 |
+
success: true,
|
87 |
+
comment
|
88 |
+
})
|
89 |
+
}
|
src/routes/api/models/[id]/comments/+server.ts
CHANGED
@@ -74,6 +74,7 @@ export async function POST({ cookies, request, params } : RequestEvent) {
|
|
74 |
user: {
|
75 |
select: {
|
76 |
name: true,
|
|
|
77 |
picture: true,
|
78 |
sub: true,
|
79 |
}
|
|
|
74 |
user: {
|
75 |
select: {
|
76 |
name: true,
|
77 |
+
preferred_username: true,
|
78 |
picture: true,
|
79 |
sub: true,
|
80 |
}
|
src/routes/gallery/+page.svelte
CHANGED
@@ -8,7 +8,8 @@
|
|
8 |
import Radio from "$lib/components/fields/Radio.svelte";
|
9 |
import { COMMUNITY_FILTER_OPTIONS } from "$lib/utils/index.js";
|
10 |
import GoTop from "$lib/components/GoTop.svelte";
|
11 |
-
import GalleryViewer from "$lib/components/community/viewer/Viewer.svelte";
|
|
|
12 |
// import UserIsLogged from "$lib/components/UserIsLogged.svelte";
|
13 |
|
14 |
export let data
|
@@ -84,5 +85,6 @@
|
|
84 |
/>
|
85 |
<GoTop />
|
86 |
</div>
|
87 |
-
<GalleryViewer form={form} />
|
|
|
88 |
</main>
|
|
|
8 |
import Radio from "$lib/components/fields/Radio.svelte";
|
9 |
import { COMMUNITY_FILTER_OPTIONS } from "$lib/utils/index.js";
|
10 |
import GoTop from "$lib/components/GoTop.svelte";
|
11 |
+
// import GalleryViewer from "$lib/components/community/viewer/Viewer.svelte";
|
12 |
+
import GalleryDrawer from "$lib/components/community/drawer/Drawer.svelte";
|
13 |
// import UserIsLogged from "$lib/components/UserIsLogged.svelte";
|
14 |
|
15 |
export let data
|
|
|
85 |
/>
|
86 |
<GoTop />
|
87 |
</div>
|
88 |
+
<!-- <GalleryViewer form={form} /> -->
|
89 |
+
<GalleryDrawer form={form} />
|
90 |
</main>
|