diff --git a/frontend/apps/artcraft-webapp/src/components/sidebar/library-tags-nav.tsx b/frontend/apps/artcraft-webapp/src/components/sidebar/library-tags-nav.tsx index 1dc3c5cb63..7f9d97c7d2 100644 --- a/frontend/apps/artcraft-webapp/src/components/sidebar/library-tags-nav.tsx +++ b/frontend/apps/artcraft-webapp/src/components/sidebar/library-tags-nav.tsx @@ -15,7 +15,7 @@ import { } from "../../pages/library/library-tags-store"; import { EASE_EMPHASIS } from "../../lib/motion"; -const MAX_SIDEBAR_TAGS = 10; +const MAX_SIDEBAR_TAGS = 5; /** * The "Tags" rows of the Assets sidebar group, rendered below the folders @@ -137,6 +137,23 @@ export function LibraryTagsNav({ ))} + {tags.length > MAX_SIDEBAR_TAGS && ( + + + + + See all tags + + {tags.length} + + + + + )} )} diff --git a/frontend/apps/artcraft-webapp/src/components/tags/TagsSection.tsx b/frontend/apps/artcraft-webapp/src/components/tags/TagsSection.tsx index 3371adeac4..4fff564816 100644 --- a/frontend/apps/artcraft-webapp/src/components/tags/TagsSection.tsx +++ b/frontend/apps/artcraft-webapp/src/components/tags/TagsSection.tsx @@ -1,76 +1,35 @@ -import { useEffect, useMemo, useRef, useState } from "react"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faTag } from "@fortawesome/pro-solid-svg-icons"; -import { TagsApi, type TagDetails, type UserInfo } from "@storyteller/api"; -import { toast } from "../toast/toast"; +import { useEffect, useMemo } from "react"; +import type { UserInfo } from "@storyteller/api"; +import { TagsSection as SharedTagsSection } from "@storyteller/ui-lightbox-modal"; import { useSession } from "../../lib/session"; import { compareTagsByUseCount, useLibraryTagsStore, } from "../../pages/library/library-tags-store"; -import { TagChipInput } from "./TagChipInput"; interface TagsSectionProps { mediaToken?: string | null; creator?: UserInfo | null; } -/** A queued save: the full desired tag set for one media file. */ -interface PendingSave { - token: string; - values: string[]; -} - /** - * The "Tags" section of the media details panel. Fetches the file's tags, - * lets the owner edit them as chips (with autocomplete over their existing - * tags), and renders read-only chips for everyone else. Hidden entirely for - * non-owners when the file has no tags. - * - * Saves send one snapshot `SetMediaFileTags` per commit, serialized and - * coalesced: while a call is in flight only the latest desired tag set is - * remembered, so rapid edits collapse into a single follow-up request and - * add/remove can never interleave. + * Store-wired wrapper around the shared tags editor (from + * `@storyteller/ui-lightbox-modal`, also used by the desktop gallery): + * session comes from the webapp session store, autocomplete suggestions from + * the preloaded tags store, and saves feed fresh use counts back into it so + * the sidebar and tag browser stay current. */ export function TagsSection({ mediaToken, creator }: TagsSectionProps) { - const tagsApi = useMemo(() => new TagsApi(), []); const { user, loggedIn } = useSession(); - const isOwner = - loggedIn && !!creator?.username && creator.username === user?.username; - - // null until the first fetch resolves — the section doesn't render (and - // can't be edited) before we know the file's current tags. - const [tags, setTags] = useState(null); - const mediaTokenRef = useRef(mediaToken); - mediaTokenRef.current = mediaToken; - /** Last server-confirmed tag set — the revert target on save failure. */ - const lastAckedRef = useRef([]); - const pendingRef = useRef(null); - const savingRef = useRef(false); - const storeTags = useLibraryTagsStore((s) => s.tags); const loadTags = useLibraryTagsStore((s) => s.loadTags); const upsertTags = useLibraryTagsStore((s) => s.upsertTags); - useEffect(() => { - setTags(null); - lastAckedRef.current = []; - pendingRef.current = null; - if (!mediaToken) return; - let cancelled = false; - tagsApi.ListMediaFileTags({ mediaFileToken: mediaToken }).then((res) => { - if (cancelled) return; - const fetched = res.success && res.data ? res.data : []; - setTags(fetched); - lastAckedRef.current = fetched; - }); - return () => { - cancelled = true; - }; - }, [mediaToken, tagsApi]); + const currentUsername = loggedIn ? (user?.username ?? null) : null; + const isOwner = + !!currentUsername && creator?.username === currentUsername; - // Preload the user's tag list once for autocomplete (client-side filtering; - // no per-keystroke network). + // Preload the user's tag list once for autocomplete. useEffect(() => { if (isOwner && !useLibraryTagsStore.getState().tagsLoaded) loadTags(); }, [isOwner, loadTags]); @@ -83,91 +42,13 @@ export function TagsSection({ mediaToken, creator }: TagsSectionProps) { [storeTags], ); - const runQueue = async () => { - savingRef.current = true; - while (pendingRef.current) { - const { token, values } = pendingRef.current; - pendingRef.current = null; - const res = await tagsApi.SetMediaFileTags({ - mediaFileToken: token, - tags: values, - }); - if (res.success && res.data) { - // Fresh use counts for the sidebar / autocomplete. - upsertTags(res.data.tags); - if (mediaTokenRef.current === token) { - lastAckedRef.current = res.data.tags; - // Don't clobber newer optimistic chips a queued edit represents. - if (!pendingRef.current) setTags(res.data.tags); - } - } else { - toast.error(res.errorMessage || "Failed to save tags."); - if (mediaTokenRef.current === token) { - pendingRef.current = null; - setTags(lastAckedRef.current); - } - } - } - savingRef.current = false; - }; - - const enqueueSave = (values: string[]) => { - if (!mediaToken) return; - pendingRef.current = { token: mediaToken, values }; - if (!savingRef.current) void runQueue(); - }; - - const handleAdd = (values: string[]) => { - const next = [ - ...(tags ?? []), - ...values.map((value) => ({ - tag_token: "", - tag_value: value, - tag_value_lowercase: value.toLowerCase(), - use_count: 0, - })), - ]; - setTags(next); - enqueueSave(next.map((t) => t.tag_value)); - }; - - const handleRemove = (value: string) => { - const next = (tags ?? []).filter((t) => t.tag_value !== value); - setTags(next); - enqueueSave(next.map((t) => t.tag_value)); - }; - - const handleClear = () => { - setTags([]); - enqueueSave([]); - }; - - if (!mediaToken || tags === null) return null; - if (!isOwner && tags.length === 0) return null; - return ( -
-
-
- - Tags -
- {isOwner && tags.length >= 2 && ( - - )} -
- t.tag_value)} - suggestions={suggestions} - disabled={!isOwner} - onAdd={handleAdd} - onRemove={handleRemove} - /> -
+ ); } diff --git a/frontend/apps/artcraft-webapp/tsconfig.app.json b/frontend/apps/artcraft-webapp/tsconfig.app.json index 7dcb4991d0..b835cce19e 100644 --- a/frontend/apps/artcraft-webapp/tsconfig.app.json +++ b/frontend/apps/artcraft-webapp/tsconfig.app.json @@ -76,6 +76,9 @@ { "path": "../../libs/components/gallery-modal/tsconfig.lib.json" }, + { + "path": "../../libs/components/lightbox-modal/tsconfig.lib.json" + }, { "path": "../../libs/components/switch/tsconfig.lib.json" }, diff --git a/frontend/apps/artcraft-webapp/tsconfig.json b/frontend/apps/artcraft-webapp/tsconfig.json index 884e5935cc..3f6ff7af48 100644 --- a/frontend/apps/artcraft-webapp/tsconfig.json +++ b/frontend/apps/artcraft-webapp/tsconfig.json @@ -41,6 +41,9 @@ { "path": "../../libs/components/gallery-modal" }, + { + "path": "../../libs/components/lightbox-modal" + }, { "path": "../../libs/components/switch" }, diff --git a/frontend/libs/components/button/tsconfig.json b/frontend/libs/components/button/tsconfig.json index 8d27e65721..059b30fa58 100644 --- a/frontend/libs/components/button/tsconfig.json +++ b/frontend/libs/components/button/tsconfig.json @@ -2,6 +2,9 @@ "files": [], "include": [], "references": [ + { + "path": "../tooltip" + }, { "path": "../../common" }, diff --git a/frontend/libs/components/button/tsconfig.lib.json b/frontend/libs/components/button/tsconfig.lib.json index e7270e4b87..3634e22e92 100644 --- a/frontend/libs/components/button/tsconfig.lib.json +++ b/frontend/libs/components/button/tsconfig.lib.json @@ -43,6 +43,9 @@ "src/**/*.tsx" ], "references": [ + { + "path": "../tooltip/tsconfig.lib.json" + }, { "path": "../../common/tsconfig.lib.json" } diff --git a/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx b/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx index 8030930485..fef3837070 100644 --- a/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx +++ b/frontend/libs/components/gallery-modal/src/lib/gallery-modal.tsx @@ -18,9 +18,15 @@ import { FoldersApi, GalleryModalApi, MediaFilesApi, + TagsApi, UsersApi, } from "@storyteller/api"; -import type { FolderInfo, FolderMediaFileListItem } from "@storyteller/api"; +import type { + FolderInfo, + FolderMediaFileListItem, + TagDetails, + TagMediaFileListItem, +} from "@storyteller/api"; import type { GalleryFolder } from "./GalleryDraggableItem"; import { compareFolders } from "./folderUtils"; import { @@ -74,6 +80,7 @@ import { faEllipsis, faPencil, faStar, + faTag, } from "@fortawesome/pro-solid-svg-icons"; import { useMediaPromptTokens, @@ -385,6 +392,71 @@ const PAGE_SIZE = 100; // Folder media is paginated via cursor; one scroll page at a time. const FOLDER_PAGE_SIZE = 60; +// The sidebar shows the five most-used tags; "All tags" opens the word cloud. +const SIDEBAR_TAG_LIMIT = 5; + +const hashString = (s: string): number => { + let h = 0; + for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0; + return h; +}; + +/** + * The all-tags browser view: a word cloud where font size and emphasis encode + * use count, and a deterministic hash shuffle spreads big words among small + * ones (stable order — no reflow between renders). Mirrors the webapp's + * library Tags tab. + */ +function TagCloudView({ + tags, + onOpen, +}: { + tags: TagDetails[]; + onOpen: (tagToken: string) => void; +}) { + const words = useMemo(() => { + const counts = tags.map((t) => t.use_count); + const min = Math.min(...counts); + const span = Math.max(1, Math.max(...counts) - min); + return [...tags] + .sort((a, b) => hashString(a.tag_token) - hashString(b.tag_token)) + .map((tag) => ({ + tag, + // sqrt compresses the top end so one mega-tag doesn't dwarf the rest. + weight: Math.sqrt((tag.use_count - min) / span), + })); + }, [tags]); + + return ( +
+
+ {words.map(({ tag, weight }) => ( + + ))} +
+
+ ); +} + const SHOW_UPLOADS_STORAGE_KEY = "gallery-modal-show-uploads"; /** Small thumbnail used in the bulk-selection footer bar. */ @@ -618,6 +690,21 @@ export const GalleryModal = React.memo( const folderCursorRef = useRef>({}); const folderHasMoreRef = useRef>({}); const folderLoadingRef = useRef>({}); + + // Tags state — sidebar tag browsing (mirrors the folder plumbing). Both + // tag views live on the assets tab: `activeTagToken` swaps the grid to + // that tag's media, `tagBrowserOpen` shows the all-tags word cloud. + const [userTags, setUserTags] = useState([]); + const [tagBrowserOpen, setTagBrowserOpen] = useState(false); + const [activeTagToken, setActiveTagToken] = useState(null); + const [tagMediaItems, setTagMediaItems] = useState< + Record + >({}); + const [tagContentLoading, setTagContentLoading] = useState(false); + const [tagLoadingMore, setTagLoadingMore] = useState(false); + const tagCursorRef = useRef>({}); + const tagHasMoreRef = useRef>({}); + const tagLoadingRef = useRef>({}); // Rename state — shared by inline (header) and modal (sidebar) flows const [renamingFolderId, setRenamingFolderId] = useState( null, @@ -714,6 +801,7 @@ export const GalleryModal = React.memo( const api = useMemo(() => new GalleryModalApi(), []); const mediaFilesApi = useMemo(() => new MediaFilesApi(), []); const foldersApi = useMemo(() => new FoldersApi(), []); + const tagsApi = useMemo(() => new TagsApi(), []); // Shared API-folder → UI-folder mapper (see folderMapping.ts). const mapFolder = mapFolderInfo; @@ -755,6 +843,30 @@ export const GalleryModal = React.memo( [activeFolderItems, groupItemsByDate], ); + // Tag view memos — backed by the per-tag media cache. + const activeTagItems = useMemo( + () => (activeTagToken ? (tagMediaItems[activeTagToken] ?? []) : []), + [activeTagToken, tagMediaItems], + ); + const tagGroupedItems = useMemo( + () => groupItemsByDate(activeTagItems), + [activeTagItems, groupItemsByDate], + ); + const activeTag = activeTagToken + ? (userTags.find((t) => t.tag_token === activeTagToken) ?? null) + : null; + // Most-used-first ordering for the sidebar tag list. + const sortedTags = useMemo( + () => + [...userTags].sort( + (a, b) => + b.use_count - a.use_count || + a.tag_value_lowercase.localeCompare(b.tag_value_lowercase), + ), + [userTags], + ); + const sidebarTags = sortedTags.slice(0, SIDEBAR_TAG_LIMIT); + // Audio tiles have no thumbnail, so they show their prompt text instead. // Resolve it via the shared batched caches (media token → prompt token → // prompt text) for audio items only; image/video walls skip the lookups. @@ -910,11 +1022,12 @@ export const GalleryModal = React.memo( [], ); - // Map a folder media-file list item → GalleryItem. The list item already - // carries media_links/cover, so no batch-get is needed (unlike `mapApiItem`, - // which reads the raw user-media list shape: origin_category / no batch token). + // Map a folder/tag media-file list item → GalleryItem (same lean wire + // shape). The list item already carries media_links/cover, so no batch-get + // is needed (unlike `mapApiItem`, which reads the raw user-media list + // shape: origin_category / no batch token). const mapFolderListItem = useCallback( - (item: FolderMediaFileListItem): GalleryItem => ({ + (item: FolderMediaFileListItem | TagMediaFileListItem): GalleryItem => ({ id: item.token, label: getLabel(item), thumbnail: @@ -1615,6 +1728,69 @@ export const GalleryModal = React.memo( [foldersApi], ); + // Load the user's tag list for the sidebar (same paging cap as folders). + const loadTags = useCallback(async () => { + try { + const all: TagDetails[] = []; + let cursor: string | undefined = undefined; + for (let page = 0; page < 50; page++) { + const res = await tagsApi.ListTags({ cursor }); + if (!res.success || !res.data) break; + all.push(...res.data); + const next = res.pagination?.maybe_cursor; + if (!next) break; + cursor = next; + } + setUserTags(all); + } catch (err) { + console.error("Failed to load tags:", err); + } + }, [tagsApi]); + + // Resolve a tag's media one page at a time — mirrors `loadFolderMedia`. + const loadTagMedia = useCallback( + async (tagToken: string, reset = false) => { + if (tagLoadingRef.current[tagToken]) return; + if (!reset && tagHasMoreRef.current[tagToken] === false) return; + tagLoadingRef.current[tagToken] = true; + if (reset) { + tagCursorRef.current[tagToken] = undefined; + tagHasMoreRef.current[tagToken] = true; + setTagContentLoading(true); + } else { + setTagLoadingMore(true); + } + try { + const listRes = await tagsApi.ListMediaFilesWithTag({ + tagToken, + cursor: reset ? undefined : tagCursorRef.current[tagToken], + limit: FOLDER_PAGE_SIZE, + }); + if (!listRes.success || !listRes.data) return; + const nextCursor = listRes.pagination?.maybe_cursor ?? undefined; + tagCursorRef.current[tagToken] = nextCursor; + tagHasMoreRef.current[tagToken] = !!nextCursor; + const ordered = listRes.data.map(mapFolderListItem); + setTagMediaItems((prev) => { + const existing = reset ? [] : (prev[tagToken] ?? []); + const seen = new Set(existing.map((i) => i.id)); + const merged = [ + ...existing, + ...ordered.filter((i) => !seen.has(i.id)), + ]; + return { ...prev, [tagToken]: merged }; + }); + } catch (err) { + console.error("Failed to load tag media:", err); + } finally { + tagLoadingRef.current[tagToken] = false; + setTagContentLoading(false); + setTagLoadingMore(false); + } + }, + [tagsApi, mapFolderListItem], + ); + // `handleAddToFolder` is defined further down; reach it through a ref so the // earlier `handleCreateFolder` can add the just-created folder's seed items. const addToFolderRef = useRef< @@ -1674,6 +1850,8 @@ export const GalleryModal = React.memo( const handleOpenFolder = useCallback( (folderId: string | null) => { setActiveFolderId(folderId); + setActiveTagToken(null); + setTagBrowserOpen(false); setRenamingFolderId(null); setBulkSelectedIds(new Set()); setFolderMenuOpen(false); @@ -1688,10 +1866,13 @@ export const GalleryModal = React.memo( handleOpenFolder(null); }, [handleOpenFolder]); - // Switch top-level tab; leaving the folder browser exits any open folder. + // Switch top-level tab; leaving the folder browser exits any open folder + // (and any open tag view). const switchGalleryTab = useCallback((tab: "unsorted" | "folders") => { setGalleryTab(tab); setActiveFolderId(null); + setActiveTagToken(null); + setTagBrowserOpen(false); setBulkSelectedIds(new Set()); setFolderMenuOpen(false); setContextMenu(null); @@ -1707,6 +1888,37 @@ export const GalleryModal = React.memo( [handleOpenFolder], ); + // Open a tag view from the sidebar or the tag browser: the tag's media + // takes over the grid on the assets tab until dismissed. + const handleOpenTagFromSidebar = useCallback( + (tagToken: string) => { + setGalleryTab("unsorted"); + setActiveFolderId(null); + setActiveTagToken(tagToken); + setBulkSelectedIds(new Set()); + setFolderMenuOpen(false); + setContextMenu(null); + loadTagMedia(tagToken, true); + }, + [loadTagMedia], + ); + + // Open the all-tags word cloud (the "Tags" browser view). + const handleOpenTagBrowser = useCallback(() => { + setGalleryTab("unsorted"); + setActiveFolderId(null); + setActiveTagToken(null); + setTagBrowserOpen(true); + setBulkSelectedIds(new Set()); + setFolderMenuOpen(false); + setContextMenu(null); + }, []); + + const handleCloseTagView = useCallback(() => { + setActiveTagToken(null); + setTagBrowserOpen(false); + }, []); + // Close any open folder menus const closeFolderMenus = useCallback(() => { setFolderMenuOpen(false); @@ -2118,6 +2330,7 @@ export const GalleryModal = React.memo( : true; if (modalIsOpen && username) { loadFolders(); + loadTags(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [mode, isOpen, galleryModalVisibleViewMode.value, username]); @@ -2250,7 +2463,15 @@ export const GalleryModal = React.memo( (e: React.UIEvent) => { const { scrollTop, scrollHeight, clientHeight } = e.currentTarget; if (scrollHeight - scrollTop - clientHeight >= 100) return; - if (activeFolderId) { + if (activeTagToken) { + // Infinite-scroll the open tag's media. + if ( + tagHasMoreRef.current[activeTagToken] !== false && + !tagLoadingRef.current[activeTagToken] + ) { + loadTagMedia(activeTagToken, false); + } + } else if (activeFolderId) { // Infinite-scroll the open folder's media. if ( folderHasMoreRef.current[activeFolderId] !== false && @@ -2266,7 +2487,7 @@ export const GalleryModal = React.memo( loadItems(); } }, - [activeFolderId, hasMore, loadItems, loadFolderMedia], + [activeTagToken, activeFolderId, hasMore, loadItems, loadFolderMedia, loadTagMedia], ); // Folder tiles for the current location, rendered on the same grid as media @@ -2390,7 +2611,55 @@ export const GalleryModal = React.memo( New folder )} - {galleryTab === "folders" && activeFolder ? ( + {activeTag ? ( + /* ── Tag view header (Tags / name) ── */ +
+ + / + +

+ {activeTag.tag_value} +

+ + {activeTag.use_count} file + {activeTag.use_count === 1 ? "" : "s"} + + +
+ ) : tagBrowserOpen ? ( + /* ── All-tags browser header ── */ +
+ +

Tags

+ +
+ ) : galleryTab === "folders" && activeFolder ? ( /* ── Folder header (breadcrumb trail) ── */
+ ))} + + + )}
)} @@ -2738,7 +3069,125 @@ export const GalleryModal = React.memo( > {/* Subfolder tiles for the current location (root or open folder) */} {folderChipsSection} - {galleryTab === "folders" && !activeFolderId ? ( + {activeTagToken ? ( + /* ── Tag view ── */ + tagContentLoading && activeTagItems.length === 0 ? ( +
+ +
+ ) : activeTagItems.length === 0 ? ( +
+
+ +
+
+ No files carry this tag +
+
+ Add tags to media from the item view's Tags section. +
+
+ ) : ( +
+ {Object.entries(tagGroupedItems).map( + ([date, dateItems], groupIndex) => { + const filteredItems = dateItems.filter(filterItem); + if (filteredItems.length === 0) return null; + return ( + +

+ {date} +

+
+ {filteredItems.map((item) => ( + handleItemClick(item)} + onImageError={(e) => { + e.currentTarget.src = + PLACEHOLDER_IMAGES.DEFAULT; + e.currentTarget.style.opacity = "0.3"; + ( + e.currentTarget as HTMLImageElement + ).dataset.brokenurl = + item.thumbnail || ""; + handleImageError(item.thumbnail!); + }} + disableTooltipAndBadge={mode === "select"} + imageFit={imageFit} + onDeleted={handleItemDeleted} + onDelete={onDeleteMedia} + onEditClicked={onEditClicked} + maxSelections={maxSelections} + bulkSelected={bulkSelectedIds.has(item.id)} + onBulkSelectToggle={() => + toggleBulkSelect(item.id) + } + bulkSelectionMode={bulkSelectionMode} + getBulkDragItems={getBulkDragItems} + folders={folders} + onAddToFolder={requestFolderDrop} + onCreateFolderFromMenu={ + handleOpenNewFolderModal + } + /> + ))} +
+
+ ); + }, + )} + {tagLoadingMore && ( +
+ +
+ )} +
+ ) + ) : tagBrowserOpen ? ( + /* ── All-tags word cloud ── */ + userTags.length === 0 ? ( +
+
+ +
+
+ No tags yet +
+
+ Open any item and add tags in its details panel — + they'll show up here. +
+
+ ) : ( + + ) + ) : galleryTab === "folders" && !activeFolderId ? ( /* ── Folders tab, root: folder cards only (above) ── */ currentSubfolders.length === 0 ? (
diff --git a/frontend/libs/components/lightbox-modal/src/index.ts b/frontend/libs/components/lightbox-modal/src/index.ts index 3b6e8f2163..1191d9cdf1 100644 --- a/frontend/libs/components/lightbox-modal/src/index.ts +++ b/frontend/libs/components/lightbox-modal/src/index.ts @@ -1,2 +1,4 @@ export * from './lib/lightbox-modal'; +export * from './lib/tags/TagsSection'; +export * from './lib/tags/TagChipInput'; diff --git a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx index b49915397a..fb1aa9c4f8 100644 --- a/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx +++ b/frontend/libs/components/lightbox-modal/src/lib/lightbox-modal.tsx @@ -61,6 +61,7 @@ import { formatDuration, formatResolution, } from "@storyteller/common"; +import { TagsSection } from "./tags/TagsSection"; interface LightboxModalProps { isOpen: boolean; @@ -1055,6 +1056,8 @@ export function LightboxModal({ /> )} + + {additionalInfo}
diff --git a/frontend/apps/artcraft-webapp/src/components/tags/TagChipInput.tsx b/frontend/libs/components/lightbox-modal/src/lib/tags/TagChipInput.tsx similarity index 95% rename from frontend/apps/artcraft-webapp/src/components/tags/TagChipInput.tsx rename to frontend/libs/components/lightbox-modal/src/lib/tags/TagChipInput.tsx index 3d5dcf38ea..b22b35b6d3 100644 --- a/frontend/apps/artcraft-webapp/src/components/tags/TagChipInput.tsx +++ b/frontend/libs/components/lightbox-modal/src/lib/tags/TagChipInput.tsx @@ -131,7 +131,7 @@ export function TagChipInput({ }; const chipClass = (value: string) => - `flex items-center gap-1 rounded-full bg-white/10 px-2.5 py-1 text-[11px] font-medium text-white/80 transition-all ${ + `flex items-center gap-1 rounded-full bg-white/10 px-2.5 py-1 text-[11px] font-medium text-base-fg/80 transition-all ${ flashValue === value ? "ring-2 ring-primary" : "" }`; @@ -146,7 +146,7 @@ export function TagChipInput({ {chips.map((value) => disabled ? ( - + {value} ) : ( @@ -181,7 +181,7 @@ export function TagChipInput({ if (draft.trim()) commit(draft); }} placeholder={chips.length === 0 ? "Add tags (comma separated)" : "Add tag"} - className="min-w-[7rem] flex-1 bg-transparent py-0.5 text-sm text-white placeholder:text-white/40 focus:outline-none" + className="min-w-[7rem] flex-1 bg-transparent py-0.5 text-sm text-base-fg placeholder:text-base-fg/40 focus:outline-none" /> )} @@ -196,13 +196,13 @@ export function TagChipInput({ onMouseDown={(e) => e.preventDefault()} onClick={() => pickSuggestion(suggestion.value)} onMouseEnter={() => setHighlightIndex(index)} - className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-white transition-colors ${ + className={`flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm text-base-fg transition-colors ${ highlight === index ? "bg-ui-controls/60" : "hover:bg-ui-controls/40" }`} > - + {suggestion.value} - + {suggestion.useCount} diff --git a/frontend/libs/components/lightbox-modal/src/lib/tags/TagsSection.tsx b/frontend/libs/components/lightbox-modal/src/lib/tags/TagsSection.tsx new file mode 100644 index 0000000000..3a309f83f4 --- /dev/null +++ b/frontend/libs/components/lightbox-modal/src/lib/tags/TagsSection.tsx @@ -0,0 +1,262 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faTag } from "@fortawesome/pro-solid-svg-icons"; +import { + TagsApi, + UsersApi, + type TagDetails, + type UserInfo, +} from "@storyteller/api"; +import { toast } from "@storyteller/ui-toaster"; +import { TagChipInput, type TagSuggestion } from "./TagChipInput"; + +export interface TagsSectionProps { + mediaToken?: string | null; + creator?: UserInfo | null; + /** + * The logged-in username when the host app already knows it (string = + * user, null = logged out). Left undefined, the section resolves the + * session itself (cached module-wide). + */ + currentUsername?: string | null; + /** + * Autocomplete suggestions supplied by the host app (e.g. the webapp's + * tags store). Left undefined, the section fetches the user's tags itself. + */ + suggestions?: TagSuggestion[]; + /** Canonical tags after every successful save (fresh use counts). */ + onSaved?: (tags: TagDetails[]) => void; +} + +/** A queued save: the full desired tag set for one media file. */ +interface PendingSave { + token: string; + values: string[]; +} + +// Session + own-tags lookups are module-cached so reopening the lightbox +// doesn't refetch (only used when the host doesn't supply the data). +let cachedUsername: string | null | undefined; +let usernameInFlight: Promise | null = null; + +const resolveSessionUsername = (): Promise => { + if (cachedUsername !== undefined) return Promise.resolve(cachedUsername); + if (!usernameInFlight) { + usernameInFlight = new UsersApi() + .GetSession() + .then((res) => { + cachedUsername = + res.success && res.data?.loggedIn && res.data.user + ? res.data.user.username + : null; + return cachedUsername; + }) + .catch(() => { + usernameInFlight = null; + return null; + }); + } + return usernameInFlight; +}; + +/** + * The "Tags" section of the media details panel. Fetches the file's tags, + * lets the owner edit them as chips (with autocomplete over their existing + * tags), and renders read-only chips for everyone else. Hidden entirely for + * non-owners when the file has no tags. + * + * Saves send one snapshot `SetMediaFileTags` per commit, serialized and + * coalesced: while a call is in flight only the latest desired tag set is + * remembered, so rapid edits collapse into a single follow-up request and + * add/remove can never interleave. + */ +export function TagsSection({ + mediaToken, + creator, + currentUsername, + suggestions, + onSaved, +}: TagsSectionProps) { + const tagsApi = useMemo(() => new TagsApi(), []); + + // Session: prop wins; otherwise resolve (and cache) it ourselves. + const [resolvedUsername, setResolvedUsername] = useState(null); + useEffect(() => { + if (currentUsername !== undefined) return; + let cancelled = false; + resolveSessionUsername().then((username) => { + if (!cancelled) setResolvedUsername(username); + }); + return () => { + cancelled = true; + }; + }, [currentUsername]); + const effectiveUsername = currentUsername ?? resolvedUsername; + const isOwner = + !!effectiveUsername && + !!creator?.username && + creator.username === effectiveUsername; + + // null until the first fetch resolves — the section doesn't render (and + // can't be edited) before we know the file's current tags. + const [tags, setTags] = useState(null); + const mediaTokenRef = useRef(mediaToken); + mediaTokenRef.current = mediaToken; + /** Last server-confirmed tag set — the revert target on save failure. */ + const lastAckedRef = useRef([]); + const pendingRef = useRef(null); + const savingRef = useRef(false); + + useEffect(() => { + setTags(null); + lastAckedRef.current = []; + pendingRef.current = null; + if (!mediaToken) return; + let cancelled = false; + tagsApi.ListMediaFileTags({ mediaFileToken: mediaToken }).then((res) => { + if (cancelled) return; + const fetched = res.success && res.data ? res.data : []; + setTags(fetched); + lastAckedRef.current = fetched; + }); + return () => { + cancelled = true; + }; + }, [mediaToken, tagsApi]); + + // Autocomplete fallback: when the host doesn't supply suggestions, fetch + // the user's tags once per mount (merged with canonical tags after saves). + const [ownTags, setOwnTags] = useState([]); + const ownTagsLoadedRef = useRef(false); + useEffect(() => { + if (suggestions !== undefined || !isOwner || ownTagsLoadedRef.current) { + return; + } + ownTagsLoadedRef.current = true; + let cancelled = false; + (async () => { + const all: TagDetails[] = []; + let cursor: string | undefined = undefined; + for (let page = 0; page < 50; page++) { + const res = await tagsApi.ListTags({ cursor }); + if (!res.success || !res.data) break; + all.push(...res.data); + const next = res.pagination?.maybe_cursor; + if (!next) break; + cursor = next ?? undefined; + } + if (cancelled) return; + setOwnTags( + all + .sort( + (a, b) => + b.use_count - a.use_count || + a.tag_value_lowercase.localeCompare(b.tag_value_lowercase), + ) + .map((t) => ({ value: t.tag_value, useCount: t.use_count })), + ); + })(); + return () => { + cancelled = true; + }; + }, [suggestions, isOwner, tagsApi]); + + const effectiveSuggestions = suggestions ?? ownTags; + + const runQueue = async () => { + savingRef.current = true; + while (pendingRef.current) { + const { token, values } = pendingRef.current; + pendingRef.current = null; + const res = await tagsApi.SetMediaFileTags({ + mediaFileToken: token, + tags: values, + }); + if (res.success && res.data) { + onSaved?.(res.data.tags); + if (suggestions === undefined && res.data.tags.length > 0) { + // Keep the self-fetched suggestion list fresh with new tags. + setOwnTags((prev) => { + const known = new Set(prev.map((s) => s.value.toLowerCase())); + const added = res.data!.tags + .filter((t) => !known.has(t.tag_value_lowercase)) + .map((t) => ({ value: t.tag_value, useCount: t.use_count })); + return added.length > 0 ? [...prev, ...added] : prev; + }); + } + if (mediaTokenRef.current === token) { + lastAckedRef.current = res.data.tags; + // Don't clobber newer optimistic chips a queued edit represents. + if (!pendingRef.current) setTags(res.data.tags); + } + } else { + toast.error(res.errorMessage || "Failed to save tags."); + if (mediaTokenRef.current === token) { + pendingRef.current = null; + setTags(lastAckedRef.current); + } + } + } + savingRef.current = false; + }; + + const enqueueSave = (values: string[]) => { + if (!mediaToken) return; + pendingRef.current = { token: mediaToken, values }; + if (!savingRef.current) void runQueue(); + }; + + const handleAdd = (values: string[]) => { + const next = [ + ...(tags ?? []), + ...values.map((value) => ({ + tag_token: "", + tag_value: value, + tag_value_lowercase: value.toLowerCase(), + use_count: 0, + })), + ]; + setTags(next); + enqueueSave(next.map((t) => t.tag_value)); + }; + + const handleRemove = (value: string) => { + const next = (tags ?? []).filter((t) => t.tag_value !== value); + setTags(next); + enqueueSave(next.map((t) => t.tag_value)); + }; + + const handleClear = () => { + setTags([]); + enqueueSave([]); + }; + + if (!mediaToken || tags === null) return null; + if (!isOwner && tags.length === 0) return null; + + return ( +
+
+
+ + Tags +
+ {isOwner && tags.length >= 2 && ( + + )} +
+ t.tag_value)} + suggestions={effectiveSuggestions} + disabled={!isOwner} + onAdd={handleAdd} + onRemove={handleRemove} + /> +
+ ); +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6f05a88330..e670b1a1df 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -371,7 +371,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", @@ -387,7 +386,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -871,7 +869,6 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", @@ -2666,7 +2663,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -2743,7 +2739,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2885,7 +2880,6 @@ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.0.tgz", "integrity": "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q==", "license": "MIT", - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" @@ -2896,7 +2890,6 @@ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.0.tgz", "integrity": "sha512-55coeOFKHv1ywEcUXJtWU5f+Jr/W5tZDvZig8DLKSwUN1JpROQ4rk/SNOQiFWmaR/VKF4zuFyW1B8JduOSv6Pg==", "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -2922,7 +2915,6 @@ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", "license": "MIT", - "peer": true, "dependencies": { "@emotion/memoize": "^0.9.0" } @@ -2946,6 +2938,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=12" } @@ -2963,6 +2956,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=12" } @@ -2980,6 +2974,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=12" } @@ -2997,6 +2992,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=12" } @@ -3014,6 +3010,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=12" } @@ -3031,6 +3028,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=12" } @@ -3048,6 +3046,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=12" } @@ -3065,6 +3064,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=12" } @@ -3082,6 +3082,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -3099,6 +3100,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -3116,6 +3118,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -3133,6 +3136,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -3150,6 +3154,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -3167,6 +3172,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -3184,6 +3190,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -3201,6 +3208,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -3218,6 +3226,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=12" } @@ -3251,6 +3260,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=12" } @@ -3284,6 +3294,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=12" } @@ -3317,6 +3328,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=12" } @@ -3334,6 +3346,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=12" } @@ -3351,6 +3364,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=12" } @@ -3368,6 +3382,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=12" } @@ -3597,7 +3612,6 @@ "resolved": "https://npm.fontawesome.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz", "integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==", "license": "MIT", - "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "6.7.2" }, @@ -3867,6 +3881,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -3911,7 +3926,6 @@ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -5387,7 +5401,6 @@ "integrity": "sha512-pYUNvaQQBEwP66TLrjmmfkDIrTmPnX0kK86HgClkWLQKkX/oCgnqDxEgNbjeCc75dwUvZP6fW2d0pZ5++XILTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@module-federation/runtime": "2.5.1", "@module-federation/webpack-bundler-runtime": "2.5.1" @@ -5565,7 +5578,6 @@ "integrity": "sha512-kzFn3ObUeBp5vaEtN1WMxhTYBuYEErxugu1RzFUERD21X3BZ+b4cWwdFJuBDlsmVjctIg/QSOoZoPXRKAO0foA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@module-federation/runtime": "0.15.0", "@module-federation/webpack-bundler-runtime": "0.15.0" @@ -5665,7 +5677,6 @@ "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", "license": "MIT", - "peer": true, "engines": { "node": "^14.21.3 || >=16" }, @@ -8040,6 +8051,7 @@ "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.86.0.tgz", "integrity": "sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew==", "license": "MIT", + "peer": true, "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } @@ -8049,6 +8061,7 @@ "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.86.0.tgz", "integrity": "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", @@ -8070,6 +8083,7 @@ "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.86.0.tgz", "integrity": "sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ==", "license": "MIT", + "peer": true, "dependencies": { "@react-native/dev-middleware": "0.86.0", "debug": "^4.4.0", @@ -8100,6 +8114,7 @@ "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.86.0.tgz", "integrity": "sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } @@ -8109,6 +8124,7 @@ "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.86.0.tgz", "integrity": "sha512-Y0zEkZzLz8ou6o/VLml1A31X/rMgc6DRjwxwzPMa94qRTMY070WeBCNTITQo4kKTBAUgbxh07oXPQqp0Tpja8w==", "license": "MIT", + "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "debug": "^4.4.0", @@ -8123,6 +8139,7 @@ "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.86.0.tgz", "integrity": "sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw==", "license": "MIT", + "peer": true, "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.86.0", @@ -8146,6 +8163,7 @@ "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", "license": "MIT", + "peer": true, "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" @@ -8162,6 +8180,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", "license": "MIT", + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -8183,6 +8202,7 @@ "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.86.0.tgz", "integrity": "sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ==", "license": "MIT", + "peer": true, "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } @@ -8192,6 +8212,7 @@ "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.86.0.tgz", "integrity": "sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==", "license": "MIT", + "peer": true, "engines": { "node": "^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0" } @@ -8200,7 +8221,8 @@ "version": "0.86.0", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.86.0.tgz", "integrity": "sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@react-oauth/google": { "version": "0.13.5", @@ -8339,7 +8361,6 @@ "integrity": "sha512-yEDrKcIICHnaJdUOdBAXQTNXoehB0J3FBcZr4CnK6Alic6cVJGGBGMtJdIO7DUwNySwoYvkUTS3qnXuk2zGYZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.21.8", "@babel/generator": "^7.21.5", @@ -9132,7 +9153,6 @@ "resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.17.5.tgz", "integrity": "sha512-SyJ5n2pQyo4ERGvjjLvL2PpRWBzlC2qzO5KkkOE2ygOiNcgOu9WQnnom9GI8KgsTRCO8JnIeLHFRcmxZnVBjfw==", "license": "MIT", - "peer": true, "dependencies": { "@remix-run/router": "1.23.3", "@types/cookie": "^0.6.0", @@ -9755,7 +9775,6 @@ "integrity": "sha512-rsD9b+Khmot5DwCMiB3cqTQo53ioPG3M/A7BySu8+0+RS7GCxKm+Z+mtsjtG/vsu4Tn2tcqCdZtA3pgLoJB+ew==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@module-federation/runtime-tools": "0.22.0", "@rspack/binding": "1.7.11", @@ -10481,7 +10500,6 @@ "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.21.3", "@svgr/babel-preset": "8.1.0", @@ -10664,7 +10682,6 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.8" @@ -10913,7 +10930,6 @@ "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -11272,7 +11288,6 @@ "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -11755,7 +11770,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.42.tgz", "integrity": "sha512-5L7SUaFC1RyDraj2yRhyBzHTobyXHmohD100CChNtyPyleoq37Mqab5Gn8XEKI04dfN/oqPdpHk38MgcQWHbZg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -11778,7 +11792,6 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.0.tgz", "integrity": "sha512-MY3oPudxvMYyesqs/kW1Bh8y9VqSmf+tzqw3ae8a9DZW68pUe3zAdHeI1jc6iAysuRdACnVknHP8AhwD4/dxtg==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -12115,7 +12128,6 @@ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", @@ -14387,7 +14399,6 @@ "resolved": "https://registry.npmjs.org/@zkochan/js-yaml/-/js-yaml-0.0.7.tgz", "integrity": "sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==", "license": "MIT", - "peer": true, "dependencies": { "argparse": "^2.0.1" }, @@ -14449,7 +14460,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -14531,7 +14541,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -14605,7 +14614,8 @@ "version": "1.4.10", "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/ansi-colors": { "version": "4.1.3", @@ -14890,7 +14900,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/assertion-error": { "version": "2.0.1", @@ -15193,6 +15204,7 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.36.0.tgz", "integrity": "sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==", "license": "MIT", + "peer": true, "dependencies": { "hermes-parser": "0.36.0" } @@ -15415,7 +15427,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -15448,6 +15459,7 @@ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "node-int64": "^0.4.0" } @@ -15850,6 +15862,7 @@ "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", @@ -15878,6 +15891,7 @@ "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.3.0.tgz", "integrity": "sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", @@ -16281,6 +16295,7 @@ "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "license": "MIT", + "peer": true, "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", @@ -16296,6 +16311,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -16305,6 +16321,7 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -16314,6 +16331,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "license": "MIT", + "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -16331,13 +16349,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/connect/node_modules/on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "license": "MIT", + "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -16350,6 +16370,7 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -16686,8 +16707,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/culori": { "version": "4.0.2", @@ -17410,8 +17430,7 @@ "version": "8.6.0", "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/embla-carousel-react": { "version": "8.6.0", @@ -17467,7 +17486,6 @@ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "iconv-lite": "^0.6.2" } @@ -17575,6 +17593,7 @@ "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", "license": "MIT", + "peer": true, "dependencies": { "stackframe": "^1.3.4" } @@ -17786,7 +17805,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -18211,7 +18229,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -18268,7 +18285,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -18394,7 +18410,6 @@ "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", @@ -19138,14 +19153,14 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/express": { "version": "4.22.2", "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -19320,6 +19335,7 @@ "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", "license": "(MIT OR Apache-2.0)", + "peer": true, "bin": { "dotslash": "bin/dotslash" }, @@ -19332,6 +19348,7 @@ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "bser": "2.1.1" } @@ -19446,7 +19463,6 @@ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -19640,7 +19656,8 @@ "version": "0.0.6", "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/follow-redirects": { "version": "1.16.0", @@ -20498,19 +20515,22 @@ "version": "250829098.0.14", "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-250829098.0.14.tgz", "integrity": "sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/hermes-estree": { "version": "0.36.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.36.0.tgz", "integrity": "sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/hermes-parser": { "version": "0.36.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.36.0.tgz", "integrity": "sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==", "license": "MIT", + "peer": true, "dependencies": { "hermes-estree": "0.36.0" } @@ -20533,7 +20553,6 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.24.tgz", "integrity": "sha512-I36D1s+HgQc55KbhEr4iybfxv/9o1zdpw+XEM6dJa91LqQD0HCoSGdxpRJCZE+aavs87j4V3Ls2OJzq8C/U4iw==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -20850,6 +20869,7 @@ "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz", "integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==", "license": "MIT", + "peer": true, "dependencies": { "queue": "6.0.2" }, @@ -22026,7 +22046,6 @@ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -22056,6 +22075,7 @@ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", @@ -22073,6 +22093,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -22085,6 +22106,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "license": "MIT", + "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -22098,13 +22120,15 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", @@ -22120,6 +22144,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "license": "MIT", + "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -22188,7 +22213,8 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/jsdom": { "version": "22.1.0", @@ -22196,7 +22222,6 @@ "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "abab": "^2.0.6", "cssstyle": "^3.0.0", @@ -22507,8 +22532,7 @@ "url": "https://github.com/sponsors/lavrton" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/language-subtag-registry": { "version": "0.3.23", @@ -22566,6 +22590,7 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } @@ -22589,6 +22614,7 @@ "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" @@ -22599,6 +22625,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -22607,14 +22634,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lightningcss": { "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", - "peer": true, "dependencies": { "detect-libc": "^2.0.3" }, @@ -23293,7 +23320,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -23616,6 +23644,7 @@ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "tmpl": "1.0.5" } @@ -23634,7 +23663,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", "integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/math-intrinsics": { "version": "1.1.0", @@ -23900,7 +23930,8 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/merge-descriptors": { "version": "1.0.3", @@ -23947,6 +23978,7 @@ "resolved": "https://registry.npmjs.org/metro/-/metro-0.84.4.tgz", "integrity": "sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", @@ -24000,6 +24032,7 @@ "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.84.4.tgz", "integrity": "sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", @@ -24015,13 +24048,15 @@ "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/metro-babel-transformer/node_modules/hermes-parser": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", "license": "MIT", + "peer": true, "dependencies": { "hermes-estree": "0.35.0" } @@ -24031,6 +24066,7 @@ "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.84.4.tgz", "integrity": "sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg==", "license": "MIT", + "peer": true, "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", @@ -24046,6 +24082,7 @@ "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.84.4.tgz", "integrity": "sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw==", "license": "MIT", + "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, @@ -24058,6 +24095,7 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", + "peer": true, "engines": { "node": ">= 14" } @@ -24067,6 +24105,7 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", + "peer": true, "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -24080,6 +24119,7 @@ "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.84.4.tgz", "integrity": "sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q==", "license": "MIT", + "peer": true, "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", @@ -24099,6 +24139,7 @@ "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.84.4.tgz", "integrity": "sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ==", "license": "MIT", + "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", @@ -24113,6 +24154,7 @@ "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.84.4.tgz", "integrity": "sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", @@ -24133,6 +24175,7 @@ "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.84.4.tgz", "integrity": "sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ==", "license": "MIT", + "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" @@ -24146,6 +24189,7 @@ "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.84.4.tgz", "integrity": "sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A==", "license": "MIT", + "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, @@ -24158,6 +24202,7 @@ "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.84.4.tgz", "integrity": "sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" @@ -24171,6 +24216,7 @@ "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.84.4.tgz", "integrity": "sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g==", "license": "MIT", + "peer": true, "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", @@ -24191,6 +24237,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -24200,6 +24247,7 @@ "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.84.4.tgz", "integrity": "sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA==", "license": "MIT", + "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", @@ -24220,6 +24268,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -24229,6 +24278,7 @@ "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.84.4.tgz", "integrity": "sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", @@ -24246,6 +24296,7 @@ "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.84.4.tgz", "integrity": "sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", @@ -24270,6 +24321,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", + "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -24282,19 +24334,22 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/metro/node_modules/hermes-estree": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.35.0.tgz", "integrity": "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/metro/node_modules/hermes-parser": { "version": "0.35.0", "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.35.0.tgz", "integrity": "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==", "license": "MIT", + "peer": true, "dependencies": { "hermes-estree": "0.35.0" } @@ -24304,6 +24359,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", + "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -24320,6 +24376,7 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -24329,6 +24386,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -24338,6 +24396,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", "license": "MIT", + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -25552,7 +25611,6 @@ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -25597,7 +25655,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/node-machine-id": { "version": "1.1.12", @@ -25784,7 +25843,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/nwsapi": { "version": "2.2.24", @@ -25799,7 +25859,6 @@ "integrity": "sha512-2wL/2fSmIbRWn6zXaQ/g3kj5DfEaTw/aJkPr6ozJh8BUq5iYKE+tS9oh0PjsVVwN6Pybe80Lu+mn9RgWyeV3xw==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -25957,6 +26016,7 @@ "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.84.4.tgz", "integrity": "sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA==", "license": "MIT", + "peer": true, "dependencies": { "flow-enums-runtime": "^0.0.6" }, @@ -26716,7 +26776,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -27022,7 +27081,6 @@ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -27110,7 +27168,6 @@ "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -27262,6 +27319,7 @@ "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", "license": "MIT", + "peer": true, "dependencies": { "asap": "~2.0.6" } @@ -27446,6 +27504,7 @@ "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", "license": "MIT", + "peer": true, "dependencies": { "inherits": "~2.0.3" } @@ -27595,7 +27654,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -27665,6 +27723,7 @@ "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "license": "MIT", + "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -27675,6 +27734,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", "license": "MIT", + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -27696,7 +27756,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -28089,6 +28148,7 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.6.1.tgz", "integrity": "sha512-zF0rsKcVYpcJwbFEnv2HkHX9cvOEgsfQo/X8lwmR2dn13S4qEQJXir9fxf5js2LQFoXqxOY7MDkOkYx2uZ4gSg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", @@ -28137,6 +28197,7 @@ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", "license": "MIT", + "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.9" }, @@ -28159,6 +28220,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" }, @@ -28185,6 +28247,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -28195,6 +28258,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -28204,6 +28268,7 @@ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "license": "MIT", + "peer": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -28217,7 +28282,8 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-spring/node_modules/react-konva": { "version": "19.2.5", @@ -28238,6 +28304,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@types/react-reconciler": "^0.33.0", "its-fine": "^2.0.0", @@ -28255,6 +28322,7 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.33.0.tgz", "integrity": "sha512-HZOXsKT0tGI9LlUw2LuedXsVeB88wFa536vVL0M6vE8zN63nI+sSr1ByxmPToP5K5bukaVscyeCJcF9guVNJ1g==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "*" } @@ -28264,6 +28332,7 @@ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-2.0.0.tgz", "integrity": "sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==", "license": "MIT", + "peer": true, "dependencies": { "@types/react-reconciler": "^0.28.9" }, @@ -28276,6 +28345,7 @@ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "*" } @@ -28285,6 +28355,7 @@ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.33.0.tgz", "integrity": "sha512-KetWRytFv1epdpJc3J4G75I4WrplZE5jOL7Yq0p34+OVOKF4Se7WrdIdVC45XsSSmUTlht2FM/fM1FZb1mfQeA==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -28360,6 +28431,7 @@ "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.86.0.tgz", "integrity": "sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw==", "license": "MIT", + "peer": true, "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" @@ -28382,13 +28454,15 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/react-spring/node_modules/ws": { "version": "7.5.11", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.11.tgz", "integrity": "sha512-zS54Oen9bITtp7kp2XM3AydrCIq1D+HwJOuH+c+e4LfpL/lotP5osijd+UoMnxwAam1GN8R4KtLAyIrIcBNpiA==", "license": "MIT", + "peer": true, "engines": { "node": ">=8.3.0" }, @@ -28449,6 +28523,7 @@ "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", "license": "MIT", + "peer": true, "peerDependencies": { "react": ">=16.13", "react-dom": ">=16.13" @@ -28642,7 +28717,8 @@ "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", @@ -28831,7 +28907,8 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/resolve": { "version": "1.22.8", @@ -29365,6 +29442,7 @@ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -30023,6 +30101,7 @@ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.4" }, @@ -30366,13 +30445,15 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/stacktrace-parser": { "version": "0.1.11", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz", "integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==", "license": "MIT", + "peer": true, "dependencies": { "type-fest": "^0.7.1" }, @@ -30903,6 +30984,7 @@ "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", "license": "MIT", + "peer": true, "peerDependencies": { "react": ">=17.0" } @@ -31204,7 +31286,6 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.48.0.tgz", "integrity": "sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -31423,14 +31504,14 @@ "version": "0.171.0", "resolved": "https://registry.npmjs.org/three/-/three-0.171.0.tgz", "integrity": "sha512-Y/lAXPaKZPcEdkKjh0JOAHVv8OOnv/NDJqm0wjfCzyQmfKxV7zvkwsnBgPBKTzJHToSOhRGQAGbPJObT59B/PQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/through2": { "version": "2.0.5", @@ -31574,7 +31655,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -31879,6 +31961,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", "license": "(MIT OR CC0-1.0)", + "peer": true, "engines": { "node": ">=8" } @@ -31980,7 +32063,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -32680,7 +32762,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -33577,7 +33658,6 @@ "integrity": "sha512-xejya+bT/j/+R/AGa1XOfRxLmNUlLtlwjRsFUILF+xHfzElmGcmFydy2gqqIrd62ptIEfwVMofd19uNWD9L7Nw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.6", @@ -33656,7 +33736,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/vscode-uri": { "version": "3.1.0", @@ -33683,6 +33764,7 @@ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "makeerror": "1.0.12" } @@ -33770,7 +33852,6 @@ "integrity": "sha512-v7RhXaJbpMlV0D7hC7lb2EbnxkoeUqf9qhKr6lozx3Q48pmFrqqNRmZFUEGmi7pSwm6fCQ2H1IjvCkHqdpVdjQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", @@ -33871,7 +33952,8 @@ "version": "3.6.20", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/whatwg-mimetype": { "version": "3.0.0", @@ -34156,7 +34238,6 @@ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -34379,7 +34460,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" }