@sparkstudio/storage-ui 1.0.30 → 1.0.32
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/dist/index.cjs +531 -17
- package/dist/index.d.cts +18 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +529 -17
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -34,6 +34,8 @@ __export(index_exports, {
|
|
|
34
34
|
HomeView: () => HomeView,
|
|
35
35
|
S3: () => S3,
|
|
36
36
|
SingleFileProcessUploader: () => SingleFileProcessUploader,
|
|
37
|
+
SingleImageUploadEditor: () => SingleImageUploadEditor,
|
|
38
|
+
SingleImageView: () => SingleImageView,
|
|
37
39
|
SparkStudioStorageSDK: () => SparkStudioStorageSDK,
|
|
38
40
|
TemporaryFileDTO: () => TemporaryFileDTO,
|
|
39
41
|
UploadContainer: () => UploadContainer,
|
|
@@ -1921,29 +1923,539 @@ var SingleFileProcessUploader = ({
|
|
|
1921
1923
|
] });
|
|
1922
1924
|
};
|
|
1923
1925
|
|
|
1924
|
-
// src/
|
|
1926
|
+
// src/components/SingleImageUploadEditor.tsx
|
|
1925
1927
|
var import_react13 = require("react");
|
|
1926
|
-
var import_authentication_ui = require("@sparkstudio/authentication-ui");
|
|
1927
1928
|
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1929
|
+
function SingleImageUploadEditor(props) {
|
|
1930
|
+
const {
|
|
1931
|
+
containerApiBaseUrl,
|
|
1932
|
+
storageApiBaseUrl,
|
|
1933
|
+
value = null,
|
|
1934
|
+
onChange,
|
|
1935
|
+
height = 280,
|
|
1936
|
+
disabled = false
|
|
1937
|
+
} = props;
|
|
1938
|
+
const [isDragging, setIsDragging] = (0, import_react13.useState)(false);
|
|
1939
|
+
const [error, setError] = (0, import_react13.useState)(null);
|
|
1940
|
+
const pendingContainerRef = (0, import_react13.useRef)(null);
|
|
1941
|
+
const [isDeleting, setIsDeleting] = (0, import_react13.useState)(false);
|
|
1942
|
+
const [isLoadingImage, setIsLoadingImage] = (0, import_react13.useState)(false);
|
|
1943
|
+
const [currentImage, setCurrentImage] = (0, import_react13.useState)(null);
|
|
1944
|
+
const sdkDb = (0, import_react13.useMemo)(
|
|
1945
|
+
() => new SparkStudioStorageSDK(containerApiBaseUrl),
|
|
1946
|
+
[containerApiBaseUrl]
|
|
1947
|
+
);
|
|
1948
|
+
const sdkS3 = (0, import_react13.useMemo)(
|
|
1949
|
+
() => new SparkStudioStorageSDK(storageApiBaseUrl),
|
|
1950
|
+
[storageApiBaseUrl]
|
|
1951
|
+
);
|
|
1952
|
+
const busy = disabled || isDeleting || isLoadingImage;
|
|
1953
|
+
function logError(context, err) {
|
|
1954
|
+
console.error(`[SingleImageUploadEditor] ${context}`, err);
|
|
1955
|
+
}
|
|
1956
|
+
function resetState() {
|
|
1957
|
+
setCurrentImage(null);
|
|
1958
|
+
setError(null);
|
|
1959
|
+
pendingContainerRef.current = null;
|
|
1960
|
+
}
|
|
1961
|
+
(0, import_react13.useEffect)(() => {
|
|
1962
|
+
let cancelled = false;
|
|
1963
|
+
async function loadImage() {
|
|
1964
|
+
if (!value) {
|
|
1965
|
+
resetState();
|
|
1966
|
+
return;
|
|
1967
|
+
}
|
|
1968
|
+
try {
|
|
1969
|
+
setError(null);
|
|
1970
|
+
setIsLoadingImage(true);
|
|
1971
|
+
const image = await sdkDb.container.Read(value);
|
|
1972
|
+
if (cancelled) {
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
setCurrentImage(image ?? null);
|
|
1976
|
+
} catch (err) {
|
|
1977
|
+
if (cancelled) {
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
logError(`Failed to load image for value '${String(value)}'`, err);
|
|
1981
|
+
resetState();
|
|
1982
|
+
} finally {
|
|
1983
|
+
if (!cancelled) {
|
|
1984
|
+
setIsLoadingImage(false);
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
loadImage();
|
|
1989
|
+
return () => {
|
|
1990
|
+
cancelled = true;
|
|
1991
|
+
};
|
|
1992
|
+
}, [value, sdkDb]);
|
|
1993
|
+
function getErrorMessage(err, fallback) {
|
|
1994
|
+
return err instanceof Error ? err.message : fallback;
|
|
1995
|
+
}
|
|
1996
|
+
function isImageFile(file) {
|
|
1997
|
+
return file.type.startsWith("image/");
|
|
1998
|
+
}
|
|
1999
|
+
async function createContainerForFile(file) {
|
|
2000
|
+
const contentType = file.type || "application/octet-stream";
|
|
2001
|
+
return sdkDb.container.CreateFileContainer(
|
|
2002
|
+
file.name,
|
|
2003
|
+
file.size,
|
|
2004
|
+
encodeURIComponent(contentType)
|
|
2005
|
+
);
|
|
2006
|
+
}
|
|
2007
|
+
async function getPresignedUrl(file) {
|
|
2008
|
+
const container = await createContainerForFile(file);
|
|
2009
|
+
pendingContainerRef.current = container;
|
|
2010
|
+
let lastError;
|
|
2011
|
+
for (let i = 1; i <= 3; i++) {
|
|
2012
|
+
try {
|
|
2013
|
+
return await sdkS3.s3.GetPreSignedUrl(container);
|
|
2014
|
+
} catch (e) {
|
|
2015
|
+
lastError = e;
|
|
2016
|
+
if (i < 3) {
|
|
2017
|
+
await new Promise((r) => setTimeout(r, 500 * i));
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
throw lastError instanceof Error ? lastError : new Error("Failed to fetch presigned URL");
|
|
2022
|
+
}
|
|
2023
|
+
async function deleteContainerFile(fileId) {
|
|
2024
|
+
const fileContainer = await sdkDb.container.Read(fileId);
|
|
2025
|
+
await sdkDb.container.DeleteContainer(fileId);
|
|
2026
|
+
await sdkS3.s3.DeleteS3(fileContainer);
|
|
2027
|
+
}
|
|
2028
|
+
const { uploads, startUploadsIfNeeded } = UseUploadManager({
|
|
2029
|
+
autoUpload: true,
|
|
2030
|
+
getPresignedUrl,
|
|
2031
|
+
onUploadComplete: async () => {
|
|
2032
|
+
setError(null);
|
|
2033
|
+
const uploaded = pendingContainerRef.current;
|
|
2034
|
+
if (uploaded) {
|
|
2035
|
+
setCurrentImage(uploaded);
|
|
2036
|
+
onChange?.(uploaded.Id);
|
|
2037
|
+
}
|
|
2038
|
+
pendingContainerRef.current = null;
|
|
2039
|
+
},
|
|
2040
|
+
onUploadError: async (_file, err) => {
|
|
2041
|
+
const pending = pendingContainerRef.current;
|
|
2042
|
+
if (pending) {
|
|
2043
|
+
try {
|
|
2044
|
+
await sdkDb.container.DeleteContainer(pending.Id);
|
|
2045
|
+
} catch (cleanupErr) {
|
|
2046
|
+
logError("Failed to cleanup pending container after upload error", cleanupErr);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
pendingContainerRef.current = null;
|
|
2050
|
+
logError("Upload failed", err);
|
|
2051
|
+
setCurrentImage(null);
|
|
2052
|
+
setError(getErrorMessage(err, "Upload failed"));
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
const visibleUploads = uploads.filter((u) => u.status !== "success");
|
|
2056
|
+
const activeUpload = visibleUploads[0];
|
|
2057
|
+
const showBottomProgress = !!activeUpload;
|
|
2058
|
+
async function replaceWithFile(file) {
|
|
2059
|
+
if (busy) {
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
if (!isImageFile(file)) {
|
|
2063
|
+
setError("Please select an image file.");
|
|
2064
|
+
return;
|
|
2065
|
+
}
|
|
2066
|
+
try {
|
|
2067
|
+
setError(null);
|
|
2068
|
+
if (value) {
|
|
2069
|
+
setIsDeleting(true);
|
|
2070
|
+
try {
|
|
2071
|
+
await deleteContainerFile(value);
|
|
2072
|
+
} catch (err) {
|
|
2073
|
+
logError(`Failed to delete existing container '${String(value)}'`, err);
|
|
2074
|
+
}
|
|
2075
|
+
setCurrentImage(null);
|
|
2076
|
+
onChange?.(null);
|
|
2077
|
+
}
|
|
2078
|
+
const dt = new DataTransfer();
|
|
2079
|
+
dt.items.add(file);
|
|
2080
|
+
startUploadsIfNeeded(dt.files);
|
|
2081
|
+
} catch (err) {
|
|
2082
|
+
logError("Failed to replace image", err);
|
|
2083
|
+
resetState();
|
|
2084
|
+
setError(getErrorMessage(err, "Failed to replace image."));
|
|
2085
|
+
} finally {
|
|
2086
|
+
setIsDeleting(false);
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
function openPicker() {
|
|
2090
|
+
if (busy) {
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
const input = document.createElement("input");
|
|
2094
|
+
input.type = "file";
|
|
2095
|
+
input.accept = "image/*";
|
|
2096
|
+
input.multiple = false;
|
|
2097
|
+
input.onchange = async () => {
|
|
2098
|
+
const file = input.files?.[0];
|
|
2099
|
+
if (!file) {
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
await replaceWithFile(file);
|
|
2103
|
+
};
|
|
2104
|
+
input.click();
|
|
2105
|
+
}
|
|
2106
|
+
async function handleRemove() {
|
|
2107
|
+
if (!value || busy) {
|
|
2108
|
+
return;
|
|
2109
|
+
}
|
|
2110
|
+
try {
|
|
2111
|
+
setError(null);
|
|
2112
|
+
setIsDeleting(true);
|
|
2113
|
+
try {
|
|
2114
|
+
await deleteContainerFile(value);
|
|
2115
|
+
} catch (err) {
|
|
2116
|
+
logError(`Failed to delete container '${String(value)}'`, err);
|
|
2117
|
+
}
|
|
2118
|
+
resetState();
|
|
2119
|
+
onChange?.(null);
|
|
2120
|
+
} catch (err) {
|
|
2121
|
+
logError("Failed to delete image", err);
|
|
2122
|
+
resetState();
|
|
2123
|
+
} finally {
|
|
2124
|
+
setIsDeleting(false);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { display: "grid", gap: 8 }, children: [
|
|
2128
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 12, color: "crimson" }, children: error }) : null,
|
|
2129
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2130
|
+
"div",
|
|
2131
|
+
{
|
|
2132
|
+
onDragEnter: (e) => {
|
|
2133
|
+
e.preventDefault();
|
|
2134
|
+
e.stopPropagation();
|
|
2135
|
+
if (!busy) {
|
|
2136
|
+
setIsDragging(true);
|
|
2137
|
+
}
|
|
2138
|
+
},
|
|
2139
|
+
onDragOver: (e) => {
|
|
2140
|
+
e.preventDefault();
|
|
2141
|
+
e.stopPropagation();
|
|
2142
|
+
if (!busy) {
|
|
2143
|
+
setIsDragging(true);
|
|
2144
|
+
}
|
|
2145
|
+
},
|
|
2146
|
+
onDragLeave: (e) => {
|
|
2147
|
+
e.preventDefault();
|
|
2148
|
+
e.stopPropagation();
|
|
2149
|
+
setIsDragging(false);
|
|
2150
|
+
},
|
|
2151
|
+
onDrop: async (e) => {
|
|
2152
|
+
e.preventDefault();
|
|
2153
|
+
e.stopPropagation();
|
|
2154
|
+
setIsDragging(false);
|
|
2155
|
+
if (busy) {
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
const file = e.dataTransfer.files?.[0];
|
|
2159
|
+
if (!file) {
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
await replaceWithFile(file);
|
|
2163
|
+
},
|
|
2164
|
+
style: {
|
|
2165
|
+
position: "relative",
|
|
2166
|
+
borderRadius: 14,
|
|
2167
|
+
border: isDragging ? "2px dashed rgba(13,110,253,0.9)" : "2px dashed rgba(0,0,0,0.15)",
|
|
2168
|
+
background: isDragging ? "rgba(13,110,253,0.06)" : "transparent",
|
|
2169
|
+
padding: 12,
|
|
2170
|
+
minHeight: height,
|
|
2171
|
+
overflow: "hidden"
|
|
2172
|
+
},
|
|
2173
|
+
children: [
|
|
2174
|
+
isDragging ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2175
|
+
"div",
|
|
2176
|
+
{
|
|
2177
|
+
style: {
|
|
2178
|
+
position: "absolute",
|
|
2179
|
+
inset: 0,
|
|
2180
|
+
borderRadius: 14,
|
|
2181
|
+
display: "grid",
|
|
2182
|
+
placeItems: "center",
|
|
2183
|
+
pointerEvents: "none",
|
|
2184
|
+
background: "rgba(13,110,253,0.08)",
|
|
2185
|
+
fontWeight: 800,
|
|
2186
|
+
zIndex: 3
|
|
2187
|
+
},
|
|
2188
|
+
children: "Drop image to replace"
|
|
2189
|
+
}
|
|
2190
|
+
) : null,
|
|
2191
|
+
currentImage ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
|
|
2192
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2193
|
+
"div",
|
|
2194
|
+
{
|
|
2195
|
+
style: {
|
|
2196
|
+
border: "1px solid rgba(0,0,0,0.08)",
|
|
2197
|
+
borderRadius: 12,
|
|
2198
|
+
overflow: "hidden",
|
|
2199
|
+
background: "rgba(0,0,0,0.03)",
|
|
2200
|
+
minHeight: height
|
|
2201
|
+
},
|
|
2202
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2203
|
+
"img",
|
|
2204
|
+
{
|
|
2205
|
+
src: currentImage.PublicUrl ?? "",
|
|
2206
|
+
alt: currentImage.Name ?? "Uploaded image",
|
|
2207
|
+
style: {
|
|
2208
|
+
display: "block",
|
|
2209
|
+
width: "100%",
|
|
2210
|
+
height,
|
|
2211
|
+
objectFit: "contain",
|
|
2212
|
+
background: "rgba(0,0,0,0.02)"
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
)
|
|
2216
|
+
}
|
|
2217
|
+
),
|
|
2218
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2219
|
+
"div",
|
|
2220
|
+
{
|
|
2221
|
+
style: {
|
|
2222
|
+
position: "absolute",
|
|
2223
|
+
top: 20,
|
|
2224
|
+
right: 20,
|
|
2225
|
+
display: "flex",
|
|
2226
|
+
gap: 8,
|
|
2227
|
+
zIndex: 2
|
|
2228
|
+
},
|
|
2229
|
+
children: [
|
|
2230
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2231
|
+
"button",
|
|
2232
|
+
{
|
|
2233
|
+
type: "button",
|
|
2234
|
+
onClick: openPicker,
|
|
2235
|
+
disabled: busy,
|
|
2236
|
+
style: {
|
|
2237
|
+
border: "1px solid rgba(0,0,0,0.18)",
|
|
2238
|
+
background: "white",
|
|
2239
|
+
borderRadius: 10,
|
|
2240
|
+
padding: "8px 10px",
|
|
2241
|
+
cursor: busy ? "not-allowed" : "pointer",
|
|
2242
|
+
fontWeight: 600,
|
|
2243
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.08)"
|
|
2244
|
+
},
|
|
2245
|
+
children: "Browse\u2026"
|
|
2246
|
+
}
|
|
2247
|
+
),
|
|
2248
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2249
|
+
"button",
|
|
2250
|
+
{
|
|
2251
|
+
type: "button",
|
|
2252
|
+
onClick: handleRemove,
|
|
2253
|
+
disabled: busy,
|
|
2254
|
+
style: {
|
|
2255
|
+
border: "1px solid rgba(220,53,69,0.35)",
|
|
2256
|
+
background: "white",
|
|
2257
|
+
color: "#dc3545",
|
|
2258
|
+
borderRadius: 10,
|
|
2259
|
+
padding: "8px 10px",
|
|
2260
|
+
cursor: busy ? "not-allowed" : "pointer",
|
|
2261
|
+
fontWeight: 600,
|
|
2262
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.08)"
|
|
2263
|
+
},
|
|
2264
|
+
children: "Remove"
|
|
2265
|
+
}
|
|
2266
|
+
)
|
|
2267
|
+
]
|
|
2268
|
+
}
|
|
2269
|
+
)
|
|
2270
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2271
|
+
"div",
|
|
2272
|
+
{
|
|
2273
|
+
style: {
|
|
2274
|
+
minHeight: height,
|
|
2275
|
+
borderRadius: 12,
|
|
2276
|
+
border: "1px solid rgba(0,0,0,0.08)",
|
|
2277
|
+
display: "grid",
|
|
2278
|
+
placeItems: "center",
|
|
2279
|
+
background: "rgba(0,0,0,0.02)",
|
|
2280
|
+
textAlign: "center",
|
|
2281
|
+
padding: 24
|
|
2282
|
+
},
|
|
2283
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
2284
|
+
"div",
|
|
2285
|
+
{
|
|
2286
|
+
style: {
|
|
2287
|
+
display: "grid",
|
|
2288
|
+
justifyItems: "center",
|
|
2289
|
+
gap: 10
|
|
2290
|
+
},
|
|
2291
|
+
children: [
|
|
2292
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2293
|
+
"button",
|
|
2294
|
+
{
|
|
2295
|
+
type: "button",
|
|
2296
|
+
onClick: openPicker,
|
|
2297
|
+
disabled: busy,
|
|
2298
|
+
style: {
|
|
2299
|
+
border: "1px solid rgba(0,0,0,0.18)",
|
|
2300
|
+
background: "white",
|
|
2301
|
+
borderRadius: 10,
|
|
2302
|
+
padding: "10px 16px",
|
|
2303
|
+
cursor: busy ? "not-allowed" : "pointer",
|
|
2304
|
+
fontWeight: 600
|
|
2305
|
+
},
|
|
2306
|
+
children: "Browse\u2026"
|
|
2307
|
+
}
|
|
2308
|
+
),
|
|
2309
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 13, opacity: 0.7 }, children: "Drag and drop an image here" })
|
|
2310
|
+
]
|
|
2311
|
+
}
|
|
2312
|
+
)
|
|
2313
|
+
}
|
|
2314
|
+
),
|
|
2315
|
+
showBottomProgress ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2316
|
+
"div",
|
|
2317
|
+
{
|
|
2318
|
+
style: {
|
|
2319
|
+
position: "absolute",
|
|
2320
|
+
left: 0,
|
|
2321
|
+
right: 0,
|
|
2322
|
+
bottom: 0,
|
|
2323
|
+
height: 4,
|
|
2324
|
+
background: "rgba(0,0,0,0.08)",
|
|
2325
|
+
zIndex: 4
|
|
2326
|
+
},
|
|
2327
|
+
title: activeUpload.file.name,
|
|
2328
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
2329
|
+
"div",
|
|
2330
|
+
{
|
|
2331
|
+
role: "progressbar",
|
|
2332
|
+
"aria-valuenow": activeUpload.progress,
|
|
2333
|
+
"aria-valuemin": 0,
|
|
2334
|
+
"aria-valuemax": 100,
|
|
2335
|
+
style: {
|
|
2336
|
+
height: "100%",
|
|
2337
|
+
width: `${activeUpload.progress}%`,
|
|
2338
|
+
transition: "width 120ms linear",
|
|
2339
|
+
background: activeUpload.status === "error" ? "#dc3545" : "#0d6efd"
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
)
|
|
2343
|
+
}
|
|
2344
|
+
) : null
|
|
2345
|
+
]
|
|
2346
|
+
}
|
|
2347
|
+
)
|
|
2348
|
+
] });
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
// src/components/SingleImageView.tsx
|
|
2352
|
+
var import_react14 = require("react");
|
|
2353
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
2354
|
+
function SingleImageView(props) {
|
|
2355
|
+
const { containerApiBaseUrl, value, height = 280, onLoaded } = props;
|
|
2356
|
+
const [imageUrl, setImageUrl] = (0, import_react14.useState)(null);
|
|
2357
|
+
const [loading, setLoading] = (0, import_react14.useState)(false);
|
|
2358
|
+
const sdk = (0, import_react14.useMemo)(
|
|
2359
|
+
() => new SparkStudioStorageSDK(containerApiBaseUrl),
|
|
2360
|
+
[containerApiBaseUrl]
|
|
2361
|
+
);
|
|
2362
|
+
(0, import_react14.useEffect)(() => {
|
|
2363
|
+
let cancelled = false;
|
|
2364
|
+
async function load() {
|
|
2365
|
+
if (!value) {
|
|
2366
|
+
setLoading(false);
|
|
2367
|
+
setImageUrl(null);
|
|
2368
|
+
onLoaded?.(null);
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
try {
|
|
2372
|
+
setLoading(true);
|
|
2373
|
+
const x = await sdk.container.Read(value);
|
|
2374
|
+
const url = x?.PublicUrl ?? null;
|
|
2375
|
+
if (cancelled) {
|
|
2376
|
+
return;
|
|
2377
|
+
}
|
|
2378
|
+
setImageUrl(url);
|
|
2379
|
+
onLoaded?.(url);
|
|
2380
|
+
} catch (err) {
|
|
2381
|
+
if (cancelled) {
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
console.error(`[SingleImageView] Failed to load image '${String(value)}'`, err);
|
|
2385
|
+
setImageUrl(null);
|
|
2386
|
+
onLoaded?.(null);
|
|
2387
|
+
} finally {
|
|
2388
|
+
if (!cancelled) {
|
|
2389
|
+
setLoading(false);
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
load();
|
|
2394
|
+
return () => {
|
|
2395
|
+
cancelled = true;
|
|
2396
|
+
};
|
|
2397
|
+
}, [sdk, value, onLoaded]);
|
|
2398
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
|
|
2399
|
+
"div",
|
|
2400
|
+
{
|
|
2401
|
+
style: {
|
|
2402
|
+
height,
|
|
2403
|
+
width: "100%",
|
|
2404
|
+
overflow: "hidden",
|
|
2405
|
+
display: "grid",
|
|
2406
|
+
placeItems: "center",
|
|
2407
|
+
position: "relative"
|
|
2408
|
+
},
|
|
2409
|
+
children: [
|
|
2410
|
+
imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2411
|
+
"img",
|
|
2412
|
+
{
|
|
2413
|
+
src: imageUrl,
|
|
2414
|
+
alt: "Image",
|
|
2415
|
+
style: {
|
|
2416
|
+
width: "100%",
|
|
2417
|
+
height: "100%",
|
|
2418
|
+
objectFit: "cover",
|
|
2419
|
+
display: "block"
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
) : null,
|
|
2423
|
+
loading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
|
|
2424
|
+
"div",
|
|
2425
|
+
{
|
|
2426
|
+
className: "spinner-border text-primary",
|
|
2427
|
+
style: { position: "absolute" }
|
|
2428
|
+
}
|
|
2429
|
+
) : null,
|
|
2430
|
+
!loading && !imageUrl ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: { fontSize: 13, opacity: 0.6 }, children: "No image" }) : null
|
|
2431
|
+
]
|
|
2432
|
+
}
|
|
2433
|
+
);
|
|
2434
|
+
}
|
|
2435
|
+
|
|
2436
|
+
// src/views/HomeView.tsx
|
|
2437
|
+
var import_react15 = require("react");
|
|
2438
|
+
var import_authentication_ui = require("@sparkstudio/authentication-ui");
|
|
2439
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1928
2440
|
var CONTAINER_API = "https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod";
|
|
1929
2441
|
var STORAGE_API = "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod";
|
|
1930
2442
|
function HomeView() {
|
|
1931
|
-
return /* @__PURE__ */ (0,
|
|
2443
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1932
2444
|
import_authentication_ui.AuthenticatorProvider,
|
|
1933
2445
|
{
|
|
1934
2446
|
googleClientId: import_authentication_ui.AppSettings.GoogleClientId,
|
|
1935
2447
|
authenticationUrl: import_authentication_ui.AppSettings.AuthenticationUrl,
|
|
1936
2448
|
accountsUrl: import_authentication_ui.AppSettings.AccountsUrl,
|
|
1937
|
-
children: /* @__PURE__ */ (0,
|
|
2449
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(HomeContent, {})
|
|
1938
2450
|
}
|
|
1939
2451
|
);
|
|
1940
2452
|
}
|
|
1941
2453
|
function HomeContent() {
|
|
1942
2454
|
const { user } = (0, import_authentication_ui.useUser)();
|
|
1943
|
-
const [ids, setIds] = (0,
|
|
1944
|
-
const [selectedId, setSelectedId] = (0,
|
|
1945
|
-
const [selectedFile, setSelectedFile] = (0,
|
|
1946
|
-
(0,
|
|
2455
|
+
const [ids, setIds] = (0, import_react15.useState)([]);
|
|
2456
|
+
const [selectedId, setSelectedId] = (0, import_react15.useState)(void 0);
|
|
2457
|
+
const [selectedFile, setSelectedFile] = (0, import_react15.useState)(null);
|
|
2458
|
+
(0, import_react15.useEffect)(() => {
|
|
1947
2459
|
if (selectedId && !ids.includes(selectedId)) {
|
|
1948
2460
|
setSelectedId(void 0);
|
|
1949
2461
|
setSelectedFile(null);
|
|
@@ -1956,10 +2468,10 @@ function HomeContent() {
|
|
|
1956
2468
|
new TemporaryFileDTO({ Name: file.name, ContentType: contentType })
|
|
1957
2469
|
);
|
|
1958
2470
|
}
|
|
1959
|
-
return /* @__PURE__ */ (0,
|
|
1960
|
-
/* @__PURE__ */ (0,
|
|
1961
|
-
user ? /* @__PURE__ */ (0,
|
|
1962
|
-
/* @__PURE__ */ (0,
|
|
2471
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
2472
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_authentication_ui.UserInfoCard, {}),
|
|
2473
|
+
user ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
|
|
2474
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1963
2475
|
SingleFileProcessUploader,
|
|
1964
2476
|
{
|
|
1965
2477
|
uploadOnDrop: true,
|
|
@@ -1970,7 +2482,7 @@ function HomeContent() {
|
|
|
1970
2482
|
}
|
|
1971
2483
|
}
|
|
1972
2484
|
),
|
|
1973
|
-
/* @__PURE__ */ (0,
|
|
2485
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1974
2486
|
ContainerIdGridPanel,
|
|
1975
2487
|
{
|
|
1976
2488
|
containerApiBaseUrl: CONTAINER_API,
|
|
@@ -1993,7 +2505,7 @@ function HomeContent() {
|
|
|
1993
2505
|
}
|
|
1994
2506
|
}
|
|
1995
2507
|
),
|
|
1996
|
-
selectedFile ? /* @__PURE__ */ (0,
|
|
2508
|
+
selectedFile ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
|
|
1997
2509
|
"div",
|
|
1998
2510
|
{
|
|
1999
2511
|
style: {
|
|
@@ -2003,9 +2515,9 @@ function HomeContent() {
|
|
|
2003
2515
|
borderRadius: 12
|
|
2004
2516
|
},
|
|
2005
2517
|
children: [
|
|
2006
|
-
/* @__PURE__ */ (0,
|
|
2007
|
-
/* @__PURE__ */ (0,
|
|
2008
|
-
/* @__PURE__ */ (0,
|
|
2518
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { fontWeight: 700 }, children: "Selected file" }),
|
|
2519
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { children: selectedFile.Name ?? "(no name)" }),
|
|
2520
|
+
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: { fontSize: 12, opacity: 0.7 }, children: selectedFile.PublicUrl ?? "(no public url)" })
|
|
2009
2521
|
]
|
|
2010
2522
|
}
|
|
2011
2523
|
) : null
|
|
@@ -2028,6 +2540,8 @@ function HomeContent() {
|
|
|
2028
2540
|
HomeView,
|
|
2029
2541
|
S3,
|
|
2030
2542
|
SingleFileProcessUploader,
|
|
2543
|
+
SingleImageUploadEditor,
|
|
2544
|
+
SingleImageView,
|
|
2031
2545
|
SparkStudioStorageSDK,
|
|
2032
2546
|
TemporaryFileDTO,
|
|
2033
2547
|
UploadContainer,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import React__default from 'react';
|
|
4
|
+
import { Guid } from '@sparkstudio/authentication-ui';
|
|
4
5
|
|
|
5
6
|
declare enum ContainerType {
|
|
6
7
|
File = 0,
|
|
@@ -230,6 +231,22 @@ interface SingleFileProcessUploaderProps {
|
|
|
230
231
|
}
|
|
231
232
|
declare const SingleFileProcessUploader: React__default.FC<SingleFileProcessUploaderProps>;
|
|
232
233
|
|
|
234
|
+
declare function SingleImageUploadEditor(props: {
|
|
235
|
+
containerApiBaseUrl: string;
|
|
236
|
+
storageApiBaseUrl: string;
|
|
237
|
+
value?: Guid | null;
|
|
238
|
+
onChange?: (fileId: Guid | null) => void;
|
|
239
|
+
height?: number;
|
|
240
|
+
disabled?: boolean;
|
|
241
|
+
}): react_jsx_runtime.JSX.Element;
|
|
242
|
+
|
|
243
|
+
declare function SingleImageView(props: {
|
|
244
|
+
containerApiBaseUrl: string;
|
|
245
|
+
value: Guid | null;
|
|
246
|
+
height?: number;
|
|
247
|
+
onLoaded?: (url: string | null) => void;
|
|
248
|
+
}): react_jsx_runtime.JSX.Element;
|
|
249
|
+
|
|
233
250
|
interface UploadDropzoneProps {
|
|
234
251
|
isDragging: boolean;
|
|
235
252
|
onDragOver?: (e: React__default.DragEvent<HTMLDivElement>) => void;
|
|
@@ -288,4 +305,4 @@ declare function UseUploadManager({ autoUpload, getPresignedUrl, onUploadComplet
|
|
|
288
305
|
|
|
289
306
|
declare function HomeView(): react_jsx_runtime.JSX.Element;
|
|
290
307
|
|
|
291
|
-
export { AWSPresignedUrlDTO, Container, ContainerDTO, ContainerIdGridPanel, ContainerType, ContainerUploadPanel, DesktopFileIcon, type DesktopFileIconProps, FileGridUploadPanel, FileIconCard, type FileIconCardProps, FileIconGrid, Home, HomeView, type IAWSPresignedUrlDTO, type IContainerDTO, type ITemporaryFileDTO, S3, SingleFileProcessUploader, type SingleFileProcessUploaderProps, SparkStudioStorageSDK, TemporaryFileDTO, UploadContainer, type UploadContainerHandle, type UploadContainerProps, UploadDropzone, UploadFileToS3, UploadProgressList, type UploadState, type UploadStatus, UseContainers, UseUploadManager };
|
|
308
|
+
export { AWSPresignedUrlDTO, Container, ContainerDTO, ContainerIdGridPanel, ContainerType, ContainerUploadPanel, DesktopFileIcon, type DesktopFileIconProps, FileGridUploadPanel, FileIconCard, type FileIconCardProps, FileIconGrid, Home, HomeView, type IAWSPresignedUrlDTO, type IContainerDTO, type ITemporaryFileDTO, S3, SingleFileProcessUploader, type SingleFileProcessUploaderProps, SingleImageUploadEditor, SingleImageView, SparkStudioStorageSDK, TemporaryFileDTO, UploadContainer, type UploadContainerHandle, type UploadContainerProps, UploadDropzone, UploadFileToS3, UploadProgressList, type UploadState, type UploadStatus, UseContainers, UseUploadManager };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import * as React from 'react';
|
|
3
3
|
import React__default from 'react';
|
|
4
|
+
import { Guid } from '@sparkstudio/authentication-ui';
|
|
4
5
|
|
|
5
6
|
declare enum ContainerType {
|
|
6
7
|
File = 0,
|
|
@@ -230,6 +231,22 @@ interface SingleFileProcessUploaderProps {
|
|
|
230
231
|
}
|
|
231
232
|
declare const SingleFileProcessUploader: React__default.FC<SingleFileProcessUploaderProps>;
|
|
232
233
|
|
|
234
|
+
declare function SingleImageUploadEditor(props: {
|
|
235
|
+
containerApiBaseUrl: string;
|
|
236
|
+
storageApiBaseUrl: string;
|
|
237
|
+
value?: Guid | null;
|
|
238
|
+
onChange?: (fileId: Guid | null) => void;
|
|
239
|
+
height?: number;
|
|
240
|
+
disabled?: boolean;
|
|
241
|
+
}): react_jsx_runtime.JSX.Element;
|
|
242
|
+
|
|
243
|
+
declare function SingleImageView(props: {
|
|
244
|
+
containerApiBaseUrl: string;
|
|
245
|
+
value: Guid | null;
|
|
246
|
+
height?: number;
|
|
247
|
+
onLoaded?: (url: string | null) => void;
|
|
248
|
+
}): react_jsx_runtime.JSX.Element;
|
|
249
|
+
|
|
233
250
|
interface UploadDropzoneProps {
|
|
234
251
|
isDragging: boolean;
|
|
235
252
|
onDragOver?: (e: React__default.DragEvent<HTMLDivElement>) => void;
|
|
@@ -288,4 +305,4 @@ declare function UseUploadManager({ autoUpload, getPresignedUrl, onUploadComplet
|
|
|
288
305
|
|
|
289
306
|
declare function HomeView(): react_jsx_runtime.JSX.Element;
|
|
290
307
|
|
|
291
|
-
export { AWSPresignedUrlDTO, Container, ContainerDTO, ContainerIdGridPanel, ContainerType, ContainerUploadPanel, DesktopFileIcon, type DesktopFileIconProps, FileGridUploadPanel, FileIconCard, type FileIconCardProps, FileIconGrid, Home, HomeView, type IAWSPresignedUrlDTO, type IContainerDTO, type ITemporaryFileDTO, S3, SingleFileProcessUploader, type SingleFileProcessUploaderProps, SparkStudioStorageSDK, TemporaryFileDTO, UploadContainer, type UploadContainerHandle, type UploadContainerProps, UploadDropzone, UploadFileToS3, UploadProgressList, type UploadState, type UploadStatus, UseContainers, UseUploadManager };
|
|
308
|
+
export { AWSPresignedUrlDTO, Container, ContainerDTO, ContainerIdGridPanel, ContainerType, ContainerUploadPanel, DesktopFileIcon, type DesktopFileIconProps, FileGridUploadPanel, FileIconCard, type FileIconCardProps, FileIconGrid, Home, HomeView, type IAWSPresignedUrlDTO, type IContainerDTO, type ITemporaryFileDTO, S3, SingleFileProcessUploader, type SingleFileProcessUploaderProps, SingleImageUploadEditor, SingleImageView, SparkStudioStorageSDK, TemporaryFileDTO, UploadContainer, type UploadContainerHandle, type UploadContainerProps, UploadDropzone, UploadFileToS3, UploadProgressList, type UploadState, type UploadStatus, UseContainers, UseUploadManager };
|
package/dist/index.js
CHANGED
|
@@ -1890,34 +1890,544 @@ var SingleFileProcessUploader = ({
|
|
|
1890
1890
|
] });
|
|
1891
1891
|
};
|
|
1892
1892
|
|
|
1893
|
+
// src/components/SingleImageUploadEditor.tsx
|
|
1894
|
+
import { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef5, useState as useState9 } from "react";
|
|
1895
|
+
import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
1896
|
+
function SingleImageUploadEditor(props) {
|
|
1897
|
+
const {
|
|
1898
|
+
containerApiBaseUrl,
|
|
1899
|
+
storageApiBaseUrl,
|
|
1900
|
+
value = null,
|
|
1901
|
+
onChange,
|
|
1902
|
+
height = 280,
|
|
1903
|
+
disabled = false
|
|
1904
|
+
} = props;
|
|
1905
|
+
const [isDragging, setIsDragging] = useState9(false);
|
|
1906
|
+
const [error, setError] = useState9(null);
|
|
1907
|
+
const pendingContainerRef = useRef5(null);
|
|
1908
|
+
const [isDeleting, setIsDeleting] = useState9(false);
|
|
1909
|
+
const [isLoadingImage, setIsLoadingImage] = useState9(false);
|
|
1910
|
+
const [currentImage, setCurrentImage] = useState9(null);
|
|
1911
|
+
const sdkDb = useMemo5(
|
|
1912
|
+
() => new SparkStudioStorageSDK(containerApiBaseUrl),
|
|
1913
|
+
[containerApiBaseUrl]
|
|
1914
|
+
);
|
|
1915
|
+
const sdkS3 = useMemo5(
|
|
1916
|
+
() => new SparkStudioStorageSDK(storageApiBaseUrl),
|
|
1917
|
+
[storageApiBaseUrl]
|
|
1918
|
+
);
|
|
1919
|
+
const busy = disabled || isDeleting || isLoadingImage;
|
|
1920
|
+
function logError(context, err) {
|
|
1921
|
+
console.error(`[SingleImageUploadEditor] ${context}`, err);
|
|
1922
|
+
}
|
|
1923
|
+
function resetState() {
|
|
1924
|
+
setCurrentImage(null);
|
|
1925
|
+
setError(null);
|
|
1926
|
+
pendingContainerRef.current = null;
|
|
1927
|
+
}
|
|
1928
|
+
useEffect4(() => {
|
|
1929
|
+
let cancelled = false;
|
|
1930
|
+
async function loadImage() {
|
|
1931
|
+
if (!value) {
|
|
1932
|
+
resetState();
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
try {
|
|
1936
|
+
setError(null);
|
|
1937
|
+
setIsLoadingImage(true);
|
|
1938
|
+
const image = await sdkDb.container.Read(value);
|
|
1939
|
+
if (cancelled) {
|
|
1940
|
+
return;
|
|
1941
|
+
}
|
|
1942
|
+
setCurrentImage(image ?? null);
|
|
1943
|
+
} catch (err) {
|
|
1944
|
+
if (cancelled) {
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
logError(`Failed to load image for value '${String(value)}'`, err);
|
|
1948
|
+
resetState();
|
|
1949
|
+
} finally {
|
|
1950
|
+
if (!cancelled) {
|
|
1951
|
+
setIsLoadingImage(false);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
loadImage();
|
|
1956
|
+
return () => {
|
|
1957
|
+
cancelled = true;
|
|
1958
|
+
};
|
|
1959
|
+
}, [value, sdkDb]);
|
|
1960
|
+
function getErrorMessage(err, fallback) {
|
|
1961
|
+
return err instanceof Error ? err.message : fallback;
|
|
1962
|
+
}
|
|
1963
|
+
function isImageFile(file) {
|
|
1964
|
+
return file.type.startsWith("image/");
|
|
1965
|
+
}
|
|
1966
|
+
async function createContainerForFile(file) {
|
|
1967
|
+
const contentType = file.type || "application/octet-stream";
|
|
1968
|
+
return sdkDb.container.CreateFileContainer(
|
|
1969
|
+
file.name,
|
|
1970
|
+
file.size,
|
|
1971
|
+
encodeURIComponent(contentType)
|
|
1972
|
+
);
|
|
1973
|
+
}
|
|
1974
|
+
async function getPresignedUrl(file) {
|
|
1975
|
+
const container = await createContainerForFile(file);
|
|
1976
|
+
pendingContainerRef.current = container;
|
|
1977
|
+
let lastError;
|
|
1978
|
+
for (let i = 1; i <= 3; i++) {
|
|
1979
|
+
try {
|
|
1980
|
+
return await sdkS3.s3.GetPreSignedUrl(container);
|
|
1981
|
+
} catch (e) {
|
|
1982
|
+
lastError = e;
|
|
1983
|
+
if (i < 3) {
|
|
1984
|
+
await new Promise((r) => setTimeout(r, 500 * i));
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
throw lastError instanceof Error ? lastError : new Error("Failed to fetch presigned URL");
|
|
1989
|
+
}
|
|
1990
|
+
async function deleteContainerFile(fileId) {
|
|
1991
|
+
const fileContainer = await sdkDb.container.Read(fileId);
|
|
1992
|
+
await sdkDb.container.DeleteContainer(fileId);
|
|
1993
|
+
await sdkS3.s3.DeleteS3(fileContainer);
|
|
1994
|
+
}
|
|
1995
|
+
const { uploads, startUploadsIfNeeded } = UseUploadManager({
|
|
1996
|
+
autoUpload: true,
|
|
1997
|
+
getPresignedUrl,
|
|
1998
|
+
onUploadComplete: async () => {
|
|
1999
|
+
setError(null);
|
|
2000
|
+
const uploaded = pendingContainerRef.current;
|
|
2001
|
+
if (uploaded) {
|
|
2002
|
+
setCurrentImage(uploaded);
|
|
2003
|
+
onChange?.(uploaded.Id);
|
|
2004
|
+
}
|
|
2005
|
+
pendingContainerRef.current = null;
|
|
2006
|
+
},
|
|
2007
|
+
onUploadError: async (_file, err) => {
|
|
2008
|
+
const pending = pendingContainerRef.current;
|
|
2009
|
+
if (pending) {
|
|
2010
|
+
try {
|
|
2011
|
+
await sdkDb.container.DeleteContainer(pending.Id);
|
|
2012
|
+
} catch (cleanupErr) {
|
|
2013
|
+
logError("Failed to cleanup pending container after upload error", cleanupErr);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
pendingContainerRef.current = null;
|
|
2017
|
+
logError("Upload failed", err);
|
|
2018
|
+
setCurrentImage(null);
|
|
2019
|
+
setError(getErrorMessage(err, "Upload failed"));
|
|
2020
|
+
}
|
|
2021
|
+
});
|
|
2022
|
+
const visibleUploads = uploads.filter((u) => u.status !== "success");
|
|
2023
|
+
const activeUpload = visibleUploads[0];
|
|
2024
|
+
const showBottomProgress = !!activeUpload;
|
|
2025
|
+
async function replaceWithFile(file) {
|
|
2026
|
+
if (busy) {
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
if (!isImageFile(file)) {
|
|
2030
|
+
setError("Please select an image file.");
|
|
2031
|
+
return;
|
|
2032
|
+
}
|
|
2033
|
+
try {
|
|
2034
|
+
setError(null);
|
|
2035
|
+
if (value) {
|
|
2036
|
+
setIsDeleting(true);
|
|
2037
|
+
try {
|
|
2038
|
+
await deleteContainerFile(value);
|
|
2039
|
+
} catch (err) {
|
|
2040
|
+
logError(`Failed to delete existing container '${String(value)}'`, err);
|
|
2041
|
+
}
|
|
2042
|
+
setCurrentImage(null);
|
|
2043
|
+
onChange?.(null);
|
|
2044
|
+
}
|
|
2045
|
+
const dt = new DataTransfer();
|
|
2046
|
+
dt.items.add(file);
|
|
2047
|
+
startUploadsIfNeeded(dt.files);
|
|
2048
|
+
} catch (err) {
|
|
2049
|
+
logError("Failed to replace image", err);
|
|
2050
|
+
resetState();
|
|
2051
|
+
setError(getErrorMessage(err, "Failed to replace image."));
|
|
2052
|
+
} finally {
|
|
2053
|
+
setIsDeleting(false);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
function openPicker() {
|
|
2057
|
+
if (busy) {
|
|
2058
|
+
return;
|
|
2059
|
+
}
|
|
2060
|
+
const input = document.createElement("input");
|
|
2061
|
+
input.type = "file";
|
|
2062
|
+
input.accept = "image/*";
|
|
2063
|
+
input.multiple = false;
|
|
2064
|
+
input.onchange = async () => {
|
|
2065
|
+
const file = input.files?.[0];
|
|
2066
|
+
if (!file) {
|
|
2067
|
+
return;
|
|
2068
|
+
}
|
|
2069
|
+
await replaceWithFile(file);
|
|
2070
|
+
};
|
|
2071
|
+
input.click();
|
|
2072
|
+
}
|
|
2073
|
+
async function handleRemove() {
|
|
2074
|
+
if (!value || busy) {
|
|
2075
|
+
return;
|
|
2076
|
+
}
|
|
2077
|
+
try {
|
|
2078
|
+
setError(null);
|
|
2079
|
+
setIsDeleting(true);
|
|
2080
|
+
try {
|
|
2081
|
+
await deleteContainerFile(value);
|
|
2082
|
+
} catch (err) {
|
|
2083
|
+
logError(`Failed to delete container '${String(value)}'`, err);
|
|
2084
|
+
}
|
|
2085
|
+
resetState();
|
|
2086
|
+
onChange?.(null);
|
|
2087
|
+
} catch (err) {
|
|
2088
|
+
logError("Failed to delete image", err);
|
|
2089
|
+
resetState();
|
|
2090
|
+
} finally {
|
|
2091
|
+
setIsDeleting(false);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
return /* @__PURE__ */ jsxs8("div", { style: { display: "grid", gap: 8 }, children: [
|
|
2095
|
+
error ? /* @__PURE__ */ jsx11("div", { style: { fontSize: 12, color: "crimson" }, children: error }) : null,
|
|
2096
|
+
/* @__PURE__ */ jsxs8(
|
|
2097
|
+
"div",
|
|
2098
|
+
{
|
|
2099
|
+
onDragEnter: (e) => {
|
|
2100
|
+
e.preventDefault();
|
|
2101
|
+
e.stopPropagation();
|
|
2102
|
+
if (!busy) {
|
|
2103
|
+
setIsDragging(true);
|
|
2104
|
+
}
|
|
2105
|
+
},
|
|
2106
|
+
onDragOver: (e) => {
|
|
2107
|
+
e.preventDefault();
|
|
2108
|
+
e.stopPropagation();
|
|
2109
|
+
if (!busy) {
|
|
2110
|
+
setIsDragging(true);
|
|
2111
|
+
}
|
|
2112
|
+
},
|
|
2113
|
+
onDragLeave: (e) => {
|
|
2114
|
+
e.preventDefault();
|
|
2115
|
+
e.stopPropagation();
|
|
2116
|
+
setIsDragging(false);
|
|
2117
|
+
},
|
|
2118
|
+
onDrop: async (e) => {
|
|
2119
|
+
e.preventDefault();
|
|
2120
|
+
e.stopPropagation();
|
|
2121
|
+
setIsDragging(false);
|
|
2122
|
+
if (busy) {
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
2125
|
+
const file = e.dataTransfer.files?.[0];
|
|
2126
|
+
if (!file) {
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
await replaceWithFile(file);
|
|
2130
|
+
},
|
|
2131
|
+
style: {
|
|
2132
|
+
position: "relative",
|
|
2133
|
+
borderRadius: 14,
|
|
2134
|
+
border: isDragging ? "2px dashed rgba(13,110,253,0.9)" : "2px dashed rgba(0,0,0,0.15)",
|
|
2135
|
+
background: isDragging ? "rgba(13,110,253,0.06)" : "transparent",
|
|
2136
|
+
padding: 12,
|
|
2137
|
+
minHeight: height,
|
|
2138
|
+
overflow: "hidden"
|
|
2139
|
+
},
|
|
2140
|
+
children: [
|
|
2141
|
+
isDragging ? /* @__PURE__ */ jsx11(
|
|
2142
|
+
"div",
|
|
2143
|
+
{
|
|
2144
|
+
style: {
|
|
2145
|
+
position: "absolute",
|
|
2146
|
+
inset: 0,
|
|
2147
|
+
borderRadius: 14,
|
|
2148
|
+
display: "grid",
|
|
2149
|
+
placeItems: "center",
|
|
2150
|
+
pointerEvents: "none",
|
|
2151
|
+
background: "rgba(13,110,253,0.08)",
|
|
2152
|
+
fontWeight: 800,
|
|
2153
|
+
zIndex: 3
|
|
2154
|
+
},
|
|
2155
|
+
children: "Drop image to replace"
|
|
2156
|
+
}
|
|
2157
|
+
) : null,
|
|
2158
|
+
currentImage ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
|
|
2159
|
+
/* @__PURE__ */ jsx11(
|
|
2160
|
+
"div",
|
|
2161
|
+
{
|
|
2162
|
+
style: {
|
|
2163
|
+
border: "1px solid rgba(0,0,0,0.08)",
|
|
2164
|
+
borderRadius: 12,
|
|
2165
|
+
overflow: "hidden",
|
|
2166
|
+
background: "rgba(0,0,0,0.03)",
|
|
2167
|
+
minHeight: height
|
|
2168
|
+
},
|
|
2169
|
+
children: /* @__PURE__ */ jsx11(
|
|
2170
|
+
"img",
|
|
2171
|
+
{
|
|
2172
|
+
src: currentImage.PublicUrl ?? "",
|
|
2173
|
+
alt: currentImage.Name ?? "Uploaded image",
|
|
2174
|
+
style: {
|
|
2175
|
+
display: "block",
|
|
2176
|
+
width: "100%",
|
|
2177
|
+
height,
|
|
2178
|
+
objectFit: "contain",
|
|
2179
|
+
background: "rgba(0,0,0,0.02)"
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
)
|
|
2183
|
+
}
|
|
2184
|
+
),
|
|
2185
|
+
/* @__PURE__ */ jsxs8(
|
|
2186
|
+
"div",
|
|
2187
|
+
{
|
|
2188
|
+
style: {
|
|
2189
|
+
position: "absolute",
|
|
2190
|
+
top: 20,
|
|
2191
|
+
right: 20,
|
|
2192
|
+
display: "flex",
|
|
2193
|
+
gap: 8,
|
|
2194
|
+
zIndex: 2
|
|
2195
|
+
},
|
|
2196
|
+
children: [
|
|
2197
|
+
/* @__PURE__ */ jsx11(
|
|
2198
|
+
"button",
|
|
2199
|
+
{
|
|
2200
|
+
type: "button",
|
|
2201
|
+
onClick: openPicker,
|
|
2202
|
+
disabled: busy,
|
|
2203
|
+
style: {
|
|
2204
|
+
border: "1px solid rgba(0,0,0,0.18)",
|
|
2205
|
+
background: "white",
|
|
2206
|
+
borderRadius: 10,
|
|
2207
|
+
padding: "8px 10px",
|
|
2208
|
+
cursor: busy ? "not-allowed" : "pointer",
|
|
2209
|
+
fontWeight: 600,
|
|
2210
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.08)"
|
|
2211
|
+
},
|
|
2212
|
+
children: "Browse\u2026"
|
|
2213
|
+
}
|
|
2214
|
+
),
|
|
2215
|
+
/* @__PURE__ */ jsx11(
|
|
2216
|
+
"button",
|
|
2217
|
+
{
|
|
2218
|
+
type: "button",
|
|
2219
|
+
onClick: handleRemove,
|
|
2220
|
+
disabled: busy,
|
|
2221
|
+
style: {
|
|
2222
|
+
border: "1px solid rgba(220,53,69,0.35)",
|
|
2223
|
+
background: "white",
|
|
2224
|
+
color: "#dc3545",
|
|
2225
|
+
borderRadius: 10,
|
|
2226
|
+
padding: "8px 10px",
|
|
2227
|
+
cursor: busy ? "not-allowed" : "pointer",
|
|
2228
|
+
fontWeight: 600,
|
|
2229
|
+
boxShadow: "0 2px 8px rgba(0,0,0,0.08)"
|
|
2230
|
+
},
|
|
2231
|
+
children: "Remove"
|
|
2232
|
+
}
|
|
2233
|
+
)
|
|
2234
|
+
]
|
|
2235
|
+
}
|
|
2236
|
+
)
|
|
2237
|
+
] }) : /* @__PURE__ */ jsx11(
|
|
2238
|
+
"div",
|
|
2239
|
+
{
|
|
2240
|
+
style: {
|
|
2241
|
+
minHeight: height,
|
|
2242
|
+
borderRadius: 12,
|
|
2243
|
+
border: "1px solid rgba(0,0,0,0.08)",
|
|
2244
|
+
display: "grid",
|
|
2245
|
+
placeItems: "center",
|
|
2246
|
+
background: "rgba(0,0,0,0.02)",
|
|
2247
|
+
textAlign: "center",
|
|
2248
|
+
padding: 24
|
|
2249
|
+
},
|
|
2250
|
+
children: /* @__PURE__ */ jsxs8(
|
|
2251
|
+
"div",
|
|
2252
|
+
{
|
|
2253
|
+
style: {
|
|
2254
|
+
display: "grid",
|
|
2255
|
+
justifyItems: "center",
|
|
2256
|
+
gap: 10
|
|
2257
|
+
},
|
|
2258
|
+
children: [
|
|
2259
|
+
/* @__PURE__ */ jsx11(
|
|
2260
|
+
"button",
|
|
2261
|
+
{
|
|
2262
|
+
type: "button",
|
|
2263
|
+
onClick: openPicker,
|
|
2264
|
+
disabled: busy,
|
|
2265
|
+
style: {
|
|
2266
|
+
border: "1px solid rgba(0,0,0,0.18)",
|
|
2267
|
+
background: "white",
|
|
2268
|
+
borderRadius: 10,
|
|
2269
|
+
padding: "10px 16px",
|
|
2270
|
+
cursor: busy ? "not-allowed" : "pointer",
|
|
2271
|
+
fontWeight: 600
|
|
2272
|
+
},
|
|
2273
|
+
children: "Browse\u2026"
|
|
2274
|
+
}
|
|
2275
|
+
),
|
|
2276
|
+
/* @__PURE__ */ jsx11("div", { style: { fontSize: 13, opacity: 0.7 }, children: "Drag and drop an image here" })
|
|
2277
|
+
]
|
|
2278
|
+
}
|
|
2279
|
+
)
|
|
2280
|
+
}
|
|
2281
|
+
),
|
|
2282
|
+
showBottomProgress ? /* @__PURE__ */ jsx11(
|
|
2283
|
+
"div",
|
|
2284
|
+
{
|
|
2285
|
+
style: {
|
|
2286
|
+
position: "absolute",
|
|
2287
|
+
left: 0,
|
|
2288
|
+
right: 0,
|
|
2289
|
+
bottom: 0,
|
|
2290
|
+
height: 4,
|
|
2291
|
+
background: "rgba(0,0,0,0.08)",
|
|
2292
|
+
zIndex: 4
|
|
2293
|
+
},
|
|
2294
|
+
title: activeUpload.file.name,
|
|
2295
|
+
children: /* @__PURE__ */ jsx11(
|
|
2296
|
+
"div",
|
|
2297
|
+
{
|
|
2298
|
+
role: "progressbar",
|
|
2299
|
+
"aria-valuenow": activeUpload.progress,
|
|
2300
|
+
"aria-valuemin": 0,
|
|
2301
|
+
"aria-valuemax": 100,
|
|
2302
|
+
style: {
|
|
2303
|
+
height: "100%",
|
|
2304
|
+
width: `${activeUpload.progress}%`,
|
|
2305
|
+
transition: "width 120ms linear",
|
|
2306
|
+
background: activeUpload.status === "error" ? "#dc3545" : "#0d6efd"
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
)
|
|
2310
|
+
}
|
|
2311
|
+
) : null
|
|
2312
|
+
]
|
|
2313
|
+
}
|
|
2314
|
+
)
|
|
2315
|
+
] });
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
// src/components/SingleImageView.tsx
|
|
2319
|
+
import { useEffect as useEffect5, useMemo as useMemo6, useState as useState10 } from "react";
|
|
2320
|
+
import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
2321
|
+
function SingleImageView(props) {
|
|
2322
|
+
const { containerApiBaseUrl, value, height = 280, onLoaded } = props;
|
|
2323
|
+
const [imageUrl, setImageUrl] = useState10(null);
|
|
2324
|
+
const [loading, setLoading] = useState10(false);
|
|
2325
|
+
const sdk = useMemo6(
|
|
2326
|
+
() => new SparkStudioStorageSDK(containerApiBaseUrl),
|
|
2327
|
+
[containerApiBaseUrl]
|
|
2328
|
+
);
|
|
2329
|
+
useEffect5(() => {
|
|
2330
|
+
let cancelled = false;
|
|
2331
|
+
async function load() {
|
|
2332
|
+
if (!value) {
|
|
2333
|
+
setLoading(false);
|
|
2334
|
+
setImageUrl(null);
|
|
2335
|
+
onLoaded?.(null);
|
|
2336
|
+
return;
|
|
2337
|
+
}
|
|
2338
|
+
try {
|
|
2339
|
+
setLoading(true);
|
|
2340
|
+
const x = await sdk.container.Read(value);
|
|
2341
|
+
const url = x?.PublicUrl ?? null;
|
|
2342
|
+
if (cancelled) {
|
|
2343
|
+
return;
|
|
2344
|
+
}
|
|
2345
|
+
setImageUrl(url);
|
|
2346
|
+
onLoaded?.(url);
|
|
2347
|
+
} catch (err) {
|
|
2348
|
+
if (cancelled) {
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
console.error(`[SingleImageView] Failed to load image '${String(value)}'`, err);
|
|
2352
|
+
setImageUrl(null);
|
|
2353
|
+
onLoaded?.(null);
|
|
2354
|
+
} finally {
|
|
2355
|
+
if (!cancelled) {
|
|
2356
|
+
setLoading(false);
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2360
|
+
load();
|
|
2361
|
+
return () => {
|
|
2362
|
+
cancelled = true;
|
|
2363
|
+
};
|
|
2364
|
+
}, [sdk, value, onLoaded]);
|
|
2365
|
+
return /* @__PURE__ */ jsxs9(
|
|
2366
|
+
"div",
|
|
2367
|
+
{
|
|
2368
|
+
style: {
|
|
2369
|
+
height,
|
|
2370
|
+
width: "100%",
|
|
2371
|
+
overflow: "hidden",
|
|
2372
|
+
display: "grid",
|
|
2373
|
+
placeItems: "center",
|
|
2374
|
+
position: "relative"
|
|
2375
|
+
},
|
|
2376
|
+
children: [
|
|
2377
|
+
imageUrl ? /* @__PURE__ */ jsx12(
|
|
2378
|
+
"img",
|
|
2379
|
+
{
|
|
2380
|
+
src: imageUrl,
|
|
2381
|
+
alt: "Image",
|
|
2382
|
+
style: {
|
|
2383
|
+
width: "100%",
|
|
2384
|
+
height: "100%",
|
|
2385
|
+
objectFit: "cover",
|
|
2386
|
+
display: "block"
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
) : null,
|
|
2390
|
+
loading ? /* @__PURE__ */ jsx12(
|
|
2391
|
+
"div",
|
|
2392
|
+
{
|
|
2393
|
+
className: "spinner-border text-primary",
|
|
2394
|
+
style: { position: "absolute" }
|
|
2395
|
+
}
|
|
2396
|
+
) : null,
|
|
2397
|
+
!loading && !imageUrl ? /* @__PURE__ */ jsx12("div", { style: { fontSize: 13, opacity: 0.6 }, children: "No image" }) : null
|
|
2398
|
+
]
|
|
2399
|
+
}
|
|
2400
|
+
);
|
|
2401
|
+
}
|
|
2402
|
+
|
|
1893
2403
|
// src/views/HomeView.tsx
|
|
1894
|
-
import { useEffect as
|
|
2404
|
+
import { useEffect as useEffect6, useState as useState11 } from "react";
|
|
1895
2405
|
import {
|
|
1896
2406
|
AppSettings,
|
|
1897
2407
|
AuthenticatorProvider,
|
|
1898
2408
|
UserInfoCard,
|
|
1899
2409
|
useUser
|
|
1900
2410
|
} from "@sparkstudio/authentication-ui";
|
|
1901
|
-
import { Fragment as
|
|
2411
|
+
import { Fragment as Fragment4, jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
1902
2412
|
var CONTAINER_API = "https://lf9zyufpuk.execute-api.us-east-2.amazonaws.com/Prod";
|
|
1903
2413
|
var STORAGE_API = "https://iq0gmcn0pd.execute-api.us-east-2.amazonaws.com/Prod";
|
|
1904
2414
|
function HomeView() {
|
|
1905
|
-
return /* @__PURE__ */
|
|
2415
|
+
return /* @__PURE__ */ jsx13(
|
|
1906
2416
|
AuthenticatorProvider,
|
|
1907
2417
|
{
|
|
1908
2418
|
googleClientId: AppSettings.GoogleClientId,
|
|
1909
2419
|
authenticationUrl: AppSettings.AuthenticationUrl,
|
|
1910
2420
|
accountsUrl: AppSettings.AccountsUrl,
|
|
1911
|
-
children: /* @__PURE__ */
|
|
2421
|
+
children: /* @__PURE__ */ jsx13(HomeContent, {})
|
|
1912
2422
|
}
|
|
1913
2423
|
);
|
|
1914
2424
|
}
|
|
1915
2425
|
function HomeContent() {
|
|
1916
2426
|
const { user } = useUser();
|
|
1917
|
-
const [ids, setIds] =
|
|
1918
|
-
const [selectedId, setSelectedId] =
|
|
1919
|
-
const [selectedFile, setSelectedFile] =
|
|
1920
|
-
|
|
2427
|
+
const [ids, setIds] = useState11([]);
|
|
2428
|
+
const [selectedId, setSelectedId] = useState11(void 0);
|
|
2429
|
+
const [selectedFile, setSelectedFile] = useState11(null);
|
|
2430
|
+
useEffect6(() => {
|
|
1921
2431
|
if (selectedId && !ids.includes(selectedId)) {
|
|
1922
2432
|
setSelectedId(void 0);
|
|
1923
2433
|
setSelectedFile(null);
|
|
@@ -1930,10 +2440,10 @@ function HomeContent() {
|
|
|
1930
2440
|
new TemporaryFileDTO({ Name: file.name, ContentType: contentType })
|
|
1931
2441
|
);
|
|
1932
2442
|
}
|
|
1933
|
-
return /* @__PURE__ */
|
|
1934
|
-
/* @__PURE__ */
|
|
1935
|
-
user ? /* @__PURE__ */
|
|
1936
|
-
/* @__PURE__ */
|
|
2443
|
+
return /* @__PURE__ */ jsxs10(Fragment4, { children: [
|
|
2444
|
+
/* @__PURE__ */ jsx13(UserInfoCard, {}),
|
|
2445
|
+
user ? /* @__PURE__ */ jsxs10(Fragment4, { children: [
|
|
2446
|
+
/* @__PURE__ */ jsx13(
|
|
1937
2447
|
SingleFileProcessUploader,
|
|
1938
2448
|
{
|
|
1939
2449
|
uploadOnDrop: true,
|
|
@@ -1944,7 +2454,7 @@ function HomeContent() {
|
|
|
1944
2454
|
}
|
|
1945
2455
|
}
|
|
1946
2456
|
),
|
|
1947
|
-
/* @__PURE__ */
|
|
2457
|
+
/* @__PURE__ */ jsx13(
|
|
1948
2458
|
ContainerIdGridPanel,
|
|
1949
2459
|
{
|
|
1950
2460
|
containerApiBaseUrl: CONTAINER_API,
|
|
@@ -1967,7 +2477,7 @@ function HomeContent() {
|
|
|
1967
2477
|
}
|
|
1968
2478
|
}
|
|
1969
2479
|
),
|
|
1970
|
-
selectedFile ? /* @__PURE__ */
|
|
2480
|
+
selectedFile ? /* @__PURE__ */ jsxs10(
|
|
1971
2481
|
"div",
|
|
1972
2482
|
{
|
|
1973
2483
|
style: {
|
|
@@ -1977,9 +2487,9 @@ function HomeContent() {
|
|
|
1977
2487
|
borderRadius: 12
|
|
1978
2488
|
},
|
|
1979
2489
|
children: [
|
|
1980
|
-
/* @__PURE__ */
|
|
1981
|
-
/* @__PURE__ */
|
|
1982
|
-
/* @__PURE__ */
|
|
2490
|
+
/* @__PURE__ */ jsx13("div", { style: { fontWeight: 700 }, children: "Selected file" }),
|
|
2491
|
+
/* @__PURE__ */ jsx13("div", { children: selectedFile.Name ?? "(no name)" }),
|
|
2492
|
+
/* @__PURE__ */ jsx13("div", { style: { fontSize: 12, opacity: 0.7 }, children: selectedFile.PublicUrl ?? "(no public url)" })
|
|
1983
2493
|
]
|
|
1984
2494
|
}
|
|
1985
2495
|
) : null
|
|
@@ -2001,6 +2511,8 @@ export {
|
|
|
2001
2511
|
HomeView,
|
|
2002
2512
|
S3,
|
|
2003
2513
|
SingleFileProcessUploader,
|
|
2514
|
+
SingleImageUploadEditor,
|
|
2515
|
+
SingleImageView,
|
|
2004
2516
|
SparkStudioStorageSDK,
|
|
2005
2517
|
TemporaryFileDTO,
|
|
2006
2518
|
UploadContainer,
|