@slopmachine/svelte 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/package.json +1 -1
- package/src/SlopImage.svelte +88 -14
package/README.md
CHANGED
package/package.json
CHANGED
package/src/SlopImage.svelte
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
buildImageUrl,
|
|
4
|
+
interpolatePrompt,
|
|
5
|
+
type SlopImageOptions,
|
|
6
|
+
} from "@slopmachine/core";
|
|
3
7
|
|
|
4
|
-
interface Props extends SlopImageOptions{
|
|
8
|
+
interface Props extends SlopImageOptions {
|
|
5
9
|
class?: string;
|
|
6
10
|
}
|
|
7
11
|
|
|
8
12
|
let {
|
|
13
|
+
bucketId,
|
|
9
14
|
prompt,
|
|
10
15
|
aspectRatio = "1:1",
|
|
11
16
|
model,
|
|
@@ -17,9 +22,22 @@
|
|
|
17
22
|
|
|
18
23
|
let isLoading = $state(true);
|
|
19
24
|
|
|
20
|
-
let
|
|
25
|
+
let computedSrc = $derived(
|
|
26
|
+
buildImageUrl({ bucketId, prompt, aspectRatio, variables, baseUrl, model }),
|
|
27
|
+
);
|
|
28
|
+
let src = $state("");
|
|
21
29
|
let prevSrc = $state("");
|
|
22
|
-
let alt = $derived(interpolatePrompt(
|
|
30
|
+
let alt = $derived(interpolatePrompt(prompt, variables) ?? "Generated image");
|
|
31
|
+
|
|
32
|
+
$effect(() => {
|
|
33
|
+
const timeout = setTimeout(() => {
|
|
34
|
+
if (src !== computedSrc) {
|
|
35
|
+
src = computedSrc;
|
|
36
|
+
}
|
|
37
|
+
}, 50);
|
|
38
|
+
|
|
39
|
+
return () => clearTimeout(timeout);
|
|
40
|
+
});
|
|
23
41
|
|
|
24
42
|
$effect(() => {
|
|
25
43
|
if (src !== prevSrc) {
|
|
@@ -28,9 +46,45 @@
|
|
|
28
46
|
}
|
|
29
47
|
});
|
|
30
48
|
|
|
49
|
+
$effect(() => {
|
|
50
|
+
if (src) {
|
|
51
|
+
const abortController = new AbortController();
|
|
52
|
+
fetch(src, { method: "HEAD", signal: abortController.signal })
|
|
53
|
+
.then(async (res) => {
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
// console.error(`Failed to load image from ${src}. Status: ${res.status} ${res.statusText}`);
|
|
56
|
+
try {
|
|
57
|
+
const errRes = await fetch(src, {
|
|
58
|
+
signal: abortController.signal,
|
|
59
|
+
});
|
|
60
|
+
const errJson = await errRes.json();
|
|
61
|
+
console.error("SlopImage error:", res.status, errJson.error);
|
|
62
|
+
} catch (e: any) {
|
|
63
|
+
if (e.name !== "AbortError") {
|
|
64
|
+
// Failed to fetch or parse error details
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
isLoading = false;
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
.catch((err) => {
|
|
71
|
+
if (err.name !== "AbortError") {
|
|
72
|
+
console.error(`Error fetching image from ${src}:`, err);
|
|
73
|
+
isLoading = false;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return () => abortController.abort();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
31
81
|
function handleLoad() {
|
|
32
82
|
isLoading = false;
|
|
33
83
|
}
|
|
84
|
+
|
|
85
|
+
function handleError() {
|
|
86
|
+
isLoading = false;
|
|
87
|
+
}
|
|
34
88
|
</script>
|
|
35
89
|
|
|
36
90
|
<div
|
|
@@ -46,7 +100,8 @@
|
|
|
46
100
|
fill="none"
|
|
47
101
|
viewBox="0 0 24 24"
|
|
48
102
|
>
|
|
49
|
-
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
|
|
103
|
+
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"
|
|
104
|
+
></circle>
|
|
50
105
|
<path
|
|
51
106
|
fill="currentColor"
|
|
52
107
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
@@ -62,6 +117,7 @@
|
|
|
62
117
|
{src}
|
|
63
118
|
{alt}
|
|
64
119
|
onload={handleLoad}
|
|
120
|
+
onerror={handleError}
|
|
65
121
|
class:loaded={!isLoading}
|
|
66
122
|
{...restProps}
|
|
67
123
|
/>
|
|
@@ -78,7 +134,10 @@
|
|
|
78
134
|
|
|
79
135
|
.loading-overlay {
|
|
80
136
|
position: absolute;
|
|
81
|
-
top: 0;
|
|
137
|
+
top: 0;
|
|
138
|
+
left: 0;
|
|
139
|
+
right: 0;
|
|
140
|
+
bottom: 0;
|
|
82
141
|
z-index: 10;
|
|
83
142
|
display: flex;
|
|
84
143
|
align-items: center;
|
|
@@ -100,8 +159,12 @@
|
|
|
100
159
|
color: var(--muted-foreground, #6b7280);
|
|
101
160
|
animation: slop-spin 1s linear infinite;
|
|
102
161
|
}
|
|
103
|
-
.spinner circle {
|
|
104
|
-
|
|
162
|
+
.spinner circle {
|
|
163
|
+
opacity: 0.25;
|
|
164
|
+
}
|
|
165
|
+
.spinner path {
|
|
166
|
+
opacity: 0.75;
|
|
167
|
+
}
|
|
105
168
|
|
|
106
169
|
.spinner-container span {
|
|
107
170
|
font-size: 0.75rem;
|
|
@@ -110,7 +173,10 @@
|
|
|
110
173
|
|
|
111
174
|
.shimmer-effect {
|
|
112
175
|
position: absolute;
|
|
113
|
-
top: 0;
|
|
176
|
+
top: 0;
|
|
177
|
+
left: 0;
|
|
178
|
+
right: 0;
|
|
179
|
+
bottom: 0;
|
|
114
180
|
background: linear-gradient(
|
|
115
181
|
90deg,
|
|
116
182
|
var(--muted, #f3f4f6) 0%,
|
|
@@ -134,12 +200,20 @@
|
|
|
134
200
|
}
|
|
135
201
|
|
|
136
202
|
@keyframes slop-shimmer {
|
|
137
|
-
0% {
|
|
138
|
-
|
|
203
|
+
0% {
|
|
204
|
+
background-position: 200% 0;
|
|
205
|
+
}
|
|
206
|
+
100% {
|
|
207
|
+
background-position: -200% 0;
|
|
208
|
+
}
|
|
139
209
|
}
|
|
140
210
|
|
|
141
211
|
@keyframes slop-spin {
|
|
142
|
-
from {
|
|
143
|
-
|
|
212
|
+
from {
|
|
213
|
+
transform: rotate(0deg);
|
|
214
|
+
}
|
|
215
|
+
to {
|
|
216
|
+
transform: rotate(360deg);
|
|
217
|
+
}
|
|
144
218
|
}
|
|
145
|
-
</style>
|
|
219
|
+
</style>
|