@raster-app/react 0.1.0-canary-32ca74d → 0.1.0-canary-56a3f84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -93,6 +93,7 @@ export function Detail({ item, busy, onPick, }) {
93
93
  const { host } = useRasterClient();
94
94
  const image = getPreviewUrl(item);
95
95
  const appUrl = item.appUrl;
96
+ const url = 'url' in item ? item.url : null;
96
97
  const facts = [
97
98
  item.width && item.height ? `${item.width} × ${item.height}` : '',
98
99
  'size' in item ? formatFileSize(item.size) : '',
@@ -101,7 +102,34 @@ export function Detail({ item, busy, onPick, }) {
101
102
  return (_jsxs("div", { className: "rs-detail", children: [_jsx("div", { className: "rs-detail-image", children: image && _jsx("img", { src: image, alt: "" }, image) }), _jsxs("div", { className: "rs-detail-info", children: [_jsx("h2", { className: "rs-heading", children: item.name }), facts.length > 0 && _jsx("p", { className: "rs-muted rs-small", children: facts.join(' · ') }), onPick && (_jsx("button", { type: "button", className: "rs-button rs-button-primary", disabled: !isReady(item) || busy, onClick: onPick, children: "Pick asset" })), appUrl && (_jsxs("a", { className: "rs-button rs-button-secondary", href: appUrl, target: "_blank", rel: "noreferrer", onClick: (event) => {
102
103
  event.preventDefault();
103
104
  host.openExternal(appUrl);
104
- }, children: [_jsx(Icon, { shape: "link", width: 12.25 }), "Open in Raster"] }))] })] }));
105
+ }, children: [_jsx(Icon, { shape: "link", width: 12.25 }), "Open in Raster"] })), url && _jsx(CopyUrl, { url: url })] })] }));
106
+ }
107
+ /** Copies the asset's CDN URL, and says so for 2 seconds, as Raster's Copy URL does. */
108
+ function CopyUrl({ url }) {
109
+ const [copied, setCopied] = useState(false);
110
+ useEffect(() => {
111
+ if (!copied)
112
+ return;
113
+ const timer = setTimeout(() => setCopied(false), 2000);
114
+ return () => clearTimeout(timer);
115
+ }, [copied]);
116
+ return (_jsxs("button", { type: "button", className: "rs-button rs-button-secondary", onClick: async () => setCopied(await copyText(url)), children: [_jsx(Icon, { shape: copied ? 'check' : 'link', width: 12.25 }), copied ? 'Copied' : 'Copy URL'] }));
117
+ }
118
+ /** The Clipboard API, or a selection copied, where a sandboxed iframe blocks that API. */
119
+ async function copyText(text) {
120
+ try {
121
+ await navigator.clipboard.writeText(text);
122
+ return true;
123
+ }
124
+ catch {
125
+ const field = document.createElement('textarea');
126
+ field.value = text;
127
+ document.body.append(field);
128
+ field.select();
129
+ const copied = document.execCommand('copy');
130
+ field.remove();
131
+ return copied;
132
+ }
105
133
  }
106
134
  /** A file on its way into Raster, as the grid shows it until Raster has processed it. */
107
135
  export function PendingTile({ name, preview, status, }) {
@@ -1 +1 @@
1
- {"version":3,"file":"picker-parts.js","sourceRoot":"","sources":["../src/picker-parts.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useVirtualizer } from '@tanstack/react-virtual'\nimport { Children, type ReactNode, useEffect, useLayoutEffect, useRef, useState } from 'react'\n\nimport {\n\ttype Asset,\n\ttype AssetVariant,\n\ttype Organization,\n\ttype RasterApiError,\n\ttype SearchHit,\n\tformatDateLong,\n\tformatFileSize,\n\tformatRelativeTime,\n\tgetBlurhashPlaceholder,\n\tgetPreviewUrl,\n\tgetThumbUrl,\n\tisReady,\n\ttoUserMessage,\n} from '@raster-app/sdk'\n\nimport { useRasterClient } from './context.js'\nimport { Icon } from './icons.js'\nimport type { PromoteState } from './use-promote-variant.js'\nimport type { UploadState } from './use-asset-upload.js'\n\nexport type ReportError = ((error: RasterApiError) => void) | undefined\n\nexport function Brand() {\n\treturn (\n\t\t<div className=\"rs-brand\">\n\t\t\t<Icon shape=\"raster\" solid width={18} />\n\t\t\tRaster\n\t\t</div>\n\t)\n}\n\n// Strict Mode runs an effect twice in development; the host hears of each error once.\nconst reported = new WeakSet<RasterApiError>()\n\nexport function ErrorCard({ error, onError }: { error: RasterApiError; onError: ReportError }) {\n\tuseEffect(() => {\n\t\tif (reported.has(error)) return\n\t\treported.add(error)\n\t\tonError?.(error)\n\t}, [error, onError])\n\treturn (\n\t\t<p role=\"alert\" className=\"rs-card\">\n\t\t\t{toUserMessage(error)}\n\t\t</p>\n\t)\n}\n\nexport function Empty({\n\tglyph,\n\ttitle,\n\ttext,\n\tchildren,\n}: {\n\tglyph: 'grid' | '∅'\n\ttitle: string\n\ttext: string\n\tchildren?: ReactNode\n}) {\n\treturn (\n\t\t<div className=\"rs-empty\">\n\t\t\t<div className=\"rs-empty-glyph\">{glyph === 'grid' ? <Icon shape=\"grid\" /> : glyph}</div>\n\t\t\t<h2 className=\"rs-heading\">{title}</h2>\n\t\t\t<p className=\"rs-muted rs-small\">{text}</p>\n\t\t\t{children}\n\t\t</div>\n\t)\n}\n\n/** Raster's tile height (`.rs-asset-thumb`) and the gap between its rows. */\nconst ROW_HEIGHT = 210 + 30\n\n/** The Raster app's columns from its window's width (`calculateGridColumns`). */\nconst BREAKPOINTS: Array<[number, number]> = [\n\t[2180, 7],\n\t[1920, 6],\n\t[1536, 5],\n\t[1280, 4],\n\t[1024, 3],\n\t[768, 2],\n]\n\n/** Raster's columns for a grid this wide: its window holds a 266px sidebar and 30px padding too. */\nexport function gridColumns(width: number): number {\n\tconst viewport = width + 266 + 2 * 30\n\treturn BREAKPOINTS.find(([min]) => viewport >= min)?.[1] ?? 1\n}\n\n/**\n * Tiles in Raster's grid, a row at a time as the Raster app draws them: only the rows in view,\n * and five either side, are mounted, however many have loaded.\n */\nexport function TileGrid({ children }: { children: ReactNode }) {\n\tconst tiles = Children.toArray(children)\n\tconst grid = useRef<HTMLDivElement>(null)\n\tconst [width, setWidth] = useState(0)\n\n\tuseLayoutEffect(() => {\n\t\tconst node = grid.current\n\t\tif (node === null) return\n\t\tsetWidth(node.clientWidth)\n\t\tif (typeof ResizeObserver === 'undefined') return\n\t\tconst observer = new ResizeObserver(() => setWidth(node.clientWidth))\n\t\tobserver.observe(node)\n\t\treturn () => observer.disconnect()\n\t}, [])\n\n\tconst columns = gridColumns(width)\n\tconst rows = useVirtualizer({\n\t\tcount: Math.ceil(tiles.length / columns),\n\t\tgetScrollElement: () => grid.current?.closest('.rs-content') ?? null,\n\t\testimateSize: () => ROW_HEIGHT,\n\t\toverscan: 5,\n\t\tscrollMargin: grid.current?.offsetTop ?? 0,\n\t})\n\n\treturn (\n\t\t<div ref={grid} className=\"rs-tile-grid\" style={{ height: rows.getTotalSize() }}>\n\t\t\t{rows.getVirtualItems().map((row) => (\n\t\t\t\t<div\n\t\t\t\t\tkey={row.key}\n\t\t\t\t\tclassName=\"rs-grid\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\ttop: row.start - rows.options.scrollMargin,\n\t\t\t\t\t\tgridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{tiles.slice(row.index * columns, (row.index + 1) * columns)}\n\t\t\t\t</div>\n\t\t\t))}\n\t\t</div>\n\t)\n}\n\n/**\n * Raster's grid tile: the picture over its blurred fill, or a checkerboard where it has alpha,\n * with a pill for a badge on the left and one for an action on the right.\n */\nexport function Tile({\n\tasset,\n\tonOpen,\n\tselected,\n\tbadge,\n\taction,\n}: {\n\tasset: Asset | SearchHit | AssetVariant\n\tonOpen: () => void\n\t/** Whether this is the version shown large, among an asset's versions. */\n\tselected?: boolean\n\tbadge?: ReactNode\n\taction?: ReactNode\n}) {\n\tconst [loaded, setLoaded] = useState(false)\n\tconst ready = isReady(asset)\n\tconst thumb = getThumbUrl(asset)\n\tconst fill = 'blurhash' in asset ? getBlurhashPlaceholder(asset) : null\n\tconst transparent = 'contentType' in asset && /png|svg|webp|gif/.test(asset.contentType ?? '')\n\tconst size = 'size' in asset ? formatFileSize(asset.size) : ''\n\tconst updated = 'updated' in asset ? formatRelativeTime(asset.updated) : ''\n\n\treturn (\n\t\t<div className=\"rs-tile\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"rs-asset-thumb\"\n\t\t\t\tdata-loaded={loaded || undefined}\n\t\t\t\tdisabled={!ready}\n\t\t\t\taria-pressed={selected}\n\t\t\t\ttitle={\n\t\t\t\t\t'created' in asset && asset.created\n\t\t\t\t\t\t? `Created ${formatDateLong(asset.created)}`\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tonClick={onOpen}\n\t\t\t>\n\t\t\t\t{transparent ? (\n\t\t\t\t\t<div className=\"rs-transparent\" />\n\t\t\t\t) : (\n\t\t\t\t\tfill && <img className=\"rs-thumb-blur\" src={fill} alt=\"\" />\n\t\t\t\t)}\n\t\t\t\t<div className=\"rs-thumb-preview\">\n\t\t\t\t\t{thumb && (\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tkey={thumb}\n\t\t\t\t\t\t\tsrc={thumb}\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\tdecoding=\"async\"\n\t\t\t\t\t\t\tonLoad={() => setLoaded(true)}\n\t\t\t\t\t\t\tonError={() => setLoaded(true)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t<div className=\"rs-thumb-meta\">\n\t\t\t\t\t<span className=\"rs-thumb-title\">{asset.name}</span>\n\t\t\t\t\t<span className=\"rs-thumb-subtitle\">\n\t\t\t\t\t\t{!ready && <span>Not ready</span>}\n\t\t\t\t\t\t{ready && size && <span>{size}</span>}\n\t\t\t\t\t\t{ready && updated && <span>{updated}</span>}\n\t\t\t\t\t\t{ready && !size && !updated && asset.width && asset.height && (\n\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t{asset.width} × {asset.height}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</button>\n\t\t\t{badge}\n\t\t\t{action}\n\t\t</div>\n\t)\n}\n\n/**\n * An opened asset or variant: the image large, its facts, and what the host can do with it.\n * Raster opens through the host, which reaches the browser from a Figma plugin too.\n */\nexport function Detail({\n\titem,\n\tbusy,\n\tonPick,\n}: {\n\titem: Asset | SearchHit | AssetVariant\n\t/** While the asset is read in full for the host, or a promote has yet to land. */\n\tbusy: boolean\n\tonPick?: () => void\n}) {\n\tconst { host } = useRasterClient()\n\tconst image = getPreviewUrl(item)\n\tconst appUrl = item.appUrl\n\tconst facts = [\n\t\titem.width && item.height ? `${item.width} × ${item.height}` : '',\n\t\t'size' in item ? formatFileSize(item.size) : '',\n\t\t'created' in item && item.created ? `Created ${formatDateLong(item.created)}` : '',\n\t].filter((fact) => fact !== '')\n\n\treturn (\n\t\t<div className=\"rs-detail\">\n\t\t\t<div className=\"rs-detail-image\">{image && <img key={image} src={image} alt=\"\" />}</div>\n\t\t\t<div className=\"rs-detail-info\">\n\t\t\t\t<h2 className=\"rs-heading\">{item.name}</h2>\n\t\t\t\t{facts.length > 0 && <p className=\"rs-muted rs-small\">{facts.join(' · ')}</p>}\n\t\t\t\t{onPick && (\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"rs-button rs-button-primary\"\n\t\t\t\t\t\tdisabled={!isReady(item) || busy}\n\t\t\t\t\t\tonClick={onPick}\n\t\t\t\t\t>\n\t\t\t\t\t\tPick asset\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t{appUrl && (\n\t\t\t\t\t<a\n\t\t\t\t\t\tclassName=\"rs-button rs-button-secondary\"\n\t\t\t\t\t\thref={appUrl}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noreferrer\"\n\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\t\t\thost.openExternal(appUrl)\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Icon shape=\"link\" width={12.25} />\n\t\t\t\t\t\tOpen in Raster\n\t\t\t\t\t</a>\n\t\t\t\t)}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n/** A file on its way into Raster, as the grid shows it until Raster has processed it. */\nexport function PendingTile({\n\tname,\n\tpreview,\n\tstatus,\n}: {\n\tname: string\n\tpreview: string\n\tstatus: string\n}) {\n\treturn (\n\t\t<div className=\"rs-tile\">\n\t\t\t<div className=\"rs-asset-thumb\" data-loaded aria-busy=\"true\">\n\t\t\t\t<div className=\"rs-thumb-preview\">\n\t\t\t\t\t<img src={preview} alt=\"\" />\n\t\t\t\t</div>\n\t\t\t\t<div className=\"rs-thumb-meta\">\n\t\t\t\t\t<span className=\"rs-thumb-title\">{name}</span>\n\t\t\t\t\t<span className=\"rs-thumb-subtitle\">\n\t\t\t\t\t\t<span className=\"rs-spinner\" />\n\t\t\t\t\t\t{status}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n/** A control that opens the file chooser, drawn as whatever `className` makes it. */\nexport function FilePicker({\n\tclassName,\n\tdisabled,\n\tfocusable = true,\n\tonFile,\n\tchildren,\n}: {\n\tclassName: string\n\tdisabled: boolean\n\tfocusable?: boolean\n\tonFile: (file: File) => void\n\tchildren: ReactNode\n}) {\n\treturn (\n\t\t<label className={className}>\n\t\t\t{children}\n\t\t\t<input\n\t\t\t\tclassName=\"rs-visually-hidden\"\n\t\t\t\ttype=\"file\"\n\t\t\t\tdisabled={disabled}\n\t\t\t\ttabIndex={focusable ? 0 : -1}\n\t\t\t\tonChange={(event) => {\n\t\t\t\t\tconst file = event.target.files?.[0]\n\t\t\t\t\t// Cleared, so picking the same file again uploads it again.\n\t\t\t\t\tevent.target.value = ''\n\t\t\t\t\tif (file) onFile(file)\n\t\t\t\t}}\n\t\t\t/>\n\t\t</label>\n\t)\n}\n\nconst UPLOADS = {\n\tuploading: 'Uploading',\n\tprocessing: 'Processing',\n\tdone: 'Uploaded',\n\t'timed-out': 'Still processing',\n} as const\nconst PROMOTES = {\n\tpromoting: 'Setting as default',\n\tdone: 'Default set',\n\t'timed-out': 'Still setting as default',\n} as const\n\n/** Where an upload and a promote stand, beside the header; each failure shows as an error. */\nexport function Progress({ upload, promote }: { upload: UploadState; promote: PromoteState }) {\n\treturn (\n\t\t<>\n\t\t\t{upload.status !== 'idle' && upload.status !== 'error' && (\n\t\t\t\t<Status status={upload.status} text={UPLOADS[upload.status]} />\n\t\t\t)}\n\t\t\t{promote.status !== 'idle' && promote.status !== 'error' && (\n\t\t\t\t<Status status={promote.status} text={PROMOTES[promote.status]} />\n\t\t\t)}\n\t\t</>\n\t)\n}\n\nfunction Status({ status, text }: { status: string; text: string }) {\n\tlet glyph = <span className=\"rs-spinner\" />\n\tif (status === 'done') glyph = <Icon shape=\"check\" />\n\tif (status === 'timed-out') glyph = <Icon shape=\"info-circle\" />\n\treturn (\n\t\t<span className=\"rs-status\" data-status={status}>\n\t\t\t{glyph}\n\t\t\t{text}\n\t\t</span>\n\t)\n}\n\n/** Raster's organization switcher: the current organization, over a menu of every one. */\nexport function OrganizationSwitcher({\n\torganizations,\n\tcurrent,\n\tonIntent,\n\tonSelect,\n}: {\n\torganizations: Array<Organization>\n\tcurrent: Organization | undefined\n\tonIntent: (organization: Organization) => void\n\tonSelect: (organizationId: string) => void\n}) {\n\tconst [open, setOpen] = useState(false)\n\tconst root = useRef<HTMLDivElement>(null)\n\n\tuseEffect(() => {\n\t\tif (!open) return\n\t\tconst close = (event: MouseEvent | KeyboardEvent) => {\n\t\t\tconst outside = event.target instanceof Node && !root.current?.contains(event.target)\n\t\t\tif (event instanceof KeyboardEvent ? event.key !== 'Escape' : !outside) return\n\t\t\t// Stopped here, so a host dialog that closes on Escape stays open.\n\t\t\tif (event instanceof KeyboardEvent) event.stopPropagation()\n\t\t\tsetOpen(false)\n\t\t}\n\t\tdocument.addEventListener('mousedown', close)\n\t\tdocument.addEventListener('keydown', close)\n\t\treturn () => {\n\t\t\tdocument.removeEventListener('mousedown', close)\n\t\t\tdocument.removeEventListener('keydown', close)\n\t\t}\n\t}, [open])\n\n\treturn (\n\t\t<div className=\"rs-org\" ref={root}>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"rs-sidebar-item rs-org-trigger\"\n\t\t\t\taria-expanded={open}\n\t\t\t\tonClick={() => setOpen(!open)}\n\t\t\t>\n\t\t\t\t<Avatar name={current?.name ?? current?.id ?? ''} />\n\t\t\t\t<span className=\"rs-library-name\">{current?.name ?? current?.id}</span>\n\t\t\t\t<Icon shape=\"chevron-up-down\" />\n\t\t\t</button>\n\t\t\t{open && (\n\t\t\t\t<div className=\"rs-menu\">\n\t\t\t\t\t<div className=\"rs-menu-heading\">Organizations</div>\n\t\t\t\t\t{organizations.map((organization) => (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\torganization.id === current?.id ? 'rs-sidebar-item is-active' : 'rs-sidebar-item'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tonMouseEnter={() => onIntent(organization)}\n\t\t\t\t\t\t\tonFocus={() => onIntent(organization)}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tsetOpen(false)\n\t\t\t\t\t\t\t\tonSelect(organization.id)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Avatar name={organization.name ?? organization.id} />\n\t\t\t\t\t\t\t<span className=\"rs-library-name\">{organization.name ?? organization.id}</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t)\n}\n\n/** Raster's letter avatar, on a gradient hued from the name as the app hues it. */\nfunction Avatar({ name }: { name: string }) {\n\tif (name === '') return null\n\tlet hex = ''\n\tfor (let index = 0; index < Math.min(name.length, 10); index++) {\n\t\thex += `000${name.charCodeAt(index).toString(16)}`.slice(-4)\n\t}\n\tconst hue = Number.parseInt(hex, 16) % 360\n\treturn (\n\t\t<span\n\t\t\tclassName=\"rs-avatar\"\n\t\t\tstyle={{\n\t\t\t\tbackgroundImage: `linear-gradient(45deg, hsl(${hue}, 70%, 50%), hsl(${(hue + 60) % 360}, 70%, 50%))`,\n\t\t\t}}\n\t\t>\n\t\t\t{name.charAt(0).toUpperCase()}\n\t\t</span>\n\t)\n}\n\n/**\n * The spinner at the end of the grid, which asks for the next page once it is within two rows\n * of view. Keyed by the item count, so it looks again after each page lands.\n */\nexport function Sentinel({ onVisible }: { onVisible: () => void }) {\n\tconst spinner = useRef<HTMLSpanElement>(null)\n\n\tuseEffect(() => {\n\t\tconst node = spinner.current\n\t\tif (node === null || typeof IntersectionObserver === 'undefined') return\n\t\tconst observer = new IntersectionObserver(\n\t\t\t(entries) => {\n\t\t\t\tif (entries.some((entry) => entry.isIntersecting)) onVisible()\n\t\t\t},\n\t\t\t{ root: node.closest('.rs-content'), rootMargin: '0px 0px 360px 0px' }\n\t\t)\n\t\tobserver.observe(node)\n\t\treturn () => observer.disconnect()\n\t}, [onVisible])\n\n\treturn <span ref={spinner} className=\"rs-spinner\" />\n}\n"]}
1
+ {"version":3,"file":"picker-parts.js","sourceRoot":"","sources":["../src/picker-parts.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useVirtualizer } from '@tanstack/react-virtual'\nimport { Children, type ReactNode, useEffect, useLayoutEffect, useRef, useState } from 'react'\n\nimport {\n\ttype Asset,\n\ttype AssetVariant,\n\ttype Organization,\n\ttype RasterApiError,\n\ttype SearchHit,\n\tformatDateLong,\n\tformatFileSize,\n\tformatRelativeTime,\n\tgetBlurhashPlaceholder,\n\tgetPreviewUrl,\n\tgetThumbUrl,\n\tisReady,\n\ttoUserMessage,\n} from '@raster-app/sdk'\n\nimport { useRasterClient } from './context.js'\nimport { Icon } from './icons.js'\nimport type { PromoteState } from './use-promote-variant.js'\nimport type { UploadState } from './use-asset-upload.js'\n\nexport type ReportError = ((error: RasterApiError) => void) | undefined\n\nexport function Brand() {\n\treturn (\n\t\t<div className=\"rs-brand\">\n\t\t\t<Icon shape=\"raster\" solid width={18} />\n\t\t\tRaster\n\t\t</div>\n\t)\n}\n\n// Strict Mode runs an effect twice in development; the host hears of each error once.\nconst reported = new WeakSet<RasterApiError>()\n\nexport function ErrorCard({ error, onError }: { error: RasterApiError; onError: ReportError }) {\n\tuseEffect(() => {\n\t\tif (reported.has(error)) return\n\t\treported.add(error)\n\t\tonError?.(error)\n\t}, [error, onError])\n\treturn (\n\t\t<p role=\"alert\" className=\"rs-card\">\n\t\t\t{toUserMessage(error)}\n\t\t</p>\n\t)\n}\n\nexport function Empty({\n\tglyph,\n\ttitle,\n\ttext,\n\tchildren,\n}: {\n\tglyph: 'grid' | '∅'\n\ttitle: string\n\ttext: string\n\tchildren?: ReactNode\n}) {\n\treturn (\n\t\t<div className=\"rs-empty\">\n\t\t\t<div className=\"rs-empty-glyph\">{glyph === 'grid' ? <Icon shape=\"grid\" /> : glyph}</div>\n\t\t\t<h2 className=\"rs-heading\">{title}</h2>\n\t\t\t<p className=\"rs-muted rs-small\">{text}</p>\n\t\t\t{children}\n\t\t</div>\n\t)\n}\n\n/** Raster's tile height (`.rs-asset-thumb`) and the gap between its rows. */\nconst ROW_HEIGHT = 210 + 30\n\n/** The Raster app's columns from its window's width (`calculateGridColumns`). */\nconst BREAKPOINTS: Array<[number, number]> = [\n\t[2180, 7],\n\t[1920, 6],\n\t[1536, 5],\n\t[1280, 4],\n\t[1024, 3],\n\t[768, 2],\n]\n\n/** Raster's columns for a grid this wide: its window holds a 266px sidebar and 30px padding too. */\nexport function gridColumns(width: number): number {\n\tconst viewport = width + 266 + 2 * 30\n\treturn BREAKPOINTS.find(([min]) => viewport >= min)?.[1] ?? 1\n}\n\n/**\n * Tiles in Raster's grid, a row at a time as the Raster app draws them: only the rows in view,\n * and five either side, are mounted, however many have loaded.\n */\nexport function TileGrid({ children }: { children: ReactNode }) {\n\tconst tiles = Children.toArray(children)\n\tconst grid = useRef<HTMLDivElement>(null)\n\tconst [width, setWidth] = useState(0)\n\n\tuseLayoutEffect(() => {\n\t\tconst node = grid.current\n\t\tif (node === null) return\n\t\tsetWidth(node.clientWidth)\n\t\tif (typeof ResizeObserver === 'undefined') return\n\t\tconst observer = new ResizeObserver(() => setWidth(node.clientWidth))\n\t\tobserver.observe(node)\n\t\treturn () => observer.disconnect()\n\t}, [])\n\n\tconst columns = gridColumns(width)\n\tconst rows = useVirtualizer({\n\t\tcount: Math.ceil(tiles.length / columns),\n\t\tgetScrollElement: () => grid.current?.closest('.rs-content') ?? null,\n\t\testimateSize: () => ROW_HEIGHT,\n\t\toverscan: 5,\n\t\tscrollMargin: grid.current?.offsetTop ?? 0,\n\t})\n\n\treturn (\n\t\t<div ref={grid} className=\"rs-tile-grid\" style={{ height: rows.getTotalSize() }}>\n\t\t\t{rows.getVirtualItems().map((row) => (\n\t\t\t\t<div\n\t\t\t\t\tkey={row.key}\n\t\t\t\t\tclassName=\"rs-grid\"\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\ttop: row.start - rows.options.scrollMargin,\n\t\t\t\t\t\tgridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{tiles.slice(row.index * columns, (row.index + 1) * columns)}\n\t\t\t\t</div>\n\t\t\t))}\n\t\t</div>\n\t)\n}\n\n/**\n * Raster's grid tile: the picture over its blurred fill, or a checkerboard where it has alpha,\n * with a pill for a badge on the left and one for an action on the right.\n */\nexport function Tile({\n\tasset,\n\tonOpen,\n\tselected,\n\tbadge,\n\taction,\n}: {\n\tasset: Asset | SearchHit | AssetVariant\n\tonOpen: () => void\n\t/** Whether this is the version shown large, among an asset's versions. */\n\tselected?: boolean\n\tbadge?: ReactNode\n\taction?: ReactNode\n}) {\n\tconst [loaded, setLoaded] = useState(false)\n\tconst ready = isReady(asset)\n\tconst thumb = getThumbUrl(asset)\n\tconst fill = 'blurhash' in asset ? getBlurhashPlaceholder(asset) : null\n\tconst transparent = 'contentType' in asset && /png|svg|webp|gif/.test(asset.contentType ?? '')\n\tconst size = 'size' in asset ? formatFileSize(asset.size) : ''\n\tconst updated = 'updated' in asset ? formatRelativeTime(asset.updated) : ''\n\n\treturn (\n\t\t<div className=\"rs-tile\">\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"rs-asset-thumb\"\n\t\t\t\tdata-loaded={loaded || undefined}\n\t\t\t\tdisabled={!ready}\n\t\t\t\taria-pressed={selected}\n\t\t\t\ttitle={\n\t\t\t\t\t'created' in asset && asset.created\n\t\t\t\t\t\t? `Created ${formatDateLong(asset.created)}`\n\t\t\t\t\t\t: undefined\n\t\t\t\t}\n\t\t\t\tonClick={onOpen}\n\t\t\t>\n\t\t\t\t{transparent ? (\n\t\t\t\t\t<div className=\"rs-transparent\" />\n\t\t\t\t) : (\n\t\t\t\t\tfill && <img className=\"rs-thumb-blur\" src={fill} alt=\"\" />\n\t\t\t\t)}\n\t\t\t\t<div className=\"rs-thumb-preview\">\n\t\t\t\t\t{thumb && (\n\t\t\t\t\t\t<img\n\t\t\t\t\t\t\tkey={thumb}\n\t\t\t\t\t\t\tsrc={thumb}\n\t\t\t\t\t\t\talt=\"\"\n\t\t\t\t\t\t\tloading=\"lazy\"\n\t\t\t\t\t\t\tdecoding=\"async\"\n\t\t\t\t\t\t\tonLoad={() => setLoaded(true)}\n\t\t\t\t\t\t\tonError={() => setLoaded(true)}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t</div>\n\t\t\t\t<div className=\"rs-thumb-meta\">\n\t\t\t\t\t<span className=\"rs-thumb-title\">{asset.name}</span>\n\t\t\t\t\t<span className=\"rs-thumb-subtitle\">\n\t\t\t\t\t\t{!ready && <span>Not ready</span>}\n\t\t\t\t\t\t{ready && size && <span>{size}</span>}\n\t\t\t\t\t\t{ready && updated && <span>{updated}</span>}\n\t\t\t\t\t\t{ready && !size && !updated && asset.width && asset.height && (\n\t\t\t\t\t\t\t<span>\n\t\t\t\t\t\t\t\t{asset.width} × {asset.height}\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</button>\n\t\t\t{badge}\n\t\t\t{action}\n\t\t</div>\n\t)\n}\n\n/**\n * An opened asset or variant: the image large, its facts, and what the host can do with it.\n * Raster opens through the host, which reaches the browser from a Figma plugin too.\n */\nexport function Detail({\n\titem,\n\tbusy,\n\tonPick,\n}: {\n\titem: Asset | SearchHit | AssetVariant\n\t/** While the asset is read in full for the host, or a promote has yet to land. */\n\tbusy: boolean\n\tonPick?: () => void\n}) {\n\tconst { host } = useRasterClient()\n\tconst image = getPreviewUrl(item)\n\tconst appUrl = item.appUrl\n\tconst url = 'url' in item ? item.url : null\n\tconst facts = [\n\t\titem.width && item.height ? `${item.width} × ${item.height}` : '',\n\t\t'size' in item ? formatFileSize(item.size) : '',\n\t\t'created' in item && item.created ? `Created ${formatDateLong(item.created)}` : '',\n\t].filter((fact) => fact !== '')\n\n\treturn (\n\t\t<div className=\"rs-detail\">\n\t\t\t<div className=\"rs-detail-image\">{image && <img key={image} src={image} alt=\"\" />}</div>\n\t\t\t<div className=\"rs-detail-info\">\n\t\t\t\t<h2 className=\"rs-heading\">{item.name}</h2>\n\t\t\t\t{facts.length > 0 && <p className=\"rs-muted rs-small\">{facts.join(' · ')}</p>}\n\t\t\t\t{onPick && (\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"rs-button rs-button-primary\"\n\t\t\t\t\t\tdisabled={!isReady(item) || busy}\n\t\t\t\t\t\tonClick={onPick}\n\t\t\t\t\t>\n\t\t\t\t\t\tPick asset\n\t\t\t\t\t</button>\n\t\t\t\t)}\n\t\t\t\t{appUrl && (\n\t\t\t\t\t<a\n\t\t\t\t\t\tclassName=\"rs-button rs-button-secondary\"\n\t\t\t\t\t\thref={appUrl}\n\t\t\t\t\t\ttarget=\"_blank\"\n\t\t\t\t\t\trel=\"noreferrer\"\n\t\t\t\t\t\tonClick={(event) => {\n\t\t\t\t\t\t\tevent.preventDefault()\n\t\t\t\t\t\t\thost.openExternal(appUrl)\n\t\t\t\t\t\t}}\n\t\t\t\t\t>\n\t\t\t\t\t\t<Icon shape=\"link\" width={12.25} />\n\t\t\t\t\t\tOpen in Raster\n\t\t\t\t\t</a>\n\t\t\t\t)}\n\t\t\t\t{url && <CopyUrl url={url} />}\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n/** Copies the asset's CDN URL, and says so for 2 seconds, as Raster's Copy URL does. */\nfunction CopyUrl({ url }: { url: string }) {\n\tconst [copied, setCopied] = useState(false)\n\n\tuseEffect(() => {\n\t\tif (!copied) return\n\t\tconst timer = setTimeout(() => setCopied(false), 2000)\n\t\treturn () => clearTimeout(timer)\n\t}, [copied])\n\n\treturn (\n\t\t<button\n\t\t\ttype=\"button\"\n\t\t\tclassName=\"rs-button rs-button-secondary\"\n\t\t\tonClick={async () => setCopied(await copyText(url))}\n\t\t>\n\t\t\t<Icon shape={copied ? 'check' : 'link'} width={12.25} />\n\t\t\t{copied ? 'Copied' : 'Copy URL'}\n\t\t</button>\n\t)\n}\n\n/** The Clipboard API, or a selection copied, where a sandboxed iframe blocks that API. */\nasync function copyText(text: string): Promise<boolean> {\n\ttry {\n\t\tawait navigator.clipboard.writeText(text)\n\t\treturn true\n\t} catch {\n\t\tconst field = document.createElement('textarea')\n\t\tfield.value = text\n\t\tdocument.body.append(field)\n\t\tfield.select()\n\t\tconst copied = document.execCommand('copy')\n\t\tfield.remove()\n\t\treturn copied\n\t}\n}\n\n/** A file on its way into Raster, as the grid shows it until Raster has processed it. */\nexport function PendingTile({\n\tname,\n\tpreview,\n\tstatus,\n}: {\n\tname: string\n\tpreview: string\n\tstatus: string\n}) {\n\treturn (\n\t\t<div className=\"rs-tile\">\n\t\t\t<div className=\"rs-asset-thumb\" data-loaded aria-busy=\"true\">\n\t\t\t\t<div className=\"rs-thumb-preview\">\n\t\t\t\t\t<img src={preview} alt=\"\" />\n\t\t\t\t</div>\n\t\t\t\t<div className=\"rs-thumb-meta\">\n\t\t\t\t\t<span className=\"rs-thumb-title\">{name}</span>\n\t\t\t\t\t<span className=\"rs-thumb-subtitle\">\n\t\t\t\t\t\t<span className=\"rs-spinner\" />\n\t\t\t\t\t\t{status}\n\t\t\t\t\t</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t)\n}\n\n/** A control that opens the file chooser, drawn as whatever `className` makes it. */\nexport function FilePicker({\n\tclassName,\n\tdisabled,\n\tfocusable = true,\n\tonFile,\n\tchildren,\n}: {\n\tclassName: string\n\tdisabled: boolean\n\tfocusable?: boolean\n\tonFile: (file: File) => void\n\tchildren: ReactNode\n}) {\n\treturn (\n\t\t<label className={className}>\n\t\t\t{children}\n\t\t\t<input\n\t\t\t\tclassName=\"rs-visually-hidden\"\n\t\t\t\ttype=\"file\"\n\t\t\t\tdisabled={disabled}\n\t\t\t\ttabIndex={focusable ? 0 : -1}\n\t\t\t\tonChange={(event) => {\n\t\t\t\t\tconst file = event.target.files?.[0]\n\t\t\t\t\t// Cleared, so picking the same file again uploads it again.\n\t\t\t\t\tevent.target.value = ''\n\t\t\t\t\tif (file) onFile(file)\n\t\t\t\t}}\n\t\t\t/>\n\t\t</label>\n\t)\n}\n\nconst UPLOADS = {\n\tuploading: 'Uploading',\n\tprocessing: 'Processing',\n\tdone: 'Uploaded',\n\t'timed-out': 'Still processing',\n} as const\nconst PROMOTES = {\n\tpromoting: 'Setting as default',\n\tdone: 'Default set',\n\t'timed-out': 'Still setting as default',\n} as const\n\n/** Where an upload and a promote stand, beside the header; each failure shows as an error. */\nexport function Progress({ upload, promote }: { upload: UploadState; promote: PromoteState }) {\n\treturn (\n\t\t<>\n\t\t\t{upload.status !== 'idle' && upload.status !== 'error' && (\n\t\t\t\t<Status status={upload.status} text={UPLOADS[upload.status]} />\n\t\t\t)}\n\t\t\t{promote.status !== 'idle' && promote.status !== 'error' && (\n\t\t\t\t<Status status={promote.status} text={PROMOTES[promote.status]} />\n\t\t\t)}\n\t\t</>\n\t)\n}\n\nfunction Status({ status, text }: { status: string; text: string }) {\n\tlet glyph = <span className=\"rs-spinner\" />\n\tif (status === 'done') glyph = <Icon shape=\"check\" />\n\tif (status === 'timed-out') glyph = <Icon shape=\"info-circle\" />\n\treturn (\n\t\t<span className=\"rs-status\" data-status={status}>\n\t\t\t{glyph}\n\t\t\t{text}\n\t\t</span>\n\t)\n}\n\n/** Raster's organization switcher: the current organization, over a menu of every one. */\nexport function OrganizationSwitcher({\n\torganizations,\n\tcurrent,\n\tonIntent,\n\tonSelect,\n}: {\n\torganizations: Array<Organization>\n\tcurrent: Organization | undefined\n\tonIntent: (organization: Organization) => void\n\tonSelect: (organizationId: string) => void\n}) {\n\tconst [open, setOpen] = useState(false)\n\tconst root = useRef<HTMLDivElement>(null)\n\n\tuseEffect(() => {\n\t\tif (!open) return\n\t\tconst close = (event: MouseEvent | KeyboardEvent) => {\n\t\t\tconst outside = event.target instanceof Node && !root.current?.contains(event.target)\n\t\t\tif (event instanceof KeyboardEvent ? event.key !== 'Escape' : !outside) return\n\t\t\t// Stopped here, so a host dialog that closes on Escape stays open.\n\t\t\tif (event instanceof KeyboardEvent) event.stopPropagation()\n\t\t\tsetOpen(false)\n\t\t}\n\t\tdocument.addEventListener('mousedown', close)\n\t\tdocument.addEventListener('keydown', close)\n\t\treturn () => {\n\t\t\tdocument.removeEventListener('mousedown', close)\n\t\t\tdocument.removeEventListener('keydown', close)\n\t\t}\n\t}, [open])\n\n\treturn (\n\t\t<div className=\"rs-org\" ref={root}>\n\t\t\t<button\n\t\t\t\ttype=\"button\"\n\t\t\t\tclassName=\"rs-sidebar-item rs-org-trigger\"\n\t\t\t\taria-expanded={open}\n\t\t\t\tonClick={() => setOpen(!open)}\n\t\t\t>\n\t\t\t\t<Avatar name={current?.name ?? current?.id ?? ''} />\n\t\t\t\t<span className=\"rs-library-name\">{current?.name ?? current?.id}</span>\n\t\t\t\t<Icon shape=\"chevron-up-down\" />\n\t\t\t</button>\n\t\t\t{open && (\n\t\t\t\t<div className=\"rs-menu\">\n\t\t\t\t\t<div className=\"rs-menu-heading\">Organizations</div>\n\t\t\t\t\t{organizations.map((organization) => (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tkey={organization.id}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclassName={\n\t\t\t\t\t\t\t\torganization.id === current?.id ? 'rs-sidebar-item is-active' : 'rs-sidebar-item'\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tonMouseEnter={() => onIntent(organization)}\n\t\t\t\t\t\t\tonFocus={() => onIntent(organization)}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tsetOpen(false)\n\t\t\t\t\t\t\t\tonSelect(organization.id)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<Avatar name={organization.name ?? organization.id} />\n\t\t\t\t\t\t\t<span className=\"rs-library-name\">{organization.name ?? organization.id}</span>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t))}\n\t\t\t\t</div>\n\t\t\t)}\n\t\t</div>\n\t)\n}\n\n/** Raster's letter avatar, on a gradient hued from the name as the app hues it. */\nfunction Avatar({ name }: { name: string }) {\n\tif (name === '') return null\n\tlet hex = ''\n\tfor (let index = 0; index < Math.min(name.length, 10); index++) {\n\t\thex += `000${name.charCodeAt(index).toString(16)}`.slice(-4)\n\t}\n\tconst hue = Number.parseInt(hex, 16) % 360\n\treturn (\n\t\t<span\n\t\t\tclassName=\"rs-avatar\"\n\t\t\tstyle={{\n\t\t\t\tbackgroundImage: `linear-gradient(45deg, hsl(${hue}, 70%, 50%), hsl(${(hue + 60) % 360}, 70%, 50%))`,\n\t\t\t}}\n\t\t>\n\t\t\t{name.charAt(0).toUpperCase()}\n\t\t</span>\n\t)\n}\n\n/**\n * The spinner at the end of the grid, which asks for the next page once it is within two rows\n * of view. Keyed by the item count, so it looks again after each page lands.\n */\nexport function Sentinel({ onVisible }: { onVisible: () => void }) {\n\tconst spinner = useRef<HTMLSpanElement>(null)\n\n\tuseEffect(() => {\n\t\tconst node = spinner.current\n\t\tif (node === null || typeof IntersectionObserver === 'undefined') return\n\t\tconst observer = new IntersectionObserver(\n\t\t\t(entries) => {\n\t\t\t\tif (entries.some((entry) => entry.isIntersecting)) onVisible()\n\t\t\t},\n\t\t\t{ root: node.closest('.rs-content'), rootMargin: '0px 0px 360px 0px' }\n\t\t)\n\t\tobserver.observe(node)\n\t\treturn () => observer.disconnect()\n\t}, [onVisible])\n\n\treturn <span ref={spinner} className=\"rs-spinner\" />\n}\n"]}
@@ -65,9 +65,11 @@ export function useAssetSearch(scope, rawQuery) {
65
65
  void fetchNextPage({ cancelRefetch: false });
66
66
  }, [hasMore, fetchNextPage]);
67
67
  useEffect(() => {
68
+ if (query === debouncedQuery)
69
+ return;
68
70
  const timer = setTimeout(() => setDebouncedQuery(query), SEARCH_DEBOUNCE_MS);
69
71
  return () => clearTimeout(timer);
70
- }, [query]);
72
+ }, [query, debouncedQuery]);
71
73
  return {
72
74
  active,
73
75
  query,
@@ -1 +1 @@
1
- {"version":3,"file":"use-asset-search.js","sourceRoot":"","sources":["../src/use-asset-search.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype InfiniteData,\n\ttype QueryKey,\n\tskipToken,\n\tuseInfiniteQuery,\n} from '@tanstack/react-query'\nimport { useCallback, useEffect, useState } from 'react'\n\nimport {\n\ttype RasterApiError,\n\tSEARCH_PAGE_SIZE,\n\ttype SearchHit,\n\ttype SearchResult,\n} from '@raster-app/sdk'\n\nimport { useRasterClient } from './context.js'\nimport { rasterKeys } from './query-keys.js'\nimport { useCredentialIdentity } from './use-session.js'\n\n/** How long typing must pause before a query is sent, so a word costs one request. */\nconst SEARCH_DEBOUNCE_MS = 300\n\n/** Where a search looks: one organization, optionally narrowed to one of its libraries. */\nexport type SearchScope = {\n\torganizationId: string\n\t/** Null searches every library in the organization. */\n\tlibraryId: string | null\n}\n\nexport type AssetSearch = {\n\t/** Whether there is a query to show results for — views swap to results on this. */\n\tactive: boolean\n\t/** The trimmed query the results are (or will be) for. */\n\tquery: string\n\t/** Null until the first page has landed. */\n\thits: Array<SearchHit> | null\n\t/** Total matches in scope, from the index — not just those loaded so far. */\n\tfound: number\n\tisSearching: boolean\n\thasMore: boolean\n\t/** Fetch the next page. A no-op while one is already in flight, at the end, or after a failure. */\n\tloadMore: () => void\n\terror: RasterApiError | null\n}\n\ntype Results = Pick<SearchResult, 'hits' | 'found'>\n\nfunction toResults(data: InfiniteData<SearchResult>): Results {\n\treturn {\n\t\t// The index can shift between pages; a hit already shown is not shown twice.\n\t\thits: Array.from(\n\t\t\tnew Map(data.pages.flatMap((page) => page.hits).map((hit) => [hit.id, hit])).values()\n\t\t),\n\t\tfound: data.pages[data.pages.length - 1]?.found ?? 0,\n\t}\n}\n\n/**\n * Full-text search over Raster, as a header search box drives it. The previous results stay\n * on screen while the next query runs, since a grid that blanks on every keystroke makes\n * typing feel destructive. A null scope switches the search off.\n */\nexport function useAssetSearch(scope: SearchScope | null, rawQuery: string): AssetSearch {\n\tconst client = useRasterClient()\n\n\tconst identity = useCredentialIdentity()\n\n\tconst [debouncedQuery, setDebouncedQuery] = useState('')\n\n\tconst query = rawQuery.trim()\n\tconst active = scope !== null && query !== ''\n\n\tconst { data, error, isFetching, isError, hasNextPage, fetchNextPage } = useInfiniteQuery<\n\t\tSearchResult,\n\t\tRasterApiError,\n\t\tResults,\n\t\tQueryKey,\n\t\tnumber\n\t>({\n\t\tqueryKey: [\n\t\t\t...rasterKeys.search(identity, scope?.organizationId ?? null),\n\t\t\tscope?.libraryId ?? null,\n\t\t\tdebouncedQuery,\n\t\t],\n\t\tqueryFn:\n\t\t\tscope === null\n\t\t\t\t? skipToken\n\t\t\t\t: ({ pageParam, signal }) =>\n\t\t\t\t\t\tclient.search({\n\t\t\t\t\t\t\torganizationId: scope.organizationId,\n\t\t\t\t\t\t\tlibraryIds: scope.libraryId === null ? undefined : [scope.libraryId],\n\t\t\t\t\t\t\tquery: debouncedQuery,\n\t\t\t\t\t\t\tpage: pageParam,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t}),\n\t\tinitialPageParam: 1,\n\t\tgetNextPageParam: (lastPage, pages) =>\n\t\t\tlastPage.hits.length === SEARCH_PAGE_SIZE && pages.length * SEARCH_PAGE_SIZE < lastPage.found\n\t\t\t\t? pages.length + 1\n\t\t\t\t: undefined,\n\t\tselect: toResults,\n\t\t// `keepPreviousData` within one sign-in's search of one organization. The observer holds\n\t\t// the last account's results after their cache is dropped, and a hit names its library but\n\t\t// not its organization, so one shown under another organization would be acted on there.\n\t\tplaceholderData: (previous, previousQuery) => {\n\t\t\tconst [, signIn, , organizationId] = previousQuery?.queryKey ?? []\n\t\t\treturn signIn === identity && organizationId === scope?.organizationId ? previous : undefined\n\t\t},\n\t\t// A blank `q` returns recent assets rather than matches.\n\t\tenabled: identity !== null && active && debouncedQuery !== '',\n\t})\n\n\tconst shown = active ? data : undefined\n\t// Up from the keystroke rather than from when the request goes out, so the spinner confirms\n\t// the typing was noticed.\n\tconst isSearching = active && (query !== debouncedQuery || isFetching)\n\t// A failure also stops paging, rather than let an observer retry it on every scroll.\n\tconst hasMore = active && hasNextPage && !isError\n\n\tconst loadMore = useCallback(() => {\n\t\tif (hasMore) void fetchNextPage({ cancelRefetch: false })\n\t}, [hasMore, fetchNextPage])\n\n\tuseEffect(() => {\n\t\tconst timer = setTimeout(() => setDebouncedQuery(query), SEARCH_DEBOUNCE_MS)\n\t\treturn () => clearTimeout(timer)\n\t}, [query])\n\n\treturn {\n\t\tactive,\n\t\tquery,\n\t\thits: shown?.hits ?? null,\n\t\tfound: shown?.found ?? 0,\n\t\tisSearching,\n\t\thasMore,\n\t\tloadMore,\n\t\terror: active ? error : null,\n\t}\n}\n"]}
1
+ {"version":3,"file":"use-asset-search.js","sourceRoot":"","sources":["../src/use-asset-search.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype InfiniteData,\n\ttype QueryKey,\n\tskipToken,\n\tuseInfiniteQuery,\n} from '@tanstack/react-query'\nimport { useCallback, useEffect, useState } from 'react'\n\nimport {\n\ttype RasterApiError,\n\tSEARCH_PAGE_SIZE,\n\ttype SearchHit,\n\ttype SearchResult,\n} from '@raster-app/sdk'\n\nimport { useRasterClient } from './context.js'\nimport { rasterKeys } from './query-keys.js'\nimport { useCredentialIdentity } from './use-session.js'\n\n/** How long typing must pause before a query is sent, so a word costs one request. */\nconst SEARCH_DEBOUNCE_MS = 300\n\n/** Where a search looks: one organization, optionally narrowed to one of its libraries. */\nexport type SearchScope = {\n\torganizationId: string\n\t/** Null searches every library in the organization. */\n\tlibraryId: string | null\n}\n\nexport type AssetSearch = {\n\t/** Whether there is a query to show results for — views swap to results on this. */\n\tactive: boolean\n\t/** The trimmed query the results are (or will be) for. */\n\tquery: string\n\t/** Null until the first page has landed. */\n\thits: Array<SearchHit> | null\n\t/** Total matches in scope, from the index — not just those loaded so far. */\n\tfound: number\n\tisSearching: boolean\n\thasMore: boolean\n\t/** Fetch the next page. A no-op while one is already in flight, at the end, or after a failure. */\n\tloadMore: () => void\n\terror: RasterApiError | null\n}\n\ntype Results = Pick<SearchResult, 'hits' | 'found'>\n\nfunction toResults(data: InfiniteData<SearchResult>): Results {\n\treturn {\n\t\t// The index can shift between pages; a hit already shown is not shown twice.\n\t\thits: Array.from(\n\t\t\tnew Map(data.pages.flatMap((page) => page.hits).map((hit) => [hit.id, hit])).values()\n\t\t),\n\t\tfound: data.pages[data.pages.length - 1]?.found ?? 0,\n\t}\n}\n\n/**\n * Full-text search over Raster, as a header search box drives it. The previous results stay\n * on screen while the next query runs, since a grid that blanks on every keystroke makes\n * typing feel destructive. A null scope switches the search off.\n */\nexport function useAssetSearch(scope: SearchScope | null, rawQuery: string): AssetSearch {\n\tconst client = useRasterClient()\n\n\tconst identity = useCredentialIdentity()\n\n\tconst [debouncedQuery, setDebouncedQuery] = useState('')\n\n\tconst query = rawQuery.trim()\n\tconst active = scope !== null && query !== ''\n\n\tconst { data, error, isFetching, isError, hasNextPage, fetchNextPage } = useInfiniteQuery<\n\t\tSearchResult,\n\t\tRasterApiError,\n\t\tResults,\n\t\tQueryKey,\n\t\tnumber\n\t>({\n\t\tqueryKey: [\n\t\t\t...rasterKeys.search(identity, scope?.organizationId ?? null),\n\t\t\tscope?.libraryId ?? null,\n\t\t\tdebouncedQuery,\n\t\t],\n\t\tqueryFn:\n\t\t\tscope === null\n\t\t\t\t? skipToken\n\t\t\t\t: ({ pageParam, signal }) =>\n\t\t\t\t\t\tclient.search({\n\t\t\t\t\t\t\torganizationId: scope.organizationId,\n\t\t\t\t\t\t\tlibraryIds: scope.libraryId === null ? undefined : [scope.libraryId],\n\t\t\t\t\t\t\tquery: debouncedQuery,\n\t\t\t\t\t\t\tpage: pageParam,\n\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t}),\n\t\tinitialPageParam: 1,\n\t\tgetNextPageParam: (lastPage, pages) =>\n\t\t\tlastPage.hits.length === SEARCH_PAGE_SIZE && pages.length * SEARCH_PAGE_SIZE < lastPage.found\n\t\t\t\t? pages.length + 1\n\t\t\t\t: undefined,\n\t\tselect: toResults,\n\t\t// `keepPreviousData` within one sign-in's search of one organization. The observer holds\n\t\t// the last account's results after their cache is dropped, and a hit names its library but\n\t\t// not its organization, so one shown under another organization would be acted on there.\n\t\tplaceholderData: (previous, previousQuery) => {\n\t\t\tconst [, signIn, , organizationId] = previousQuery?.queryKey ?? []\n\t\t\treturn signIn === identity && organizationId === scope?.organizationId ? previous : undefined\n\t\t},\n\t\t// A blank `q` returns recent assets rather than matches.\n\t\tenabled: identity !== null && active && debouncedQuery !== '',\n\t})\n\n\tconst shown = active ? data : undefined\n\t// Up from the keystroke rather than from when the request goes out, so the spinner confirms\n\t// the typing was noticed.\n\tconst isSearching = active && (query !== debouncedQuery || isFetching)\n\t// A failure also stops paging, rather than let an observer retry it on every scroll.\n\tconst hasMore = active && hasNextPage && !isError\n\n\tconst loadMore = useCallback(() => {\n\t\tif (hasMore) void fetchNextPage({ cancelRefetch: false })\n\t}, [hasMore, fetchNextPage])\n\n\tuseEffect(() => {\n\t\tif (query === debouncedQuery) return\n\t\tconst timer = setTimeout(() => setDebouncedQuery(query), SEARCH_DEBOUNCE_MS)\n\t\treturn () => clearTimeout(timer)\n\t}, [query, debouncedQuery])\n\n\treturn {\n\t\tactive,\n\t\tquery,\n\t\thits: shown?.hits ?? null,\n\t\tfound: shown?.found ?? 0,\n\t\tisSearching,\n\t\thasMore,\n\t\tloadMore,\n\t\terror: active ? error : null,\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raster-app/react",
3
- "version": "0.1.0-canary-32ca74d",
3
+ "version": "0.1.0-canary-56a3f84",
4
4
  "description": "React provider and hooks for the Raster SDK: session, organizations, libraries, assets, search and uploads.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://raster.app/docs/api/sdk",
@@ -40,7 +40,7 @@
40
40
  "test": "bun test"
41
41
  },
42
42
  "dependencies": {
43
- "@raster-app/sdk": "0.1.0-canary-32ca74d",
43
+ "@raster-app/sdk": "0.1.0-canary-56a3f84",
44
44
  "@tanstack/react-query": "^5.103.1",
45
45
  "@tanstack/react-virtual": "^3.14.13"
46
46
  },