enzostvs HF staff commited on
Commit
f05d33c
β€’
1 Parent(s): c5a4bec

gallery viewer

Browse files
package-lock.json CHANGED
@@ -30,6 +30,7 @@
30
  "@types/node": "^20.11.2",
31
  "@typescript-eslint/eslint-plugin": "^6.0.0",
32
  "@typescript-eslint/parser": "^6.0.0",
 
33
  "autoprefixer": "^10.4.16",
34
  "eslint": "^8.28.0",
35
  "eslint-config-prettier": "^9.0.0",
@@ -3049,6 +3050,15 @@
3049
  "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
3050
  "dev": true
3051
  },
 
 
 
 
 
 
 
 
 
3052
  "node_modules/acorn": {
3053
  "version": "8.11.2",
3054
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
 
30
  "@types/node": "^20.11.2",
31
  "@typescript-eslint/eslint-plugin": "^6.0.0",
32
  "@typescript-eslint/parser": "^6.0.0",
33
+ "@zerodevx/svelte-toast": "^0.9.5",
34
  "autoprefixer": "^10.4.16",
35
  "eslint": "^8.28.0",
36
  "eslint-config-prettier": "^9.0.0",
 
3050
  "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
3051
  "dev": true
3052
  },
3053
+ "node_modules/@zerodevx/svelte-toast": {
3054
+ "version": "0.9.5",
3055
+ "resolved": "https://registry.npmjs.org/@zerodevx/svelte-toast/-/svelte-toast-0.9.5.tgz",
3056
+ "integrity": "sha512-JLeB/oRdJfT+dz9A5bgd3Z7TuQnBQbeUtXrGIrNWMGqWbabpepBF2KxtWVhL2qtxpRqhae2f6NAOzH7xs4jUSw==",
3057
+ "dev": true,
3058
+ "peerDependencies": {
3059
+ "svelte": "^3.57.0 || ^4.0.0"
3060
+ }
3061
+ },
3062
  "node_modules/acorn": {
3063
  "version": "8.11.2",
3064
  "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
package.json CHANGED
@@ -21,6 +21,7 @@
21
  "@types/node": "^20.11.2",
22
  "@typescript-eslint/eslint-plugin": "^6.0.0",
23
  "@typescript-eslint/parser": "^6.0.0",
 
24
  "autoprefixer": "^10.4.16",
25
  "eslint": "^8.28.0",
26
  "eslint-config-prettier": "^9.0.0",
 
21
  "@types/node": "^20.11.2",
22
  "@typescript-eslint/eslint-plugin": "^6.0.0",
23
  "@typescript-eslint/parser": "^6.0.0",
24
+ "@zerodevx/svelte-toast": "^0.9.5",
25
  "autoprefixer": "^10.4.16",
26
  "eslint": "^8.28.0",
27
  "eslint-config-prettier": "^9.0.0",
src/lib/components/UserIsLogged.svelte CHANGED
@@ -15,7 +15,7 @@
15
  <!-- svelte-ignore a11y-no-static-element-interactions -->
16
  <!-- svelte-ignore a11y-click-events-have-key-events -->
17
  <div
18
- class="w-full cursor-pointer"
19
  on:click={handleClick}
20
  >
21
  <div class:pointer-events-none={!user}>
 
15
  <!-- svelte-ignore a11y-no-static-element-interactions -->
16
  <!-- svelte-ignore a11y-click-events-have-key-events -->
17
  <div
18
+ class="w-full cursor-pointer block"
19
  on:click={handleClick}
20
  >
21
  <div class:pointer-events-none={!user}>
src/lib/components/community/Card.svelte CHANGED
@@ -1,15 +1,34 @@
1
  <script lang="ts">
2
- import type { CommunityCard } from "$lib/type";
3
- import Button from "$lib/components/Button.svelte";
4
  import { env } from "$env/dynamic/public";
5
-
 
 
 
 
6
  import Reactions from "./reactions/Reactions.svelte";
7
 
8
  export let card: CommunityCard;
9
- </script>
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  <div
12
  class="cursor-pointer group bg-neutral-700 rounded-xl h-[400px] relative flex items-start justify-between flex-col p-5 transition-all duration-200 brightness-75 hover:brightness-100 z-[1] overflow-hidden"
 
13
  >
14
  <div class="w-full h-full absolute top-0 left-0 -z-[1] rounded-xl overflow-hidden">
15
  <div class="w-full h-full bg-center bg-cover transition-all duration-200 group-hover:scale-110 " style="background-image: url('{env.PUBLIC_FILE_UPLOAD_DIR}/{card.image}');"></div>
@@ -19,11 +38,6 @@
19
  <p class="text-white font-semibold text-lg">{card.prompt}</p>
20
  <p class="text-white/75 font-regular text-base">{card.model.id}</p>
21
  </div>
22
- <Button theme="light" size="md" href={`/generate?model=${card.model.id}`}>
23
- Try it now
24
- </Button>
25
- </div>
26
- <div class="flex items-center justify-start gap-2">
27
- <Reactions reactions={card.reactions} gallery_id={card.id} />
28
  </div>
 
29
  </div>
 
1
  <script lang="ts">
 
 
2
  import { env } from "$env/dynamic/public";
3
+ import { goto } from "$app/navigation";
4
+ import { page } from "$app/stores";
5
+
6
+ import type { CommunityCard } from "$lib/type";
7
+ import { galleryStore } from "$lib/stores/use-gallery";
8
  import Reactions from "./reactions/Reactions.svelte";
9
 
10
  export let card: CommunityCard;
 
11
 
12
+ const handleClick = async () => {
13
+ const request = await fetch(`/api/community/${card?.id}`);
14
+ const { gallery, next, previous } = await request.json();
15
+ galleryStore.set({
16
+ gallery,
17
+ open: true,
18
+ next,
19
+ previous
20
+ });
21
+
22
+ $page.url.searchParams.set('gallery', card?.id);
23
+ goto(`?${$page.url.searchParams.toString()}`);
24
+ };
25
+ </script>
26
+
27
+ <!-- svelte-ignore a11y-no-static-element-interactions -->
28
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
29
  <div
30
  class="cursor-pointer group bg-neutral-700 rounded-xl h-[400px] relative flex items-start justify-between flex-col p-5 transition-all duration-200 brightness-75 hover:brightness-100 z-[1] overflow-hidden"
31
+ on:click={handleClick}
32
  >
33
  <div class="w-full h-full absolute top-0 left-0 -z-[1] rounded-xl overflow-hidden">
34
  <div class="w-full h-full bg-center bg-cover transition-all duration-200 group-hover:scale-110 " style="background-image: url('{env.PUBLIC_FILE_UPLOAD_DIR}/{card.image}');"></div>
 
38
  <p class="text-white font-semibold text-lg">{card.prompt}</p>
39
  <p class="text-white/75 font-regular text-base">{card.model.id}</p>
40
  </div>
 
 
 
 
 
 
41
  </div>
42
+ <Reactions reactions={card.reactions} gallery_id={card.id} />
43
  </div>
src/lib/components/community/reactions/Reaction.svelte CHANGED
@@ -1,6 +1,4 @@
1
  <script lang="ts">
2
- import UserIsLogged from "$lib/components/UserIsLogged.svelte";
3
-
4
  export let emoji: string;
5
  export let count: number;
6
  export let gallery_id: string;
@@ -22,13 +20,11 @@
22
  }
23
  </script>
24
 
25
- <UserIsLogged>
26
- <button
27
- class="rounded-full bg-white text-neutral-800 font-bold flex items-center justify-start gap-1.5 px-3 py-1 border border-white hover:bg-neutral-200 text-sm"
28
- class:bg-opacity-60={!liked}
29
- on:click={() => handleReaction(emoji)}
30
- >
31
- <span class="text-base">{emoji}</span>
32
- {count}
33
- </button>
34
- </UserIsLogged>
 
1
  <script lang="ts">
 
 
2
  export let emoji: string;
3
  export let count: number;
4
  export let gallery_id: string;
 
20
  }
21
  </script>
22
 
23
+ <button
24
+ class="rounded-full bg-white text-neutral-800 font-bold flex items-center justify-start gap-1.5 px-3 py-1 border border-white hover:bg-neutral-200 text-sm"
25
+ class:bg-opacity-60={!liked}
26
+ on:click={() => handleReaction(emoji)}
27
+ >
28
+ <span class="text-base">{emoji}</span>
29
+ {count}
30
+ </button>
 
 
src/lib/components/community/reactions/Reactions.svelte CHANGED
@@ -1,9 +1,10 @@
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
 
@@ -24,26 +25,30 @@
24
  $: groupedReactions = groupReactionsByEmoji(reactions);
25
  </script>
26
 
27
- {#each groupedReactions as reaction}
28
- <Reaction
29
- emoji={reaction.emoji}
30
- count={reaction?.count}
31
- liked={reaction?.liked}
32
- {gallery_id}
33
- onReact={(emoji, id, deleted) => {
34
- if (deleted) {
35
- reactions = reactions.filter((reaction) => reaction.id !== id);
36
- } else {
 
 
 
 
 
 
 
 
 
 
 
 
37
  reactions = [...reactions, { emoji, userId: user?.sub, galleryId: gallery_id, id }];
38
- }
39
- }}
40
- />
41
- {/each}
42
- <Add
43
- count={groupedReactions?.length}
44
- reactions={groupedReactions}
45
- {gallery_id}
46
- onAdd={(emoji, id) => {
47
- reactions = [...reactions, { emoji, userId: user?.sub, galleryId: gallery_id, id }];
48
- }}
49
- />
 
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
+ import UserIsLogged from "$lib/components/UserIsLogged.svelte";
8
 
9
  let user = get(userStore);
10
 
 
25
  $: groupedReactions = groupReactionsByEmoji(reactions);
26
  </script>
27
 
28
+ <UserIsLogged>
29
+ <div class="flex items-center justify-start gap-2">
30
+ {#each groupedReactions as reaction}
31
+ <Reaction
32
+ emoji={reaction.emoji}
33
+ count={reaction?.count}
34
+ liked={reaction?.liked}
35
+ {gallery_id}
36
+ onReact={(emoji, id, deleted) => {
37
+ if (deleted) {
38
+ reactions = reactions.filter((reaction) => reaction.id !== id);
39
+ } else {
40
+ reactions = [...reactions, { emoji, userId: user?.sub, galleryId: gallery_id, id }];
41
+ }
42
+ }}
43
+ />
44
+ {/each}
45
+ <Add
46
+ count={groupedReactions?.length}
47
+ reactions={groupedReactions}
48
+ {gallery_id}
49
+ onAdd={(emoji, id) => {
50
  reactions = [...reactions, { emoji, userId: user?.sub, galleryId: gallery_id, id }];
51
+ }}
52
+ />
53
+ </div>
54
+ </UserIsLogged>
 
 
 
 
 
 
 
 
src/lib/components/community/viewer/Viewer.svelte ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { clickoutside } from '@svelte-put/clickoutside';
3
+ import { goto } from "$app/navigation";
4
+ import { page } from "$app/stores";
5
+ import { get } from "svelte/store";
6
+ import { env } from "$env/dynamic/public";
7
+ import Icon from "@iconify/svelte";
8
+
9
+ import { galleryStore } from "$lib/stores/use-gallery";
10
+ import UserIsLogged from '$lib/components/UserIsLogged.svelte';
11
+ import Reactions from '../reactions/Reactions.svelte';
12
+ import Button from '$lib/components/Button.svelte';
13
+
14
+ let { open, gallery, previous, next } = get(galleryStore);
15
+ let loading = false;
16
+
17
+ galleryStore.subscribe((value) => {
18
+ open = value?.open;
19
+ gallery = value?.gallery;
20
+ previous = value?.previous;
21
+ next = value?.next;
22
+ });
23
+
24
+ const handleClose = () => {
25
+ galleryStore.update((value) => {
26
+ return {
27
+ ...value,
28
+ open: false,
29
+ };
30
+ });
31
+
32
+ $page.url.searchParams.delete('model');
33
+ goto(`?${$page.url.searchParams.toString()}`);
34
+ };
35
+
36
+ const handlePagination = async (id?: string) => {
37
+ if (!id) return;
38
+ loading = true;
39
+ const request = await fetch(`/api/community/${id}`);
40
+ const { gallery, next, previous } = await request.json();
41
+ galleryStore.set({
42
+ gallery,
43
+ open: true,
44
+ next,
45
+ previous
46
+ });
47
+ loading = false;
48
+ $page.url.searchParams.set('gallery', id);
49
+ goto(`?${$page.url.searchParams.toString()}`);
50
+ };
51
+ </script>
52
+
53
+ <div
54
+ class="w-full fixed top-0 left-0 h-full bg-black bg-opacity-50 z-40 backdrop-blur transition-all duration-100 p-6 lg:p-10 flex items-center justify-center"
55
+ class:opacity-0={!open}
56
+ class:pointer-events-none={!open}
57
+ >
58
+ <div
59
+ class="mx-auto lg:h-2/3 w-full max-w-6xl bg-neutral-900 transition-all duration-200 grid grid-cols-1 lg:grid-cols-2 rounded-xl overflow-hidden"
60
+ class:translate-x-full={!open}
61
+ use:clickoutside on:clickoutside={handleClose}
62
+ >
63
+ {#if gallery?.id}
64
+ <img src={env.PUBLIC_FILE_UPLOAD_DIR}/{gallery?.image} alt={gallery?.prompt} class="w-full h-full object-cover" />
65
+ <div class="flex flex-col justify-between w-full">
66
+ <div class="w-full p-8">
67
+ <header class="w-full flex items-start justify-between">
68
+ <div class="flex items-center justify-start gap-4">
69
+ <img src={gallery?.user?.picture} class="w-12 h-12 rounded-full object-cover" alt={gallery?.user?.name} />
70
+ <div>
71
+ <p class="text-neutral-100 font-bold text-lg">
72
+ {gallery?.user?.name}
73
+ </p>
74
+ <p class="text-neutral-400 text-sm">
75
+ @{gallery?.user?.preferred_username}
76
+ </p>
77
+ </div>
78
+ </div>
79
+ <button on:click={handleClose}>
80
+ <Icon icon="carbon:close" class="w-6 h-6 text-white" />
81
+ </button>
82
+ </header>
83
+ <div class="mt-8 grid grid-cols-1 gap-5">
84
+ <Reactions reactions={gallery?.reactions} gallery_id={gallery.id} />
85
+ <div>
86
+ <a
87
+ href="/generate?model={gallery?.model?.id}"
88
+ class="flex items-center justify-start gap-4 rounded-lg cursor-pointer w-full text-left transition-all duration-200 hover:bg-neutral-950/50 p-3 -mx-3 group relative"
89
+ >
90
+ <img src={gallery?.model?.image} alt={gallery?.model?.title} class="w-14 h-14 rounded-lg object-cover" />
91
+ <div>
92
+ <p class="text-neutral-200 text-base font-medium">{gallery?.model?.title}</p>
93
+ <p class="text-neutral-400 text-sm">{gallery?.model?.id}</p>
94
+ </div>
95
+ <div class="rounded-full absolute top-1/2 -translate-y-1/2 text-neutral-100 w-8 h-8 right-4 bg-pink-500 flex items-center justify-center transition-all duration-200 group-hover:opacity-100 opacity-0">
96
+ <Icon icon="tabler:arrow-up" class="w-5 h-5 transform rotate-45 font-bold" />
97
+ </div>
98
+ </a>
99
+ </div>
100
+ <div>
101
+ <p class="text-neutral-400 font-semibold text-xs uppercase">
102
+ Prompt
103
+ </p>
104
+ <p class="text-neutral-200 text-base font-medium mt-2">"{gallery?.prompt}"</p>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ <footer class="border-t border-neutral-800 px-8 py-6 flex items-center justify-between">
109
+ <Button
110
+ size="lg"
111
+ theme="dark"
112
+ disabled={!previous}
113
+ loading={loading}
114
+ onClick={() => handlePagination(previous)}
115
+ >
116
+ Previous
117
+ </Button>
118
+ <Button
119
+ size="lg"
120
+ theme="light"
121
+ loading={loading}
122
+ disabled={!next}
123
+ onClick={() => handlePagination(next)}
124
+ >
125
+ Next
126
+ </Button>
127
+ </footer>
128
+ </div>
129
+ {/if}
130
+ </div>
131
+ </div>
src/lib/components/generate/Response.svelte CHANGED
@@ -39,8 +39,6 @@
39
  generation = value;
40
  })
41
 
42
- $: console.log(generation);
43
-
44
  // create a ms countup depending on the generation time, to show the user how long it took to generate the image
45
  let ms = 0;
46
  let interval: any;
@@ -109,7 +107,7 @@
109
  <p class="text-neutral-400 font-semibold text-xs uppercase">
110
  Model selected
111
  </p>
112
- <div class="flex items-center justify-start gap-4 px-2 py-2.5 hover:bg-neutral-800/60 transition-all duration-200 rounded-lg cursor-pointer w-full text-left">
113
  <img src={generation?.form?.model.image} alt={generation?.form?.model.title} class="w-14 h-14 rounded-lg object-cover" />
114
  <div>
115
  <p class="text-neutral-200 text-base font-medium">{generation?.form?.model.title}</p>
 
39
  generation = value;
40
  })
41
 
 
 
42
  // create a ms countup depending on the generation time, to show the user how long it took to generate the image
43
  let ms = 0;
44
  let interval: any;
 
107
  <p class="text-neutral-400 font-semibold text-xs uppercase">
108
  Model selected
109
  </p>
110
+ <div class="flex items-center justify-start gap-4 px-2 py-2.5 rounded-lg cursor-pointer w-full text-left">
111
  <img src={generation?.form?.model.image} alt={generation?.form?.model.title} class="w-14 h-14 rounded-lg object-cover" />
112
  <div>
113
  <p class="text-neutral-200 text-base font-medium">{generation?.form?.model.title}</p>
src/lib/components/models/Card.svelte CHANGED
@@ -10,7 +10,10 @@
10
  const handleClick = async () => {
11
  const request = await fetch(`/api/models/${card?.id?.replace("/", "@")}?full=true`);
12
  const { model } = await request.json();
13
- modelStore.set(model);
 
 
 
14
  $page.url.searchParams.set('model', card?.id);
15
  goto(`?${$page.url.searchParams.toString()}`);
16
  };
 
10
  const handleClick = async () => {
11
  const request = await fetch(`/api/models/${card?.id?.replace("/", "@")}?full=true`);
12
  const { model } = await request.json();
13
+ modelStore.set({
14
+ model,
15
+ open: true
16
+ });
17
  $page.url.searchParams.set('model', card?.id);
18
  goto(`?${$page.url.searchParams.toString()}`);
19
  };
src/lib/components/models/drawer/Drawer.svelte CHANGED
@@ -10,14 +10,20 @@
10
  import Comments from '$lib/components/models/drawer/comments/Comments.svelte';
11
  import { env } from '$env/dynamic/public';
12
 
13
- let data = get(modelStore);
14
 
15
  modelStore.subscribe((value) => {
16
- data = value;
 
17
  });
18
 
19
  const handleClose = () => {
20
- modelStore.set(undefined);
 
 
 
 
 
21
 
22
  $page.url.searchParams.delete('model');
23
  goto(`?${$page.url.searchParams.toString()}`);
@@ -26,30 +32,30 @@
26
 
27
  <div
28
  class="w-full fixed top-0 left-0 h-full bg-black bg-opacity-50 z-40 backdrop-blur transition-all duration-100"
29
- class:opacity-0={!data?.id}
30
- class:pointer-events-none={!data?.id}
31
  >
32
  <div
33
  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"
34
- class:translate-x-full={!data?.id}
35
  use:clickoutside on:clickoutside={handleClose}
36
  >
37
  <div class="p-8 overflow-auto">
38
  <header class="flex w-full justify-between items-start mb-6">
39
  <div class="flex items-center justify-start gap-3 lg:gap-6">
40
- <img src={data?.image} class="lg:w-16 lg:h-16 w-12 h-12 rounded-xl bg-red-500" alt={data?.id} />
41
  <div>
42
  <p class="text-white font-semibold text-lg lg:text-2xl mb-1 truncate">
43
- {data?.title ?? data?.id}
44
  </p>
45
  <div class="justify-start items-center gap-2 flex">
46
  <div class="bg-red-500 bg-opacity-20 border border-red-500 px-3 py-1.5 rounded-full text-neutral-100 flex items-center justify-center gap-1 font-bold text-xs">
47
  <Icon icon="solar:heart-bold" class="lg:w-4 lg:h-4 w-3 h-3 text-red-500" />
48
- {data?.likes ?? 0}
49
  </div>
50
  <div class="bg-blue-500 bg-opacity-20 border border-blue-500 px-3 py-1.5 rounded-full text-neutral-100 flex items-center justify-center gap-1 font-bold text-xs">
51
  <Icon icon="solar:download-square-bold" class="lg:w-4 lg:h-4 w-3 h-3 text-blue-500" />
52
- {data?.downloads ?? 0}
53
  </div>
54
  </div>
55
  </div>
@@ -59,11 +65,11 @@
59
  </button>
60
  </header>
61
  <main>
62
- {#if data?.gallery && data?.gallery?.length > 0}
63
  <div>
64
  <p class="text-neutral-400 uppercase text-xs font-bold">Examples</p>
65
  <div class="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-6 gap-5 mt-2">
66
- {#each data?.gallery as example}
67
  <div class="w-full h-[120px] relative z-[1] mb-3 overflow-hidden">
68
  <img src="{env.PUBLIC_FILE_UPLOAD_DIR}/{example.image}" class="w-full h-full bg-center bg-cover rounded-lg object-cover object-center bg-neutral-800" alt={example.prompt} />
69
  </div>
@@ -75,11 +81,11 @@
75
  </div>
76
  <footer class="p-8 border-t border-neutral-900 bg-neutral-900/30">
77
  <p class="font-semibold text-neutral-100 text-base lg:text-lg mb-6">
78
- Commentaires ({data?.comments?.length ?? 0})
79
  </p>
80
- {#if data?.id}
81
  <UserIsLogged>
82
- <Comments comments={data?.comments} model={data} />
83
  </UserIsLogged>
84
  {/if}
85
  </footer>
 
10
  import Comments from '$lib/components/models/drawer/comments/Comments.svelte';
11
  import { env } from '$env/dynamic/public';
12
 
13
+ let { open, model } = get(modelStore);
14
 
15
  modelStore.subscribe((value) => {
16
+ open = value?.open;
17
+ model = value?.model;
18
  });
19
 
20
  const handleClose = () => {
21
+ modelStore.update((value) => {
22
+ return {
23
+ ...value,
24
+ open: false,
25
+ };
26
+ });
27
 
28
  $page.url.searchParams.delete('model');
29
  goto(`?${$page.url.searchParams.toString()}`);
 
32
 
33
  <div
34
  class="w-full fixed top-0 left-0 h-full bg-black bg-opacity-50 z-40 backdrop-blur transition-all duration-100"
35
+ class:opacity-0={!open}
36
+ class:pointer-events-none={!open}
37
  >
38
  <div
39
  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"
40
+ class:translate-x-full={!open}
41
  use:clickoutside on:clickoutside={handleClose}
42
  >
43
  <div class="p-8 overflow-auto">
44
  <header class="flex w-full justify-between items-start mb-6">
45
  <div class="flex items-center justify-start gap-3 lg:gap-6">
46
+ <img src={model?.image} class="lg:w-16 lg:h-16 w-12 h-12 rounded-xl bg-neutral-800 object-cover" alt={model?.id} />
47
  <div>
48
  <p class="text-white font-semibold text-lg lg:text-2xl mb-1 truncate">
49
+ {model?.title ?? model?.id}
50
  </p>
51
  <div class="justify-start items-center gap-2 flex">
52
  <div class="bg-red-500 bg-opacity-20 border border-red-500 px-3 py-1.5 rounded-full text-neutral-100 flex items-center justify-center gap-1 font-bold text-xs">
53
  <Icon icon="solar:heart-bold" class="lg:w-4 lg:h-4 w-3 h-3 text-red-500" />
54
+ {model?.likes ?? 0}
55
  </div>
56
  <div class="bg-blue-500 bg-opacity-20 border border-blue-500 px-3 py-1.5 rounded-full text-neutral-100 flex items-center justify-center gap-1 font-bold text-xs">
57
  <Icon icon="solar:download-square-bold" class="lg:w-4 lg:h-4 w-3 h-3 text-blue-500" />
58
+ {model?.downloads ?? 0}
59
  </div>
60
  </div>
61
  </div>
 
65
  </button>
66
  </header>
67
  <main>
68
+ {#if model?.gallery && model?.gallery?.length > 0}
69
  <div>
70
  <p class="text-neutral-400 uppercase text-xs font-bold">Examples</p>
71
  <div class="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-6 gap-5 mt-2">
72
+ {#each model?.gallery as example}
73
  <div class="w-full h-[120px] relative z-[1] mb-3 overflow-hidden">
74
  <img src="{env.PUBLIC_FILE_UPLOAD_DIR}/{example.image}" class="w-full h-full bg-center bg-cover rounded-lg object-cover object-center bg-neutral-800" alt={example.prompt} />
75
  </div>
 
81
  </div>
82
  <footer class="p-8 border-t border-neutral-900 bg-neutral-900/30">
83
  <p class="font-semibold text-neutral-100 text-base lg:text-lg mb-6">
84
+ Commentaires ({model?.comments?.length ?? 0})
85
  </p>
86
+ {#if model?.id}
87
  <UserIsLogged>
88
+ <Comments comments={model?.comments} model={model} />
89
  </UserIsLogged>
90
  {/if}
91
  </footer>
src/lib/components/models/drawer/comments/Comments.svelte CHANGED
@@ -1,4 +1,6 @@
1
  <script lang="ts">
 
 
2
  import Button from "$lib/components/Button.svelte";
3
  import type { ModelCard, CommentType } from "$lib/type";
4
  import Comment from "./Comment.svelte";
@@ -8,7 +10,6 @@
8
 
9
  let text = "";
10
  let loading = false;
11
- let error: string | undefined = undefined;
12
 
13
  const handleSubmit = async () => {
14
  loading = true;
@@ -22,7 +23,7 @@
22
 
23
  const comment_response = await comment_request.json();
24
  if (comment_response.error) {
25
- error = comment_response.error;
26
  } else {
27
  comments = [comment_response.comment, ...comments];
28
  text = "";
 
1
  <script lang="ts">
2
+ import { error } from "$lib/utils/toaster";
3
+
4
  import Button from "$lib/components/Button.svelte";
5
  import type { ModelCard, CommentType } from "$lib/type";
6
  import Comment from "./Comment.svelte";
 
10
 
11
  let text = "";
12
  let loading = false;
 
13
 
14
  const handleSubmit = async () => {
15
  loading = true;
 
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 = "";
src/lib/stores/use-gallery.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { writable } from "svelte/store";
2
+
3
+ import type { CommunityCard } from "$lib/type";
4
+
5
+ interface Props {
6
+ open: boolean,
7
+ gallery: CommunityCard | undefined,
8
+ previous?: string,
9
+ next?: string,
10
+ }
11
+
12
+ export const galleryStore = writable<Props>({
13
+ open: false,
14
+ gallery: undefined,
15
+ previous: undefined,
16
+ next: undefined,
17
+ });
src/lib/stores/use-model.ts CHANGED
@@ -2,4 +2,12 @@ import { writable } from "svelte/store";
2
 
3
  import type { ModelCard } from "$lib/type";
4
 
5
- export const modelStore = writable<ModelCard | undefined>(undefined);
 
 
 
 
 
 
 
 
 
2
 
3
  import type { ModelCard } from "$lib/type";
4
 
5
+ interface Props {
6
+ open: boolean,
7
+ model: ModelCard | undefined
8
+ }
9
+
10
+ export const modelStore = writable<Props>({
11
+ open: false,
12
+ model: undefined
13
+ });
src/lib/type.ts CHANGED
@@ -10,6 +10,8 @@ export interface CommunityCard {
10
  id: string,
11
  model: ModelCard,
12
  prompt: string,
 
 
13
  image: string,
14
  }
15
 
 
10
  id: string,
11
  model: ModelCard,
12
  prompt: string,
13
+ createdAt: Date,
14
+ user: UserType,
15
  image: string,
16
  }
17
 
src/lib/utils/toaster.ts ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { toast } from '@zerodevx/svelte-toast'
2
+
3
+ export const success = (message: string) => toast.push(message, {
4
+ theme: {
5
+ '--toastBackground': '#4caf50',
6
+ '--toastProgressBackground': '#81c784',
7
+ '--toastProgressAfterBackground': '#4caf50',
8
+ '--toastColor': '#fff',
9
+ }
10
+ })
11
+
12
+ export const error = (message: string) => toast.push(message, {
13
+ theme: {
14
+ '--toastBackground': '#f44336',
15
+ '--toastProgressBackground': '#e57373',
16
+ '--toastProgressAfterBackground': '#f44336',
17
+ '--toastColor': '#fff',
18
+ }
19
+ })
src/routes/+layout.svelte CHANGED
@@ -1,6 +1,7 @@
1
  <script>
2
  import { get } from "svelte/store";
3
  import Icon from "@iconify/svelte";
 
4
 
5
  import Sidebar from "$lib/components/sidebar/Sidebar.svelte";
6
  import "$lib/styles/tailwind.css"
@@ -23,6 +24,7 @@
23
  <main id="app" class="flex-1 h-screen overflow-y-auto">
24
  <slot />
25
  </main>
 
26
  <Dialog {open} onClose={() => loginModalStore.set(false)}>
27
  <div class="grid grid-cols-1 gap-7 text-center">
28
  <Icon icon="fluent-emoji:sparkles" class="w-12 h-12 mx-auto" />
 
1
  <script>
2
  import { get } from "svelte/store";
3
  import Icon from "@iconify/svelte";
4
+ import { SvelteToast } from '@zerodevx/svelte-toast'
5
 
6
  import Sidebar from "$lib/components/sidebar/Sidebar.svelte";
7
  import "$lib/styles/tailwind.css"
 
24
  <main id="app" class="flex-1 h-screen overflow-y-auto">
25
  <slot />
26
  </main>
27
+ <SvelteToast />
28
  <Dialog {open} onClose={() => loginModalStore.set(false)}>
29
  <div class="grid grid-cols-1 gap-7 text-center">
30
  <Icon icon="fluent-emoji:sparkles" class="w-12 h-12 mx-auto" />
src/routes/+page.svelte CHANGED
@@ -16,8 +16,6 @@
16
 
17
  export let data
18
 
19
- console.log(data)
20
-
21
  let form = {
22
  filter: "hotest",
23
  search: "",
 
16
 
17
  export let data
18
 
 
 
19
  let form = {
20
  filter: "hotest",
21
  search: "",
src/routes/+page.ts CHANGED
@@ -4,7 +4,7 @@ export async function load({ fetch, url }) {
4
  let model;
5
 
6
  if (model_param) {
7
- const model_request = await fetch(`/api/models/${model_param?.replace("/", "@")}`, {
8
  method: "GET",
9
  headers: {
10
  "Content-Type": "application/json"
 
4
  let model;
5
 
6
  if (model_param) {
7
+ const model_request = await fetch(`/api/models/${model_param?.replace("/", "@")}?full=true`, {
8
  method: "GET",
9
  headers: {
10
  "Content-Type": "application/json"
src/routes/api/community/[id]/+server.ts ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { json, type RequestEvent } from '@sveltejs/kit';
2
+ import prisma from '$lib/prisma';
3
+
4
+ /** @type {import('./$types').RequestHandler} */
5
+
6
+ export async function GET({ params } : RequestEvent) {
7
+ const id = params.id;
8
+
9
+ const gallery = await prisma.gallery.findFirst({
10
+ where: {
11
+ id,
12
+ },
13
+ select: {
14
+ image: true,
15
+ id: true,
16
+ prompt: true,
17
+ createdAt: true,
18
+ user: {
19
+ select: {
20
+ id: true,
21
+ name: true,
22
+ sub: true,
23
+ picture: true,
24
+ preferred_username: true,
25
+ }
26
+ },
27
+ model: {
28
+ select: {
29
+ title: true,
30
+ image: true,
31
+ id: true,
32
+ }
33
+ },
34
+ reactions: {
35
+ select: {
36
+ id: true,
37
+ emoji: true,
38
+ userId: true,
39
+ user: {
40
+ select: {
41
+ id: true,
42
+ name: true,
43
+ sub: true,
44
+ picture: true,
45
+ preferred_username: true,
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ })
52
+
53
+ if (!gallery) {
54
+ return json({
55
+ error: {
56
+ token: "Gallery not found"
57
+ }
58
+ }, { status: 404 })
59
+ }
60
+
61
+ const next = await prisma.gallery.findFirst({
62
+ where: {
63
+ isPublic: true,
64
+ createdAt: {
65
+ lt: gallery.createdAt
66
+ }
67
+ },
68
+ select: {
69
+ id: true,
70
+ }
71
+ })
72
+
73
+ const previous = await prisma.gallery.findFirst({
74
+ where: {
75
+ isPublic: true,
76
+ createdAt: {
77
+ gt: gallery.createdAt
78
+ }
79
+ },
80
+ select: {
81
+ id: true,
82
+ }
83
+ })
84
+
85
+ return json({
86
+ gallery,
87
+ next: next ? next.id : undefined,
88
+ previous: previous ? previous.id : undefined,
89
+ })
90
+ }
src/routes/api/models/[id]/comments/+server.ts CHANGED
@@ -66,6 +66,18 @@ export async function POST({ cookies, request, params } : RequestEvent) {
66
  id
67
  }
68
  }
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
70
  })
71
 
 
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
+ sub: true,
79
+ }
80
+ }
81
  }
82
  })
83
 
src/routes/gallery/+page.svelte CHANGED
@@ -8,6 +8,7 @@
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 UserIsLogged from "$lib/components/UserIsLogged.svelte";
12
 
13
  export let data
@@ -83,4 +84,5 @@
83
  />
84
  <GoTop />
85
  </div>
 
86
  </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 UserIsLogged from "$lib/components/UserIsLogged.svelte";
13
 
14
  export let data
 
84
  />
85
  <GoTop />
86
  </div>
87
+ <GalleryViewer />
88
  </main>