@openrails/contentkit-ui 0.68.0 → 0.69.0

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.
Files changed (91) hide show
  1. package/README.md +55 -10
  2. package/dist/client/client.d.ts +4 -2
  3. package/dist/client/client.js +1 -0
  4. package/dist/client/client.js.map +1 -1
  5. package/dist/client/content/bans.js +7 -7
  6. package/dist/client/content/bans.js.map +1 -1
  7. package/dist/client/content/comments.js +16 -14
  8. package/dist/client/content/comments.js.map +1 -1
  9. package/dist/client/content/favorites.js +5 -5
  10. package/dist/client/content/favorites.js.map +1 -1
  11. package/dist/client/content/moderation.js +5 -5
  12. package/dist/client/content/moderation.js.map +1 -1
  13. package/dist/client/content/polls.js +34 -31
  14. package/dist/client/content/polls.js.map +1 -1
  15. package/dist/client/content/posts.js +21 -20
  16. package/dist/client/content/posts.js.map +1 -1
  17. package/dist/client/content/reactions.js +4 -4
  18. package/dist/client/content/reactions.js.map +1 -1
  19. package/dist/client/generated/error-codes.d.ts +1 -1
  20. package/dist/client/generated/error-codes.js +4 -0
  21. package/dist/client/generated/error-codes.js.map +1 -1
  22. package/dist/client/generated/routes.js.map +1 -1
  23. package/dist/client/http.d.ts +6 -2
  24. package/dist/client/http.js +11 -3
  25. package/dist/client/http.js.map +1 -1
  26. package/dist/client/media/client.js +24 -24
  27. package/dist/client/media/client.js.map +1 -1
  28. package/dist/client/media/rules.d.ts +2 -0
  29. package/dist/client/media/rules.js +6 -2
  30. package/dist/client/media/rules.js.map +1 -1
  31. package/dist/client/taxonomy.js +2 -2
  32. package/dist/client/taxonomy.js.map +1 -1
  33. package/dist/components/media-folder-editor.d.ts +17 -3
  34. package/dist/components/media-folder-editor.js +146 -99
  35. package/dist/components/media-folder-editor.js.map +1 -1
  36. package/dist/components/slot-upload.js +102 -100
  37. package/dist/components/slot-upload.js.map +1 -1
  38. package/dist/locales/de.js +1 -0
  39. package/dist/locales/de.js.map +1 -1
  40. package/dist/locales/en.d.ts +1 -0
  41. package/dist/locales/en.js +1 -0
  42. package/dist/locales/en.js.map +1 -1
  43. package/dist/locales/es.js +1 -0
  44. package/dist/locales/es.js.map +1 -1
  45. package/dist/locales/ja.js +1 -0
  46. package/dist/locales/ja.js.map +1 -1
  47. package/dist/locales/ko.js +1 -0
  48. package/dist/locales/ko.js.map +1 -1
  49. package/dist/locales/zh.js +1 -0
  50. package/dist/locales/zh.js.map +1 -1
  51. package/dist/react/canonical.d.ts +2 -2
  52. package/dist/react/canonical.js +2 -12
  53. package/dist/react/canonical.js.map +1 -1
  54. package/dist/react/comments.js +35 -27
  55. package/dist/react/comments.js.map +1 -1
  56. package/dist/react/context.d.ts +7 -0
  57. package/dist/react/context.js +20 -15
  58. package/dist/react/context.js.map +1 -1
  59. package/dist/react/engagement.js +2 -2
  60. package/dist/react/engagement.js.map +1 -1
  61. package/dist/react/folder.d.ts +29 -9
  62. package/dist/react/folder.js +170 -113
  63. package/dist/react/folder.js.map +1 -1
  64. package/dist/react/polls.js +3 -3
  65. package/dist/react/polls.js.map +1 -1
  66. package/dist/react/posts.js +2 -2
  67. package/dist/react/posts.js.map +1 -1
  68. package/dist/react/provider.d.ts +7 -3
  69. package/dist/react/provider.js +34 -20
  70. package/dist/react/provider.js.map +1 -1
  71. package/dist/react/public.js +11 -11
  72. package/dist/react/public.js.map +1 -1
  73. package/dist/react/read.js +93 -92
  74. package/dist/react/read.js.map +1 -1
  75. package/dist/react/resources.d.ts +6 -3
  76. package/dist/react/resources.js +40 -40
  77. package/dist/react/resources.js.map +1 -1
  78. package/dist/react/slot.d.ts +4 -2
  79. package/dist/react/slot.js +96 -92
  80. package/dist/react/slot.js.map +1 -1
  81. package/dist/react/store.d.ts +1 -1
  82. package/dist/react/store.js +21 -20
  83. package/dist/react/store.js.map +1 -1
  84. package/dist/react/upload.d.ts +3 -2
  85. package/dist/react/upload.js +20 -15
  86. package/dist/react/upload.js.map +1 -1
  87. package/dist/react/use-resource.d.ts +1 -0
  88. package/dist/react/use-resource.js +38 -30
  89. package/dist/react/use-resource.js.map +1 -1
  90. package/dist/react.js +15 -15
  91. package/package.json +7 -5
@@ -1 +1 @@
1
- {"version":3,"file":"media-folder-editor.js","names":[],"sources":["../../src/components/media-folder-editor.tsx"],"sourcesContent":["import {\n Alert02Icon,\n CropIcon,\n Delete02Icon,\n Edit02Icon,\n File01Icon,\n Image01Icon,\n ImageAdd01Icon,\n Loading03Icon,\n MusicNote01Icon,\n Refresh01Icon,\n RepeatIcon,\n Video01Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useEffect, useImperativeHandle, useMemo, useRef, useState, type DragEvent, type ReactNode, type Ref } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport type { AspectRatio } from \"../client/aspect.js\";\nimport { failureError } from \"../client/errors.js\";\nimport { isAudioType, isImageType, isSubtitleType, isVideoType } from \"../client/gallery.js\";\nimport type { FileInfo, RefBody, UploadRule } from \"../client/generated/wire.js\";\nimport { stem } from \"../client/media/client.js\";\nimport type { QueueItem } from \"../client/media/queue.js\";\nimport { acceptOf, ratioLabel } from \"../client/media/rules.js\";\nimport { isProcessing } from \"../client/media/windows.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { megabytes, type Translator } from \"../i18n/messages.js\";\nimport { useEditorCrop } from \"../react/crop.js\";\nimport { nameIn, thumbnailOf, useMediaFolder, type FolderGroup, type MediaFolderOptions, type UseMediaFolder } from \"../react/folder.js\";\nimport { ContentKitUiRoot } from \"../scope.js\";\nimport { Alert, AlertDescription, AlertTitle } from \"#ckui/ui/alert\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Progress } from \"#ckui/ui/progress\";\nimport { EncodeProgress } from \"./encode-progress.js\";\nimport { ImageCropDialog } from \"./image-crop-dialog.js\";\nimport { progressLabel, progressValue } from \"./progress.js\";\nimport { SortableList } from \"./sortable-list.js\";\n\nexport interface MediaFolderEditorHandle {\n /** Stops every upload and empties the queue (before deleting a draft). */\n discard: () => void;\n /** Commits the uploaded files (commit=\"manual\"). */\n commit: () => Promise<FileInfo[]>;\n /** The folder's state and updates. */\n folder: UseMediaFolder;\n}\n\nexport interface MediaFolderEditorProps extends MediaFolderOptions {\n /** The item whose folder this edits. */\n item: RefBody;\n ref?: Ref<MediaFolderEditorHandle>;\n /** Heading; default \"Media\". */\n label?: ReactNode;\n /** Beside \"Add files\" in the header, e.g. a ZIP import. */\n toolbar?: ReactNode;\n /** Extra actions on each upload, beside crop, rename, replace and remove. */\n rowActions?: (file: FileInfo) => ReactNode;\n /** Below the lists, e.g. what readers without access see. */\n footer?: ReactNode;\n /** Shapes offered when cropping an image whose path has no preset shape; \"\" is the original's. */\n aspects?: readonly AspectRatio[];\n /** Asked before removing selected uploads; default the browser's confirm; false skips it. */\n confirmRemove?: ((count: number) => boolean | Promise<boolean>) | false;\n disabled?: boolean;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\nconst ASPECTS: readonly AspectRatio[] = [\"\", \"1:1\", \"4:5\", \"16:9\"];\n\ntype Kind = \"image\" | \"video\" | \"audio\" | \"subtitle\" | \"file\";\n\nfunction kindOf(type: string | undefined): Kind {\n if (isImageType(type)) return \"image\";\n if (isVideoType(type)) return \"video\";\n if (isAudioType(type)) return \"audio\";\n if (isSubtitleType(type)) return \"subtitle\";\n return \"file\";\n}\n\nconst ICONS: Record<Kind, IconSvgElement> = { image: Image01Icon, video: Video01Icon, audio: MusicNote01Icon, subtitle: File01Icon, file: File01Icon };\n\n/** What a rule takes: one kind when every type is of it, else files. */\nfunction ruleKind(rule: UploadRule | undefined): Kind {\n const kinds = new Set((rule?.types ?? []).map(kindOf));\n return kinds.size === 1 ? [...kinds][0]! : \"file\";\n}\n\n/** The rules as a sentence for the drop zone: sizes, caps and video shapes. */\nexport function describeRules(t: Translator[\"t\"], rules: readonly UploadRule[]): string {\n const parts: string[] = [];\n for (const r of rules) {\n if (!r.types.length) continue;\n const what = t(`folder.kinds.${ruleKind(r)}`);\n const size = r.max_bytes > 0 ? t(\"folder.limitSize\", { what, size: megabytes(r.max_bytes) }) : what;\n parts.push(r.max ? `${size}, ${t(\"folder.limitMax\", { max: r.max })}` : size);\n if (r.min_aspect && r.max_aspect) parts.push(t(\"folder.limitAspect\", { min: ratioLabel(r.min_aspect), max: ratioLabel(r.max_aspect) }));\n }\n return parts.join(\" · \");\n}\n\n/**\n * An item's folder for its editors: drop or pick files (screened against the\n * kind's upload rules), a queue with progress, retry and remove, and each\n * upload path's uploads as a sortable list with thumbnail, name, edited\n * badge, encode progress and failures; crop, rename, replace and bulk\n * remove. commit=\"manual\" adds the queue with \"Add N\"; \"auto\" commits as\n * files finish (a draft; discard() through the ref).\n */\nexport function MediaFolderEditor(p: MediaFolderEditorProps) {\n // The queue belongs to one item: another item gets a fresh editor.\n return <FolderEditor key={`${p.item.kind}/${p.item.id}`} {...p} />;\n}\n\nfunction FolderEditor(p: MediaFolderEditorProps) {\n const { item, ref, label, toolbar, rowActions, footer, aspects = ASPECTS, confirmRemove, disabled, className, appearance, ...options } = p;\n const { t, error: errorText } = useMessages();\n const folder = useMediaFolder(item, options);\n const { groups, queue, read } = folder;\n useImperativeHandle(ref, () => ({ discard: folder.discard, commit: folder.commit, folder }), [folder]);\n const auto = options.commit === \"auto\";\n const rules = groups.flatMap((g) => (g.rule ? [g.rule] : []));\n const accept = acceptOf(rules);\n const count = folder.uploads.length + queue.items.length;\n const empty = count === 0;\n const off = !!disabled;\n\n const [selected, setSelected] = useState<ReadonlySet<string>>(new Set());\n useEffect(() => {\n const paths = new Set(folder.uploads.map((f) => f.path));\n setSelected((s) => ([...s].every((x) => paths.has(x)) ? s : new Set([...s].filter((x) => paths.has(x)))));\n }, [folder.uploads]);\n const [cropping, setCropping] = useState<string | null>(null);\n const crop = useEditorCrop(item, cropping, { client: options.client, onError: options.onError, onSaved: () => setCropping(null) });\n const cropRule = cropping ? groups.find((g) => g.files.some((f) => f.path === cropping))?.rule : undefined;\n const [renaming, setRenaming] = useState<string | null>(null);\n const replaceInput = useRef<HTMLInputElement>(null);\n const [replacing, setReplacing] = useState<string | null>(null);\n const [over, setOver] = useState(false);\n\n const drop = {\n onDragOver: (e: DragEvent) => {\n if (off || !e.dataTransfer.types.includes(\"Files\")) return;\n e.preventDefault();\n setOver(true);\n },\n onDragLeave: (e: DragEvent) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setOver(false);\n },\n onDrop: (e: DragEvent) => {\n if (off || !e.dataTransfer.files.length) return;\n e.preventDefault();\n setOver(false);\n folder.add(e.dataTransfer.files);\n },\n };\n\n const removeSelected = async () => {\n const paths = [...selected];\n const ok = confirmRemove === false ? true : confirmRemove ? await confirmRemove(paths.length) : globalThis.confirm?.(t(\"folder.confirmRemove\", { count: paths.length })) ?? true;\n if (!ok) return;\n await folder.remove(paths).then(() => setSelected(new Set()), () => {});\n };\n\n const uploaded = queue.items.filter((i) => i.status === \"uploaded\").length;\n const picker = (big: boolean) => (\n <label\n className={cn(\n big\n ? \"flex cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border px-4 py-8 text-center text-sm text-muted-foreground transition-colors hover:border-primary hover:bg-primary/5\"\n : \"inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-sm font-medium transition-colors hover:bg-muted focus-within:ring-3 focus-within:ring-ring/50\",\n big && over && \"border-primary bg-primary/5\",\n off && \"pointer-events-none opacity-50\",\n )}\n data-ckui={big ? \"folder-drop\" : \"folder-add\"}\n >\n <HugeiconsIcon icon={ImageAdd01Icon} strokeWidth={1.75} className={big ? \"size-7\" : \"size-4\"} />\n {big ? <strong className=\"font-medium text-foreground\">{t(\"folder.drop\")}</strong> : t(\"folder.add\")}\n {big && rules.length > 0 && <span className=\"text-xs\">{describeRules(t, rules)}</span>}\n <input\n type=\"file\"\n multiple\n accept={accept}\n className=\"sr-only\"\n disabled={off}\n data-ckui=\"file\"\n onChange={(e) => {\n folder.add([...(e.target.files ?? [])]);\n e.target.value = \"\";\n }}\n />\n </label>\n );\n\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-3\", className)} data-ckui=\"media-folder\" data-over={over ? \"\" : undefined} {...drop}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\" data-ckui=\"folder-header\">\n <h3 className=\"text-sm font-semibold\">{label ?? t(\"folder.label\")}</h3>\n <div className=\"flex flex-wrap items-center gap-2\">\n {toolbar}\n {!empty && picker(false)}\n </div>\n </div>\n {empty && read.read && picker(true)}\n {empty && !read.read && read.loading && (\n <div className=\"flex justify-center py-6 text-muted-foreground\" data-ckui=\"folder-loading\">\n <HugeiconsIcon icon={Loading03Icon} strokeWidth={2} className=\"size-5 motion-safe:animate-spin\" />\n </div>\n )}\n\n {folder.refused.length > 0 && (\n <Alert variant=\"destructive\" role=\"alert\" data-ckui=\"folder-refused\">\n <HugeiconsIcon icon={Alert02Icon} strokeWidth={2} />\n <AlertDescription>\n <ul className=\"grid gap-0.5\">\n {folder.refused.map((r, i) => (\n <li key={i}>\n <span className=\"font-medium\">{r.file.name}</span>: {errorText(r.error)}\n </li>\n ))}\n </ul>\n <Button variant=\"outline\" size=\"xs\" className=\"mt-1 w-fit\" onClick={folder.dismiss}>\n {t(\"folder.dismiss\")}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n {queue.blocked && (\n <Alert variant=\"destructive\" role=\"alert\" data-ckui=\"folder-paused\">\n <HugeiconsIcon icon={Alert02Icon} strokeWidth={2} />\n <AlertTitle>{t(\"folder.paused\")}</AlertTitle>\n <AlertDescription>\n {errorText(queue.blocked)}\n <Button variant=\"outline\" size=\"xs\" className=\"mt-1 w-fit\" onClick={() => queue.start()}>\n {t(\"folder.resume\")}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n {folder.error && (\n <Alert variant=\"destructive\" role=\"alert\" data-ckui=\"folder-error\">\n <HugeiconsIcon icon={Alert02Icon} strokeWidth={2} />\n <AlertDescription>{errorText(folder.error)}</AlertDescription>\n </Alert>\n )}\n\n {selected.size > 0 && (\n <div className=\"flex flex-wrap items-center gap-2 rounded-md bg-muted px-3 py-1.5 text-sm\" data-ckui=\"folder-selection\">\n <span className=\"tabular-nums\">{t(\"folder.selected\", { count: selected.size })}</span>\n <Button variant=\"destructive\" size=\"xs\" disabled={off || folder.updating} onClick={() => void removeSelected()}>\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} />\n {t(\"folder.removeSelected\")}\n </Button>\n <Button variant=\"ghost\" size=\"xs\" onClick={() => setSelected(new Set())}>\n {t(\"folder.clearSelection\")}\n </Button>\n </div>\n )}\n\n {groups.map((g) =>\n g.files.length ? (\n <Group\n key={g.path}\n group={g}\n titled={groups.length > 1}\n folder={folder}\n disabled={off}\n selected={selected}\n onSelect={(path, on) => setSelected((s) => (on ? new Set([...s, path]) : new Set([...s].filter((x) => x !== path))))}\n renaming={renaming}\n onRename={setRenaming}\n onCrop={setCropping}\n onReplace={(path) => {\n setReplacing(path);\n replaceInput.current?.click();\n }}\n rowActions={rowActions}\n />\n ) : null,\n )}\n\n {queue.items.length > 0 && (\n <SortableList\n items={queue.items}\n id={(i) => i.id}\n name={(i) => i.file.name}\n disabled={auto || off}\n label={t(\"folder.label\")}\n onMove={(from, to) => queue.move(queue.items[from]!.id, to)}\n row={(i) => ({ className: \"flex min-h-11 items-center gap-2 px-1\", \"data-ckui\": \"queue-row\", \"data-status\": i.status })}\n >\n {(i, handle) => <QueueRow item={i} handle={auto ? null : handle} auto={auto} folder={folder} />}\n </SortableList>\n )}\n\n {((!auto && uploaded > 0) || (auto && folder.error && uploaded > 0)) && (\n <Button className=\"w-fit\" disabled={off || !queue.ready || folder.committing} onClick={() => void folder.commit().catch(() => {})} data-ckui=\"folder-commit\">\n {folder.committing && <HugeiconsIcon icon={Loading03Icon} strokeWidth={2} className=\"motion-safe:animate-spin\" />}\n {auto ? t(\"folder.commitAgain\") : t(\"folder.commit\", { count: queue.items.length })}\n </Button>\n )}\n {footer}\n\n <input\n ref={replaceInput}\n type=\"file\"\n accept={accept}\n className=\"sr-only\"\n tabIndex={-1}\n aria-hidden\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f && replacing) void folder.replace(replacing, f).catch(() => {});\n setReplacing(null);\n }}\n />\n <ImageCropDialog\n open={!!cropping}\n onOpenChange={(o) => !o && crop.status !== \"saving\" && setCropping(null)}\n source={\"source\" in crop ? (crop.source ?? null) : null}\n aspect={cropRule?.aspect ?? \"\"}\n aspects={cropRule?.aspect ? undefined : aspects}\n initialEdit={\"edit\" in crop ? crop.edit : null}\n minWidth={cropRule?.min_width}\n title={t(\"folder.crop\")}\n busy={crop.status === \"saving\"}\n error={crop.status === \"error\" ? errorText(crop.error) : undefined}\n onConfirm={(edit) => void crop.save(edit)}\n />\n </ContentKitUiRoot>\n );\n}\n\nfunction Group({\n group,\n titled,\n folder,\n disabled,\n selected,\n onSelect,\n renaming,\n onRename,\n onCrop,\n onReplace,\n rowActions,\n}: {\n group: FolderGroup;\n titled: boolean;\n folder: UseMediaFolder;\n disabled: boolean;\n selected: ReadonlySet<string>;\n onSelect: (path: string, on: boolean) => void;\n renaming: string | null;\n onRename: (path: string | null) => void;\n onCrop: (path: string) => void;\n onReplace: (path: string) => void;\n rowActions?: (file: FileInfo) => ReactNode;\n}) {\n const { t } = useMessages();\n const kind = ruleKind(group.rule);\n const title = t(`folder.kinds.${kind}`);\n const max = group.rule?.max;\n return (\n <section className=\"grid gap-1\" data-ckui=\"folder-group\" data-path={group.path}>\n {titled && (\n <h4 className=\"px-1 text-xs font-medium text-muted-foreground tabular-nums\">\n {title} · {max ? `${group.files.length} / ${max}` : group.files.length}\n </h4>\n )}\n <SortableList\n items={group.files}\n id={(f) => f.path}\n name={(f) => nameIn(group, f.path)}\n disabled={disabled || folder.updating}\n label={title}\n onMove={(from, to) => void folder.move(group.files[from]!.path, to).catch(() => {})}\n row={(f) => ({ className: \"flex min-h-12 items-center gap-2 px-1\", \"data-ckui\": \"upload-row\", \"data-path\": f.path })}\n >\n {(f, handle) => (\n <UploadRow\n file={f}\n group={group}\n handle={handle}\n folder={folder}\n disabled={disabled}\n selected={selected.has(f.path)}\n onSelect={(on) => onSelect(f.path, on)}\n renaming={renaming === f.path}\n onRename={onRename}\n onCrop={onCrop}\n onReplace={onReplace}\n actions={rowActions?.(f)}\n />\n )}\n </SortableList>\n </section>\n );\n}\n\nfunction Thumb({ kind, url, busy }: { kind: Kind; url?: string; busy?: boolean }) {\n return (\n <span className=\"relative flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted text-muted-foreground\" data-ckui=\"thumb\">\n {url ? <img src={url} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"size-full object-cover\" /> : <HugeiconsIcon icon={ICONS[kind]} strokeWidth={1.75} className=\"size-4.5\" />}\n {busy && !url && <span aria-hidden className=\"absolute inset-1.5 rounded-full border-2 border-current/25 border-t-current motion-safe:animate-spin\" />}\n </span>\n );\n}\n\nfunction UploadRow({\n file: f,\n group,\n handle,\n folder,\n disabled,\n selected,\n onSelect,\n renaming,\n onRename,\n onCrop,\n onReplace,\n actions,\n}: {\n file: FileInfo;\n group: FolderGroup;\n handle: ReactNode;\n folder: UseMediaFolder;\n disabled: boolean;\n selected: boolean;\n onSelect: (on: boolean) => void;\n renaming: boolean;\n onRename: (path: string | null) => void;\n onCrop: (path: string) => void;\n onReplace: (path: string) => void;\n actions?: ReactNode;\n}) {\n const { t, error: errorText } = useMessages();\n const kind = kindOf(f.type);\n const name = nameIn(group, f.path);\n const busy = isProcessing(f);\n const thumb = useMemo(() => thumbnailOf(folder.read.read, f), [folder.read.read, f]);\n const locked = disabled || folder.updating;\n const failure = f.failed ? errorText(failureError(f.failed)) : undefined;\n return (\n <>\n {handle}\n <input\n type=\"checkbox\"\n className=\"size-4 shrink-0 accent-primary\"\n checked={selected}\n disabled={disabled}\n aria-label={t(\"folder.select\", { name })}\n onChange={(e) => onSelect(e.target.checked)}\n />\n <Thumb kind={kind} url={thumb?.url} busy={busy} />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5\">\n {renaming ? (\n <RenameInput name={stem(name)} label={t(\"folder.renameLabel\", { name })} onDone={(to) => (to === null ? onRename(null) : folder.rename(f.path, to).then(() => onRename(null)))} />\n ) : (\n <span className=\"max-w-full truncate text-sm\" title={f.path} data-ckui=\"upload-name\">\n {name}\n </span>\n )}\n {f.edit && (\n <span className=\"shrink-0 rounded-sm border border-border px-1.5 text-[11px] text-muted-foreground\" data-ckui=\"edited\">\n {t(\"folder.edited\")}\n </span>\n )}\n {failure && (\n <span className=\"shrink-0 rounded-sm bg-destructive/10 px-1.5 text-[11px] font-medium text-destructive\" title={failure} data-ckui=\"failed\">\n {kind === \"video\" ? t(\"folder.failedVideo\") : t(\"folder.failed\")}\n </span>\n )}\n </div>\n {failure && <p className=\"text-xs text-destructive\">{failure}</p>}\n {busy && kind === \"video\" && <EncodeProgress progress={f.progress} className=\"max-w-sm text-xs\" />}\n {busy && kind !== \"video\" && <span className=\"text-xs text-muted-foreground\">{t(\"folder.processing\")}</span>}\n </div>\n <span className=\"flex shrink-0 items-center gap-0.5\" data-ckui=\"upload-actions\">\n {actions}\n {kind === \"image\" && (\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"folder.crop\")} title={t(\"folder.crop\")} disabled={locked || !!f.staged || !f.size} onClick={() => onCrop(f.path)}>\n <HugeiconsIcon icon={CropIcon} strokeWidth={2} />\n </Button>\n )}\n {!group.rule?.named && (\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"common.rename\")} title={t(\"common.rename\")} disabled={locked} onClick={() => onRename(f.path)}>\n <HugeiconsIcon icon={Edit02Icon} strokeWidth={2} />\n </Button>\n )}\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"common.replace\")} title={t(\"common.replace\")} disabled={locked} onClick={() => onReplace(f.path)}>\n <HugeiconsIcon icon={RepeatIcon} strokeWidth={2} />\n </Button>\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"common.remove\")} title={t(\"common.remove\")} disabled={locked} onClick={() => void folder.remove([f.path]).catch(() => {})}>\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} />\n </Button>\n </span>\n </>\n );\n}\n\nfunction RenameInput({ name, label, onDone }: { name: string; label: string; onDone: (to: string | null) => unknown }) {\n const [value, setValue] = useState(name);\n const done = useRef(false);\n const finish = (to: string | null) => {\n if (done.current) return;\n done.current = true;\n Promise.resolve(onDone(to && to.trim() && to.trim() !== name ? to.trim() : null)).catch(() => (done.current = false));\n };\n return (\n <input\n autoFocus\n aria-label={label}\n value={value}\n className=\"h-7 min-w-0 flex-1 rounded-md border border-input bg-background px-2 text-sm outline-none focus-visible:ring-3 focus-visible:ring-ring/50\"\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n finish(value);\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n finish(null);\n }\n }}\n onBlur={() => finish(value)}\n data-ckui=\"rename\"\n />\n );\n}\n\nfunction queueLabel(t: Translator[\"t\"], errorText: Translator[\"error\"], i: QueueItem, auto: boolean): string {\n if (i.status === \"failed\") return errorText(i.error);\n if (i.status === \"uploading\") return progressLabel(t, i.progress);\n if (i.status === \"uploaded\") {\n const p = i.processing;\n if (p?.failed) return errorText(failureError(p.failed));\n if (i.unattached && !i.processed) return p?.progress && p.progress.phase !== \"queued\" ? t(\"folder.processingPercent\", { percent: Math.round(p.progress.percent) }) : t(\"folder.processing\");\n return auto ? t(\"folder.adding\") : t(\"folder.uploaded\");\n }\n return i.status === \"committed\" ? t(\"folder.adding\") : t(\"folder.queued\");\n}\n\nfunction QueueRow({ item: i, handle, auto, folder }: { item: QueueItem; handle: ReactNode; auto: boolean; folder: UseMediaFolder }) {\n const { t, error: errorText } = useMessages();\n const { queue } = folder;\n const kind = kindOf(i.file.type);\n const label = queueLabel(t, errorText, i, auto);\n const value = i.status === \"uploading\" ? progressValue(i.progress) : i.unattached && !i.processed && i.processing?.progress ? i.processing.progress.percent : null;\n return (\n <>\n {handle}\n <Thumb kind={kind} busy={i.status === \"uploading\" || (!!i.unattached && !i.processed)} />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <span className=\"truncate text-sm\" title={i.path}>\n {i.file.name}\n </span>\n {value !== null && <Progress value={value} aria-label={label} className=\"max-w-sm\" />}\n <span className={cn(\"text-xs\", i.status === \"failed\" ? \"text-destructive\" : \"text-muted-foreground\")} data-ckui=\"queue-status\">\n {label}\n </span>\n </div>\n <span className=\"flex shrink-0 items-center gap-0.5\">\n {i.status === \"failed\" && (\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"common.retry\")} title={t(\"common.retry\")} onClick={() => queue.retry(i.id)}>\n <HugeiconsIcon icon={Refresh01Icon} strokeWidth={2} />\n </Button>\n )}\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"common.remove\")} title={t(\"common.remove\")} onClick={() => queue.remove(i.id)}>\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} />\n </Button>\n </span>\n </>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"media-folder-editor.js","names":[],"sources":["../../src/components/media-folder-editor.tsx"],"sourcesContent":["import {\n Alert02Icon,\n CropIcon,\n Delete02Icon,\n Edit02Icon,\n File01Icon,\n Image01Icon,\n ImageAdd01Icon,\n Loading03Icon,\n MusicNote01Icon,\n Refresh01Icon,\n RepeatIcon,\n Video01Icon,\n} from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon, type IconSvgElement } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useEffect, useImperativeHandle, useMemo, useRef, useState, type DragEvent, type ReactNode, type Ref } from \"react\";\nimport type { ContentKitUiAppearance } from \"../appearance.js\";\nimport type { AspectRatio } from \"../client/aspect.js\";\nimport { failureError } from \"../client/errors.js\";\nimport { isAudioType, isImageType, isSubtitleType, isVideoType } from \"../client/gallery.js\";\nimport type { FileInfo, RefBody, UploadRule } from \"../client/generated/wire.js\";\nimport { stem } from \"../client/media/client.js\";\nimport type { QueueItem } from \"../client/media/queue.js\";\nimport { acceptOf, ratioLabel } from \"../client/media/rules.js\";\nimport { isProcessing } from \"../client/media/windows.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport { megabytes, type Translator } from \"../i18n/messages.js\";\nimport { useEditorCrop } from \"../react/crop.js\";\nimport { nameIn, thumbnailOf, useMediaFolder, type FolderGroup, type MediaFolderOptions, type UseMediaFolder } from \"../react/folder.js\";\nimport { ContentKitUiRoot } from \"../scope.js\";\nimport { Alert, AlertDescription, AlertTitle } from \"#ckui/ui/alert\";\nimport { Button } from \"#ckui/ui/button\";\nimport { Progress } from \"#ckui/ui/progress\";\nimport { EncodeProgress } from \"./encode-progress.js\";\nimport { ImageCropDialog } from \"./image-crop-dialog.js\";\nimport { progressLabel, progressValue } from \"./progress.js\";\nimport { SortableList } from \"./sortable-list.js\";\n\nexport interface MediaFolderEditorHandle {\n /** Stops every upload and empties the queue (before deleting a draft). */\n discard: () => void;\n /** Commits the uploaded files (commit=\"manual\"). */\n commit: () => Promise<FileInfo[]>;\n /** The folder's state and updates. */\n folder: UseMediaFolder;\n}\n\nexport interface MediaFolderEditorViewProps {\n ref?: Ref<MediaFolderEditorHandle>;\n /** Heading; default \"Media\". */\n label?: ReactNode;\n /** Beside \"Add files\" in the header, e.g. a ZIP import. */\n toolbar?: ReactNode;\n /** Extra actions on each upload, beside crop, rename, replace and remove. */\n rowActions?: (file: FileInfo) => ReactNode;\n /** Below the lists, e.g. what readers without access see. */\n footer?: ReactNode;\n /** Shapes offered when cropping an image whose path has no preset shape; \"\" is the original's. */\n aspects?: readonly AspectRatio[];\n /** Asked before removing selected uploads; default the browser's confirm; false skips it. */\n confirmRemove?: ((count: number) => boolean | Promise<boolean>) | false;\n disabled?: boolean;\n className?: string;\n appearance?: ContentKitUiAppearance;\n}\n\n/**\n * Either the item, and the editor opens its folder with the options; or a\n * folder the host opened (`folder={useMediaFolder(ref, options)}`), whose\n * state the host reads and renders with.\n */\nexport type MediaFolderEditorProps = MediaFolderEditorViewProps &\n (\n | ({\n /** The item whose folder this edits. */\n item: RefBody;\n folder?: never;\n } & MediaFolderOptions)\n | ({\n /** The host's folder (useMediaFolder); its ref may be null until the item exists. */\n folder: UseMediaFolder;\n item?: never;\n } & { [K in keyof MediaFolderOptions]?: never })\n );\n\nconst ASPECTS: readonly AspectRatio[] = [\"\", \"1:1\", \"4:5\", \"16:9\"];\n\ntype Kind = \"image\" | \"video\" | \"audio\" | \"subtitle\" | \"file\";\n\nfunction kindOf(type: string | undefined): Kind {\n if (isImageType(type)) return \"image\";\n if (isVideoType(type)) return \"video\";\n if (isAudioType(type)) return \"audio\";\n if (isSubtitleType(type)) return \"subtitle\";\n return \"file\";\n}\n\nconst ICONS: Record<Kind, IconSvgElement> = { image: Image01Icon, video: Video01Icon, audio: MusicNote01Icon, subtitle: File01Icon, file: File01Icon };\n\n/** What a rule takes: one kind when every type is of it, else files. */\nfunction ruleKind(rule: UploadRule | undefined): Kind {\n const kinds = new Set((rule?.types ?? []).map(kindOf));\n return kinds.size === 1 ? [...kinds][0]! : \"file\";\n}\n\n/** The rules as a sentence for the drop zone: sizes, caps and video shapes. */\nexport function describeRules(t: Translator[\"t\"], rules: readonly UploadRule[]): string {\n const parts: string[] = [];\n for (const r of rules) {\n if (!r.types.length) continue;\n const what = t(`folder.kinds.${ruleKind(r)}`);\n const size = r.max_bytes > 0 ? t(\"folder.limitSize\", { what, size: megabytes(r.max_bytes) }) : what;\n parts.push(r.max ? `${size}, ${t(\"folder.limitMax\", { max: r.max })}` : size);\n if (r.min_aspect && r.max_aspect) parts.push(t(\"folder.limitAspect\", { min: ratioLabel(r.min_aspect), max: ratioLabel(r.max_aspect) }));\n }\n return parts.join(\" · \");\n}\n\n/**\n * An item's folder for its editors: drop or pick files (screened against the\n * kind's upload rules), a queue with progress, retry and remove, and each\n * upload path's uploads as a sortable list with thumbnail, name, edited\n * badge, encode progress and failures; crop, rename, replace and bulk\n * remove. commit=\"manual\" adds the queue with \"Add N\"; \"auto\" commits as\n * files finish (a draft; discard() through the ref).\n */\nexport function MediaFolderEditor(p: MediaFolderEditorProps) {\n // Editor state (selection, crop, rename) belongs to one item.\n if (p.folder) {\n const { folder, ...view } = p;\n return <FolderView key={folder.ref ? `${folder.ref.kind}/${folder.ref.id}` : \"\"} folder={folder} {...view} />;\n }\n // The queue belongs to one item: another item gets a fresh editor.\n return <OwnFolder key={`${p.item.kind}/${p.item.id}`} {...p} />;\n}\n\nfunction OwnFolder(p: MediaFolderEditorViewProps & MediaFolderOptions & { item: RefBody }) {\n const { item, ref, label, toolbar, rowActions, footer, aspects, confirmRemove, disabled, className, appearance, ...options } = p;\n const folder = useMediaFolder(item, options);\n return <FolderView {...{ folder, ref, label, toolbar, rowActions, footer, aspects, confirmRemove, disabled, className, appearance }} />;\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\n\nfunction FolderView(p: MediaFolderEditorViewProps & { folder: UseMediaFolder }) {\n const { folder, ref, label, toolbar, rowActions, footer, aspects = ASPECTS, confirmRemove, disabled, className, appearance } = p;\n const { t, error: errorText } = useMessages();\n const { groups, queue, read, options } = folder;\n useImperativeHandle(ref, () => ({ discard: folder.discard, commit: folder.commit, folder }), [folder]);\n const auto = options.commit === \"auto\";\n const rules = groups.flatMap((g) => (g.rule ? [g.rule] : []));\n const accept = acceptOf(rules);\n const count = folder.uploads.length + queue.items.length + folder.waiting.length;\n const empty = count === 0;\n const off = !!disabled;\n\n const [selected, setSelected] = useState<ReadonlySet<string>>(new Set());\n useEffect(() => {\n const paths = new Set(folder.uploads.map((f) => f.path));\n setSelected((s) => ([...s].every((x) => paths.has(x)) ? s : new Set([...s].filter((x) => paths.has(x)))));\n }, [folder.uploads]);\n const [cropping, setCropping] = useState<string | null>(null);\n const crop = useEditorCrop(folder.ref ?? NO_ITEM, cropping, { client: options.client, onError: options.onError, onSaved: () => setCropping(null) });\n const cropRule = cropping ? groups.find((g) => g.files.some((f) => f.path === cropping))?.rule : undefined;\n const [renaming, setRenaming] = useState<string | null>(null);\n const replaceInput = useRef<HTMLInputElement>(null);\n const [replacing, setReplacing] = useState<string | null>(null);\n const [over, setOver] = useState(false);\n\n const drop = {\n onDragOver: (e: DragEvent) => {\n if (off || !e.dataTransfer.types.includes(\"Files\")) return;\n e.preventDefault();\n setOver(true);\n },\n onDragLeave: (e: DragEvent) => {\n if (!e.currentTarget.contains(e.relatedTarget as Node | null)) setOver(false);\n },\n onDrop: (e: DragEvent) => {\n if (off || !e.dataTransfer.files.length) return;\n e.preventDefault();\n setOver(false);\n folder.add(e.dataTransfer.files);\n },\n };\n\n const removeSelected = async () => {\n const paths = [...selected];\n const ok = confirmRemove === false ? true : confirmRemove ? await confirmRemove(paths.length) : globalThis.confirm?.(t(\"folder.confirmRemove\", { count: paths.length })) ?? true;\n if (!ok) return;\n await folder.remove(paths).then(() => setSelected(new Set()), () => {});\n };\n\n const uploaded = queue.items.filter((i) => i.status === \"uploaded\").length;\n const picker = (big: boolean) => (\n <label\n className={cn(\n big\n ? \"flex cursor-pointer flex-col items-center justify-center gap-2 rounded-lg border border-dashed border-border px-4 py-8 text-center text-sm text-muted-foreground transition-colors hover:border-primary hover:bg-primary/5\"\n : \"inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background px-2.5 text-sm font-medium transition-colors hover:bg-muted focus-within:ring-3 focus-within:ring-ring/50\",\n big && over && \"border-primary bg-primary/5\",\n off && \"pointer-events-none opacity-50\",\n )}\n data-ckui={big ? \"folder-drop\" : \"folder-add\"}\n >\n <HugeiconsIcon icon={ImageAdd01Icon} strokeWidth={1.75} className={big ? \"size-7\" : \"size-4\"} />\n {big ? <strong className=\"font-medium text-foreground\">{t(\"folder.drop\")}</strong> : t(\"folder.add\")}\n {big && rules.length > 0 && <span className=\"text-xs\">{describeRules(t, rules)}</span>}\n <input\n type=\"file\"\n multiple\n accept={accept}\n className=\"sr-only\"\n disabled={off}\n data-ckui=\"file\"\n onChange={(e) => {\n folder.add([...(e.target.files ?? [])]);\n e.target.value = \"\";\n }}\n />\n </label>\n );\n\n return (\n <ContentKitUiRoot appearance={appearance} className={cn(\"grid gap-3\", className)} data-ckui=\"media-folder\" data-over={over ? \"\" : undefined} {...drop}>\n <div className=\"flex flex-wrap items-center justify-between gap-2\" data-ckui=\"folder-header\">\n <h3 className=\"text-sm font-semibold\">{label ?? t(\"folder.label\")}</h3>\n <div className=\"flex flex-wrap items-center gap-2\">\n {toolbar}\n {!empty && picker(false)}\n </div>\n </div>\n {empty && (read.read || !folder.ref) && picker(true)}\n {empty && !read.read && read.loading && (\n <div className=\"flex justify-center py-6 text-muted-foreground\" data-ckui=\"folder-loading\">\n <HugeiconsIcon icon={Loading03Icon} strokeWidth={2} className=\"size-5 motion-safe:animate-spin\" />\n </div>\n )}\n\n {folder.refused.length > 0 && (\n <Alert variant=\"destructive\" role=\"alert\" data-ckui=\"folder-refused\">\n <HugeiconsIcon icon={Alert02Icon} strokeWidth={2} />\n <AlertDescription>\n <ul className=\"grid gap-0.5\">\n {folder.refused.map((r, i) => (\n <li key={i}>\n <span className=\"font-medium\">{r.file.name}</span>: {errorText(r.error)}\n </li>\n ))}\n </ul>\n <Button variant=\"outline\" size=\"xs\" className=\"mt-1 w-fit\" onClick={folder.dismiss}>\n {t(\"folder.dismiss\")}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n {queue.blocked && (\n <Alert variant=\"destructive\" role=\"alert\" data-ckui=\"folder-paused\">\n <HugeiconsIcon icon={Alert02Icon} strokeWidth={2} />\n <AlertTitle>{t(\"folder.paused\")}</AlertTitle>\n <AlertDescription>\n {errorText(queue.blocked)}\n <Button variant=\"outline\" size=\"xs\" className=\"mt-1 w-fit\" onClick={() => queue.start()}>\n {t(\"folder.resume\")}\n </Button>\n </AlertDescription>\n </Alert>\n )}\n {folder.error && (\n <Alert variant=\"destructive\" role=\"alert\" data-ckui=\"folder-error\">\n <HugeiconsIcon icon={Alert02Icon} strokeWidth={2} />\n <AlertDescription>{errorText(folder.error)}</AlertDescription>\n </Alert>\n )}\n\n {selected.size > 0 && (\n <div className=\"flex flex-wrap items-center gap-2 rounded-md bg-muted px-3 py-1.5 text-sm\" data-ckui=\"folder-selection\">\n <span className=\"tabular-nums\">{t(\"folder.selected\", { count: selected.size })}</span>\n <Button variant=\"destructive\" size=\"xs\" disabled={off || folder.updating} onClick={() => void removeSelected()}>\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} />\n {t(\"folder.removeSelected\")}\n </Button>\n <Button variant=\"ghost\" size=\"xs\" onClick={() => setSelected(new Set())}>\n {t(\"folder.clearSelection\")}\n </Button>\n </div>\n )}\n\n {groups.map((g) =>\n g.files.length ? (\n <Group\n key={g.path}\n group={g}\n titled={groups.length > 1}\n folder={folder}\n disabled={off}\n selected={selected}\n onSelect={(path, on) => setSelected((s) => (on ? new Set([...s, path]) : new Set([...s].filter((x) => x !== path))))}\n renaming={renaming}\n onRename={setRenaming}\n onCrop={setCropping}\n onReplace={(path) => {\n setReplacing(path);\n replaceInput.current?.click();\n }}\n rowActions={rowActions}\n />\n ) : null,\n )}\n\n {folder.waiting.length > 0 && (\n <ul className=\"grid\" aria-label={t(\"folder.label\")}>\n {folder.waiting.map((f, i) => (\n <li key={i} className=\"flex min-h-11 items-center gap-2 px-1\" data-ckui=\"queue-row\" data-status=\"waiting\">\n <Thumb kind={kindOf(f.type)} />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <span className=\"truncate text-sm\">{f.name}</span>\n <span className=\"text-xs text-muted-foreground\" data-ckui=\"queue-status\">\n {t(\"folder.queued\")}\n </span>\n </div>\n </li>\n ))}\n </ul>\n )}\n\n {queue.items.length > 0 && (\n <SortableList\n items={queue.items}\n id={(i) => i.id}\n name={(i) => i.file.name}\n disabled={auto || off}\n label={t(\"folder.label\")}\n onMove={(from, to) => queue.move(queue.items[from]!.id, to)}\n row={(i) => ({ className: \"flex min-h-11 items-center gap-2 px-1\", \"data-ckui\": \"queue-row\", \"data-status\": i.status })}\n >\n {(i, handle) => <QueueRow item={i} handle={auto ? null : handle} auto={auto} folder={folder} />}\n </SortableList>\n )}\n\n {((!auto && uploaded > 0) || (auto && folder.error && uploaded > 0)) && (\n <Button className=\"w-fit\" disabled={off || !queue.ready || folder.committing} onClick={() => void folder.commit().catch(() => {})} data-ckui=\"folder-commit\">\n {folder.committing && <HugeiconsIcon icon={Loading03Icon} strokeWidth={2} className=\"motion-safe:animate-spin\" />}\n {auto ? t(\"folder.commitAgain\") : t(\"folder.commit\", { count: queue.items.filter((i) => i.status !== \"committed\").length })}\n </Button>\n )}\n {footer}\n\n <input\n ref={replaceInput}\n type=\"file\"\n accept={accept}\n className=\"sr-only\"\n tabIndex={-1}\n aria-hidden\n onChange={(e) => {\n const f = e.target.files?.[0];\n e.target.value = \"\";\n if (f && replacing) void folder.replace(replacing, f).catch(() => {});\n setReplacing(null);\n }}\n />\n <ImageCropDialog\n open={!!cropping}\n onOpenChange={(o) => !o && crop.status !== \"saving\" && setCropping(null)}\n source={\"source\" in crop ? (crop.source ?? null) : null}\n aspect={cropRule?.aspect ?? \"\"}\n aspects={cropRule?.aspect ? undefined : aspects}\n initialEdit={\"edit\" in crop ? crop.edit : null}\n minWidth={cropRule?.min_width}\n title={t(\"folder.crop\")}\n busy={crop.status === \"saving\"}\n error={crop.status === \"error\" ? errorText(crop.error) : undefined}\n onConfirm={(edit) => void crop.save(edit)}\n />\n </ContentKitUiRoot>\n );\n}\n\nfunction Group({\n group,\n titled,\n folder,\n disabled,\n selected,\n onSelect,\n renaming,\n onRename,\n onCrop,\n onReplace,\n rowActions,\n}: {\n group: FolderGroup;\n titled: boolean;\n folder: UseMediaFolder;\n disabled: boolean;\n selected: ReadonlySet<string>;\n onSelect: (path: string, on: boolean) => void;\n renaming: string | null;\n onRename: (path: string | null) => void;\n onCrop: (path: string) => void;\n onReplace: (path: string) => void;\n rowActions?: (file: FileInfo) => ReactNode;\n}) {\n const { t } = useMessages();\n const kind = ruleKind(group.rule);\n const title = t(`folder.kinds.${kind}`);\n const max = group.rule?.max;\n return (\n <section className=\"grid gap-1\" data-ckui=\"folder-group\" data-path={group.path}>\n {titled && (\n <h4 className=\"px-1 text-xs font-medium text-muted-foreground tabular-nums\">\n {title} · {max ? `${group.files.length} / ${max}` : group.files.length}\n </h4>\n )}\n <SortableList\n items={group.files}\n id={(f) => f.path}\n name={(f) => nameIn(group, f.path)}\n disabled={disabled || folder.updating}\n label={title}\n onMove={(from, to) => void folder.move(group.files[from]!.path, to).catch(() => {})}\n row={(f) => ({ className: \"flex min-h-12 items-center gap-2 px-1\", \"data-ckui\": \"upload-row\", \"data-path\": f.path })}\n >\n {(f, handle) => (\n <UploadRow\n file={f}\n group={group}\n handle={handle}\n folder={folder}\n disabled={disabled}\n selected={selected.has(f.path)}\n onSelect={(on) => onSelect(f.path, on)}\n renaming={renaming === f.path}\n onRename={onRename}\n onCrop={onCrop}\n onReplace={onReplace}\n actions={rowActions?.(f)}\n />\n )}\n </SortableList>\n </section>\n );\n}\n\nfunction Thumb({ kind, url, busy }: { kind: Kind; url?: string; busy?: boolean }) {\n return (\n <span className=\"relative flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted text-muted-foreground\" data-ckui=\"thumb\">\n {url ? <img src={url} alt=\"\" loading=\"lazy\" decoding=\"async\" className=\"size-full object-cover\" /> : <HugeiconsIcon icon={ICONS[kind]} strokeWidth={1.75} className=\"size-4.5\" />}\n {busy && !url && <span aria-hidden className=\"absolute inset-1.5 rounded-full border-2 border-current/25 border-t-current motion-safe:animate-spin\" />}\n </span>\n );\n}\n\nfunction UploadRow({\n file: f,\n group,\n handle,\n folder,\n disabled,\n selected,\n onSelect,\n renaming,\n onRename,\n onCrop,\n onReplace,\n actions,\n}: {\n file: FileInfo;\n group: FolderGroup;\n handle: ReactNode;\n folder: UseMediaFolder;\n disabled: boolean;\n selected: boolean;\n onSelect: (on: boolean) => void;\n renaming: boolean;\n onRename: (path: string | null) => void;\n onCrop: (path: string) => void;\n onReplace: (path: string) => void;\n actions?: ReactNode;\n}) {\n const { t, error: errorText } = useMessages();\n const kind = kindOf(f.type);\n const name = nameIn(group, f.path);\n const busy = isProcessing(f);\n const thumb = useMemo(() => thumbnailOf(folder.read.read, f), [folder.read.read, f]);\n const locked = disabled || folder.updating;\n const failure = f.failed ? errorText(failureError(f.failed)) : undefined;\n return (\n <>\n {handle}\n <input\n type=\"checkbox\"\n className=\"size-4 shrink-0 accent-primary\"\n checked={selected}\n disabled={disabled}\n aria-label={t(\"folder.select\", { name })}\n onChange={(e) => onSelect(e.target.checked)}\n />\n <Thumb kind={kind} url={thumb?.url} busy={busy} />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5\">\n {renaming ? (\n <RenameInput name={stem(name)} label={t(\"folder.renameLabel\", { name })} onDone={(to) => (to === null ? onRename(null) : folder.rename(f.path, to).then(() => onRename(null)))} />\n ) : (\n <span className=\"max-w-full truncate text-sm\" title={f.path} data-ckui=\"upload-name\">\n {name}\n </span>\n )}\n {f.edit && (\n <span className=\"shrink-0 rounded-sm border border-border px-1.5 text-[11px] text-muted-foreground\" data-ckui=\"edited\">\n {t(\"folder.edited\")}\n </span>\n )}\n {failure && (\n <span className=\"shrink-0 rounded-sm bg-destructive/10 px-1.5 text-[11px] font-medium text-destructive\" title={failure} data-ckui=\"failed\">\n {kind === \"video\" ? t(\"folder.failedVideo\") : t(\"folder.failed\")}\n </span>\n )}\n </div>\n {failure && <p className=\"text-xs text-destructive\">{failure}</p>}\n {busy && kind === \"video\" && <EncodeProgress progress={f.progress} className=\"max-w-sm text-xs\" />}\n {busy && kind !== \"video\" && <span className=\"text-xs text-muted-foreground\">{t(\"folder.processing\")}</span>}\n </div>\n <span className=\"flex shrink-0 items-center gap-0.5\" data-ckui=\"upload-actions\">\n {actions}\n {kind === \"image\" && (\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"folder.crop\")} title={t(\"folder.crop\")} disabled={locked || !!f.staged || !f.size} onClick={() => onCrop(f.path)}>\n <HugeiconsIcon icon={CropIcon} strokeWidth={2} />\n </Button>\n )}\n {!group.rule?.named && (\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"common.rename\")} title={t(\"common.rename\")} disabled={locked} onClick={() => onRename(f.path)}>\n <HugeiconsIcon icon={Edit02Icon} strokeWidth={2} />\n </Button>\n )}\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"common.replace\")} title={t(\"common.replace\")} disabled={locked} onClick={() => onReplace(f.path)}>\n <HugeiconsIcon icon={RepeatIcon} strokeWidth={2} />\n </Button>\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"common.remove\")} title={t(\"common.remove\")} disabled={locked} onClick={() => void folder.remove([f.path]).catch(() => {})}>\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} />\n </Button>\n </span>\n </>\n );\n}\n\nfunction RenameInput({ name, label, onDone }: { name: string; label: string; onDone: (to: string | null) => unknown }) {\n const [value, setValue] = useState(name);\n const done = useRef(false);\n const finish = (to: string | null) => {\n if (done.current) return;\n done.current = true;\n Promise.resolve(onDone(to && to.trim() && to.trim() !== name ? to.trim() : null)).catch(() => (done.current = false));\n };\n return (\n <input\n autoFocus\n aria-label={label}\n value={value}\n className=\"h-7 min-w-0 flex-1 rounded-md border border-input bg-background px-2 text-sm outline-none focus-visible:ring-3 focus-visible:ring-ring/50\"\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n finish(value);\n } else if (e.key === \"Escape\") {\n e.preventDefault();\n finish(null);\n }\n }}\n onBlur={() => finish(value)}\n data-ckui=\"rename\"\n />\n );\n}\n\nfunction queueLabel(t: Translator[\"t\"], errorText: Translator[\"error\"], i: QueueItem, auto: boolean): string {\n if (i.status === \"failed\") return errorText(i.error);\n if (i.status === \"uploading\") return progressLabel(t, i.progress);\n if (i.status === \"uploaded\") {\n const p = i.processing;\n if (p?.failed) return errorText(failureError(p.failed));\n if (i.unattached && !i.processed) return p?.progress && p.progress.phase !== \"queued\" ? t(\"folder.processingPercent\", { percent: Math.round(p.progress.percent) }) : t(\"folder.processing\");\n return auto ? t(\"folder.adding\") : t(\"folder.uploaded\");\n }\n return i.status === \"committed\" ? t(\"folder.adding\") : t(\"folder.queued\");\n}\n\nfunction QueueRow({ item: i, handle, auto, folder }: { item: QueueItem; handle: ReactNode; auto: boolean; folder: UseMediaFolder }) {\n const { t, error: errorText } = useMessages();\n const { queue } = folder;\n const kind = kindOf(i.file.type);\n const label = queueLabel(t, errorText, i, auto);\n const value = i.status === \"uploading\" ? progressValue(i.progress) : i.unattached && !i.processed && i.processing?.progress ? i.processing.progress.percent : null;\n return (\n <>\n {handle}\n <Thumb kind={kind} busy={i.status === \"uploading\" || (!!i.unattached && !i.processed)} />\n <div className=\"grid min-w-0 flex-1 gap-1\">\n <span className=\"truncate text-sm\" title={i.path}>\n {i.file.name}\n </span>\n {value !== null && <Progress value={value} aria-label={label} className=\"max-w-sm\" />}\n <span className={cn(\"text-xs\", i.status === \"failed\" ? \"text-destructive\" : \"text-muted-foreground\")} data-ckui=\"queue-status\">\n {label}\n </span>\n </div>\n <span className=\"flex shrink-0 items-center gap-0.5\">\n {i.status === \"failed\" && (\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"common.retry\")} title={t(\"common.retry\")} onClick={() => queue.retry(i.id)}>\n <HugeiconsIcon icon={Refresh01Icon} strokeWidth={2} />\n </Button>\n )}\n <Button variant=\"ghost\" size=\"icon-sm\" aria-label={t(\"common.remove\")} title={t(\"common.remove\")} onClick={() => queue.remove(i.id)}>\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} />\n </Button>\n </span>\n </>\n );\n}\n"],"mappings":"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"}
@@ -1,213 +1,215 @@
1
1
  import { ratio as e } from "../client/aspect.js";
2
- import { useMessages as t } from "../i18n/context.js";
3
- import { ContentKitUiRoot as n } from "../scope.js";
4
- import { Button as r } from "../ui/button.js";
5
- import { SlotImage as i } from "./slot-image.js";
6
- import { SlotEditError as a, SlotEditor as o, useSlotEditor as s } from "./slot-editor.js";
7
- import { useState as c } from "react";
8
- import { Fragment as l, jsx as u, jsxs as d } from "react/jsx-runtime";
9
- import { cn as f } from "cn";
10
- import { Camera01Icon as p, CropIcon as m, Delete02Icon as h, ImageUpload01Icon as g, Loading03Icon as _ } from "@hugeicons/core-free-icons";
11
- import { HugeiconsIcon as v } from "@hugeicons/react";
2
+ import { presetFor as t } from "../client/media/rules.js";
3
+ import { useMessages as n } from "../i18n/context.js";
4
+ import { ContentKitUiRoot as r } from "../scope.js";
5
+ import { usePresets as i } from "../react/public.js";
6
+ import { Button as a } from "../ui/button.js";
7
+ import { SlotImage as o } from "./slot-image.js";
8
+ import { SlotEditError as s, SlotEditor as c, useSlotEditor as l } from "./slot-editor.js";
9
+ import { useState as u } from "react";
10
+ import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
11
+ import { cn as m } from "cn";
12
+ import { Camera01Icon as h, CropIcon as g, Delete02Icon as _, ImageUpload01Icon as v, Loading03Icon as y } from "@hugeicons/core-free-icons";
13
+ import { HugeiconsIcon as b } from "@hugeicons/react";
12
14
  //#region src/components/slot-upload.tsx
13
- function y(e) {
14
- return /* @__PURE__ */ u(x, {
15
+ function x(e) {
16
+ return /* @__PURE__ */ f(C, {
15
17
  ...e,
16
18
  variant: "avatar"
17
19
  });
18
20
  }
19
- function b(e) {
20
- return /* @__PURE__ */ u(x, {
21
+ function S(e) {
22
+ return /* @__PURE__ */ f(C, {
21
23
  ...e,
22
24
  variant: "cover"
23
25
  });
24
26
  }
25
- function x({ variant: e, ...t }) {
26
- let r = t.path ?? e;
27
- return /* @__PURE__ */ u(n, {
28
- className: f("text-sm", t.className),
27
+ function C({ variant: e, ...n }) {
28
+ let a = n.path ?? e, { presets: o } = i({ client: n.client }), s = t(o, n.item.kind, a, n.image?.preset);
29
+ return /* @__PURE__ */ f(r, {
30
+ className: m("text-sm", n.className),
29
31
  "data-ckui": e === "avatar" ? "avatar-upload" : "cover-upload",
30
- children: /* @__PURE__ */ u(o, {
31
- item: t.item,
32
- path: r,
33
- image: t.image,
34
- client: t.client,
35
- read: t.read,
36
- onChange: t.onChange,
37
- aspect: t.aspect || t.image?.aspect || (e === "avatar" ? "1:1" : "3:1"),
38
- minWidth: t.minWidth,
39
- animation: t.animation,
40
- targetWidth: t.targetWidth ?? (e === "avatar" ? 512 : 3e3),
32
+ children: /* @__PURE__ */ f(c, {
33
+ item: n.item,
34
+ path: a,
35
+ image: n.image,
36
+ client: n.client,
37
+ read: n.read,
38
+ onChange: n.onChange,
39
+ aspect: n.aspect || n.image?.aspect || s?.aspect || (e === "avatar" ? "1:1" : "3:1"),
40
+ minWidth: n.minWidth,
41
+ animation: n.animation,
42
+ targetWidth: n.targetWidth ?? (e === "avatar" ? 512 : 3e3),
41
43
  round: e === "avatar",
42
- accept: t.accept,
43
- disabled: t.disabled,
44
- removable: t.removable ?? e === "avatar",
45
- decode: t.decode,
46
- children: /* @__PURE__ */ u(S, {
47
- ...t,
44
+ accept: n.accept,
45
+ disabled: n.disabled,
46
+ removable: n.removable ?? e === "avatar",
47
+ decode: n.decode,
48
+ children: /* @__PURE__ */ f(w, {
49
+ ...n,
48
50
  variant: e
49
51
  })
50
52
  })
51
53
  });
52
54
  }
53
- function S({ variant: n, ...o }) {
54
- let { t: y } = t(), b = s(), { crop: x, has: S, busy: w, disabled: T } = b, E = {
55
- preset: o.image?.preset ?? n,
56
- aspect: b.image.aspect,
57
- renditions: b.image.renditions
58
- }, [D, O] = c(!1), k = x.aspect, A = o.targetWidth ?? (n === "avatar" ? 512 : 3e3), j = (e) => {
55
+ function w({ variant: t, ...r }) {
56
+ let { t: i } = n(), c = l(), { crop: x, has: S, busy: C, disabled: w } = c, E = {
57
+ preset: r.image?.preset ?? t,
58
+ aspect: c.image.aspect,
59
+ renditions: c.image.renditions
60
+ }, [D, O] = u(!1), k = x.aspect, A = r.targetWidth ?? (t === "avatar" ? 512 : 3e3), j = (e) => {
59
61
  e.preventDefault(), O(!1);
60
62
  let t = [...e.dataTransfer.files].find((e) => e.type.startsWith("image/")) ?? e.dataTransfer.files[0];
61
- t && b.pick(t);
62
- }, M = o.hint ?? (n === "avatar" ? y("avatar.hint", { size: A }) : y("cover.hint", {
63
+ t && c.pick(t);
64
+ }, M = r.hint ?? (t === "avatar" ? i("avatar.hint", { size: A }) : i("cover.hint", {
63
65
  width: A,
64
66
  height: Math.round(A / (e(k) ?? 3))
65
- })), N = o.label ?? y(n === "avatar" ? "avatar.label" : "cover.label"), P = (e) => /* @__PURE__ */ d(l, { children: [
66
- /* @__PURE__ */ d(r, {
67
+ })), N = r.label ?? i(t === "avatar" ? "avatar.label" : "cover.label"), P = (e) => /* @__PURE__ */ p(d, { children: [
68
+ /* @__PURE__ */ p(a, {
67
69
  variant: e && S ? "secondary" : "outline",
68
70
  size: "sm",
69
- disabled: T,
70
- onClick: b.choose,
71
+ disabled: w,
72
+ onClick: c.choose,
71
73
  "data-ckui": "change",
72
- children: [/* @__PURE__ */ u(v, {
73
- icon: w ? _ : p,
74
+ children: [/* @__PURE__ */ f(b, {
75
+ icon: C ? y : h,
74
76
  strokeWidth: 2,
75
77
  "data-icon": "inline-start",
76
- className: w ? "animate-spin" : void 0
77
- }), y(S ? "common.change" : "common.upload")]
78
+ className: C ? "animate-spin" : void 0
79
+ }), i(S ? "common.change" : "common.upload")]
78
80
  }),
79
- S && x.canRecrop && /* @__PURE__ */ d(r, {
81
+ S && x.canRecrop && /* @__PURE__ */ p(a, {
80
82
  variant: e ? "secondary" : "ghost",
81
83
  size: "sm",
82
- disabled: T,
83
- onClick: b.recrop,
84
+ disabled: w,
85
+ onClick: c.recrop,
84
86
  "data-ckui": "edit-crop",
85
- children: [/* @__PURE__ */ u(v, {
86
- icon: m,
87
+ children: [/* @__PURE__ */ f(b, {
88
+ icon: g,
87
89
  strokeWidth: 2,
88
90
  "data-icon": "inline-start"
89
- }), y("common.editCrop")]
91
+ }), i("common.editCrop")]
90
92
  }),
91
- S && b.removable && /* @__PURE__ */ d(r, {
93
+ S && c.removable && /* @__PURE__ */ p(a, {
92
94
  variant: e ? "secondary" : "ghost",
93
95
  size: "sm",
94
- disabled: T,
95
- onClick: b.remove,
96
+ disabled: w,
97
+ onClick: c.remove,
96
98
  "data-ckui": "remove",
97
- children: [/* @__PURE__ */ u(v, {
98
- icon: h,
99
+ children: [/* @__PURE__ */ f(b, {
100
+ icon: _,
99
101
  strokeWidth: 2,
100
102
  "data-icon": "inline-start"
101
- }), y("common.remove")]
103
+ }), i("common.remove")]
102
104
  })
103
105
  ] });
104
- return n === "avatar" ? /* @__PURE__ */ d("div", {
106
+ return t === "avatar" ? /* @__PURE__ */ p("div", {
105
107
  className: "flex items-center gap-4",
106
- children: [/* @__PURE__ */ d("div", {
108
+ children: [/* @__PURE__ */ p("div", {
107
109
  className: "relative size-20 shrink-0",
108
- children: [/* @__PURE__ */ u(i, {
110
+ children: [/* @__PURE__ */ f(o, {
109
111
  image: S ? E : null,
110
112
  round: !0,
111
- emptyLabel: y("avatar.empty"),
113
+ emptyLabel: i("avatar.empty"),
112
114
  className: "size-full ring-1 ring-foreground/10"
113
- }), w && /* @__PURE__ */ u(C, {
115
+ }), C && /* @__PURE__ */ f(T, {
114
116
  round: !0,
115
- label: y(x.status === "decoding" ? "progress.decoding" : "common.saving")
117
+ label: i(x.status === "decoding" ? "progress.decoding" : "common.saving")
116
118
  })]
117
- }), /* @__PURE__ */ d("div", {
119
+ }), /* @__PURE__ */ p("div", {
118
120
  className: "grid min-w-0 gap-1",
119
121
  children: [
120
- /* @__PURE__ */ u("div", {
122
+ /* @__PURE__ */ f("div", {
121
123
  className: "font-medium",
122
124
  children: N
123
125
  }),
124
- /* @__PURE__ */ u("div", {
126
+ /* @__PURE__ */ f("div", {
125
127
  className: "text-xs text-muted-foreground",
126
128
  children: M
127
129
  }),
128
- /* @__PURE__ */ u("div", {
130
+ /* @__PURE__ */ f("div", {
129
131
  className: "mt-1.5 flex flex-wrap gap-2",
130
132
  children: P(!1)
131
133
  }),
132
- /* @__PURE__ */ u(a, {})
134
+ /* @__PURE__ */ f(s, {})
133
135
  ]
134
136
  })]
135
- }) : /* @__PURE__ */ d("div", {
137
+ }) : /* @__PURE__ */ p("div", {
136
138
  className: "grid gap-2",
137
139
  children: [
138
- /* @__PURE__ */ d("div", {
139
- className: f("group/cover relative overflow-hidden rounded-xl ring-1 ring-foreground/10 transition-shadow", D && "ring-2 ring-ring"),
140
+ /* @__PURE__ */ p("div", {
141
+ className: m("group/cover relative overflow-hidden rounded-xl ring-1 ring-foreground/10 transition-shadow", D && "ring-2 ring-ring"),
140
142
  onDragOver: (e) => {
141
- e.preventDefault(), T || O(!0);
143
+ e.preventDefault(), w || O(!0);
142
144
  },
143
145
  onDragLeave: () => O(!1),
144
146
  onDrop: j,
145
147
  children: [
146
- /* @__PURE__ */ u(i, {
148
+ /* @__PURE__ */ f(o, {
147
149
  image: S ? E : null,
148
150
  aspect: k,
149
151
  className: "w-full rounded-none",
150
- fallback: /* @__PURE__ */ d("div", {
151
- "aria-label": y("cover.empty"),
152
+ fallback: /* @__PURE__ */ p("div", {
153
+ "aria-label": i("cover.empty"),
152
154
  role: "group",
153
155
  className: "absolute inset-0 flex flex-col items-center justify-center gap-1.5 bg-[repeating-linear-gradient(135deg,transparent_0_10px,color-mix(in_oklch,var(--ckui-foreground)_3%,transparent)_10px_20px)] p-4 text-center",
154
156
  children: [
155
- /* @__PURE__ */ u(v, {
156
- icon: g,
157
+ /* @__PURE__ */ f(b, {
158
+ icon: v,
157
159
  strokeWidth: 1.5,
158
160
  className: "size-7 text-muted-foreground"
159
161
  }),
160
- /* @__PURE__ */ u("span", {
162
+ /* @__PURE__ */ f("span", {
161
163
  className: "hidden text-xs text-muted-foreground sm:block",
162
- children: y("cover.drop")
164
+ children: i("cover.drop")
163
165
  }),
164
- /* @__PURE__ */ u("div", {
166
+ /* @__PURE__ */ f("div", {
165
167
  className: "mt-1 flex gap-2",
166
168
  children: P(!1)
167
169
  })
168
170
  ]
169
171
  })
170
172
  }),
171
- w && /* @__PURE__ */ u(C, { label: y(x.status === "decoding" ? "progress.decoding" : "common.saving") }),
172
- S && /* @__PURE__ */ u("div", {
173
+ C && /* @__PURE__ */ f(T, { label: i(x.status === "decoding" ? "progress.decoding" : "common.saving") }),
174
+ S && /* @__PURE__ */ f("div", {
173
175
  className: "absolute right-2 bottom-2 flex gap-2 max-sm:hidden [&_button]:bg-background/80 [&_button]:backdrop-blur-sm",
174
176
  children: P(!0)
175
177
  })
176
178
  ]
177
179
  }),
178
- /* @__PURE__ */ d("div", {
180
+ /* @__PURE__ */ p("div", {
179
181
  className: "flex flex-wrap items-center justify-between gap-x-3 gap-y-1.5",
180
- children: [/* @__PURE__ */ d("div", {
182
+ children: [/* @__PURE__ */ p("div", {
181
183
  className: "grid min-w-0 gap-0.5 sm:flex sm:flex-1 sm:items-baseline sm:justify-between sm:gap-3",
182
- children: [/* @__PURE__ */ u("div", {
184
+ children: [/* @__PURE__ */ f("div", {
183
185
  className: "font-medium",
184
186
  children: N
185
- }), /* @__PURE__ */ u("div", {
187
+ }), /* @__PURE__ */ f("div", {
186
188
  className: "text-xs text-muted-foreground sm:truncate",
187
189
  children: M
188
190
  })]
189
- }), S && /* @__PURE__ */ u("div", {
191
+ }), S && /* @__PURE__ */ f("div", {
190
192
  className: "flex gap-2 sm:hidden",
191
193
  children: P(!1)
192
194
  })]
193
195
  }),
194
- /* @__PURE__ */ u(a, {})
196
+ /* @__PURE__ */ f(s, {})
195
197
  ]
196
198
  });
197
199
  }
198
- function C({ round: e, label: t }) {
199
- return /* @__PURE__ */ u("div", {
200
+ function T({ round: e, label: t }) {
201
+ return /* @__PURE__ */ f("div", {
200
202
  role: "status",
201
203
  "aria-label": t,
202
- className: f("absolute inset-0 flex items-center justify-center bg-background/60 backdrop-blur-[1px]", e && "rounded-full"),
203
- children: /* @__PURE__ */ u(v, {
204
- icon: _,
204
+ className: m("absolute inset-0 flex items-center justify-center bg-background/60 backdrop-blur-[1px]", e && "rounded-full"),
205
+ children: /* @__PURE__ */ f(b, {
206
+ icon: y,
205
207
  strokeWidth: 2,
206
208
  className: "size-5 animate-spin text-foreground"
207
209
  })
208
210
  });
209
211
  }
210
212
  //#endregion
211
- export { y as AvatarUpload, b as CoverUpload };
213
+ export { x as AvatarUpload, S as CoverUpload };
212
214
 
213
215
  //# sourceMappingURL=slot-upload.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"slot-upload.js","names":[],"sources":["../../src/components/slot-upload.tsx"],"sourcesContent":["import { ratio, type AspectRatio } from \"../client/aspect.js\";\nimport { Camera01Icon, CropIcon, Delete02Icon, ImageUpload01Icon, Loading03Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useState, type DragEvent, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport type { CropSource } from \"../client/image.js\";\nimport { ContentKitUiRoot } from \"../scope.js\";\nimport type { PublicPreset } from \"../client/public.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { SlotEditError, SlotEditor, useSlotEditor } from \"./slot-editor.js\";\nimport { SlotImage } from \"./slot-image.js\";\n\nexport interface SlotUploadProps {\n /** The item that owns the upload. */\n item: RefBody;\n /** The upload path; default \"avatar\" or \"cover\". */\n path?: string;\n /** The public preset showing it. */\n image?: PublicPreset | null;\n client?: ContentKitClient;\n /** An editor read of the item from the host; otherwise fetched. */\n read?: ReadResult | null;\n /** Called with the processed upload after every save, null after a removal. */\n onChange?: (file: FileInfo | null) => void;\n /** The output's \"W:H\"; default the preset's aspect, else \"1:1\" (avatar) or \"3:1\" (cover). */\n aspect?: AspectRatio;\n /** The narrowest edit the server accepts (the preset's Image.MinWidth). */\n minWidth?: number;\n /** \"reject\" refuses animated images before uploading. */\n animation?: \"reject\";\n /** Crops narrower than this many source pixels get a sharpness warning; default 512 (avatar) or 3000 (cover). */\n targetWidth?: number;\n /** `accept` of the file input. Default \"image/*\". */\n accept?: string;\n disabled?: boolean;\n /** Offers Remove once set; default true for avatars, false for covers. */\n removable?: boolean;\n label?: ReactNode;\n hint?: ReactNode;\n className?: string;\n /** Replaces decodeImage (tests). */\n decode?: (file: File) => Promise<CropSource>;\n}\n\ntype Variant = \"avatar\" | \"cover\";\n\n/** Round 1:1 avatar field: image, label, hint, Change / Edit crop, crop dialog, progress, errors. For custom layouts use SlotEditor. */\nexport function AvatarUpload(p: SlotUploadProps) {\n return <SlotUpload {...p} variant=\"avatar\" />;\n}\n\n/** Wide cover field at the preset's aspect (e.g. 3:1), with drop-to-upload. For custom layouts use SlotEditor. */\nexport function CoverUpload(p: SlotUploadProps) {\n return <SlotUpload {...p} variant=\"cover\" />;\n}\n\nfunction SlotUpload({ variant, ...p }: SlotUploadProps & { variant: Variant }) {\n const path = p.path ?? variant;\n return (\n <ContentKitUiRoot className={cn(\"text-sm\", p.className)} data-ckui={variant === \"avatar\" ? \"avatar-upload\" : \"cover-upload\"}>\n <SlotEditor\n item={p.item}\n path={path}\n image={p.image}\n client={p.client}\n read={p.read}\n onChange={p.onChange}\n aspect={p.aspect || p.image?.aspect || (variant === \"avatar\" ? \"1:1\" : \"3:1\")}\n minWidth={p.minWidth}\n animation={p.animation}\n targetWidth={p.targetWidth ?? (variant === \"avatar\" ? 512 : 3000)}\n round={variant === \"avatar\"}\n accept={p.accept}\n disabled={p.disabled}\n removable={p.removable ?? variant === \"avatar\"}\n decode={p.decode}\n >\n <SlotUploadLayout {...p} variant={variant} />\n </SlotEditor>\n </ContentKitUiRoot>\n );\n}\n\nfunction SlotUploadLayout({ variant, ...p }: SlotUploadProps & { variant: Variant }) {\n const { t } = useMessages();\n const s = useSlotEditor();\n const { crop, has, busy, disabled } = s;\n const image = { preset: p.image?.preset ?? variant, aspect: s.image.aspect, renditions: s.image.renditions };\n const [dragging, setDragging] = useState(false);\n const aspect = crop.aspect;\n const target = p.targetWidth ?? (variant === \"avatar\" ? 512 : 3000);\n const onDrop = (e: DragEvent) => {\n e.preventDefault();\n setDragging(false);\n const f = [...e.dataTransfer.files].find((f) => f.type.startsWith(\"image/\")) ?? e.dataTransfer.files[0];\n if (f) s.pick(f);\n };\n\n const hint =\n p.hint ?? (variant === \"avatar\" ? t(\"avatar.hint\", { size: target }) : t(\"cover.hint\", { width: target, height: Math.round(target / (ratio(aspect) ?? 3)) }));\n const label = p.label ?? t(variant === \"avatar\" ? \"avatar.label\" : \"cover.label\");\n\n // overlay: buttons on top of the cover image; otherwise a plain row.\n const actions = (overlay: boolean) => (\n <>\n <Button variant={overlay && has ? \"secondary\" : \"outline\"} size=\"sm\" disabled={disabled} onClick={s.choose} data-ckui=\"change\">\n <HugeiconsIcon icon={busy ? Loading03Icon : Camera01Icon} strokeWidth={2} data-icon=\"inline-start\" className={busy ? \"animate-spin\" : undefined} />\n {has ? t(\"common.change\") : t(\"common.upload\")}\n </Button>\n {has && crop.canRecrop && (\n <Button variant={overlay ? \"secondary\" : \"ghost\"} size=\"sm\" disabled={disabled} onClick={s.recrop} data-ckui=\"edit-crop\">\n <HugeiconsIcon icon={CropIcon} strokeWidth={2} data-icon=\"inline-start\" />\n {t(\"common.editCrop\")}\n </Button>\n )}\n {has && s.removable && (\n <Button variant={overlay ? \"secondary\" : \"ghost\"} size=\"sm\" disabled={disabled} onClick={s.remove} data-ckui=\"remove\">\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} data-icon=\"inline-start\" />\n {t(\"common.remove\")}\n </Button>\n )}\n </>\n );\n\n return variant === \"avatar\" ? (\n <div className=\"flex items-center gap-4\">\n <div className=\"relative size-20 shrink-0\">\n <SlotImage image={has ? image : null} round emptyLabel={t(\"avatar.empty\")} className=\"size-full ring-1 ring-foreground/10\" />\n {busy && <BusyOverlay round label={t(crop.status === \"decoding\" ? \"progress.decoding\" : \"common.saving\")} />}\n </div>\n <div className=\"grid min-w-0 gap-1\">\n <div className=\"font-medium\">{label}</div>\n <div className=\"text-xs text-muted-foreground\">{hint}</div>\n <div className=\"mt-1.5 flex flex-wrap gap-2\">{actions(false)}</div>\n <SlotEditError />\n </div>\n </div>\n ) : (\n <div className=\"grid gap-2\">\n <div\n className={cn(\n \"group/cover relative overflow-hidden rounded-xl ring-1 ring-foreground/10 transition-shadow\",\n dragging && \"ring-2 ring-ring\",\n )}\n onDragOver={(e) => {\n e.preventDefault();\n if (!disabled) setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n >\n <SlotImage\n image={has ? image : null}\n aspect={aspect}\n className=\"w-full rounded-none\"\n fallback={\n <div aria-label={t(\"cover.empty\")} role=\"group\" className=\"absolute inset-0 flex flex-col items-center justify-center gap-1.5 bg-[repeating-linear-gradient(135deg,transparent_0_10px,color-mix(in_oklch,var(--ckui-foreground)_3%,transparent)_10px_20px)] p-4 text-center\">\n <HugeiconsIcon icon={ImageUpload01Icon} strokeWidth={1.5} className=\"size-7 text-muted-foreground\" />\n <span className=\"hidden text-xs text-muted-foreground sm:block\">{t(\"cover.drop\")}</span>\n <div className=\"mt-1 flex gap-2\">{actions(false)}</div>\n </div>\n }\n />\n {busy && <BusyOverlay label={t(crop.status === \"decoding\" ? \"progress.decoding\" : \"common.saving\")} />}\n {has && (\n <div className=\"absolute right-2 bottom-2 flex gap-2 max-sm:hidden [&_button]:bg-background/80 [&_button]:backdrop-blur-sm\">\n {actions(true)}\n </div>\n )}\n </div>\n <div className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-1.5\">\n <div className=\"grid min-w-0 gap-0.5 sm:flex sm:flex-1 sm:items-baseline sm:justify-between sm:gap-3\">\n <div className=\"font-medium\">{label}</div>\n <div className=\"text-xs text-muted-foreground sm:truncate\">{hint}</div>\n </div>\n {has && <div className=\"flex gap-2 sm:hidden\">{actions(false)}</div>}\n </div>\n <SlotEditError />\n </div>\n );\n}\n\nfunction BusyOverlay({ round, label }: { round?: boolean; label: string }) {\n return (\n <div role=\"status\" aria-label={label} className={cn(\"absolute inset-0 flex items-center justify-center bg-background/60 backdrop-blur-[1px]\", round && \"rounded-full\")}>\n <HugeiconsIcon icon={Loading03Icon} strokeWidth={2} className=\"size-5 animate-spin text-foreground\" />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"slot-upload.js","names":[],"sources":["../../src/components/slot-upload.tsx"],"sourcesContent":["import { ratio, type AspectRatio } from \"../client/aspect.js\";\nimport { Camera01Icon, CropIcon, Delete02Icon, ImageUpload01Icon, Loading03Icon } from \"@hugeicons/core-free-icons\";\nimport { HugeiconsIcon } from \"@hugeicons/react\";\nimport { cn } from \"cn\";\nimport { useState, type DragEvent, type ReactNode } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { useMessages } from \"../i18n/context.js\";\nimport type { CropSource } from \"../client/image.js\";\nimport { ContentKitUiRoot } from \"../scope.js\";\nimport type { PublicPreset } from \"../client/public.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { presetFor } from \"../client/media/rules.js\";\nimport { usePresets } from \"../react/public.js\";\nimport { Button } from \"#ckui/ui/button\";\nimport { SlotEditError, SlotEditor, useSlotEditor } from \"./slot-editor.js\";\nimport { SlotImage } from \"./slot-image.js\";\n\nexport interface SlotUploadProps {\n /** The item that owns the upload. */\n item: RefBody;\n /** The upload path; default \"avatar\" or \"cover\". */\n path?: string;\n /** The public preset showing it. */\n image?: PublicPreset | null;\n client?: ContentKitClient;\n /** An editor read of the item from the host; otherwise fetched. */\n read?: ReadResult | null;\n /** Called with the processed upload after every save, null after a removal. */\n onChange?: (file: FileInfo | null) => void;\n /** The output's \"W:H\"; default the preset's aspect, else \"1:1\" (avatar) or \"3:1\" (cover). */\n aspect?: AspectRatio;\n /** The narrowest edit the server accepts (the preset's Image.MinWidth). */\n minWidth?: number;\n /** \"reject\" refuses animated images before uploading. */\n animation?: \"reject\";\n /** Crops narrower than this many source pixels get a sharpness warning; default 512 (avatar) or 3000 (cover). */\n targetWidth?: number;\n /** `accept` of the file input. Default \"image/*\". */\n accept?: string;\n disabled?: boolean;\n /** Offers Remove once set; default true for avatars, false for covers. */\n removable?: boolean;\n label?: ReactNode;\n hint?: ReactNode;\n className?: string;\n /** Replaces decodeImage (tests). */\n decode?: (file: File) => Promise<CropSource>;\n}\n\ntype Variant = \"avatar\" | \"cover\";\n\n/** Round 1:1 avatar field: image, label, hint, Change / Edit crop, crop dialog, progress, errors. For custom layouts use SlotEditor. */\nexport function AvatarUpload(p: SlotUploadProps) {\n return <SlotUpload {...p} variant=\"avatar\" />;\n}\n\n/** Wide cover field at the preset's aspect (e.g. 3:1), with drop-to-upload. For custom layouts use SlotEditor. */\nexport function CoverUpload(p: SlotUploadProps) {\n return <SlotUpload {...p} variant=\"cover\" />;\n}\n\nfunction SlotUpload({ variant, ...p }: SlotUploadProps & { variant: Variant }) {\n const path = p.path ?? variant;\n const { presets } = usePresets({ client: p.client });\n const rule = presetFor(presets, p.item.kind, path, p.image?.preset);\n return (\n <ContentKitUiRoot className={cn(\"text-sm\", p.className)} data-ckui={variant === \"avatar\" ? \"avatar-upload\" : \"cover-upload\"}>\n <SlotEditor\n item={p.item}\n path={path}\n image={p.image}\n client={p.client}\n read={p.read}\n onChange={p.onChange}\n aspect={p.aspect || p.image?.aspect || rule?.aspect || (variant === \"avatar\" ? \"1:1\" : \"3:1\")}\n minWidth={p.minWidth}\n animation={p.animation}\n targetWidth={p.targetWidth ?? (variant === \"avatar\" ? 512 : 3000)}\n round={variant === \"avatar\"}\n accept={p.accept}\n disabled={p.disabled}\n removable={p.removable ?? variant === \"avatar\"}\n decode={p.decode}\n >\n <SlotUploadLayout {...p} variant={variant} />\n </SlotEditor>\n </ContentKitUiRoot>\n );\n}\n\nfunction SlotUploadLayout({ variant, ...p }: SlotUploadProps & { variant: Variant }) {\n const { t } = useMessages();\n const s = useSlotEditor();\n const { crop, has, busy, disabled } = s;\n const image = { preset: p.image?.preset ?? variant, aspect: s.image.aspect, renditions: s.image.renditions };\n const [dragging, setDragging] = useState(false);\n const aspect = crop.aspect;\n const target = p.targetWidth ?? (variant === \"avatar\" ? 512 : 3000);\n const onDrop = (e: DragEvent) => {\n e.preventDefault();\n setDragging(false);\n const f = [...e.dataTransfer.files].find((f) => f.type.startsWith(\"image/\")) ?? e.dataTransfer.files[0];\n if (f) s.pick(f);\n };\n\n const hint =\n p.hint ?? (variant === \"avatar\" ? t(\"avatar.hint\", { size: target }) : t(\"cover.hint\", { width: target, height: Math.round(target / (ratio(aspect) ?? 3)) }));\n const label = p.label ?? t(variant === \"avatar\" ? \"avatar.label\" : \"cover.label\");\n\n // overlay: buttons on top of the cover image; otherwise a plain row.\n const actions = (overlay: boolean) => (\n <>\n <Button variant={overlay && has ? \"secondary\" : \"outline\"} size=\"sm\" disabled={disabled} onClick={s.choose} data-ckui=\"change\">\n <HugeiconsIcon icon={busy ? Loading03Icon : Camera01Icon} strokeWidth={2} data-icon=\"inline-start\" className={busy ? \"animate-spin\" : undefined} />\n {has ? t(\"common.change\") : t(\"common.upload\")}\n </Button>\n {has && crop.canRecrop && (\n <Button variant={overlay ? \"secondary\" : \"ghost\"} size=\"sm\" disabled={disabled} onClick={s.recrop} data-ckui=\"edit-crop\">\n <HugeiconsIcon icon={CropIcon} strokeWidth={2} data-icon=\"inline-start\" />\n {t(\"common.editCrop\")}\n </Button>\n )}\n {has && s.removable && (\n <Button variant={overlay ? \"secondary\" : \"ghost\"} size=\"sm\" disabled={disabled} onClick={s.remove} data-ckui=\"remove\">\n <HugeiconsIcon icon={Delete02Icon} strokeWidth={2} data-icon=\"inline-start\" />\n {t(\"common.remove\")}\n </Button>\n )}\n </>\n );\n\n return variant === \"avatar\" ? (\n <div className=\"flex items-center gap-4\">\n <div className=\"relative size-20 shrink-0\">\n <SlotImage image={has ? image : null} round emptyLabel={t(\"avatar.empty\")} className=\"size-full ring-1 ring-foreground/10\" />\n {busy && <BusyOverlay round label={t(crop.status === \"decoding\" ? \"progress.decoding\" : \"common.saving\")} />}\n </div>\n <div className=\"grid min-w-0 gap-1\">\n <div className=\"font-medium\">{label}</div>\n <div className=\"text-xs text-muted-foreground\">{hint}</div>\n <div className=\"mt-1.5 flex flex-wrap gap-2\">{actions(false)}</div>\n <SlotEditError />\n </div>\n </div>\n ) : (\n <div className=\"grid gap-2\">\n <div\n className={cn(\n \"group/cover relative overflow-hidden rounded-xl ring-1 ring-foreground/10 transition-shadow\",\n dragging && \"ring-2 ring-ring\",\n )}\n onDragOver={(e) => {\n e.preventDefault();\n if (!disabled) setDragging(true);\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n >\n <SlotImage\n image={has ? image : null}\n aspect={aspect}\n className=\"w-full rounded-none\"\n fallback={\n <div aria-label={t(\"cover.empty\")} role=\"group\" className=\"absolute inset-0 flex flex-col items-center justify-center gap-1.5 bg-[repeating-linear-gradient(135deg,transparent_0_10px,color-mix(in_oklch,var(--ckui-foreground)_3%,transparent)_10px_20px)] p-4 text-center\">\n <HugeiconsIcon icon={ImageUpload01Icon} strokeWidth={1.5} className=\"size-7 text-muted-foreground\" />\n <span className=\"hidden text-xs text-muted-foreground sm:block\">{t(\"cover.drop\")}</span>\n <div className=\"mt-1 flex gap-2\">{actions(false)}</div>\n </div>\n }\n />\n {busy && <BusyOverlay label={t(crop.status === \"decoding\" ? \"progress.decoding\" : \"common.saving\")} />}\n {has && (\n <div className=\"absolute right-2 bottom-2 flex gap-2 max-sm:hidden [&_button]:bg-background/80 [&_button]:backdrop-blur-sm\">\n {actions(true)}\n </div>\n )}\n </div>\n <div className=\"flex flex-wrap items-center justify-between gap-x-3 gap-y-1.5\">\n <div className=\"grid min-w-0 gap-0.5 sm:flex sm:flex-1 sm:items-baseline sm:justify-between sm:gap-3\">\n <div className=\"font-medium\">{label}</div>\n <div className=\"text-xs text-muted-foreground sm:truncate\">{hint}</div>\n </div>\n {has && <div className=\"flex gap-2 sm:hidden\">{actions(false)}</div>}\n </div>\n <SlotEditError />\n </div>\n );\n}\n\nfunction BusyOverlay({ round, label }: { round?: boolean; label: string }) {\n return (\n <div role=\"status\" aria-label={label} className={cn(\"absolute inset-0 flex items-center justify-center bg-background/60 backdrop-blur-[1px]\", round && \"rounded-full\")}>\n <HugeiconsIcon icon={Loading03Icon} strokeWidth={2} className=\"size-5 animate-spin text-foreground\" />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -232,6 +232,7 @@ var t = {
232
232
  checksum_mismatch: "Die Datei hat sich während des Uploads geändert. Bitte versuche es erneut.",
233
233
  rate_limited: "Zu viele Uploads. Versuche es in {seconds} s erneut.",
234
234
  unavailable: "Der Medienspeicher ist nicht erreichbar. Bitte versuche es gleich noch einmal.",
235
+ upgrade_required: "Dieses Element wird gerade aktualisiert. Bitte versuche es später noch einmal.",
235
236
  internal_error: "Auf unserer Seite ist etwas schiefgelaufen. Bitte versuche es erneut.",
236
237
  allowedTypes: "Verwende {allowed}.",
237
238
  tooLargeBy: "Diese Datei ist {size} groß; das Limit liegt bei {max}.",