diff --git a/apps/playground/package.json b/apps/playground/package.json index e224c8a77..2c1d07749 100644 --- a/apps/playground/package.json +++ b/apps/playground/package.json @@ -23,6 +23,7 @@ "@opendatacapture/schemas": "workspace:*", "axios": "catalog:", "esbuild-wasm": "catalog:", + "idb-keyval": "^6.2.2", "immer": "^10.1.1", "jszip": "^3.10.1", "jwt-decode": "^4.0.0", diff --git a/apps/playground/src/components/Header/ActionsDropdown/ActionsDropdown.tsx b/apps/playground/src/components/Header/ActionsDropdown/ActionsDropdown.tsx index d32391a50..0a391d29d 100644 --- a/apps/playground/src/components/Header/ActionsDropdown/ActionsDropdown.tsx +++ b/apps/playground/src/components/Header/ActionsDropdown/ActionsDropdown.tsx @@ -8,6 +8,7 @@ import { useAppStore } from '@/store'; import { DeleteInstrumentDialog } from './DeleteInstrumentDialog'; import { LoginDialog } from './LoginDialog'; import { RestoreDefaultsDialog } from './RestoreDefaultsDialog'; +import { StorageUsageDialog } from './StorageUsageDialog'; import { UploadBundleDialog } from './UploadBundleDialog'; import { UserSettingsDialog } from './UserSettingsDialog'; @@ -17,6 +18,7 @@ export const ActionsDropdown = () => { const [showRestoreDefaultsDialog, setShowRestoreDefaultsDialog] = useState(false); const [showUploadBundleDialog, setShowUploadBundleDialog] = useState(false); const [showLoginDialog, setShowLoginDialog] = useState(false); + const [showStorageUsageDialog, setShowStorageUsageDialog] = useState(false); const selectedInstrument = useAppStore((store) => store.selectedInstrument); @@ -54,6 +56,11 @@ export const ActionsDropdown = () => { User Settings + setShowStorageUsageDialog(true)}> + + Storage Usage + + setShowDeleteInstrumentDialog(true)}> { }} /> + ); }; diff --git a/apps/playground/src/components/Header/ActionsDropdown/LoginDialog.tsx b/apps/playground/src/components/Header/ActionsDropdown/LoginDialog.tsx index c13fbf995..68754ff21 100644 --- a/apps/playground/src/components/Header/ActionsDropdown/LoginDialog.tsx +++ b/apps/playground/src/components/Header/ActionsDropdown/LoginDialog.tsx @@ -18,7 +18,8 @@ type $LoginData = z.infer; const $LoginData = z.object({ apiBaseUrl: z.url(), username: z.string().min(1), - password: z.string().min(1) + password: z.string().min(1), + legacyLogin: z.boolean() }); export type LoginDialogProps = { @@ -39,10 +40,13 @@ export const LoginDialog = ({ isOpen, setIsOpen }: LoginDialogProps) => { revalidateToken(); }, [isOpen]); - const getAdminToken = (credentials: $LoginCredentials): ResultAsync<{ accessToken: string }, string> => { + const getAdminToken = ( + credentials: $LoginCredentials, + baseUrl: string + ): ResultAsync<{ accessToken: string }, string> => { return asyncResultify(async () => { try { - const response = await axios.post(`${apiBaseUrl}/v1/auth/login`, credentials, { + const response = await axios.post(`${baseUrl}/v1/auth/login`, credentials, { headers: { Accept: 'application/json' }, @@ -59,10 +63,10 @@ export const LoginDialog = ({ isOpen, setIsOpen }: LoginDialogProps) => { }); }; - const getLimitedToken = (adminToken: string): ResultAsync<{ accessToken: string }, string> => { + const getLimitedToken = (adminToken: string, baseUrl: string): ResultAsync<{ accessToken: string }, string> => { return asyncResultify(async () => { try { - const response = await axios.get(`${apiBaseUrl}/v1/auth/create-instrument-token`, { + const response = await axios.get(`${baseUrl}/v1/auth/create-instrument-token`, { headers: { Accept: 'application/json', Authorization: `Bearer ${adminToken}` @@ -80,20 +84,24 @@ export const LoginDialog = ({ isOpen, setIsOpen }: LoginDialogProps) => { }); }; - const handleSubmit = async ({ apiBaseUrl, ...credentials }: $LoginData) => { + const handleSubmit = async ({ apiBaseUrl, legacyLogin, ...credentials }: $LoginData) => { updateSettings({ apiBaseUrl }); - const adminTokenResult = await getAdminToken(credentials); + const adminTokenResult = await getAdminToken(credentials, apiBaseUrl); if (adminTokenResult.isErr()) { addNotification({ type: 'error', title: 'Login Failed', message: adminTokenResult.error }); return; } - const limitedTokenResult = await getLimitedToken(adminTokenResult.value.accessToken); - if (limitedTokenResult.isErr()) { - addNotification({ type: 'error', title: 'Failed to Get Limited Token', message: limitedTokenResult.error }); - return; - } - login(limitedTokenResult.value.accessToken); + if (legacyLogin) { + login(adminTokenResult.value.accessToken); + } else { + const limitedTokenResult = await getLimitedToken(adminTokenResult.value.accessToken, apiBaseUrl); + if (limitedTokenResult.isErr()) { + addNotification({ type: 'error', title: 'Failed to Get Limited Token', message: limitedTokenResult.error }); + return; + } + login(limitedTokenResult.value.accessToken); + } addNotification({ type: 'success' }); }; @@ -137,6 +145,20 @@ export const LoginDialog = ({ isOpen, setIsOpen }: LoginDialogProps) => { placeholder: 'e.g., https://demo.opendatacapture.org/api', label: 'API Base URL', variant: 'input' + }, + legacyLogin: { + description: [ + "Use the user's full access token instead of a granular access token.", + 'Note that this can introduce security risks and should not be used on shared machines.', + 'It is required only for ODC versions prior to v1.12.0.' + ].join(''), + kind: 'boolean', + label: 'Legacy Login Mode', + variant: 'radio', + options: { + false: 'No (Recommended)', + true: 'Yes' + } } } }, @@ -156,7 +178,7 @@ export const LoginDialog = ({ isOpen, setIsOpen }: LoginDialogProps) => { } } ]} - initialValues={{ apiBaseUrl }} + initialValues={{ apiBaseUrl, legacyLogin: false }} validationSchema={$LoginData} onSubmit={async (data) => { await handleSubmit(data); diff --git a/apps/playground/src/components/Header/ActionsDropdown/StorageUsageDialog.tsx b/apps/playground/src/components/Header/ActionsDropdown/StorageUsageDialog.tsx new file mode 100644 index 000000000..abd654f52 --- /dev/null +++ b/apps/playground/src/components/Header/ActionsDropdown/StorageUsageDialog.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from 'react'; + +import { formatByteSize } from '@douglasneuroinformatics/libjs'; +import { Button, Dialog } from '@douglasneuroinformatics/libui/components'; + +import { useAppStore } from '@/store'; +export type StorageUsageDialogProps = { + isOpen: boolean; + setIsOpen: (value: boolean) => void; +}; + +export const StorageUsageDialog = ({ isOpen, setIsOpen }: StorageUsageDialogProps) => { + const [storageEstimate, setStorageEstimate] = useState(null); + const [updateKey, setUpdateKey] = useState(0); + const [message, setMessage] = useState('Loading...'); + + const updateStorage = async () => { + setMessage('Loading...'); + const [updated] = await Promise.all([ + await navigator.storage.estimate(), + new Promise((resolve) => setTimeout(resolve, 500)) + ]); + setStorageEstimate(updated); + setMessage(null); + }; + + useEffect(() => { + void updateStorage(); + }, [isOpen, updateKey]); + + return ( + + + + Storage Usage + + Check the details below to see how much storage your browser is using for instruments and how much space is + still available. + + + + {message ? ( + {message} + ) : ( + <> + Usage: {storageEstimate?.usage ? formatByteSize(storageEstimate.usage, true) : 'N/A'} + Quota: {storageEstimate?.quota ? formatByteSize(storageEstimate.quota, true) : 'N/A'} + > + )} + + + { + useAppStore.persist.clearStorage(); + setMessage('Deleting...'); + void new Promise((resolve) => setTimeout(resolve, 2000)).then(() => { + setUpdateKey(updateKey + 1); + }); + }} + > + Drop Database (Irreversible) + + setIsOpen(false)}> + Close + + + + + ); +}; diff --git a/apps/playground/src/store/index.ts b/apps/playground/src/store/index.ts index 323f0018c..95c45bcd1 100644 --- a/apps/playground/src/store/index.ts +++ b/apps/playground/src/store/index.ts @@ -1,8 +1,10 @@ /* eslint-disable import/exports-last */ +import { del, get, set } from 'idb-keyval'; import { jwtDecode } from 'jwt-decode'; import { pick } from 'lodash-es'; import { create } from 'zustand'; import { createJSONStorage, devtools, persist, subscribeWithSelector } from 'zustand/middleware'; +import type { StateStorage } from 'zustand/middleware'; import { immer } from 'zustand/middleware/immer'; import { resolveIndexFilename } from '@/utils/file'; @@ -16,6 +18,18 @@ import { createViewerSlice } from './slices/viewer.slice'; import type { AppStore } from './types'; +const storage: StateStorage = { + getItem: async (name: string): Promise => { + return (await get(name)) ?? null; + }, + removeItem: async (name: string): Promise => { + await del(name); + }, + setItem: async (name: string, value: string): Promise => { + await set(name, value); + } +}; + export const useAppStore = create( devtools( persist( @@ -64,7 +78,7 @@ export const useAppStore = create( _accessToken: state.auth?.accessToken }; }, - storage: createJSONStorage(() => localStorage), + storage: createJSONStorage(() => storage), version: 1 } ) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 81379da29..12f8e0d46 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -454,10 +454,10 @@ importers: version: 3.4.1 '@astrojs/starlight': specifier: ^0.34.3 - version: 0.34.5(astro@5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)) + version: 0.34.5(astro@5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)) '@astrojs/starlight-tailwind': specifier: 4.0.1 - version: 4.0.1(@astrojs/starlight@0.34.5(astro@5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)))(tailwindcss@4.1.11) + version: 4.0.1(@astrojs/starlight@0.34.5(astro@5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)))(tailwindcss@4.1.11) '@opendatacapture/runtime-core': specifier: workspace:* version: link:../../packages/runtime-core @@ -469,7 +469,7 @@ importers: version: 4.1.11(vite@6.3.6(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) astro: specifier: ^5.13.1 - version: 5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0) + version: 5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0) github-slugger: specifier: ^2.0.0 version: 2.0.0 @@ -524,6 +524,9 @@ importers: esbuild-wasm: specifier: 'catalog:' version: 0.23.1 + idb-keyval: + specifier: ^6.2.2 + version: 6.2.2 immer: specifier: ^10.1.1 version: 10.1.1 @@ -8435,6 +8438,10 @@ packages: { integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== } engines: { node: '>=0.10.0' } + idb-keyval@6.2.2: + resolution: + { integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg== } + ieee754@1.2.1: resolution: { integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== } @@ -13195,12 +13202,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.3.0(astro@5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0))': + '@astrojs/mdx@4.3.0(astro@5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0))': dependencies: '@astrojs/markdown-remark': 6.3.2 '@mdx-js/mdx': 3.1.0(acorn@8.15.0) acorn: 8.15.0 - astro: 5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0) + astro: 5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0) es-module-lexer: 1.7.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 @@ -13224,22 +13231,22 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.25.76 - '@astrojs/starlight-tailwind@4.0.1(@astrojs/starlight@0.34.5(astro@5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)))(tailwindcss@4.1.11)': + '@astrojs/starlight-tailwind@4.0.1(@astrojs/starlight@0.34.5(astro@5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)))(tailwindcss@4.1.11)': dependencies: - '@astrojs/starlight': 0.34.5(astro@5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)) + '@astrojs/starlight': 0.34.5(astro@5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)) tailwindcss: 4.1.11 - '@astrojs/starlight@0.34.5(astro@5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0))': + '@astrojs/starlight@0.34.5(astro@5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0))': dependencies: '@astrojs/markdown-remark': 6.3.2 - '@astrojs/mdx': 4.3.0(astro@5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)) + '@astrojs/mdx': 4.3.0(astro@5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)) '@astrojs/sitemap': 3.4.1 '@pagefind/default-ui': 1.3.0 '@types/hast': 3.0.4 '@types/js-yaml': 4.0.9 '@types/mdast': 4.0.4 - astro: 5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0) - astro-expressive-code: 0.41.3(astro@5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)) + astro: 5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0) + astro-expressive-code: 0.41.3(astro@5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.4 @@ -13265,7 +13272,7 @@ snapshots: '@astrojs/telemetry@3.3.0': dependencies: ci-info: 4.3.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) dlv: 1.1.3 dset: 3.1.4 is-docker: 3.0.0 @@ -13299,7 +13306,7 @@ snapshots: '@babel/traverse': 7.28.0 '@babel/types': 7.28.1 convert-source-map: 2.0.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -13457,7 +13464,7 @@ snapshots: '@babel/parser': 7.28.0 '@babel/template': 7.27.2 '@babel/types': 7.28.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -14137,7 +14144,7 @@ snapshots: '@eslint/config-array@0.19.2': dependencies: '@eslint/object-schema': 2.1.6 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -14155,7 +14162,7 @@ snapshots: '@eslint/eslintrc@3.3.1': dependencies: ajv: 6.12.6 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 @@ -16365,7 +16372,7 @@ snapshots: '@swc-node/sourcemap-support': 0.5.1 '@swc/core': 1.12.11(@swc/helpers@0.5.17) colorette: 2.0.20 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) oxc-resolver: 5.3.0 pirates: 4.0.7 tslib: 2.8.1 @@ -16713,7 +16720,7 @@ snapshots: '@tokenizer/inflate@0.2.7': dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) fflate: 0.8.2 token-types: 6.0.3 transitivePeerDependencies: @@ -17004,7 +17011,7 @@ snapshots: '@typescript-eslint/types': 8.36.0 '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 8.36.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) eslint: 9.23.0(jiti@2.4.2) typescript: 5.6.3 transitivePeerDependencies: @@ -17014,7 +17021,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.6.3) '@typescript-eslint/types': 8.36.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -17032,7 +17039,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.6.3) '@typescript-eslint/utils': 8.36.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.6.3) - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) eslint: 9.23.0(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.6.3) typescript: 5.6.3 @@ -17047,7 +17054,7 @@ snapshots: '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.6.3) '@typescript-eslint/types': 8.36.0 '@typescript-eslint/visitor-keys': 8.36.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 @@ -17131,7 +17138,7 @@ snapshots: '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 ast-v8-to-istanbul: 0.3.3 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 @@ -17368,7 +17375,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color optional: true @@ -17592,7 +17599,7 @@ snapshots: '@typescript-eslint/scope-manager': 8.36.0 '@typescript-eslint/types': 8.36.0 astrojs-compiler-sync: 1.1.1(@astrojs/compiler@2.12.2) - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) entities: 6.0.1 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -17603,12 +17610,12 @@ snapshots: transitivePeerDependencies: - supports-color - astro-expressive-code@0.41.3(astro@5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)): + astro-expressive-code@0.41.3(astro@5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0)): dependencies: - astro: 5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0) + astro: 5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0) rehype-expressive-code: 0.41.3 - astro@5.14.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0): + astro@5.14.4(@types/node@22.16.3)(idb-keyval@6.2.2)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.0)(tsx@4.20.3)(typescript@5.6.3)(yaml@2.8.0): dependencies: '@astrojs/compiler': 2.12.2 '@astrojs/internal-helpers': 0.7.4 @@ -17626,7 +17633,7 @@ snapshots: common-ancestor-path: 1.0.1 cookie: 1.0.2 cssesc: 3.0.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) deterministic-object-hash: 2.0.2 devalue: 5.3.2 diff: 5.2.0 @@ -17662,7 +17669,7 @@ snapshots: ultrahtml: 1.6.0 unifont: 0.6.0 unist-util-visit: 5.0.0 - unstorage: 1.17.1 + unstorage: 1.17.1(idb-keyval@6.2.2) vfile: 6.0.3 vite: 6.3.6(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) vitefu: 1.1.1(vite@6.3.6(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)) @@ -17853,7 +17860,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) http-errors: 2.0.0 iconv-lite: 0.6.3 on-finished: 2.4.1 @@ -18704,7 +18711,7 @@ snapshots: esbuild-register@3.6.0(esbuild@0.25.6): dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) esbuild: 0.25.6 transitivePeerDependencies: - supports-color @@ -18885,7 +18892,7 @@ snapshots: '@es-joy/jsdoccomment': 0.50.2 are-docs-informative: 0.0.2 comment-parser: 1.4.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) escape-string-regexp: 4.0.0 eslint: 9.23.0(jiti@2.4.2) espree: 10.4.0 @@ -19010,7 +19017,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) escape-string-regexp: 4.0.0 eslint-scope: 8.4.0 eslint-visitor-keys: 4.2.1 @@ -19195,7 +19202,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -19390,7 +19397,7 @@ snapshots: finalhandler@2.1.0: dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -19440,7 +19447,7 @@ snapshots: follow-redirects@1.15.9(debug@4.4.1): optionalDependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) fontace@0.3.0: dependencies: @@ -19997,7 +20004,7 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color optional: true @@ -20018,7 +20025,7 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) transitivePeerDependencies: - supports-color optional: true @@ -20053,6 +20060,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + idb-keyval@6.2.2: {} + ieee754@1.2.1: {} ignore-by-default@1.0.1: {} @@ -20315,7 +20324,7 @@ snapshots: istanbul-lib-source-maps@5.0.6: dependencies: '@jridgewell/trace-mapping': 0.3.29 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -21219,7 +21228,7 @@ snapshots: micromark@4.0.2: dependencies: '@types/debug': 4.1.12 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) decode-named-character-reference: 1.2.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -22659,7 +22668,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -22766,7 +22775,7 @@ snapshots: send@1.2.0: dependencies: - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -23030,7 +23039,7 @@ snapshots: socks-proxy-agent@6.2.1: dependencies: agent-base: 6.0.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) socks: 2.8.6 transitivePeerDependencies: - supports-color @@ -23147,7 +23156,7 @@ snapshots: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -23356,7 +23365,7 @@ snapshots: dependencies: component-emitter: 1.3.1 cookiejar: 2.1.4 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) fast-safe-stringify: 2.1.1 form-data: 4.0.4 formidable: 3.5.4 @@ -23857,7 +23866,7 @@ snapshots: picomatch: 4.0.2 webpack-virtual-modules: 0.6.2 - unstorage@1.17.1: + unstorage@1.17.1(idb-keyval@6.2.2): dependencies: anymatch: 3.1.3 chokidar: 4.0.3 @@ -23867,6 +23876,8 @@ snapshots: node-fetch-native: 1.6.7 ofetch: 1.4.1 ufo: 1.6.1 + optionalDependencies: + idb-keyval: 6.2.2 untildify@4.0.0: {} @@ -23987,7 +23998,7 @@ snapshots: vite-node@3.2.4(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.8.2)(yaml@2.8.0): dependencies: cac: 6.7.14 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) es-module-lexer: 1.7.0 pathe: 2.0.3 vite: 6.3.5(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.8.2)(yaml@2.8.0) @@ -24008,7 +24019,7 @@ snapshots: vite-plugin-compression@0.5.1(vite@6.3.5(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0)): dependencies: chalk: 4.1.2 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) fs-extra: 10.1.0 vite: 6.3.5(@types/node@22.16.3)(jiti@2.4.2)(lightningcss@1.30.1)(tsx@4.20.3)(yaml@2.8.0) transitivePeerDependencies: @@ -24077,7 +24088,7 @@ snapshots: '@vitest/spy': 3.2.4 '@vitest/utils': 3.2.4 chai: 5.2.1 - debug: 4.4.1(supports-color@8.1.1) + debug: 4.4.1(supports-color@5.5.0) expect-type: 1.2.2 magic-string: 0.30.17 pathe: 2.0.3
{message}
Usage: {storageEstimate?.usage ? formatByteSize(storageEstimate.usage, true) : 'N/A'}
Quota: {storageEstimate?.quota ? formatByteSize(storageEstimate.quota, true) : 'N/A'}