Spaces:
Runtime error
Runtime error
design
Browse files
frontend/src/lib/ColorPalette.svelte
CHANGED
@@ -11,17 +11,36 @@
|
|
11 |
}
|
12 |
return d3.hcl(color.h, color.c, 100).formatHex();
|
13 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
</script>
|
15 |
|
16 |
<div class="flex flex-col items-center">
|
17 |
<div class="flex bg-black">
|
18 |
-
{#each colors as color}
|
19 |
-
<div
|
|
|
|
|
|
|
20 |
<svg class="max-w-full" width="100" viewBox="0 0 50 50">
|
21 |
<rect x="0" y="0" width="50" height="50" fill={color.formatHex()} />
|
22 |
</svg>
|
23 |
<span
|
24 |
-
|
|
|
|
|
|
|
|
|
25 |
style="color:{getLabelColor(color)}">{color.formatHex()}</span
|
26 |
>
|
27 |
</div>
|
|
|
11 |
}
|
12 |
return d3.hcl(color.h, color.c, 100).formatHex();
|
13 |
}
|
14 |
+
let isCopying = -1;
|
15 |
+
|
16 |
+
async function copyStringToClipboard(text: string, n: number) {
|
17 |
+
// usingo Clipboard API
|
18 |
+
if (isCopying > -1) return;
|
19 |
+
isCopying = n;
|
20 |
+
await navigator.permissions.query({ name: 'clipboard-write' });
|
21 |
+
await navigator.clipboard.writeText(text);
|
22 |
+
setTimeout(() => {
|
23 |
+
isCopying = -1;
|
24 |
+
}, 500);
|
25 |
+
}
|
26 |
</script>
|
27 |
|
28 |
<div class="flex flex-col items-center">
|
29 |
<div class="flex bg-black">
|
30 |
+
{#each colors as color, i}
|
31 |
+
<div
|
32 |
+
class="cursor-pointer aspect-square relative"
|
33 |
+
on:click={() => copyStringToClipboard(color.formatHex(), i)}
|
34 |
+
>
|
35 |
<svg class="max-w-full" width="100" viewBox="0 0 50 50">
|
36 |
<rect x="0" y="0" width="50" height="50" fill={color.formatHex()} />
|
37 |
</svg>
|
38 |
<span
|
39 |
+
title="Copy single color"
|
40 |
+
class="cursor-pointer absolute bottom-0 text-center text-xs pl-1 font-bold uppercase {isCopying ===
|
41 |
+
i
|
42 |
+
? 'animate-ping'
|
43 |
+
: ''}"
|
44 |
style="color:{getLabelColor(color)}">{color.formatHex()}</span
|
45 |
>
|
46 |
</div>
|
frontend/src/lib/Palette.svelte
CHANGED
@@ -14,6 +14,7 @@
|
|
14 |
$: imageSrc = promptData.images[seletecdImage].imgURL;
|
15 |
|
16 |
let isCopying = false;
|
|
|
17 |
async function copyStringToClipboard(text: string) {
|
18 |
// usingo Clipboard API
|
19 |
if (isCopying) return;
|
@@ -36,7 +37,7 @@
|
|
36 |
<div class="row-start-3 md:row-start-2 col-span-6 md:col-span-4 flex items-center justify-center">
|
37 |
<ColorPalette {colors} />
|
38 |
</div>
|
39 |
-
<div class="row-start-2 col-span-6 md:col-span-2 flex justify-center pb-3">
|
40 |
<div class="relative">
|
41 |
<img class="relative max-w-[100px] w-full" src={imageSrc} alt={prompt} />
|
42 |
<div class="absolute flex justify-around w-full">
|
@@ -52,15 +53,24 @@
|
|
52 |
</div>
|
53 |
</div>
|
54 |
</div>
|
55 |
-
<div
|
|
|
|
|
56 |
<div class="flex justify-center items-center">
|
57 |
-
<button class="button" on:click={() => dispatch('remix', { prompt })}> Remix </button>
|
58 |
<button
|
59 |
class="button"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
disabled={isCopying}
|
61 |
on:click={() => copyStringToClipboard(colors.map((color) => color.formatHex()).join(', '))}
|
62 |
>
|
63 |
-
{isCopying? 'Copied' : 'Copy'}
|
64 |
</button>
|
65 |
</div>
|
66 |
</div>
|
|
|
14 |
$: imageSrc = promptData.images[seletecdImage].imgURL;
|
15 |
|
16 |
let isCopying = false;
|
17 |
+
|
18 |
async function copyStringToClipboard(text: string) {
|
19 |
// usingo Clipboard API
|
20 |
if (isCopying) return;
|
|
|
37 |
<div class="row-start-3 md:row-start-2 col-span-6 md:col-span-4 flex items-center justify-center">
|
38 |
<ColorPalette {colors} />
|
39 |
</div>
|
40 |
+
<div class="row-start-2 col-span-6 md:col-span-2 flex justify-center md:justify-end pb-3">
|
41 |
<div class="relative">
|
42 |
<img class="relative max-w-[100px] w-full" src={imageSrc} alt={prompt} />
|
43 |
<div class="absolute flex justify-around w-full">
|
|
|
53 |
</div>
|
54 |
</div>
|
55 |
</div>
|
56 |
+
<div
|
57 |
+
class="row-start-4 col-span-6 md:col-span-2 md:col-start-5 flex justify-center md:justify-end"
|
58 |
+
>
|
59 |
<div class="flex justify-center items-center">
|
|
|
60 |
<button
|
61 |
class="button"
|
62 |
+
title="Send this prompt to input so you can remix it"
|
63 |
+
on:click={() => dispatch('remix', { prompt })}
|
64 |
+
>
|
65 |
+
Remix
|
66 |
+
</button>
|
67 |
+
<button
|
68 |
+
class="button"
|
69 |
+
title="Copy all colors to clipboard"
|
70 |
disabled={isCopying}
|
71 |
on:click={() => copyStringToClipboard(colors.map((color) => color.formatHex()).join(', '))}
|
72 |
>
|
73 |
+
{isCopying ? 'Copied' : 'Copy'}
|
74 |
</button>
|
75 |
</div>
|
76 |
</div>
|
frontend/src/lib/utils.ts
CHANGED
@@ -12,7 +12,6 @@ function sortColors(colors: Color[]): Color[] {
|
|
12 |
return colors
|
13 |
.map((color) => d3.hcl(color))
|
14 |
.sort((a, b) => {
|
15 |
-
console.log(a);
|
16 |
const aa = a.h;
|
17 |
const bb = b.h;
|
18 |
|
@@ -109,4 +108,4 @@ function slugify(text: string) {
|
|
109 |
.replace(/\-\-+/g, '-')
|
110 |
.replace(/^-+/, '')
|
111 |
.replace(/-+$/, '');
|
112 |
-
}
|
|
|
12 |
return colors
|
13 |
.map((color) => d3.hcl(color))
|
14 |
.sort((a, b) => {
|
|
|
15 |
const aa = a.h;
|
16 |
const bb = b.h;
|
17 |
|
|
|
108 |
.replace(/\-\-+/g, '-')
|
109 |
.replace(/^-+/, '')
|
110 |
.replace(/-+$/, '');
|
111 |
+
}
|
frontend/src/routes/+page.svelte
CHANGED
@@ -41,7 +41,6 @@
|
|
41 |
};
|
42 |
const websocket = new WebSocket(PUBLIC_WS_ENDPOINT);
|
43 |
websocket.onopen = async function (event) {
|
44 |
-
console.log(event);
|
45 |
websocket.send(JSON.stringify({ hash: hash }));
|
46 |
};
|
47 |
websocket.onclose = (evt) => {
|
@@ -53,7 +52,6 @@
|
|
53 |
websocket.onmessage = async function (event) {
|
54 |
try {
|
55 |
const data = JSON.parse(event.data);
|
56 |
-
console.log(data);
|
57 |
$loadingState = '';
|
58 |
switch (data.msg) {
|
59 |
case 'send_data':
|
@@ -74,7 +72,6 @@
|
|
74 |
break;
|
75 |
case 'process_completed':
|
76 |
$loadingState = data.success ? 'Complete' : 'Error';
|
77 |
-
console.log(data.output);
|
78 |
const images = await extractColorsImages(data.output.data[0], _prompt);
|
79 |
promptsData = [
|
80 |
{
|
@@ -82,6 +79,10 @@
|
|
82 |
images
|
83 |
}
|
84 |
].concat(promptsData);
|
|
|
|
|
|
|
|
|
85 |
websocket.close();
|
86 |
$isLoading = false;
|
87 |
return;
|
@@ -90,7 +91,7 @@
|
|
90 |
break;
|
91 |
}
|
92 |
} catch (e) {
|
93 |
-
console.
|
94 |
$isLoading = false;
|
95 |
$loadingState = 'Error';
|
96 |
}
|
@@ -115,20 +116,37 @@
|
|
115 |
</script>
|
116 |
|
117 |
<div class="max-w-screen-md mx-auto px-3 py-8 relative z-0">
|
118 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
<form class="grid grid-cols-6" on:submit|preventDefault={() => generatePalette(prompt)}>
|
120 |
<input
|
121 |
-
class="
|
122 |
placeholder="A photo of a beautiful sunset in San Francisco"
|
|
|
123 |
type="text"
|
124 |
name="prompt"
|
125 |
bind:value={prompt}
|
126 |
disabled={$isLoading}
|
127 |
/>
|
128 |
<button
|
129 |
-
class="
|
130 |
on:click|preventDefault={() => generatePalette(prompt)}
|
131 |
disabled={$isLoading}
|
|
|
|
|
132 |
>
|
133 |
Create Palette
|
134 |
</button>
|
@@ -138,8 +156,13 @@
|
|
138 |
{/if}
|
139 |
</div>
|
140 |
|
|
|
|
|
|
|
|
|
|
|
141 |
{#if promptsData}
|
142 |
-
<div
|
143 |
{#each promptsData as promptData}
|
144 |
<Pallette {promptData} on:remix={remix} />
|
145 |
<div class="border-b border-gray-200 py-2" />
|
@@ -147,3 +170,15 @@
|
|
147 |
</div>
|
148 |
{/if}
|
149 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
41 |
};
|
42 |
const websocket = new WebSocket(PUBLIC_WS_ENDPOINT);
|
43 |
websocket.onopen = async function (event) {
|
|
|
44 |
websocket.send(JSON.stringify({ hash: hash }));
|
45 |
};
|
46 |
websocket.onclose = (evt) => {
|
|
|
52 |
websocket.onmessage = async function (event) {
|
53 |
try {
|
54 |
const data = JSON.parse(event.data);
|
|
|
55 |
$loadingState = '';
|
56 |
switch (data.msg) {
|
57 |
case 'send_data':
|
|
|
72 |
break;
|
73 |
case 'process_completed':
|
74 |
$loadingState = data.success ? 'Complete' : 'Error';
|
|
|
75 |
const images = await extractColorsImages(data.output.data[0], _prompt);
|
76 |
promptsData = [
|
77 |
{
|
|
|
79 |
images
|
80 |
}
|
81 |
].concat(promptsData);
|
82 |
+
window.scrollTo({
|
83 |
+
top: 0,
|
84 |
+
behavior: 'smooth'
|
85 |
+
});
|
86 |
websocket.close();
|
87 |
$isLoading = false;
|
88 |
return;
|
|
|
91 |
break;
|
92 |
}
|
93 |
} catch (e) {
|
94 |
+
console.error(e);
|
95 |
$isLoading = false;
|
96 |
$loadingState = 'Error';
|
97 |
}
|
|
|
116 |
</script>
|
117 |
|
118 |
<div class="max-w-screen-md mx-auto px-3 py-8 relative z-0">
|
119 |
+
<h1 class="text-3xl font-bold leading-normal">Palette generation with Stable Diffussion</h1>
|
120 |
+
<p class="text-sm">
|
121 |
+
Original ideas:
|
122 |
+
|
123 |
+
<a
|
124 |
+
class="link"
|
125 |
+
target="_blank"
|
126 |
+
rel="nofollow noopener"
|
127 |
+
href="https://twitter.com/mattdesl/status/1569457653298139136"
|
128 |
+
>
|
129 |
+
Matt DesLauriers
|
130 |
+
</a>,
|
131 |
+
<a class="link" href="https://drib.net/homage"> dribnet </a>
|
132 |
+
</p>
|
133 |
+
<div class="relative sticky top-0 z-50 bg-white dark:bg-black p-3">
|
134 |
<form class="grid grid-cols-6" on:submit|preventDefault={() => generatePalette(prompt)}>
|
135 |
<input
|
136 |
+
class="input"
|
137 |
placeholder="A photo of a beautiful sunset in San Francisco"
|
138 |
+
title="Input prompt to generate image and obtain palette"
|
139 |
type="text"
|
140 |
name="prompt"
|
141 |
bind:value={prompt}
|
142 |
disabled={$isLoading}
|
143 |
/>
|
144 |
<button
|
145 |
+
class="button"
|
146 |
on:click|preventDefault={() => generatePalette(prompt)}
|
147 |
disabled={$isLoading}
|
148 |
+
title="Generate Palette"
|
149 |
+
|
150 |
>
|
151 |
Create Palette
|
152 |
</button>
|
|
|
156 |
{/if}
|
157 |
</div>
|
158 |
|
159 |
+
<div class="flex items-center gap-4 my-10">
|
160 |
+
<div class="font-bold text-sm">156 submitted palettes</div>
|
161 |
+
<div class="grow border-b border-gray-200" />
|
162 |
+
</div>
|
163 |
+
|
164 |
{#if promptsData}
|
165 |
+
<div>
|
166 |
{#each promptsData as promptData}
|
167 |
<Pallette {promptData} on:remix={remix} />
|
168 |
<div class="border-b border-gray-200 py-2" />
|
|
|
170 |
</div>
|
171 |
{/if}
|
172 |
</div>
|
173 |
+
|
174 |
+
<style lang="postcss" scoped>
|
175 |
+
.link {
|
176 |
+
@apply text-xs underline font-bold hover:no-underline hover:text-gray-500 visited:text-gray-500;
|
177 |
+
}
|
178 |
+
.input {
|
179 |
+
@apply text-sm disabled:opacity-50 col-span-4 md:col-span-5 italic dark:placeholder:text-black placeholder:text-white text-white dark:text-black placeholder:text-opacity-40 dark:bg-white bg-slate-900 border-2 border-black rounded-2xl px-2 shadow-sm focus:outline-none focus:border-gray-400 focus:ring-1;
|
180 |
+
}
|
181 |
+
.button {
|
182 |
+
@apply disabled:opacity-50 col-span-2 md:col-span-1 dark:bg-white dark:text-black border-2 border-black rounded-2xl ml-2 px-2 py-2 text-xs shadow-sm font-bold focus:outline-none focus:border-gray-400;
|
183 |
+
}
|
184 |
+
</style>
|
frontend/tailwind.config.cjs
CHANGED
@@ -2,7 +2,11 @@
|
|
2 |
module.exports = {
|
3 |
content: ['./src/**/*.{html,js,svelte,ts}'],
|
4 |
theme: {
|
5 |
-
extend: {
|
|
|
|
|
|
|
|
|
6 |
},
|
7 |
plugins: [require('@tailwindcss/forms'), require('@tailwindcss/line-clamp')]
|
8 |
};
|
|
|
2 |
module.exports = {
|
3 |
content: ['./src/**/*.{html,js,svelte,ts}'],
|
4 |
theme: {
|
5 |
+
extend: {
|
6 |
+
animation: {
|
7 |
+
ping: 'ping 0.5s cubic-bezier(0, 0, 0.2, 1)'
|
8 |
+
}
|
9 |
+
}
|
10 |
},
|
11 |
plugins: [require('@tailwindcss/forms'), require('@tailwindcss/line-clamp')]
|
12 |
};
|