@raster-app/react 0.0.0-stage → 0.1.0-canary-fd0f8dd
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/LICENSE +21 -0
- package/README.md +122 -2
- package/dist/context.d.ts +30 -0
- package/dist/context.js +50 -0
- package/dist/context.js.map +1 -0
- package/dist/icons.d.ts +22 -0
- package/dist/icons.js +21 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/picker-parts.d.ts +71 -0
- package/dist/picker-parts.js +150 -0
- package/dist/picker-parts.js.map +1 -0
- package/dist/picker-sign-in.d.ts +7 -0
- package/dist/picker-sign-in.js +18 -0
- package/dist/picker-sign-in.js.map +1 -0
- package/dist/query-keys.d.ts +13 -0
- package/dist/query-keys.js +14 -0
- package/dist/query-keys.js.map +1 -0
- package/dist/raster-picker.d.ts +23 -0
- package/dist/raster-picker.js +188 -0
- package/dist/raster-picker.js.map +1 -0
- package/dist/state-store.d.ts +18 -0
- package/dist/state-store.js +47 -0
- package/dist/state-store.js.map +1 -0
- package/dist/styles.css +1000 -0
- package/dist/use-asset-search.d.ts +28 -0
- package/dist/use-asset-search.js +82 -0
- package/dist/use-asset-search.js.map +1 -0
- package/dist/use-asset-upload.d.ts +46 -0
- package/dist/use-asset-upload.js +73 -0
- package/dist/use-asset-upload.js.map +1 -0
- package/dist/use-assets.d.ts +24 -0
- package/dist/use-assets.js +63 -0
- package/dist/use-assets.js.map +1 -0
- package/dist/use-libraries.d.ts +19 -0
- package/dist/use-libraries.js +57 -0
- package/dist/use-libraries.js.map +1 -0
- package/dist/use-navigation.d.ts +31 -0
- package/dist/use-navigation.js +111 -0
- package/dist/use-navigation.js.map +1 -0
- package/dist/use-organizations.d.ts +36 -0
- package/dist/use-organizations.js +23 -0
- package/dist/use-organizations.js.map +1 -0
- package/dist/use-promote-variant.d.ts +43 -0
- package/dist/use-promote-variant.js +65 -0
- package/dist/use-promote-variant.js.map +1 -0
- package/dist/use-session.d.ts +22 -0
- package/dist/use-session.js +37 -0
- package/dist/use-session.js.map +1 -0
- package/dist/use-sign-in.d.ts +34 -0
- package/dist/use-sign-in.js +79 -0
- package/dist/use-sign-in.js.map +1 -0
- package/package.json +54 -4
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type ReportError } from './picker-parts.js';
|
|
2
|
+
/** The picker's sign-in: the device grant, or an organization API key when nothing is bound. */
|
|
3
|
+
export declare function SignIn({ organizationId, scope, onError, }: {
|
|
4
|
+
organizationId: string | undefined;
|
|
5
|
+
scope: 'user' | 'org';
|
|
6
|
+
onError: ReportError;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useRasterClient } from './context.js';
|
|
4
|
+
import { Icon } from './icons.js';
|
|
5
|
+
import { Brand, ErrorCard } from './picker-parts.js';
|
|
6
|
+
import { useSignIn } from './use-sign-in.js';
|
|
7
|
+
/** The picker's sign-in: the device grant, or an organization API key when nothing is bound. */
|
|
8
|
+
export function SignIn({ organizationId, scope, onError, }) {
|
|
9
|
+
const client = useRasterClient();
|
|
10
|
+
const [apiKey, setApiKey] = useState('');
|
|
11
|
+
const { state, signInWithDevice, connectApiKey, cancel } = useSignIn({ scope, organizationId });
|
|
12
|
+
const busy = state.status === 'connecting' || state.status === 'awaitingApproval';
|
|
13
|
+
return (_jsx("div", { className: "rs-center rs-scroll", children: _jsxs("div", { className: "rs-window rs-auth", children: [_jsx(Brand, {}), _jsxs("div", { children: [_jsx("h1", { className: "rs-title", children: "Sign in" }), _jsxs("p", { className: "rs-muted rs-small", children: ["Connect ", client.host.name, " to your Raster assets."] })] }), organizationId !== undefined && (_jsxs("p", { className: "rs-card", children: ["Choose ", organizationId, " on the consent page. Any other grant fails."] })), _jsxs("button", { type: "button", className: "rs-button rs-button-primary", disabled: busy, onClick: () => void signInWithDevice(), children: ["Sign in with Raster", _jsx(Icon, { shape: "arrow-right", width: 12.25 })] }), state.status === 'awaitingApproval' && (_jsxs("div", { className: "rs-device", children: [_jsx("span", { className: "rs-label", children: "Approve this code in Raster" }), _jsx("strong", { className: "rs-device-code rs-selectable", children: state.device.userCode }), _jsxs("div", { className: "rs-row", children: [_jsxs("a", { className: "rs-button rs-button-secondary rs-button-sm", href: state.device.verificationUrl, target: "_blank", rel: "noreferrer", children: [_jsx(Icon, { shape: "link", width: 12.25 }), "Open the consent page"] }), _jsx("button", { type: "button", className: "rs-button rs-button-sm", onClick: cancel, children: "Cancel" })] })] })), organizationId === undefined && (_jsxs("form", { onSubmit: (event) => {
|
|
14
|
+
event.preventDefault();
|
|
15
|
+
void connectApiKey(apiKey);
|
|
16
|
+
}, children: [_jsx("hr", { className: "rs-hr" }), _jsx("label", { className: "rs-label", htmlFor: "rs-api-key", children: "Or connect an organization API key" }), _jsxs("div", { className: "rs-row", children: [_jsx("input", { id: "rs-api-key", className: "rs-input", type: "password", autoComplete: "off", disabled: busy, value: apiKey, onChange: (event) => setApiKey(event.target.value) }), _jsx("button", { type: "submit", className: "rs-button rs-button-sm", disabled: busy, children: "Connect" })] })] })), state.status === 'connecting' && (_jsxs("p", { className: "rs-status", children: [_jsx("span", { className: "rs-spinner" }), "Connecting"] })), state.status === 'error' && _jsx(ErrorCard, { error: state.error, onError: onError })] }) }));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=picker-sign-in.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"picker-sign-in.js","sourceRoot":"","sources":["../src/picker-sign-in.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useState } from 'react'\n\nimport { useRasterClient } from './context.js'\nimport { Icon } from './icons.js'\nimport { Brand, ErrorCard, type ReportError } from './picker-parts.js'\nimport { useSignIn } from './use-sign-in.js'\n\n/** The picker's sign-in: the device grant, or an organization API key when nothing is bound. */\nexport function SignIn({\n\torganizationId,\n\tscope,\n\tonError,\n}: {\n\torganizationId: string | undefined\n\tscope: 'user' | 'org'\n\tonError: ReportError\n}) {\n\tconst client = useRasterClient()\n\tconst [apiKey, setApiKey] = useState('')\n\tconst { state, signInWithDevice, connectApiKey, cancel } = useSignIn({ scope, organizationId })\n\tconst busy = state.status === 'connecting' || state.status === 'awaitingApproval'\n\n\treturn (\n\t\t<div className=\"rs-center rs-scroll\">\n\t\t\t<div className=\"rs-window rs-auth\">\n\t\t\t\t<Brand />\n\t\t\t\t<div>\n\t\t\t\t\t<h1 className=\"rs-title\">Sign in</h1>\n\t\t\t\t\t<p className=\"rs-muted rs-small\">Connect {client.host.name} to your Raster assets.</p>\n\t\t\t\t</div>\n\t\t\t\t{organizationId !== undefined && (\n\t\t\t\t\t<p className=\"rs-card\">\n\t\t\t\t\t\tChoose {organizationId} on the consent page. Any other grant fails.\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName=\"rs-button rs-button-primary\"\n\t\t\t\t\tdisabled={busy}\n\t\t\t\t\tonClick={() => void signInWithDevice()}\n\t\t\t\t>\n\t\t\t\t\tSign in with Raster\n\t\t\t\t\t<Icon shape=\"arrow-right\" width={12.25} />\n\t\t\t\t</button>\n\t\t\t\t{state.status === 'awaitingApproval' && (\n\t\t\t\t\t<div className=\"rs-device\">\n\t\t\t\t\t\t<span className=\"rs-label\">Approve this code in Raster</span>\n\t\t\t\t\t\t<strong className=\"rs-device-code rs-selectable\">{state.device.userCode}</strong>\n\t\t\t\t\t\t<div className=\"rs-row\">\n\t\t\t\t\t\t\t<a\n\t\t\t\t\t\t\t\tclassName=\"rs-button rs-button-secondary rs-button-sm\"\n\t\t\t\t\t\t\t\thref={state.device.verificationUrl}\n\t\t\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\t\t\trel=\"noreferrer\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Icon shape=\"link\" width={12.25} />\n\t\t\t\t\t\t\t\tOpen the consent page\n\t\t\t\t\t\t\t</a>\n\t\t\t\t\t\t\t<button type=\"button\" className=\"rs-button rs-button-sm\" onClick={cancel}>\n\t\t\t\t\t\t\t\tCancel\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t{organizationId === undefined && (\n\t\t\t\t\t<form\n\t\t\t\t\t\tonSubmit={(event) => {\n\t\t\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\t\t\tvoid connectApiKey(apiKey)\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<hr className=\"rs-hr\" />\n\t\t\t\t\t\t<label className=\"rs-label\" htmlFor=\"rs-api-key\">\n\t\t\t\t\t\t\tOr connect an organization API key\n\t\t\t\t\t\t</label>\n\t\t\t\t\t\t<div className=\"rs-row\">\n\t\t\t\t\t\t\t<input\n\t\t\t\t\t\t\t\tid=\"rs-api-key\"\n\t\t\t\t\t\t\t\tclassName=\"rs-input\"\n\t\t\t\t\t\t\t\ttype=\"password\"\n\t\t\t\t\t\t\t\tautoComplete=\"off\"\n\t\t\t\t\t\t\t\tdisabled={busy}\n\t\t\t\t\t\t\t\tvalue={apiKey}\n\t\t\t\t\t\t\t\tonChange={(event) => setApiKey(event.target.value)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t<button type=\"submit\" className=\"rs-button rs-button-sm\" disabled={busy}>\n\t\t\t\t\t\t\t\tConnect\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</form>\n\t\t\t\t)}\n\t\t\t\t{state.status === 'connecting' && (\n\t\t\t\t\t<p className=\"rs-status\">\n\t\t\t\t\t\t<span className=\"rs-spinner\" />\n\t\t\t\t\t\tConnecting\n\t\t\t\t\t</p>\n\t\t\t\t)}\n\t\t\t\t{state.status === 'error' && <ErrorCard error={state.error} onError={onError} />}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every Raster query sits under the sign-in it was fetched with (`credentialIdentity()`), so
|
|
3
|
+
* one account's cache is never served to the next. These are the prefixes queries are
|
|
4
|
+
* invalidated by; a query appends whatever else it varies on.
|
|
5
|
+
*/
|
|
6
|
+
export declare const rasterKeys: {
|
|
7
|
+
all: readonly ["raster"];
|
|
8
|
+
signIn: (identity: string | null) => readonly ["raster", string | null];
|
|
9
|
+
me: (identity: string | null) => readonly ["raster", string | null, "me"];
|
|
10
|
+
libraries: (identity: string | null, organizationId: string | null) => readonly ["raster", string | null, "libraries", string | null];
|
|
11
|
+
assets: (identity: string | null, organizationId: string | null, libraryId: string | null) => readonly ["raster", string | null, "assets", string | null, string | null];
|
|
12
|
+
search: (identity: string | null, organizationId: string | null) => readonly ["raster", string | null, "search", string | null];
|
|
13
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every Raster query sits under the sign-in it was fetched with (`credentialIdentity()`), so
|
|
3
|
+
* one account's cache is never served to the next. These are the prefixes queries are
|
|
4
|
+
* invalidated by; a query appends whatever else it varies on.
|
|
5
|
+
*/
|
|
6
|
+
export const rasterKeys = {
|
|
7
|
+
all: ['raster'],
|
|
8
|
+
signIn: (identity) => ['raster', identity],
|
|
9
|
+
me: (identity) => ['raster', identity, 'me'],
|
|
10
|
+
libraries: (identity, organizationId) => ['raster', identity, 'libraries', organizationId],
|
|
11
|
+
assets: (identity, organizationId, libraryId) => ['raster', identity, 'assets', organizationId, libraryId],
|
|
12
|
+
search: (identity, organizationId) => ['raster', identity, 'search', organizationId],
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=query-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-keys.js","sourceRoot":"","sources":["../src/query-keys.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,GAAG,EAAE,CAAC,QAAQ,CAAU;IACxB,MAAM,EAAE,CAAC,QAAuB,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAU;IAClE,EAAE,EAAE,CAAC,QAAuB,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,CAAU;IACpE,SAAS,EAAE,CAAC,QAAuB,EAAE,cAA6B,EAAE,EAAE,CACrE,CAAC,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,cAAc,CAAU;IAC3D,MAAM,EAAE,CAAC,QAAuB,EAAE,cAA6B,EAAE,SAAwB,EAAE,EAAE,CAC5F,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,CAAU;IACnE,MAAM,EAAE,CAAC,QAAuB,EAAE,cAA6B,EAAE,EAAE,CAClE,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,cAAc,CAAU;CACxD,CAAA","sourcesContent":["/**\n * Every Raster query sits under the sign-in it was fetched with (`credentialIdentity()`), so\n * one account's cache is never served to the next. These are the prefixes queries are\n * invalidated by; a query appends whatever else it varies on.\n */\nexport const rasterKeys = {\n\tall: ['raster'] as const,\n\tsignIn: (identity: string | null) => ['raster', identity] as const,\n\tme: (identity: string | null) => ['raster', identity, 'me'] as const,\n\tlibraries: (identity: string | null, organizationId: string | null) =>\n\t\t['raster', identity, 'libraries', organizationId] as const,\n\tassets: (identity: string | null, organizationId: string | null, libraryId: string | null) =>\n\t\t['raster', identity, 'assets', organizationId, libraryId] as const,\n\tsearch: (identity: string | null, organizationId: string | null) =>\n\t\t['raster', identity, 'search', organizationId] as const,\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Asset, type RasterApiError } from '@raster-app/sdk';
|
|
2
|
+
export type RasterPickerProps = {
|
|
3
|
+
/**
|
|
4
|
+
* The picked asset, whole whether it came from a library, a search or an asset's versions, and
|
|
5
|
+
* its organization. `getFullSizeUrl(asset)` is the image.
|
|
6
|
+
*/
|
|
7
|
+
onPick: (asset: Asset, organizationId: string) => void;
|
|
8
|
+
/** Binds the device sign-in to this organization; any other grant fails. */
|
|
9
|
+
organizationId?: string;
|
|
10
|
+
/** The consent the device sign-in asks for: `org` when `organizationId` is set, else `user`. */
|
|
11
|
+
scope?: 'user' | 'org';
|
|
12
|
+
/** Each failure the picker shows, once, for the host to log. */
|
|
13
|
+
onError?: (error: RasterApiError) => void;
|
|
14
|
+
/** Follows the system's color scheme when unset. */
|
|
15
|
+
theme?: 'light' | 'dark';
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Raster's asset picker, drawn as Raster draws it (`@raster-app/react/styles.css`): sign-in, the
|
|
19
|
+
* organization's libraries, search and uploads. A tile opens its asset large, to pick or open in
|
|
20
|
+
* Raster, over its versions with the default to set. It fills its container, inside a
|
|
21
|
+
* `RasterProvider`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function RasterPicker({ onPick, organizationId, scope, onError, theme }: RasterPickerProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMutation } from '@tanstack/react-query';
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
import { isReady, } from '@raster-app/sdk';
|
|
5
|
+
import { useRasterClient } from './context.js';
|
|
6
|
+
import { Icon } from './icons.js';
|
|
7
|
+
import { Brand, Detail, Empty, ErrorCard, FilePicker, OrganizationSwitcher, PendingTile, Progress, Sentinel, Tile, } from './picker-parts.js';
|
|
8
|
+
import { SignIn } from './picker-sign-in.js';
|
|
9
|
+
import { useAssetSearch } from './use-asset-search.js';
|
|
10
|
+
import { useAssetUpload } from './use-asset-upload.js';
|
|
11
|
+
import { useAssets, usePrefetchAssets } from './use-assets.js';
|
|
12
|
+
import { useLibraries, usePrefetchLibraries } from './use-libraries.js';
|
|
13
|
+
import { useRasterNavigation } from './use-navigation.js';
|
|
14
|
+
import { useOrganizations } from './use-organizations.js';
|
|
15
|
+
import { usePromoteVariant } from './use-promote-variant.js';
|
|
16
|
+
import { useSession } from './use-session.js';
|
|
17
|
+
const COUNT = new Intl.NumberFormat('en-US', { notation: 'compact' });
|
|
18
|
+
/**
|
|
19
|
+
* Raster's asset picker, drawn as Raster draws it (`@raster-app/react/styles.css`): sign-in, the
|
|
20
|
+
* organization's libraries, search and uploads. A tile opens its asset large, to pick or open in
|
|
21
|
+
* Raster, over its versions with the default to set. It fills its container, inside a
|
|
22
|
+
* `RasterProvider`.
|
|
23
|
+
*/
|
|
24
|
+
export function RasterPicker({ onPick, organizationId, scope, onError, theme }) {
|
|
25
|
+
const { credentials, isConfigured } = useSession();
|
|
26
|
+
let body = (_jsx("div", { className: "rs-center", children: _jsx("span", { className: "rs-spinner" }) }));
|
|
27
|
+
if (credentials !== undefined && !isConfigured) {
|
|
28
|
+
body = (_jsx(SignIn, { organizationId: organizationId, scope: scope ?? (organizationId === undefined ? 'user' : 'org'), onError: onError }));
|
|
29
|
+
}
|
|
30
|
+
else if (isConfigured) {
|
|
31
|
+
body = _jsx(Browser, { onPick: onPick, onError: onError });
|
|
32
|
+
}
|
|
33
|
+
return (_jsx("div", { className: "rs rs-picker", "data-theme": theme, children: body }));
|
|
34
|
+
}
|
|
35
|
+
function Browser({ onPick, onError, }) {
|
|
36
|
+
const client = useRasterClient();
|
|
37
|
+
const session = useSession();
|
|
38
|
+
const organizations = useOrganizations();
|
|
39
|
+
const navigation = useRasterNavigation({ organizations: organizations.data ?? null });
|
|
40
|
+
const { organizationId, library, query } = navigation;
|
|
41
|
+
const organization = organizations.data?.find((entry) => entry.id === organizationId);
|
|
42
|
+
const libraries = useLibraries({ organizationId, organizationName: organization?.name ?? null });
|
|
43
|
+
const assets = useAssets({ organizationId, libraryId: library?.id ?? null });
|
|
44
|
+
const search = useAssetSearch(organizationId === null ? null : { organizationId, libraryId: library?.id ?? null }, query);
|
|
45
|
+
const upload = useAssetUpload();
|
|
46
|
+
const promote = usePromoteVariant();
|
|
47
|
+
const read = useMutation({
|
|
48
|
+
mutationFn: (options) => client.assets.get(options),
|
|
49
|
+
});
|
|
50
|
+
const prefetchAssets = usePrefetchAssets();
|
|
51
|
+
const prefetchLibraries = usePrefetchLibraries();
|
|
52
|
+
const searchInput = useRef(null);
|
|
53
|
+
const [pending, setPending] = useState(null);
|
|
54
|
+
const [confirming, setConfirming] = useState(null);
|
|
55
|
+
const [dragging, setDragging] = useState(false);
|
|
56
|
+
const dragDepth = useRef(0);
|
|
57
|
+
// The version shown large, or the search hit opened, for the scope it was chosen in.
|
|
58
|
+
const [focus, setFocus] = useState(null);
|
|
59
|
+
const scope = [organizationId, library?.id, navigation.asset?.id, query].join('/');
|
|
60
|
+
const focusId = focus?.scope === scope ? focus.id : null;
|
|
61
|
+
const access = libraries.data?.find((entry) => entry.id === library?.id)?.access ?? null;
|
|
62
|
+
const writable = access === 'write';
|
|
63
|
+
// The asset as the list last read it, which refetches after an upload or a promote.
|
|
64
|
+
const opened = navigation.asset === null
|
|
65
|
+
? null
|
|
66
|
+
: (assets.assets?.find((entry) => entry.id === navigation.asset?.id) ?? navigation.asset);
|
|
67
|
+
const versions = !search.active && opened !== null;
|
|
68
|
+
const variants = (opened?.variants ?? []).filter((variant) => variant !== null);
|
|
69
|
+
const shown = search.active ? search : library === null ? null : assets;
|
|
70
|
+
const items = search.active ? search.hits : assets.assets;
|
|
71
|
+
const hit = search.active ? (search.hits?.find((entry) => entry.id === focusId) ?? null) : null;
|
|
72
|
+
/** Hand the host the whole asset: a search hit or a variant carries less, so it is read first. */
|
|
73
|
+
function pick(item) {
|
|
74
|
+
if (organizationId === null)
|
|
75
|
+
return;
|
|
76
|
+
const loaded = assets.assets?.find((entry) => entry.id === item.id);
|
|
77
|
+
if (loaded) {
|
|
78
|
+
onPick(loaded, organizationId);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// A variant lives in its original's library, which is the open one.
|
|
82
|
+
const libraryId = ('libraryId' in item ? item.libraryId : null) ?? library?.id;
|
|
83
|
+
if (!libraryId)
|
|
84
|
+
return;
|
|
85
|
+
read.mutate({ organizationId, libraryId, assetId: item.id }, { onSuccess: (asset) => onPick(asset, organizationId) });
|
|
86
|
+
}
|
|
87
|
+
/** Upload one file into the open library, or as a variant of `parentId`. */
|
|
88
|
+
async function send(file, parentId) {
|
|
89
|
+
if (organizationId === null || library === null || !writable)
|
|
90
|
+
return;
|
|
91
|
+
const preview = URL.createObjectURL(file);
|
|
92
|
+
setPending({ name: file.name, preview, parentId });
|
|
93
|
+
await upload.upload({ organizationId, libraryId: library.id, parentId, source: file });
|
|
94
|
+
URL.revokeObjectURL(preview);
|
|
95
|
+
// A later upload may have taken the placeholder over by now.
|
|
96
|
+
setPending((current) => (current?.preview === preview ? null : current));
|
|
97
|
+
}
|
|
98
|
+
/** The first press asks to confirm; the second promotes. */
|
|
99
|
+
function setDefault(variant) {
|
|
100
|
+
if (confirming !== variant.id) {
|
|
101
|
+
setConfirming(variant.id);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
setConfirming(null);
|
|
105
|
+
if (organizationId === null || library === null || opened === null)
|
|
106
|
+
return;
|
|
107
|
+
void promote.promote({
|
|
108
|
+
organizationId,
|
|
109
|
+
libraryId: library.id,
|
|
110
|
+
assetId: opened.id,
|
|
111
|
+
variantId: variant.id,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const pendingTile = pending !== null &&
|
|
115
|
+
!search.active &&
|
|
116
|
+
pending.parentId === (opened?.id ?? null) && (_jsx(PendingTile, { name: pending.name, preview: pending.preview, status: upload.state.status === 'uploading' ? 'Uploading' : 'Processing' }));
|
|
117
|
+
let status = '';
|
|
118
|
+
if (search.active)
|
|
119
|
+
status = search.isSearching ? 'Searching' : `${search.found} found`;
|
|
120
|
+
else if (library !== null && !versions)
|
|
121
|
+
status = `${assets.assets?.length ?? 0} loaded`;
|
|
122
|
+
let heading = (_jsxs(_Fragment, { children: [_jsx("h1", { className: "rs-heading", children: search.active ? 'Search' : (library?.name ?? 'Libraries') }), !search.active && library !== null && (_jsx("span", { className: "rs-sidebar-count is-active", children: access ?? 'no access' }))] }));
|
|
123
|
+
let content = (_jsx(Empty, { glyph: "grid", title: "Choose a library", text: "Or search every library." }));
|
|
124
|
+
if (versions) {
|
|
125
|
+
const focused = [opened, ...variants].find((entry) => entry.id === focusId) ?? opened;
|
|
126
|
+
heading = (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "rs-crumb", onClick: () => navigation.openAsset(null), children: library?.name }), _jsx("span", { className: "rs-muted", children: "/" }), _jsx("h1", { className: "rs-heading", children: opened.name }), _jsxs("span", { className: "rs-sidebar-count is-active", children: [variants.length + 1, " versions"] })] }));
|
|
127
|
+
content = (_jsxs(_Fragment, { children: [_jsx(Detail, { item: focused, busy: read.isPending, onPick: () => pick(focused) }), _jsxs("div", { className: "rs-grid", children: [_jsx(Tile, { asset: opened, selected: focused.id === opened.id, onOpen: () => setFocus({ scope, id: opened.id }), badge: _jsxs("span", { className: "rs-pill rs-tile-badge", children: [_jsx(Icon, { shape: "star", solid: true, width: 10.5 }), "Default"] }) }), variants.map((variant) => (_jsx(Tile, { asset: variant, selected: focused.id === variant.id, onOpen: () => setFocus({ scope, id: variant.id }), action: writable &&
|
|
128
|
+
isReady(variant) && (_jsxs("button", { type: "button", className: confirming === variant.id
|
|
129
|
+
? 'rs-pill rs-tile-action is-confirming'
|
|
130
|
+
: 'rs-pill rs-tile-action', disabled: promote.state.status === 'promoting', onClick: () => setDefault(variant), children: [_jsx(Icon, { shape: "star", width: 10.5 }), confirming === variant.id ? 'Confirm' : 'Set as default'] })) }, variant.id))), pendingTile] })] }));
|
|
131
|
+
}
|
|
132
|
+
else if (hit) {
|
|
133
|
+
heading = (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "rs-crumb", onClick: () => setFocus(null), children: "Search" }), _jsx("span", { className: "rs-muted", children: "/" }), _jsx("h1", { className: "rs-heading", children: hit.name })] }));
|
|
134
|
+
content = _jsx(Detail, { item: hit, busy: read.isPending, onPick: () => pick(hit) });
|
|
135
|
+
}
|
|
136
|
+
else if (shown === null ? organizations.isLoading || libraries.isLoading : items === null) {
|
|
137
|
+
content = shown?.error ? null : (_jsx("div", { className: "rs-center", children: _jsx("span", { className: "rs-spinner" }) }));
|
|
138
|
+
}
|
|
139
|
+
else if (shown !== null && items?.length === 0 && !pendingTile) {
|
|
140
|
+
content = search.active ? (_jsx(Empty, { glyph: "\u2205", title: "No results", text: `Nothing matches “${search.query}”.` })) : (_jsx(Empty, { glyph: "\u2205", title: "No assets", text: "This library is empty.", children: writable && library !== null && (_jsx(FilePicker, { className: "rs-button rs-button-primary rs-button-sm", disabled: false, onFile: (file) => void send(file, null), children: "Upload files" })) }));
|
|
141
|
+
}
|
|
142
|
+
else if (shown !== null && items) {
|
|
143
|
+
content = (_jsxs(_Fragment, { children: [_jsxs("div", { className: "rs-grid", children: [pendingTile, search.active
|
|
144
|
+
? search.hits?.map((entry) => (_jsx(Tile, { asset: entry, onOpen: () => setFocus({ scope, id: entry.id }) }, entry.id)))
|
|
145
|
+
: assets.assets?.map((asset) => {
|
|
146
|
+
const count = asset.variants?.filter(Boolean).length ?? 0;
|
|
147
|
+
return (_jsx(Tile, { asset: asset, onOpen: () => {
|
|
148
|
+
setConfirming(null);
|
|
149
|
+
navigation.openAsset(asset);
|
|
150
|
+
}, badge: count > 0 && (_jsxs("span", { className: "rs-pill rs-tile-badge", children: [_jsx(Icon, { shape: "layers", width: 10.5 }), count + 1, " versions"] })) }, asset.id));
|
|
151
|
+
})] }), shown.hasMore && (_jsx("div", { className: "rs-center", children: _jsx(Sentinel, { onVisible: shown.loadMore }, items.length) }))] }));
|
|
152
|
+
}
|
|
153
|
+
const dropTarget = writable && library !== null && !search.active;
|
|
154
|
+
return (_jsxs(_Fragment, { children: [_jsxs("aside", { className: "rs-sidebar", children: [_jsx(Brand, {}), _jsx(OrganizationSwitcher, { organizations: organizations.data ?? [], current: organization, onIntent: (entry) => entry.id !== organizationId && prefetchLibraries(entry.id, entry.name), onSelect: navigation.selectOrganization }), _jsxs("button", { type: "button", className: search.active ? 'rs-sidebar-item is-active' : 'rs-sidebar-item', onClick: () => searchInput.current?.focus(), children: [_jsx(Icon, { shape: "search" }), "Search"] }), _jsxs("nav", { className: "rs-scroll", children: [_jsx("h2", { className: "rs-sidebar-heading", children: "Libraries" }), libraries.data?.map((entry) => {
|
|
155
|
+
const open = entry.id === library?.id;
|
|
156
|
+
const warm = () => organizationId !== null && prefetchAssets(organizationId, entry.id);
|
|
157
|
+
const toGrid = () => {
|
|
158
|
+
navigation.setQuery('');
|
|
159
|
+
navigation.openAsset(null);
|
|
160
|
+
};
|
|
161
|
+
return (_jsxs("div", { className: open ? 'rs-library is-expanded' : 'rs-library', children: [_jsxs("button", { type: "button", className: "rs-sidebar-item rs-library-row", disabled: entry.access === null, onMouseEnter: warm, onFocus: warm, onClick: () => navigation.openLibrary(entry), children: [_jsx(Icon, { shape: "grid" }), _jsx("span", { className: "rs-library-name", children: entry.name }), _jsx("span", { className: open ? 'rs-sidebar-count is-active' : 'rs-sidebar-count', children: COUNT.format(entry.assetsCount ?? 0) })] }), _jsx("div", { className: open ? 'rs-library-menu is-expanded' : 'rs-library-menu', "aria-hidden": !open, children: _jsxs("div", { children: [_jsxs("button", { type: "button", tabIndex: open ? 0 : -1, className: open && !search.active && opened === null
|
|
162
|
+
? 'rs-sidebar-item is-active'
|
|
163
|
+
: 'rs-sidebar-item', onClick: toGrid, children: [_jsx(Icon, { shape: "circle" }), "All Assets"] }), _jsxs(FilePicker, { className: "rs-sidebar-item", disabled: !open || entry.access !== 'write', focusable: open, onFile: (file) => {
|
|
164
|
+
toGrid();
|
|
165
|
+
void send(file, null);
|
|
166
|
+
}, children: [_jsx(Icon, { shape: "upload" }), "Upload"] })] }) })] }, entry.id));
|
|
167
|
+
})] }), _jsxs("div", { className: "rs-sidebar-footer", children: [_jsx("span", { className: "rs-sidebar-item rs-muted", children: session.isSignedIn ? (session.userName ?? 'Signed in') : 'API key' }), _jsx("button", { type: "button", className: "rs-sidebar-item", onClick: () => void session.signOut(), children: "Sign out" })] })] }), _jsxs("section", { className: "rs-main", "aria-label": "Assets", onDragEnter: (event) => {
|
|
168
|
+
if (!dropTarget || !event.dataTransfer.types.includes('Files'))
|
|
169
|
+
return;
|
|
170
|
+
dragDepth.current += 1;
|
|
171
|
+
setDragging(true);
|
|
172
|
+
}, onDragOver: (event) => {
|
|
173
|
+
if (dropTarget)
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
}, onDragLeave: () => {
|
|
176
|
+
dragDepth.current = Math.max(0, dragDepth.current - 1);
|
|
177
|
+
if (dragDepth.current === 0)
|
|
178
|
+
setDragging(false);
|
|
179
|
+
}, onDrop: (event) => {
|
|
180
|
+
event.preventDefault();
|
|
181
|
+
dragDepth.current = 0;
|
|
182
|
+
setDragging(false);
|
|
183
|
+
const file = event.dataTransfer.files[0];
|
|
184
|
+
if (file && dropTarget)
|
|
185
|
+
void send(file, versions ? opened.id : null);
|
|
186
|
+
}, children: [_jsxs("header", { className: "rs-header", children: [heading, _jsx("span", { className: "rs-muted rs-small", children: status }), _jsx(Progress, { upload: upload.state, promote: promote.state }), _jsxs("div", { className: "rs-search", children: [_jsx(Icon, { shape: "search", width: 12.25 }), _jsx("input", { ref: searchInput, className: "rs-input", "aria-label": "Search Raster", placeholder: library === null ? 'Search every library…' : `Search ${library.name}…`, value: query, onChange: (event) => navigation.setQuery(event.target.value) }), query !== '' && (_jsx("button", { type: "button", className: "rs-icon-button", "aria-label": "Clear search", onClick: () => navigation.setQuery(''), children: _jsx(Icon, { shape: "x", width: 10.5 }) }))] }), versions && writable && (_jsxs(FilePicker, { className: "rs-button rs-button-sm", disabled: false, onFile: (file) => void send(file, opened.id), children: [_jsx(Icon, { shape: "upload", width: 12.25 }), "Upload variant"] })), _jsx("button", { type: "button", className: "rs-button rs-button-sm", onClick: navigation.refresh, children: "Refresh" })] }), _jsxs("div", { className: "rs-content rs-scroll", children: [organizations.error && _jsx(ErrorCard, { error: organizations.error, onError: onError }), libraries.error && _jsx(ErrorCard, { error: libraries.error, onError: onError }), shown?.error && _jsx(ErrorCard, { error: shown.error, onError: onError }), read.error && _jsx(ErrorCard, { error: read.error, onError: onError }), upload.state.status === 'error' && (_jsx(ErrorCard, { error: upload.state.error, onError: onError })), promote.state.status === 'error' && (_jsx(ErrorCard, { error: promote.state.error, onError: onError })), content] }), _jsx("div", { className: dragging ? 'rs-drop is-active' : 'rs-drop' })] })] }));
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=raster-picker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"raster-picker.js","sourceRoot":"","sources":["../src/raster-picker.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useMutation } from '@tanstack/react-query'\nimport { type ReactNode, useRef, useState } from 'react'\n\nimport {\n\ttype Asset,\n\ttype AssetVariant,\n\ttype RasterApiError,\n\ttype SearchHit,\n\tisReady,\n} from '@raster-app/sdk'\n\nimport { useRasterClient } from './context.js'\nimport { Icon } from './icons.js'\nimport {\n\tBrand,\n\tDetail,\n\tEmpty,\n\tErrorCard,\n\tFilePicker,\n\tOrganizationSwitcher,\n\tPendingTile,\n\tProgress,\n\ttype ReportError,\n\tSentinel,\n\tTile,\n} from './picker-parts.js'\nimport { SignIn } from './picker-sign-in.js'\nimport { useAssetSearch } from './use-asset-search.js'\nimport { useAssetUpload } from './use-asset-upload.js'\nimport { useAssets, usePrefetchAssets } from './use-assets.js'\nimport { useLibraries, usePrefetchLibraries } from './use-libraries.js'\nimport { useRasterNavigation } from './use-navigation.js'\nimport { useOrganizations } from './use-organizations.js'\nimport { usePromoteVariant } from './use-promote-variant.js'\nimport { useSession } from './use-session.js'\n\nexport type RasterPickerProps = {\n\t/**\n\t * The picked asset, whole whether it came from a library, a search or an asset's versions, and\n\t * its organization. `getFullSizeUrl(asset)` is the image.\n\t */\n\tonPick: (asset: Asset, organizationId: string) => void\n\t/** Binds the device sign-in to this organization; any other grant fails. */\n\torganizationId?: string\n\t/** The consent the device sign-in asks for: `org` when `organizationId` is set, else `user`. */\n\tscope?: 'user' | 'org'\n\t/** Each failure the picker shows, once, for the host to log. */\n\tonError?: (error: RasterApiError) => void\n\t/** Follows the system's color scheme when unset. */\n\ttheme?: 'light' | 'dark'\n}\n\nconst COUNT = new Intl.NumberFormat('en-US', { notation: 'compact' })\n\n/** A file on its way in, which the grid shows until Raster has processed it. */\ntype Pending = { name: string; preview: string; parentId: string | null }\n\n/**\n * Raster's asset picker, drawn as Raster draws it (`@raster-app/react/styles.css`): sign-in, the\n * organization's libraries, search and uploads. A tile opens its asset large, to pick or open in\n * Raster, over its versions with the default to set. It fills its container, inside a\n * `RasterProvider`.\n */\nexport function RasterPicker({ onPick, organizationId, scope, onError, theme }: RasterPickerProps) {\n\tconst { credentials, isConfigured } = useSession()\n\n\tlet body = (\n\t\t<div className=\"rs-center\">\n\t\t\t<span className=\"rs-spinner\" />\n\t\t</div>\n\t)\n\tif (credentials !== undefined && !isConfigured) {\n\t\tbody = (\n\t\t\t<SignIn\n\t\t\t\torganizationId={organizationId}\n\t\t\t\tscope={scope ?? (organizationId === undefined ? 'user' : 'org')}\n\t\t\t\tonError={onError}\n\t\t\t/>\n\t\t)\n\t} else if (isConfigured) {\n\t\tbody = <Browser onPick={onPick} onError={onError} />\n\t}\n\treturn (\n\t\t<div className=\"rs rs-picker\" data-theme={theme}>\n\t\t\t{body}\n\t\t</div>\n\t)\n}\n\nfunction Browser({\n\tonPick,\n\tonError,\n}: {\n\tonPick: RasterPickerProps['onPick']\n\tonError: ReportError\n}) {\n\tconst client = useRasterClient()\n\tconst session = useSession()\n\tconst organizations = useOrganizations()\n\tconst navigation = useRasterNavigation({ organizations: organizations.data ?? null })\n\tconst { organizationId, library, query } = navigation\n\tconst organization = organizations.data?.find((entry) => entry.id === organizationId)\n\tconst libraries = useLibraries({ organizationId, organizationName: organization?.name ?? null })\n\tconst assets = useAssets({ organizationId, libraryId: library?.id ?? null })\n\tconst search = useAssetSearch(\n\t\torganizationId === null ? null : { organizationId, libraryId: library?.id ?? null },\n\t\tquery\n\t)\n\tconst upload = useAssetUpload()\n\tconst promote = usePromoteVariant()\n\tconst read = useMutation<Asset, RasterApiError, Parameters<typeof client.assets.get>[0]>({\n\t\tmutationFn: (options) => client.assets.get(options),\n\t})\n\tconst prefetchAssets = usePrefetchAssets()\n\tconst prefetchLibraries = usePrefetchLibraries()\n\tconst searchInput = useRef<HTMLInputElement>(null)\n\tconst [pending, setPending] = useState<Pending | null>(null)\n\tconst [confirming, setConfirming] = useState<string | null>(null)\n\tconst [dragging, setDragging] = useState(false)\n\tconst dragDepth = useRef(0)\n\t// The version shown large, or the search hit opened, for the scope it was chosen in.\n\tconst [focus, setFocus] = useState<{ scope: string; id: string } | null>(null)\n\tconst scope = [organizationId, library?.id, navigation.asset?.id, query].join('/')\n\tconst focusId = focus?.scope === scope ? focus.id : null\n\n\tconst access = libraries.data?.find((entry) => entry.id === library?.id)?.access ?? null\n\tconst writable = access === 'write'\n\t// The asset as the list last read it, which refetches after an upload or a promote.\n\tconst opened =\n\t\tnavigation.asset === null\n\t\t\t? null\n\t\t\t: (assets.assets?.find((entry) => entry.id === navigation.asset?.id) ?? navigation.asset)\n\tconst versions = !search.active && opened !== null\n\tconst variants = (opened?.variants ?? []).filter(\n\t\t(variant): variant is AssetVariant => variant !== null\n\t)\n\tconst shown = search.active ? search : library === null ? null : assets\n\tconst items = search.active ? search.hits : assets.assets\n\tconst hit = search.active ? (search.hits?.find((entry) => entry.id === focusId) ?? null) : null\n\n\t/** Hand the host the whole asset: a search hit or a variant carries less, so it is read first. */\n\tfunction pick(item: Asset | SearchHit | AssetVariant) {\n\t\tif (organizationId === null) return\n\t\tconst loaded = assets.assets?.find((entry) => entry.id === item.id)\n\t\tif (loaded) {\n\t\t\tonPick(loaded, organizationId)\n\t\t\treturn\n\t\t}\n\t\t// A variant lives in its original's library, which is the open one.\n\t\tconst libraryId = ('libraryId' in item ? item.libraryId : null) ?? library?.id\n\t\tif (!libraryId) return\n\t\tread.mutate(\n\t\t\t{ organizationId, libraryId, assetId: item.id },\n\t\t\t{ onSuccess: (asset) => onPick(asset, organizationId) }\n\t\t)\n\t}\n\n\t/** Upload one file into the open library, or as a variant of `parentId`. */\n\tasync function send(file: File, parentId: string | null) {\n\t\tif (organizationId === null || library === null || !writable) return\n\t\tconst preview = URL.createObjectURL(file)\n\t\tsetPending({ name: file.name, preview, parentId })\n\t\tawait upload.upload({ organizationId, libraryId: library.id, parentId, source: file })\n\t\tURL.revokeObjectURL(preview)\n\t\t// A later upload may have taken the placeholder over by now.\n\t\tsetPending((current) => (current?.preview === preview ? null : current))\n\t}\n\n\t/** The first press asks to confirm; the second promotes. */\n\tfunction setDefault(variant: AssetVariant) {\n\t\tif (confirming !== variant.id) {\n\t\t\tsetConfirming(variant.id)\n\t\t\treturn\n\t\t}\n\t\tsetConfirming(null)\n\t\tif (organizationId === null || library === null || opened === null) return\n\t\tvoid promote.promote({\n\t\t\torganizationId,\n\t\t\tlibraryId: library.id,\n\t\t\tassetId: opened.id,\n\t\t\tvariantId: variant.id,\n\t\t})\n\t}\n\n\tconst pendingTile = pending !== null &&\n\t\t!search.active &&\n\t\tpending.parentId === (opened?.id ?? null) && (\n\t\t\t<PendingTile\n\t\t\t\tname={pending.name}\n\t\t\t\tpreview={pending.preview}\n\t\t\t\tstatus={upload.state.status === 'uploading' ? 'Uploading' : 'Processing'}\n\t\t\t/>\n\t\t)\n\n\tlet status = ''\n\tif (search.active) status = search.isSearching ? 'Searching' : `${search.found} found`\n\telse if (library !== null && !versions) status = `${assets.assets?.length ?? 0} loaded`\n\n\tlet heading: ReactNode = (\n\t\t<>\n\t\t\t<h1 className=\"rs-heading\">{search.active ? 'Search' : (library?.name ?? 'Libraries')}</h1>\n\t\t\t{!search.active && library !== null && (\n\t\t\t\t<span className=\"rs-sidebar-count is-active\">{access ?? 'no access'}</span>\n\t\t\t)}\n\t\t</>\n\t)\n\tlet content: ReactNode = (\n\t\t<Empty glyph=\"grid\" title=\"Choose a library\" text=\"Or search every library.\" />\n\t)\n\tif (versions) {\n\t\tconst focused = [opened, ...variants].find((entry) => entry.id === focusId) ?? opened\n\t\theading = (\n\t\t\t<>\n\t\t\t\t<button type=\"button\" className=\"rs-crumb\" onClick={() => navigation.openAsset(null)}>\n\t\t\t\t\t{library?.name}\n\t\t\t\t</button>\n\t\t\t\t<span className=\"rs-muted\">/</span>\n\t\t\t\t<h1 className=\"rs-heading\">{opened.name}</h1>\n\t\t\t\t<span className=\"rs-sidebar-count is-active\">{variants.length + 1} versions</span>\n\t\t\t</>\n\t\t)\n\t\tcontent = (\n\t\t\t<>\n\t\t\t\t<Detail item={focused} busy={read.isPending} onPick={() => pick(focused)} />\n\t\t\t\t<div className=\"rs-grid\">\n\t\t\t\t\t<Tile\n\t\t\t\t\t\tasset={opened}\n\t\t\t\t\t\tselected={focused.id === opened.id}\n\t\t\t\t\t\tonOpen={() => setFocus({ scope, id: opened.id })}\n\t\t\t\t\t\tbadge={\n\t\t\t\t\t\t\t<span className=\"rs-pill rs-tile-badge\">\n\t\t\t\t\t\t\t\t<Icon shape=\"star\" solid width={10.5} />\n\t\t\t\t\t\t\t\tDefault\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t}\n\t\t\t\t\t/>\n\t\t\t\t\t{variants.map((variant) => (\n\t\t\t\t\t\t<Tile\n\t\t\t\t\t\t\tkey={variant.id}\n\t\t\t\t\t\t\tasset={variant}\n\t\t\t\t\t\t\tselected={focused.id === variant.id}\n\t\t\t\t\t\t\tonOpen={() => setFocus({ scope, id: variant.id })}\n\t\t\t\t\t\t\taction={\n\t\t\t\t\t\t\t\twritable &&\n\t\t\t\t\t\t\t\tisReady(variant) && (\n\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\t\t\t\tconfirming === variant.id\n\t\t\t\t\t\t\t\t\t\t\t\t? 'rs-pill rs-tile-action is-confirming'\n\t\t\t\t\t\t\t\t\t\t\t\t: 'rs-pill rs-tile-action'\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tdisabled={promote.state.status === 'promoting'}\n\t\t\t\t\t\t\t\t\t\tonClick={() => setDefault(variant)}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t<Icon shape=\"star\" width={10.5} />\n\t\t\t\t\t\t\t\t\t\t{confirming === variant.id ? 'Confirm' : 'Set as default'}\n\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t/>\n\t\t\t\t\t))}\n\t\t\t\t\t{pendingTile}\n\t\t\t\t</div>\n\t\t\t</>\n\t\t)\n\t} else if (hit) {\n\t\theading = (\n\t\t\t<>\n\t\t\t\t<button type=\"button\" className=\"rs-crumb\" onClick={() => setFocus(null)}>\n\t\t\t\t\tSearch\n\t\t\t\t</button>\n\t\t\t\t<span className=\"rs-muted\">/</span>\n\t\t\t\t<h1 className=\"rs-heading\">{hit.name}</h1>\n\t\t\t</>\n\t\t)\n\t\tcontent = <Detail item={hit} busy={read.isPending} onPick={() => pick(hit)} />\n\t} else if (shown === null ? organizations.isLoading || libraries.isLoading : items === null) {\n\t\tcontent = shown?.error ? null : (\n\t\t\t<div className=\"rs-center\">\n\t\t\t\t<span className=\"rs-spinner\" />\n\t\t\t</div>\n\t\t)\n\t} else if (shown !== null && items?.length === 0 && !pendingTile) {\n\t\tcontent = search.active ? (\n\t\t\t<Empty glyph=\"∅\" title=\"No results\" text={`Nothing matches “${search.query}”.`} />\n\t\t) : (\n\t\t\t<Empty glyph=\"∅\" title=\"No assets\" text=\"This library is empty.\">\n\t\t\t\t{writable && library !== null && (\n\t\t\t\t\t<FilePicker\n\t\t\t\t\t\tclassName=\"rs-button rs-button-primary rs-button-sm\"\n\t\t\t\t\t\tdisabled={false}\n\t\t\t\t\t\tonFile={(file) => void send(file, null)}\n\t\t\t\t\t>\n\t\t\t\t\t\tUpload files\n\t\t\t\t\t</FilePicker>\n\t\t\t\t)}\n\t\t\t</Empty>\n\t\t)\n\t} else if (shown !== null && items) {\n\t\tcontent = (\n\t\t\t<>\n\t\t\t\t<div className=\"rs-grid\">\n\t\t\t\t\t{pendingTile}\n\t\t\t\t\t{search.active\n\t\t\t\t\t\t? search.hits?.map((entry) => (\n\t\t\t\t\t\t\t\t<Tile\n\t\t\t\t\t\t\t\t\tkey={entry.id}\n\t\t\t\t\t\t\t\t\tasset={entry}\n\t\t\t\t\t\t\t\t\tonOpen={() => setFocus({ scope, id: entry.id })}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t))\n\t\t\t\t\t\t: assets.assets?.map((asset) => {\n\t\t\t\t\t\t\t\tconst count = asset.variants?.filter(Boolean).length ?? 0\n\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t<Tile\n\t\t\t\t\t\t\t\t\t\tkey={asset.id}\n\t\t\t\t\t\t\t\t\t\tasset={asset}\n\t\t\t\t\t\t\t\t\t\tonOpen={() => {\n\t\t\t\t\t\t\t\t\t\t\tsetConfirming(null)\n\t\t\t\t\t\t\t\t\t\t\tnavigation.openAsset(asset)\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\tbadge={\n\t\t\t\t\t\t\t\t\t\t\tcount > 0 && (\n\t\t\t\t\t\t\t\t\t\t\t\t<span className=\"rs-pill rs-tile-badge\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<Icon shape=\"layers\" width={10.5} />\n\t\t\t\t\t\t\t\t\t\t\t\t\t{count + 1} versions\n\t\t\t\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t})}\n\t\t\t\t</div>\n\t\t\t\t{shown.hasMore && (\n\t\t\t\t\t<div className=\"rs-center\">\n\t\t\t\t\t\t<Sentinel key={items.length} onVisible={shown.loadMore} />\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t</>\n\t\t)\n\t}\n\n\tconst dropTarget = writable && library !== null && !search.active\n\n\treturn (\n\t\t<>\n\t\t\t<aside className=\"rs-sidebar\">\n\t\t\t\t<Brand />\n\t\t\t\t<OrganizationSwitcher\n\t\t\t\t\torganizations={organizations.data ?? []}\n\t\t\t\t\tcurrent={organization}\n\t\t\t\t\tonIntent={(entry) =>\n\t\t\t\t\t\tentry.id !== organizationId && prefetchLibraries(entry.id, entry.name)\n\t\t\t\t\t}\n\t\t\t\t\tonSelect={navigation.selectOrganization}\n\t\t\t\t/>\n\t\t\t\t<button\n\t\t\t\t\ttype=\"button\"\n\t\t\t\t\tclassName={search.active ? 'rs-sidebar-item is-active' : 'rs-sidebar-item'}\n\t\t\t\t\tonClick={() => searchInput.current?.focus()}\n\t\t\t\t>\n\t\t\t\t\t<Icon shape=\"search\" />\n\t\t\t\t\tSearch\n\t\t\t\t</button>\n\t\t\t\t<nav className=\"rs-scroll\">\n\t\t\t\t\t<h2 className=\"rs-sidebar-heading\">Libraries</h2>\n\t\t\t\t\t{libraries.data?.map((entry) => {\n\t\t\t\t\t\tconst open = entry.id === library?.id\n\t\t\t\t\t\tconst warm = () => organizationId !== null && prefetchAssets(organizationId, entry.id)\n\t\t\t\t\t\tconst toGrid = () => {\n\t\t\t\t\t\t\tnavigation.setQuery('')\n\t\t\t\t\t\t\tnavigation.openAsset(null)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div key={entry.id} className={open ? 'rs-library is-expanded' : 'rs-library'}>\n\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\tclassName=\"rs-sidebar-item rs-library-row\"\n\t\t\t\t\t\t\t\t\tdisabled={entry.access === null}\n\t\t\t\t\t\t\t\t\tonMouseEnter={warm}\n\t\t\t\t\t\t\t\t\tonFocus={warm}\n\t\t\t\t\t\t\t\t\tonClick={() => navigation.openLibrary(entry)}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<Icon shape=\"grid\" />\n\t\t\t\t\t\t\t\t\t<span className=\"rs-library-name\">{entry.name}</span>\n\t\t\t\t\t\t\t\t\t<span className={open ? 'rs-sidebar-count is-active' : 'rs-sidebar-count'}>\n\t\t\t\t\t\t\t\t\t\t{COUNT.format(entry.assetsCount ?? 0)}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tclassName={open ? 'rs-library-menu is-expanded' : 'rs-library-menu'}\n\t\t\t\t\t\t\t\t\taria-hidden={!open}\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<div>\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\ttabIndex={open ? 0 : -1}\n\t\t\t\t\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\t\t\t\t\topen && !search.active && opened === null\n\t\t\t\t\t\t\t\t\t\t\t\t\t? 'rs-sidebar-item is-active'\n\t\t\t\t\t\t\t\t\t\t\t\t\t: 'rs-sidebar-item'\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tonClick={toGrid}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<Icon shape=\"circle\" />\n\t\t\t\t\t\t\t\t\t\t\tAll Assets\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t<FilePicker\n\t\t\t\t\t\t\t\t\t\t\tclassName=\"rs-sidebar-item\"\n\t\t\t\t\t\t\t\t\t\t\tdisabled={!open || entry.access !== 'write'}\n\t\t\t\t\t\t\t\t\t\t\tfocusable={open}\n\t\t\t\t\t\t\t\t\t\t\tonFile={(file) => {\n\t\t\t\t\t\t\t\t\t\t\t\ttoGrid()\n\t\t\t\t\t\t\t\t\t\t\t\tvoid send(file, null)\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t\t<Icon shape=\"upload\" />\n\t\t\t\t\t\t\t\t\t\t\tUpload\n\t\t\t\t\t\t\t\t\t\t</FilePicker>\n\t\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)\n\t\t\t\t\t})}\n\t\t\t\t</nav>\n\t\t\t\t<div className=\"rs-sidebar-footer\">\n\t\t\t\t\t<span className=\"rs-sidebar-item rs-muted\">\n\t\t\t\t\t\t{session.isSignedIn ? (session.userName ?? 'Signed in') : 'API key'}\n\t\t\t\t\t</span>\n\t\t\t\t\t<button type=\"button\" className=\"rs-sidebar-item\" onClick={() => void session.signOut()}>\n\t\t\t\t\t\tSign out\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</aside>\n\t\t\t<section\n\t\t\t\tclassName=\"rs-main\"\n\t\t\t\taria-label=\"Assets\"\n\t\t\t\tonDragEnter={(event) => {\n\t\t\t\t\tif (!dropTarget || !event.dataTransfer.types.includes('Files')) return\n\t\t\t\t\tdragDepth.current += 1\n\t\t\t\t\tsetDragging(true)\n\t\t\t\t}}\n\t\t\t\tonDragOver={(event) => {\n\t\t\t\t\tif (dropTarget) event.preventDefault()\n\t\t\t\t}}\n\t\t\t\tonDragLeave={() => {\n\t\t\t\t\tdragDepth.current = Math.max(0, dragDepth.current - 1)\n\t\t\t\t\tif (dragDepth.current === 0) setDragging(false)\n\t\t\t\t}}\n\t\t\t\tonDrop={(event) => {\n\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\tdragDepth.current = 0\n\t\t\t\t\tsetDragging(false)\n\t\t\t\t\tconst file = event.dataTransfer.files[0]\n\t\t\t\t\tif (file && dropTarget) void send(file, versions ? opened.id : null)\n\t\t\t\t}}\n\t\t\t>\n\t\t\t\t<header className=\"rs-header\">\n\t\t\t\t\t{heading}\n\t\t\t\t\t<span className=\"rs-muted rs-small\">{status}</span>\n\t\t\t\t\t<Progress upload={upload.state} promote={promote.state} />\n\t\t\t\t\t<div className=\"rs-search\">\n\t\t\t\t\t\t<Icon shape=\"search\" width={12.25} />\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\tref={searchInput}\n\t\t\t\t\t\t\tclassName=\"rs-input\"\n\t\t\t\t\t\t\taria-label=\"Search Raster\"\n\t\t\t\t\t\t\tplaceholder={library === null ? 'Search every library…' : `Search ${library.name}…`}\n\t\t\t\t\t\t\tvalue={query}\n\t\t\t\t\t\t\tonChange={(event) => navigation.setQuery(event.target.value)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\t{query !== '' && (\n\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\tclassName=\"rs-icon-button\"\n\t\t\t\t\t\t\t\taria-label=\"Clear search\"\n\t\t\t\t\t\t\t\tonClick={() => navigation.setQuery('')}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t<Icon shape=\"x\" width={10.5} />\n\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t\t{versions && writable && (\n\t\t\t\t\t\t<FilePicker\n\t\t\t\t\t\t\tclassName=\"rs-button rs-button-sm\"\n\t\t\t\t\t\t\tdisabled={false}\n\t\t\t\t\t\t\tonFile={(file) => void send(file, opened.id)}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Icon shape=\"upload\" width={12.25} />\n\t\t\t\t\t\t\tUpload variant\n\t\t\t\t\t\t</FilePicker>\n\t\t\t\t\t)}\n\t\t\t\t\t<button type=\"button\" className=\"rs-button rs-button-sm\" onClick={navigation.refresh}>\n\t\t\t\t\t\tRefresh\n\t\t\t\t\t</button>\n\t\t\t\t</header>\n\t\t\t\t<div className=\"rs-content rs-scroll\">\n\t\t\t\t\t{organizations.error && <ErrorCard error={organizations.error} onError={onError} />}\n\t\t\t\t\t{libraries.error && <ErrorCard error={libraries.error} onError={onError} />}\n\t\t\t\t\t{shown?.error && <ErrorCard error={shown.error} onError={onError} />}\n\t\t\t\t\t{read.error && <ErrorCard error={read.error} onError={onError} />}\n\t\t\t\t\t{upload.state.status === 'error' && (\n\t\t\t\t\t\t<ErrorCard error={upload.state.error} onError={onError} />\n\t\t\t\t\t)}\n\t\t\t\t\t{promote.state.status === 'error' && (\n\t\t\t\t\t\t<ErrorCard error={promote.state.error} onError={onError} />\n\t\t\t\t\t)}\n\t\t\t\t\t{content}\n\t\t\t\t</div>\n\t\t\t\t<div className={dragging ? 'rs-drop is-active' : 'rs-drop'} />\n\t\t\t</section>\n\t\t</>\n\t)\n}\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where `useRasterNavigation` remembers the last organization and library, so a reopen lands
|
|
3
|
+
* where the user left off. Async because Figma can only reach `figma.clientStorage` through
|
|
4
|
+
* a message to its sandbox.
|
|
5
|
+
*/
|
|
6
|
+
export interface StateStore {
|
|
7
|
+
get<T>(key: string): Promise<T | null>;
|
|
8
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
9
|
+
delete(key: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/** Held for the lifetime of the page, which means a reopen does not restore where the user was. */
|
|
12
|
+
export declare function memoryStateStore(): StateStore;
|
|
13
|
+
/**
|
|
14
|
+
* In `localStorage`. Every failure reads as nothing stored: it is absent outside a browser,
|
|
15
|
+
* throws in a Safari private window or wherever site data is blocked, and an older version's
|
|
16
|
+
* value may no longer parse.
|
|
17
|
+
*/
|
|
18
|
+
export declare function localStorageStateStore(prefix?: string): StateStore;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/** Held for the lifetime of the page, which means a reopen does not restore where the user was. */
|
|
2
|
+
export function memoryStateStore() {
|
|
3
|
+
const values = new Map();
|
|
4
|
+
return {
|
|
5
|
+
async get(key) {
|
|
6
|
+
// biome-ignore lint/plugin: the caller names the type of what it stored under `key`, which no guard can check.
|
|
7
|
+
return values.get(key) ?? null;
|
|
8
|
+
},
|
|
9
|
+
async set(key, value) {
|
|
10
|
+
values.set(key, value);
|
|
11
|
+
},
|
|
12
|
+
async delete(key) {
|
|
13
|
+
values.delete(key);
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* In `localStorage`. Every failure reads as nothing stored: it is absent outside a browser,
|
|
19
|
+
* throws in a Safari private window or wherever site data is blocked, and an older version's
|
|
20
|
+
* value may no longer parse.
|
|
21
|
+
*/
|
|
22
|
+
export function localStorageStateStore(prefix = 'raster.state.') {
|
|
23
|
+
return {
|
|
24
|
+
async get(key) {
|
|
25
|
+
try {
|
|
26
|
+
const raw = globalThis.localStorage?.getItem(prefix + key);
|
|
27
|
+
return raw == null ? null : JSON.parse(raw);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
async set(key, value) {
|
|
34
|
+
try {
|
|
35
|
+
globalThis.localStorage?.setItem(prefix + key, JSON.stringify(value));
|
|
36
|
+
}
|
|
37
|
+
catch { }
|
|
38
|
+
},
|
|
39
|
+
async delete(key) {
|
|
40
|
+
try {
|
|
41
|
+
globalThis.localStorage?.removeItem(prefix + key);
|
|
42
|
+
}
|
|
43
|
+
catch { }
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=state-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-store.js","sourceRoot":"","sources":["../src/state-store.ts"],"names":[],"mappings":"AAWA,mGAAmG;AACnG,MAAM,UAAU,gBAAgB;IAC/B,MAAM,MAAM,GAAG,IAAI,GAAG,EAAmB,CAAA;IACzC,OAAO;QACN,KAAK,CAAC,GAAG,CAAI,GAAW;YACvB,+GAA+G;YAC/G,OAAQ,MAAM,CAAC,GAAG,CAAC,GAAG,CAAmB,IAAI,IAAI,CAAA;QAClD,CAAC;QACD,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,KAAQ;YACjC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QACvB,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,GAAW;YACvB,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACnB,CAAC;KACD,CAAA;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAM,GAAG,eAAe;IAC9D,OAAO;QACN,KAAK,CAAC,GAAG,CAAI,GAAW;YACvB,IAAI,CAAC;gBACJ,MAAM,GAAG,GAAG,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,MAAM,GAAG,GAAG,CAAC,CAAA;gBAC1D,OAAO,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAC5C,CAAC;YAAC,MAAM,CAAC;gBACR,OAAO,IAAI,CAAA;YACZ,CAAC;QACF,CAAC;QACD,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,KAAQ;YACjC,IAAI,CAAC;gBACJ,UAAU,CAAC,YAAY,EAAE,OAAO,CAAC,MAAM,GAAG,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAA;YACtE,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACX,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,GAAW;YACvB,IAAI,CAAC;gBACJ,UAAU,CAAC,YAAY,EAAE,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC,CAAA;YAClD,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACX,CAAC;KACD,CAAA;AACF,CAAC","sourcesContent":["/**\n * Where `useRasterNavigation` remembers the last organization and library, so a reopen lands\n * where the user left off. Async because Figma can only reach `figma.clientStorage` through\n * a message to its sandbox.\n */\nexport interface StateStore {\n\tget<T>(key: string): Promise<T | null>\n\tset<T>(key: string, value: T): Promise<void>\n\tdelete(key: string): Promise<void>\n}\n\n/** Held for the lifetime of the page, which means a reopen does not restore where the user was. */\nexport function memoryStateStore(): StateStore {\n\tconst values = new Map<string, unknown>()\n\treturn {\n\t\tasync get<T>(key: string) {\n\t\t\t// biome-ignore lint/plugin: the caller names the type of what it stored under `key`, which no guard can check.\n\t\t\treturn (values.get(key) as T | undefined) ?? null\n\t\t},\n\t\tasync set<T>(key: string, value: T) {\n\t\t\tvalues.set(key, value)\n\t\t},\n\t\tasync delete(key: string) {\n\t\t\tvalues.delete(key)\n\t\t},\n\t}\n}\n\n/**\n * In `localStorage`. Every failure reads as nothing stored: it is absent outside a browser,\n * throws in a Safari private window or wherever site data is blocked, and an older version's\n * value may no longer parse.\n */\nexport function localStorageStateStore(prefix = 'raster.state.'): StateStore {\n\treturn {\n\t\tasync get<T>(key: string): Promise<T | null> {\n\t\t\ttry {\n\t\t\t\tconst raw = globalThis.localStorage?.getItem(prefix + key)\n\t\t\t\treturn raw == null ? null : JSON.parse(raw)\n\t\t\t} catch {\n\t\t\t\treturn null\n\t\t\t}\n\t\t},\n\t\tasync set<T>(key: string, value: T) {\n\t\t\ttry {\n\t\t\t\tglobalThis.localStorage?.setItem(prefix + key, JSON.stringify(value))\n\t\t\t} catch {}\n\t\t},\n\t\tasync delete(key: string) {\n\t\t\ttry {\n\t\t\t\tglobalThis.localStorage?.removeItem(prefix + key)\n\t\t\t} catch {}\n\t\t},\n\t}\n}\n"]}
|