enzostvs HF staff commited on
Commit
8a18175
β€’
1 Parent(s): 332141a

gallery comment

Browse files
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 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 "./Comment.svelte";
7
 
8
  export let comments: CommentType[] = [];
9
  export let model: ModelCard;
@@ -36,32 +36,30 @@
36
  }
37
  </script>
38
 
39
- <div>
40
- <div class="grid grid-cols-1 gap-3">
41
- {#if comments?.length === 0}
42
- <p class="text-neutral-500 text-sm">No comments yet! Be the first one!</p>
43
- {/if}
44
- {#each comments as comment}
45
- <Comment comment={comment} />
46
- {/each}
47
- </div>
48
- <div class="flex gap-4 items-start justify-between flex-col lg:flex-row mt-7">
49
- <textarea
50
- value={text}
51
- class="rounded-xl bg-neutral-900 text-neutral-200 text-base placeholder:text-neutral-500 outline-none resize-none p-4 w-full"
52
- placeholder="Write a comment..."
53
- on:input={handleChange}
54
- />
55
- <Button
56
- theme="blue"
57
- size="md"
58
- icon="carbon:send-alt-filled"
59
- iconPosition="right"
60
- loading={loading}
61
- disabled={text.length < 3}
62
- onClick={handleSubmit}
63
- >
64
- Post
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>