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