@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 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/views/HomeView.tsx
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, import_jsx_runtime11.jsx)(
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, import_jsx_runtime11.jsx)(HomeContent, {})
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, import_react13.useState)([]);
1942
- const [selectedId, setSelectedId] = (0, import_react13.useState)(void 0);
1943
- const [selectedFile, setSelectedFile] = (0, import_react13.useState)(null);
1944
- (0, import_react13.useEffect)(() => {
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, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1958
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_authentication_ui.UserInfoCard, {}),
1959
- user ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1960
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
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, import_jsx_runtime11.jsx)(
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, import_jsx_runtime11.jsxs)(
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, import_jsx_runtime11.jsx)("div", { style: { fontWeight: 700 }, children: "Selected file" }),
1998
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { children: selectedFile.Name ?? "(no name)" }),
1999
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { fontSize: 12, opacity: 0.7 }, children: selectedFile.PublicUrl ?? "(no public url)" })
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 useEffect4, useState as useState9 } from "react";
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 Fragment3, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
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__ */ jsx11(
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__ */ jsx11(HomeContent, {})
2377
+ children: /* @__PURE__ */ jsx13(HomeContent, {})
1910
2378
  }
1911
2379
  );
1912
2380
  }
1913
2381
  function HomeContent() {
1914
2382
  const { user } = useUser();
1915
- const [ids, setIds] = useState9([]);
1916
- const [selectedId, setSelectedId] = useState9(void 0);
1917
- const [selectedFile, setSelectedFile] = useState9(null);
1918
- useEffect4(() => {
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__ */ jsxs8(Fragment3, { children: [
1932
- /* @__PURE__ */ jsx11(UserInfoCard, {}),
1933
- user ? /* @__PURE__ */ jsxs8(Fragment3, { children: [
1934
- /* @__PURE__ */ jsx11(
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__ */ jsx11(
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__ */ jsxs8(
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__ */ jsx11("div", { style: { fontWeight: 700 }, children: "Selected file" }),
1972
- /* @__PURE__ */ jsx11("div", { children: selectedFile.Name ?? "(no name)" }),
1973
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 12, opacity: 0.7 }, children: selectedFile.PublicUrl ?? "(no public url)" })
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sparkstudio/storage-ui",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "type": "module",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",