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 |
};
|