@openrails/contentkit-ui 0.68.0 → 0.68.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -10
- package/dist/client/client.d.ts +4 -2
- package/dist/client/client.js +1 -0
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/bans.js +7 -7
- package/dist/client/content/bans.js.map +1 -1
- package/dist/client/content/comments.js +16 -14
- package/dist/client/content/comments.js.map +1 -1
- package/dist/client/content/favorites.js +5 -5
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/moderation.js +5 -5
- package/dist/client/content/moderation.js.map +1 -1
- package/dist/client/content/polls.js +34 -31
- package/dist/client/content/polls.js.map +1 -1
- package/dist/client/content/posts.js +21 -20
- package/dist/client/content/posts.js.map +1 -1
- package/dist/client/content/reactions.js +4 -4
- package/dist/client/content/reactions.js.map +1 -1
- package/dist/client/http.d.ts +6 -2
- package/dist/client/http.js +11 -3
- package/dist/client/http.js.map +1 -1
- package/dist/client/media/client.js +24 -24
- package/dist/client/media/client.js.map +1 -1
- package/dist/client/media/rules.d.ts +2 -0
- package/dist/client/media/rules.js +6 -2
- package/dist/client/media/rules.js.map +1 -1
- package/dist/client/taxonomy.js +2 -2
- package/dist/client/taxonomy.js.map +1 -1
- package/dist/components/media-folder-editor.d.ts +17 -3
- package/dist/components/media-folder-editor.js +146 -99
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/slot-upload.js +102 -100
- package/dist/components/slot-upload.js.map +1 -1
- package/dist/react/canonical.d.ts +2 -2
- package/dist/react/canonical.js +2 -12
- package/dist/react/canonical.js.map +1 -1
- package/dist/react/comments.js +35 -27
- package/dist/react/comments.js.map +1 -1
- package/dist/react/context.d.ts +7 -0
- package/dist/react/context.js +20 -15
- package/dist/react/context.js.map +1 -1
- package/dist/react/engagement.js +2 -2
- package/dist/react/engagement.js.map +1 -1
- package/dist/react/folder.d.ts +29 -9
- package/dist/react/folder.js +170 -113
- package/dist/react/folder.js.map +1 -1
- package/dist/react/polls.js +3 -3
- package/dist/react/polls.js.map +1 -1
- package/dist/react/posts.js +2 -2
- package/dist/react/posts.js.map +1 -1
- package/dist/react/provider.d.ts +7 -3
- package/dist/react/provider.js +34 -20
- package/dist/react/provider.js.map +1 -1
- package/dist/react/public.js +11 -11
- package/dist/react/public.js.map +1 -1
- package/dist/react/read.js +93 -92
- package/dist/react/read.js.map +1 -1
- package/dist/react/resources.d.ts +6 -3
- package/dist/react/resources.js +40 -40
- package/dist/react/resources.js.map +1 -1
- package/dist/react/slot.d.ts +4 -2
- package/dist/react/slot.js +96 -92
- package/dist/react/slot.js.map +1 -1
- package/dist/react/store.d.ts +1 -1
- package/dist/react/store.js +21 -20
- package/dist/react/store.js.map +1 -1
- package/dist/react/upload.d.ts +3 -2
- package/dist/react/upload.js +20 -15
- package/dist/react/upload.js.map +1 -1
- package/dist/react/use-resource.d.ts +1 -0
- package/dist/react/use-resource.js +38 -30
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +15 -15
- package/package.json +7 -5
package/dist/react/slot.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slot.js","names":[],"sources":["../../src/react/slot.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { ratio, type AspectRatio } from \"../client/aspect.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { centeredCrop, constrainCrop, editedSize, rotation, sameEdit, type Size } from \"../client/crop.js\";\nimport { ContentKitError, toContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { decodeImage, isAnimatedImage, type CropSource } from \"../client/image.js\";\nimport { samePath, stem, type Progress } from \"../client/media/client.js\";\nimport { publicRenditions, type PublicPreset } from \"../client/public.js\";\nimport type { Rendition } from \"../client/rendition.js\";\nimport { useContentKitClient } from \"./context.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { withUpload } from \"./store.js\";\n\nexport interface SlotImageOptions {\n ref: RefBody;\n /** The upload path, e.g. \"cover\" or \"avatar\". */\n path: string;\n /** The public preset showing the upload. */\n image?: PublicPreset | null;\n /** An editor read of the item the host already has; skips the fetch. */\n read?: ReadResult | null;\n /** Overrides the provider's client; without one only a supplied read shows. */\n client?: ContentKitClient | null;\n}\n\nexport interface UseSlotImage {\n /** The upload at path as an editor reads it; null when there is none. */\n file: FileInfo | null;\n /** The current published files; empty until the worker publishes them. */\n renditions: Rendition[];\n /** \"W:H\": the preset's, else \"1:1\". */\n aspect: AspectRatio;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Replaces the upload after a save without refetching. */\n set: (file: FileInfo | null) => void;\n}\n\n/** An upload path's state (an editor read) and its public image. */\nexport function useSlotImage(o: SlotImageOptions): UseSlotImage {\n const r = useMediaRead(o.ref, { editor: true, prefix: stem(o.path), read: o.read, client: o.client });\n const read = r.read;\n const file = read?.files.find((f) => f.upload && samePath(f.path, o.path)) ?? null;\n const { path } = o;\n const update = r.set;\n const set = useCallback((f: FileInfo | null) => update(withUpload(read, path, f)), [read, path, update]);\n const renditions = useMemo(() => {\n if (!read) return publicRenditions(o.image);\n const published = read.public?.find((p) => samePath(p.from, path) && (!o.image || p.preset === o.image.preset));\n return publicRenditions(published);\n }, [read, o.image, path]);\n return { file, renditions, aspect: o.image?.aspect || \"1:1\", loading: r.loading, error: r.error, reload: r.reload, set };\n}\n\nexport type SlotCropMode = \"new\" | \"recrop\";\n\n/** edit: null is the centred crop at the aspect, unrotated. */\nexport type SlotCropState =\n | { status: \"idle\" }\n | { status: \"decoding\" }\n | { status: \"cropping\"; source: CropSource; edit: Edit | null; mode: SlotCropMode }\n | { status: \"saving\"; source: CropSource; edit: Edit | null; mode: SlotCropMode; progress?: Progress; rendering?: boolean }\n | { status: \"done\"; file: FileInfo }\n | { status: \"error\"; error: ContentKitError; source?: CropSource; edit?: Edit | null; mode?: SlotCropMode };\n\nexport interface SlotCropOptions {\n ref: RefBody;\n /** The upload path, e.g. \"avatar\". */\n path: string;\n /** The current upload (useSlotImage's file): its path, size and edit for recrop(). */\n file?: FileInfo | null;\n /** The output's \"W:H\"; default \"1:1\". */\n aspect?: AspectRatio;\n /** \"reject\": refuse animated images before uploading (the preset's Image.Animation). */\n animation?: \"reject\";\n onSaved?: (file: FileInfo) => void;\n /** Replaces decodeImage (tests, custom decoders). */\n decode?: (file: File) => Promise<CropSource>;\n /** How long save() waits for the worker to render. Default 120 s. */\n renderTimeout?: number;\n /** Every failed decode or save (aborts excepted); the state shows it too. */\n onError?: (e: ContentKitError, operation: \"slot.decode\" | \"slot.save\") => void;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport type UseSlotCrop = SlotCropState & {\n aspect: AspectRatio;\n /** The output's size in source pixels (crop, then rotation) while a source is open. */\n cropped?: Size;\n /** Opens a picked file for cropping. */\n pick: (file: File) => Promise<void>;\n /** Re-crops the committed upload; false when there is none. */\n canRecrop: boolean;\n recrop: () => Promise<void>;\n setEdit: (e: Edit | null) => void;\n /** Uploads the file and puts it with the edit (new), or edits the upload (recrop); then waits for the render. */\n save: (edit?: Edit | null) => Promise<FileInfo | undefined>;\n /** Aborts a save and closes the source. */\n cancel: () => void;\n};\n\n/** The edit's output size: its crop (or the centred crop at aspect), turned. */\nexport function editOutput(source: Size, edit: Edit | null | undefined, shape: AspectRatio): Size {\n const aspect = ratio(shape);\n const rot = rotation(edit?.rotate ?? 0);\n const c = edit?.crop ? constrainCrop(edit.crop, source, aspect, rot) : centeredCrop(source, aspect, rot);\n return editedSize({ width: c.w, height: c.h }, rot);\n}\n\n/** pick → crop → save → done, and recrop() → crop → save for an existing upload. */\nexport function useSlotCrop(o: SlotCropOptions): UseSlotCrop {\n const { media } = useContentKitClient(o.client);\n const [s, setS] = useState<SlotCropState>({ status: \"idle\" });\n const cur = useRef(s);\n cur.current = s;\n const ctl = useRef<AbortController | null>(null);\n const picks = useRef(0);\n const opts = useRef(o);\n opts.current = o;\n const aspect = o.aspect ?? \"1:1\";\n\n const set = useCallback((next: SlotCropState) => {\n const prev = cur.current;\n const src = \"source\" in prev ? prev.source : undefined;\n const keep = \"source\" in next ? next.source : undefined;\n if (src && src !== keep) src.revoke?.();\n cur.current = next;\n setS(next);\n }, []);\n\n useEffect(\n () => () => {\n ctl.current?.abort();\n const c = cur.current;\n if (\"source\" in c) c.source?.revoke?.();\n },\n [],\n );\n\n const open = useCallback(\n async (load: () => Promise<CropSource>, mode: SlotCropMode, edit: Edit | null) => {\n ctl.current?.abort();\n ctl.current = null;\n const n = ++picks.current;\n set({ status: \"decoding\" });\n try {\n const source = await load();\n if (n !== picks.current) return source.revoke?.();\n set({ status: \"cropping\", source, edit, mode });\n } catch (e) {\n if (n !== picks.current) return;\n const error = e instanceof ContentKitError ? e : new ContentKitError(\"decode\", e instanceof Error ? e.message : String(e), { cause: e });\n set({ status: \"error\", error });\n opts.current.onError?.(error, \"slot.decode\");\n }\n },\n [set],\n );\n\n const pick = useCallback(\n (file: File) =>\n open(async () => {\n if (opts.current.animation === \"reject\" && (await isAnimatedImage(file)))\n throw new ContentKitError(\"animation_not_allowed\", \"animated images are not allowed here\", { status: 422 });\n return (opts.current.decode ?? decodeImage)(file);\n }, \"new\", null),\n [open],\n );\n\n const canRecrop = !!o.file && !o.file.staged && (o.file.size ?? 0) > 0 && o.file.type.startsWith(\"image/\");\n const recrop = useCallback(async () => {\n const f = opts.current.file;\n if (!f) return;\n await open(() => media.editorView(opts.current.ref, f.path), \"recrop\", f.edit ?? null);\n }, [media, open]);\n\n const setEdit = useCallback(\n (edit: Edit | null) => {\n const c = cur.current;\n if (c.status === \"cropping\") {\n if (!sameEdit(c.edit, edit)) set({ ...c, edit });\n } else if (c.status === \"error\" && c.source && c.mode) set({ status: \"cropping\", source: c.source, edit, mode: c.mode });\n },\n [set],\n );\n\n const save = useCallback(\n async (next?: Edit | null) => {\n if (next !== undefined) setEdit(next);\n const c = cur.current;\n if (!(c.status === \"cropping\" || (c.status === \"error\" && c.source && c.mode))) return undefined;\n const { source, mode } = c as { source: CropSource; mode: SlotCropMode };\n const edit = c.edit ?? null;\n const { ref, path, renderTimeout: timeout } = opts.current;\n const a = (ctl.current = new AbortController());\n const saving = (progress?: Progress) =>\n a === ctl.current && set({ status: \"saving\", source, edit, mode, progress, rendering: progress?.phase === \"processing\" });\n saving();\n try {\n let file: FileInfo;\n if (mode === \"new\") file = await media.put(source.file!, { ref, path, edit, signal: a.signal, timeout, onProgress: saving });\n else {\n const at = opts.current.file?.path ?? path;\n await media.commit(ref, [{ op: \"edit\", path: at, ...(edit ? { edit } : {}) }], { signal: a.signal });\n saving({ phase: \"processing\", loaded: 0, total: 0 });\n file = await media.waitFor(ref, at, { signal: a.signal, timeout });\n }\n if (a !== ctl.current) return undefined;\n set({ status: \"done\", file });\n opts.current.onSaved?.(file);\n return file;\n } catch (e) {\n if (a !== ctl.current) return undefined;\n const error = toContentKitError(e);\n set(error.code === \"aborted\" ? { status: \"cropping\", source, edit, mode } : { status: \"error\", error, source, edit, mode });\n if (error.code !== \"aborted\") opts.current.onError?.(error, \"slot.save\");\n return undefined;\n }\n },\n [media, set, setEdit],\n );\n\n const cancel = useCallback(() => {\n picks.current++;\n ctl.current?.abort();\n ctl.current = null;\n set({ status: \"idle\" });\n }, [set]);\n\n const cropped = useMemo(() => (\"source\" in s && s.source ? editOutput(s.source, s.edit, aspect) : undefined), [s, aspect]);\n return { ...s, aspect, cropped, pick, canRecrop, recrop, setEdit, save, cancel };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"slot.js","names":[],"sources":["../../src/react/slot.ts"],"sourcesContent":["import { useCallback, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport { ratio, type AspectRatio } from \"../client/aspect.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { centeredCrop, constrainCrop, editedSize, rotation, sameEdit, type Size } from \"../client/crop.js\";\nimport { ContentKitError, toContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, PresetRule, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport { decodeImage, isAnimatedImage, type CropSource } from \"../client/image.js\";\nimport { samePath, stem, type Progress } from \"../client/media/client.js\";\nimport { presetFor } from \"../client/media/rules.js\";\nimport { publicRenditions, type PublicPreset } from \"../client/public.js\";\nimport type { Rendition } from \"../client/rendition.js\";\nimport { useContentKitClient, useReadScope } from \"./context.js\";\nimport { usePresets } from \"./public.js\";\nimport { useMediaRead } from \"./read.js\";\nimport { withUpload } from \"./store.js\";\n\nexport interface SlotImageOptions {\n ref: RefBody;\n /** The upload path, e.g. \"cover\" or \"avatar\". */\n path: string;\n /** The public preset showing the upload. */\n image?: PublicPreset | null;\n /** An editor read of the item the host already has; skips the fetch. */\n read?: ReadResult | null;\n /** Overrides the provider's client; without one only a supplied read shows. */\n client?: ContentKitClient | null;\n}\n\nexport interface UseSlotImage {\n /** The upload at path as an editor reads it; null when there is none. */\n file: FileInfo | null;\n /** The current published files; empty until the worker publishes them. */\n renditions: Rendition[];\n /** \"W:H\": the image's, else its public preset's (GET /media/presets), else \"1:1\". */\n aspect: AspectRatio;\n /** The path's public preset (aspect, widths, min_width) once the presets load. */\n rule?: PresetRule;\n loading: boolean;\n error?: ContentKitError;\n reload: () => void;\n /** Replaces the upload after a save without refetching. */\n set: (file: FileInfo | null) => void;\n}\n\n/** An upload path's state (an editor read) and its public image. */\nexport function useSlotImage(o: SlotImageOptions): UseSlotImage {\n const r = useMediaRead(o.ref, { editor: true, prefix: stem(o.path), read: o.read, client: o.client });\n const { presets } = usePresets({ client: o.client });\n const rule = presetFor(presets, o.ref.kind, o.path, o.image?.preset);\n const read = r.read;\n const file = read?.files.find((f) => f.upload && samePath(f.path, o.path)) ?? null;\n const { path } = o;\n const update = r.set;\n const set = useCallback((f: FileInfo | null) => update(withUpload(read, path, f)), [read, path, update]);\n const renditions = useMemo(() => {\n if (!read) return publicRenditions(o.image);\n const published = read.public?.find((p) => samePath(p.from, path) && (!o.image || p.preset === o.image.preset));\n return publicRenditions(published);\n }, [read, o.image, path]);\n return { file, renditions, aspect: o.image?.aspect || rule?.aspect || \"1:1\", rule, loading: r.loading, error: r.error, reload: r.reload, set };\n}\n\nexport type SlotCropMode = \"new\" | \"recrop\";\n\n/** edit: null is the centred crop at the aspect, unrotated. */\nexport type SlotCropState =\n | { status: \"idle\" }\n | { status: \"decoding\" }\n | { status: \"cropping\"; source: CropSource; edit: Edit | null; mode: SlotCropMode }\n | { status: \"saving\"; source: CropSource; edit: Edit | null; mode: SlotCropMode; progress?: Progress; rendering?: boolean }\n | { status: \"done\"; file: FileInfo }\n | { status: \"error\"; error: ContentKitError; source?: CropSource; edit?: Edit | null; mode?: SlotCropMode };\n\nexport interface SlotCropOptions {\n ref: RefBody;\n /** The upload path, e.g. \"avatar\". */\n path: string;\n /** The current upload (useSlotImage's file): its path, size and edit for recrop(). */\n file?: FileInfo | null;\n /** The output's \"W:H\"; default \"1:1\". */\n aspect?: AspectRatio;\n /** \"reject\": refuse animated images before uploading (the preset's Image.Animation). */\n animation?: \"reject\";\n onSaved?: (file: FileInfo) => void;\n /** Replaces decodeImage (tests, custom decoders). */\n decode?: (file: File) => Promise<CropSource>;\n /** How long save() waits for the worker to render. Default 120 s. */\n renderTimeout?: number;\n /** Every failed decode or save (aborts excepted); the state shows it too. */\n onError?: (e: ContentKitError, operation: \"slot.decode\" | \"slot.save\") => void;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport type UseSlotCrop = SlotCropState & {\n aspect: AspectRatio;\n /** The output's size in source pixels (crop, then rotation) while a source is open. */\n cropped?: Size;\n /** Opens a picked file for cropping. */\n pick: (file: File) => Promise<void>;\n /** Re-crops the committed upload; false when there is none. */\n canRecrop: boolean;\n recrop: () => Promise<void>;\n setEdit: (e: Edit | null) => void;\n /** Uploads the file and puts it with the edit (new), or edits the upload (recrop); then waits for the render. */\n save: (edit?: Edit | null) => Promise<FileInfo | undefined>;\n /** Aborts a save and closes the source. */\n cancel: () => void;\n};\n\n/** The edit's output size: its crop (or the centred crop at aspect), turned. */\nexport function editOutput(source: Size, edit: Edit | null | undefined, shape: AspectRatio): Size {\n const aspect = ratio(shape);\n const rot = rotation(edit?.rotate ?? 0);\n const c = edit?.crop ? constrainCrop(edit.crop, source, aspect, rot) : centeredCrop(source, aspect, rot);\n return editedSize({ width: c.w, height: c.h }, rot);\n}\n\n/** pick → crop → save → done, and recrop() → crop → save for an existing upload. */\nexport function useSlotCrop(o: SlotCropOptions): UseSlotCrop {\n const { media } = useContentKitClient(o.client);\n const scope = useReadScope();\n const [s, setS] = useState<SlotCropState>({ status: \"idle\" });\n const cur = useRef(s);\n cur.current = s;\n const ctl = useRef<AbortController | null>(null);\n const picks = useRef(0);\n const opts = useRef(o);\n opts.current = o;\n const aspect = o.aspect ?? \"1:1\";\n\n const set = useCallback((next: SlotCropState) => {\n const prev = cur.current;\n const src = \"source\" in prev ? prev.source : undefined;\n const keep = \"source\" in next ? next.source : undefined;\n if (src && src !== keep) src.revoke?.();\n cur.current = next;\n setS(next);\n }, []);\n\n const open = useCallback(\n async (load: () => Promise<CropSource>, mode: SlotCropMode, edit: Edit | null) => {\n ctl.current?.abort();\n ctl.current = null;\n const n = ++picks.current;\n set({ status: \"decoding\" });\n try {\n const source = await load();\n if (n !== picks.current) return source.revoke?.();\n set({ status: \"cropping\", source, edit, mode });\n } catch (e) {\n if (n !== picks.current) return;\n const error = e instanceof ContentKitError ? e : new ContentKitError(\"decode\", e instanceof Error ? e.message : String(e), { cause: e });\n set({ status: \"error\", error });\n opts.current.onError?.(error, \"slot.decode\");\n }\n },\n [set],\n );\n\n const pick = useCallback(\n (file: File) =>\n open(async () => {\n if (opts.current.animation === \"reject\" && (await isAnimatedImage(file)))\n throw new ContentKitError(\"animation_not_allowed\", \"animated images are not allowed here\", { status: 422 });\n return (opts.current.decode ?? decodeImage)(file);\n }, \"new\", null),\n [open],\n );\n\n const canRecrop = !!o.file && !o.file.staged && (o.file.size ?? 0) > 0 && o.file.type.startsWith(\"image/\");\n const recrop = useCallback(async () => {\n const f = opts.current.file;\n if (!f) return;\n await open(() => media.editorView(opts.current.ref, f.path), \"recrop\", f.edit ?? null);\n }, [media, open]);\n\n const setEdit = useCallback(\n (edit: Edit | null) => {\n const c = cur.current;\n if (c.status === \"cropping\") {\n if (!sameEdit(c.edit, edit)) set({ ...c, edit });\n } else if (c.status === \"error\" && c.source && c.mode) set({ status: \"cropping\", source: c.source, edit, mode: c.mode });\n },\n [set],\n );\n\n const save = useCallback(\n async (next?: Edit | null) => {\n if (next !== undefined) setEdit(next);\n const c = cur.current;\n if (!(c.status === \"cropping\" || (c.status === \"error\" && c.source && c.mode))) return undefined;\n const { source, mode } = c as { source: CropSource; mode: SlotCropMode };\n const edit = c.edit ?? null;\n const { ref, path, renderTimeout: timeout } = opts.current;\n const a = (ctl.current = new AbortController());\n const saving = (progress?: Progress) =>\n a === ctl.current && set({ status: \"saving\", source, edit, mode, progress, rendering: progress?.phase === \"processing\" });\n saving();\n try {\n let file: FileInfo;\n if (mode === \"new\") file = await media.put(source.file!, { ref, path, edit, signal: a.signal, timeout, onProgress: saving });\n else {\n const at = opts.current.file?.path ?? path;\n await media.commit(ref, [{ op: \"edit\", path: at, ...(edit ? { edit } : {}) }], { signal: a.signal });\n if (a !== ctl.current) return undefined;\n saving({ phase: \"processing\", loaded: 0, total: 0 });\n file = await media.waitFor(ref, at, { signal: a.signal, timeout });\n }\n if (a !== ctl.current) return undefined;\n set({ status: \"done\", file });\n opts.current.onSaved?.(file);\n return file;\n } catch (e) {\n if (a !== ctl.current) return undefined;\n const error = toContentKitError(e);\n set(error.code === \"aborted\" ? { status: \"cropping\", source, edit, mode } : { status: \"error\", error, source, edit, mode });\n if (error.code !== \"aborted\") opts.current.onError?.(error, \"slot.save\");\n return undefined;\n }\n },\n [media, set, setEdit],\n );\n\n const cancel = useCallback(() => {\n picks.current++;\n ctl.current?.abort();\n ctl.current = null;\n set({ status: \"idle\" });\n }, [set]);\n\n // Cancel before old work can reach the current scope's editor callbacks.\n useLayoutEffect(() => cancel, [scope, media, cancel]);\n\n const cropped = useMemo(() => (\"source\" in s && s.source ? editOutput(s.source, s.edit, aspect) : undefined), [s, aspect]);\n return { ...s, aspect, cropped, pick, canRecrop, recrop, setEdit, save, cancel };\n}\n"],"mappings":"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"}
|
package/dist/react/store.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export declare class ReadStore {
|
|
|
36
36
|
private readonly client;
|
|
37
37
|
private readonly slots;
|
|
38
38
|
constructor(client: ContentKitClient);
|
|
39
|
-
subscribe(key: string, ref: RefBody, options: ReadOptions, listener: () => void, policy?: ReadPolicy): () => void;
|
|
39
|
+
subscribe(key: string, ref: RefBody, options: ReadOptions, listener: () => void, policy?: ReadPolicy, scope?: string): () => void;
|
|
40
40
|
snapshot(key: string): ReadEntry;
|
|
41
41
|
load(key: string, mode?: LoadMode): void;
|
|
42
42
|
/** Replaces the read for every hook showing it, without refetching. */
|
package/dist/react/store.js
CHANGED
|
@@ -35,23 +35,24 @@ var u = class {
|
|
|
35
35
|
client;
|
|
36
36
|
slots = /* @__PURE__ */ new Map();
|
|
37
37
|
constructor(e) {
|
|
38
|
-
this.client = e, e.subscribe((e) => this.apply(e));
|
|
38
|
+
this.client = e, e.subscribe((e, t) => this.apply(e, t));
|
|
39
39
|
}
|
|
40
|
-
subscribe(e, t, n, r, i = {}) {
|
|
41
|
-
let
|
|
42
|
-
|
|
40
|
+
subscribe(e, t, n, r, i = {}, a = "{}") {
|
|
41
|
+
let s = this.slots.get(e);
|
|
42
|
+
s || this.slots.set(e, s = {
|
|
43
43
|
key: e,
|
|
44
|
+
scope: a,
|
|
44
45
|
ref: t,
|
|
45
46
|
options: n,
|
|
46
47
|
listeners: /* @__PURE__ */ new Map(),
|
|
47
48
|
entry: o
|
|
48
49
|
});
|
|
49
|
-
let
|
|
50
|
-
return
|
|
51
|
-
|
|
52
|
-
if (this.slots.get(e) ===
|
|
53
|
-
if (
|
|
54
|
-
|
|
50
|
+
let c = s;
|
|
51
|
+
return c.listeners.set(r, i), this.schedule(c), () => {
|
|
52
|
+
c.listeners.delete(r), queueMicrotask(() => {
|
|
53
|
+
if (this.slots.get(e) === c) {
|
|
54
|
+
if (c.listeners.size) return this.schedule(c);
|
|
55
|
+
c.ctl?.abort(), clearTimeout(c.timer), this.slots.delete(e);
|
|
55
56
|
}
|
|
56
57
|
});
|
|
57
58
|
};
|
|
@@ -121,22 +122,22 @@ var u = class {
|
|
|
121
122
|
reloadViewers(e) {
|
|
122
123
|
for (let [t, n] of this.slots) !n.options.editor && n.ref.kind === e.kind && n.ref.id === e.id && (n.entry.loaded || n.ctl) && this.load(t, "reload");
|
|
123
124
|
}
|
|
124
|
-
apply(e) {
|
|
125
|
+
apply(e, t) {
|
|
125
126
|
if (e.type !== "media.committed" && e.type !== "media.processed") return;
|
|
126
|
-
let { ref:
|
|
127
|
-
for (let [
|
|
128
|
-
if (
|
|
127
|
+
let { ref: n } = e;
|
|
128
|
+
for (let [r, i] of this.slots) {
|
|
129
|
+
if (i.ref.kind !== n.kind || i.ref.id !== n.id) continue;
|
|
129
130
|
if (e.type === "media.committed") {
|
|
130
|
-
(
|
|
131
|
+
(i.entry.loaded || i.ctl) && this.load(r, "reload");
|
|
131
132
|
continue;
|
|
132
133
|
}
|
|
133
|
-
let
|
|
134
|
-
if (
|
|
135
|
-
if (!
|
|
136
|
-
this.load(
|
|
134
|
+
let a = i.entry.read, { file: o } = e;
|
|
135
|
+
if (a) {
|
|
136
|
+
if (!i.options.editor || i.scope !== t) {
|
|
137
|
+
this.load(r, "reload");
|
|
137
138
|
continue;
|
|
138
139
|
}
|
|
139
|
-
(!
|
|
140
|
+
(!i.options.prefix || o.path.startsWith(i.options.prefix)) && (this.update(i, { read: l(a, o.path, o) }), i.ctl && this.load(r, "reload"));
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
143
|
}
|
package/dist/react/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","names":[],"sources":["../../src/react/store.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\nimport type { ReadOptions } from \"../client/media/api.js\";\nimport { samePath, sleep } from \"../client/media/client.js\";\nimport { isProcessing, processing } from \"../client/media/windows.js\";\nimport { expiryDelay } from \"../client/playback.js\";\n\nexport interface ReadEntry {\n read: ReadResult | null;\n loading: boolean;\n /** A read or set() has completed. */\n loaded: boolean;\n error?: ContentKitError;\n}\n\n/** What one reader asks of a shared read beyond the read itself. */\nexport interface ReadPolicy {\n /** Poll at this interval (ms) while uploads process (editor reads). */\n poll?: number | false;\n /** Read again shortly before the URLs expire. */\n refresh?: boolean;\n}\n\n/**\n * How load() treats a read in flight: \"initial\" reads only when nothing is\n * loaded or loading; \"refresh\" joins one in flight (timers, a player's grant\n * refresh); \"reload\" restarts it (after a change the in-flight read predates).\n */\nexport type LoadMode = \"initial\" | \"refresh\" | \"reload\";\n\ninterface Slot {\n key: string;\n ref: RefBody;\n options: ReadOptions;\n listeners: Map<() => void, ReadPolicy>;\n entry: ReadEntry;\n ctl?: AbortController;\n timer?: ReturnType<typeof setTimeout>;\n}\n\nconst IDLE: ReadEntry = { read: null, loading: false, loaded: false };\n\n/** rate_limited reads are retried this often before the error shows. */\nconst RATE_LIMIT_RETRIES = 3;\n\nexport function readKey(ref: RefBody, o: ReadOptions = {}): string {\n return JSON.stringify([ref.kind, ref.id, o.prefix ?? \"\", o.offset ?? 0, o.limit ?? 0, !!o.download, !!o.editor]);\n}\n\n/** The read with the upload at path replaced by file in place (appended when new, removed when null). */\nexport function withUpload(read: ReadResult | null, path: string, file: FileInfo | null): ReadResult {\n const all = read?.files ?? [];\n const at = all.findIndex((x) => x.upload && samePath(x.path, path));\n const files = all.filter((_, i) => i !== at);\n if (file) files.splice(at < 0 ? files.length : at, 0, file);\n return { access: \"full\", expires: 0, total: 0, offset: 0, limit: 0, ...read, files };\n}\n\n/**\n * The reads hooks share, per client: one request per item and options while\n * any hook shows it. set() updates every reader in place; a commit reloads\n * the item's reads and a processed upload is patched into its editor reads.\n * Readers may ask for polling while uploads process and a read before the\n * URLs expire; the slot runs one timer for all of them.\n */\nexport class ReadStore {\n private readonly slots = new Map<string, Slot>();\n\n constructor(private readonly client: ContentKitClient) {\n client.subscribe((c) => this.apply(c));\n }\n\n subscribe(key: string, ref: RefBody, options: ReadOptions, listener: () => void, policy: ReadPolicy = {}): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { key, ref, options, listeners: new Map(), entry: IDLE }));\n const slot = s;\n slot.listeners.set(listener, policy);\n this.schedule(slot);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (this.slots.get(key) !== slot) return;\n if (slot.listeners.size) return this.schedule(slot);\n slot.ctl?.abort();\n clearTimeout(slot.timer);\n this.slots.delete(key);\n });\n };\n }\n\n snapshot(key: string): ReadEntry {\n return this.slots.get(key)?.entry ?? IDLE;\n }\n\n load(key: string, mode: LoadMode = \"initial\"): void {\n const s = this.slots.get(key);\n if (!s) return;\n if (mode === \"initial\" && (s.entry.loaded || s.ctl)) return;\n if (mode === \"refresh\" && s.ctl) return;\n s.ctl?.abort();\n clearTimeout(s.timer);\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, error: undefined });\n this.fetch(s, ctl.signal).then(\n (read) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const before = s.entry.read;\n this.update(s, { read, loading: false, loaded: true, error: undefined });\n this.schedule(s);\n if (s.options.editor && finished(before, read)) this.reloadViewers(s.ref);\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n this.update(s, { read: null, loading: false, loaded: true, error: toContentKitError(e) });\n },\n );\n }\n\n /** Replaces the read for every hook showing it, without refetching. */\n set(key: string, read: ReadResult): void {\n const s = this.slots.get(key);\n if (!s) return;\n s.ctl?.abort();\n s.ctl = undefined;\n this.update(s, { read, loading: false, loaded: true, error: undefined });\n this.schedule(s);\n }\n\n // The client retries transport faults; a read rate limit waits it out here.\n private async fetch(s: Slot, signal: AbortSignal): Promise<ReadResult> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.client.media.read(s.ref, { ...s.options, signal });\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"rate_limited\" || attempt >= RATE_LIMIT_RETRIES || signal.aborted) throw err;\n await sleep(Math.min(30, err.retryAfter ?? 1) * 1000, signal);\n }\n }\n }\n\n // One timer per slot: the soonest of a poll (while uploads process) and the refresh before expiry.\n private schedule(s: Slot): void {\n clearTimeout(s.timer);\n s.timer = undefined;\n const read = s.entry.read;\n if (!read || s.ctl || !s.listeners.size) return;\n let delay: number | null = null;\n const polls = [...s.listeners.values()].map((p) => p.poll).filter((p): p is number => typeof p === \"number\" && p > 0);\n if (polls.length && processing(read)) delay = Math.min(...polls);\n if ([...s.listeners.values()].some((p) => p.refresh)) {\n const d = expiryDelay(read.expires);\n if (d !== null) delay = delay === null ? d : Math.min(delay, d);\n }\n if (delay !== null) s.timer = setTimeout(() => this.load(s.key, \"refresh\"), delay);\n }\n\n // Uploads finished processing: the item's viewer reads list new files and URLs.\n private reloadViewers(ref: RefBody): void {\n for (const [key, s] of this.slots) {\n if (!s.options.editor && s.ref.kind === ref.kind && s.ref.id === ref.id && (s.entry.loaded || s.ctl)) this.load(key, \"reload\");\n }\n }\n\n private apply(change: ContentKitChange): void {\n if (change.type !== \"media.committed\" && change.type !== \"media.processed\") return;\n const { ref } = change;\n for (const [key, s] of this.slots) {\n if (s.ref.kind !== ref.kind || s.ref.id !== ref.id) continue;\n if (change.type === \"media.committed\") {\n if (s.entry.loaded || s.ctl) this.load(key, \"reload\");\n continue;\n }\n const read = s.entry.read;\n const { file } = change;\n if (!read) continue;\n // A viewer read cannot be patched: its URLs and public files change.\n if (!s.options.editor) {\n this.load(key, \"reload\");\n continue;\n }\n if (s.options.prefix && !file.path.startsWith(s.options.prefix)) continue;\n this.update(s, { read: withUpload(read, file.path, file) });\n // A read in flight may predate the processing.\n if (s.ctl) this.load(key, \"reload\");\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<ReadEntry>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners.keys()) l();\n }\n}\n\n/** Some upload processing in before is done (or failed) in after. */\nfunction finished(before: ReadResult | null, after: ReadResult): boolean {\n if (!before) return false;\n const now = new Map(after.files.filter((f) => f.upload).map((f) => [f.path, f]));\n return before.files.some((f) => isProcessing(f) && now.has(f.path) && !isProcessing(now.get(f.path)!));\n}\n\nconst stores = new WeakMap<ContentKitClient, ReadStore>();\n\n/** The client's shared read store. */\nexport function storeFor(client: ContentKitClient): ReadStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ReadStore(client)));\n return s;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"store.js","names":[],"sources":["../../src/react/store.ts"],"sourcesContent":["import type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { FileInfo, ReadResult, RefBody } from \"../client/generated/wire.js\";\nimport type { ContentKitChange } from \"../client/http.js\";\nimport type { ReadOptions } from \"../client/media/api.js\";\nimport { samePath, sleep } from \"../client/media/client.js\";\nimport { isProcessing, processing } from \"../client/media/windows.js\";\nimport { expiryDelay } from \"../client/playback.js\";\n\nexport interface ReadEntry {\n read: ReadResult | null;\n loading: boolean;\n /** A read or set() has completed. */\n loaded: boolean;\n error?: ContentKitError;\n}\n\n/** What one reader asks of a shared read beyond the read itself. */\nexport interface ReadPolicy {\n /** Poll at this interval (ms) while uploads process (editor reads). */\n poll?: number | false;\n /** Read again shortly before the URLs expire. */\n refresh?: boolean;\n}\n\n/**\n * How load() treats a read in flight: \"initial\" reads only when nothing is\n * loaded or loading; \"refresh\" joins one in flight (timers, a player's grant\n * refresh); \"reload\" restarts it (after a change the in-flight read predates).\n */\nexport type LoadMode = \"initial\" | \"refresh\" | \"reload\";\n\ninterface Slot {\n key: string;\n scope: string;\n ref: RefBody;\n options: ReadOptions;\n listeners: Map<() => void, ReadPolicy>;\n entry: ReadEntry;\n ctl?: AbortController;\n timer?: ReturnType<typeof setTimeout>;\n}\n\nconst IDLE: ReadEntry = { read: null, loading: false, loaded: false };\n\n/** rate_limited reads are retried this often before the error shows. */\nconst RATE_LIMIT_RETRIES = 3;\n\nexport function readKey(ref: RefBody, o: ReadOptions = {}): string {\n return JSON.stringify([ref.kind, ref.id, o.prefix ?? \"\", o.offset ?? 0, o.limit ?? 0, !!o.download, !!o.editor]);\n}\n\n/** The read with the upload at path replaced by file in place (appended when new, removed when null). */\nexport function withUpload(read: ReadResult | null, path: string, file: FileInfo | null): ReadResult {\n const all = read?.files ?? [];\n const at = all.findIndex((x) => x.upload && samePath(x.path, path));\n const files = all.filter((_, i) => i !== at);\n if (file) files.splice(at < 0 ? files.length : at, 0, file);\n return { access: \"full\", expires: 0, total: 0, offset: 0, limit: 0, ...read, files };\n}\n\n/**\n * The reads hooks share, per client: one request per item and options while\n * any hook shows it. set() updates every reader in place; a commit reloads\n * the item's reads and a processed upload is patched into its editor reads.\n * Readers may ask for polling while uploads process and a read before the\n * URLs expire; the slot runs one timer for all of them.\n */\nexport class ReadStore {\n private readonly slots = new Map<string, Slot>();\n\n constructor(private readonly client: ContentKitClient) {\n client.subscribe((c, scope) => this.apply(c, scope));\n }\n\n subscribe(key: string, ref: RefBody, options: ReadOptions, listener: () => void, policy: ReadPolicy = {}, scope = \"{}\"): () => void {\n let s = this.slots.get(key);\n if (!s) this.slots.set(key, (s = { key, scope, ref, options, listeners: new Map(), entry: IDLE }));\n const slot = s;\n slot.listeners.set(listener, policy);\n this.schedule(slot);\n return () => {\n slot.listeners.delete(listener);\n // Kept across a StrictMode remount; dropped once no hook shows it.\n queueMicrotask(() => {\n if (this.slots.get(key) !== slot) return;\n if (slot.listeners.size) return this.schedule(slot);\n slot.ctl?.abort();\n clearTimeout(slot.timer);\n this.slots.delete(key);\n });\n };\n }\n\n snapshot(key: string): ReadEntry {\n return this.slots.get(key)?.entry ?? IDLE;\n }\n\n load(key: string, mode: LoadMode = \"initial\"): void {\n const s = this.slots.get(key);\n if (!s) return;\n if (mode === \"initial\" && (s.entry.loaded || s.ctl)) return;\n if (mode === \"refresh\" && s.ctl) return;\n s.ctl?.abort();\n clearTimeout(s.timer);\n const ctl = (s.ctl = new AbortController());\n this.update(s, { loading: true, error: undefined });\n this.fetch(s, ctl.signal).then(\n (read) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n const before = s.entry.read;\n this.update(s, { read, loading: false, loaded: true, error: undefined });\n this.schedule(s);\n if (s.options.editor && finished(before, read)) this.reloadViewers(s.ref);\n },\n (e) => {\n if (s.ctl !== ctl) return;\n s.ctl = undefined;\n this.update(s, { read: null, loading: false, loaded: true, error: toContentKitError(e) });\n },\n );\n }\n\n /** Replaces the read for every hook showing it, without refetching. */\n set(key: string, read: ReadResult): void {\n const s = this.slots.get(key);\n if (!s) return;\n s.ctl?.abort();\n s.ctl = undefined;\n this.update(s, { read, loading: false, loaded: true, error: undefined });\n this.schedule(s);\n }\n\n // The client retries transport faults; a read rate limit waits it out here.\n private async fetch(s: Slot, signal: AbortSignal): Promise<ReadResult> {\n for (let attempt = 0; ; attempt++) {\n try {\n return await this.client.media.read(s.ref, { ...s.options, signal });\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"rate_limited\" || attempt >= RATE_LIMIT_RETRIES || signal.aborted) throw err;\n await sleep(Math.min(30, err.retryAfter ?? 1) * 1000, signal);\n }\n }\n }\n\n // One timer per slot: the soonest of a poll (while uploads process) and the refresh before expiry.\n private schedule(s: Slot): void {\n clearTimeout(s.timer);\n s.timer = undefined;\n const read = s.entry.read;\n if (!read || s.ctl || !s.listeners.size) return;\n let delay: number | null = null;\n const polls = [...s.listeners.values()].map((p) => p.poll).filter((p): p is number => typeof p === \"number\" && p > 0);\n if (polls.length && processing(read)) delay = Math.min(...polls);\n if ([...s.listeners.values()].some((p) => p.refresh)) {\n const d = expiryDelay(read.expires);\n if (d !== null) delay = delay === null ? d : Math.min(delay, d);\n }\n if (delay !== null) s.timer = setTimeout(() => this.load(s.key, \"refresh\"), delay);\n }\n\n // Uploads finished processing: the item's viewer reads list new files and URLs.\n private reloadViewers(ref: RefBody): void {\n for (const [key, s] of this.slots) {\n if (!s.options.editor && s.ref.kind === ref.kind && s.ref.id === ref.id && (s.entry.loaded || s.ctl)) this.load(key, \"reload\");\n }\n }\n\n private apply(change: ContentKitChange, scope: string | null): void {\n if (change.type !== \"media.committed\" && change.type !== \"media.processed\") return;\n const { ref } = change;\n for (const [key, s] of this.slots) {\n if (s.ref.kind !== ref.kind || s.ref.id !== ref.id) continue;\n if (change.type === \"media.committed\") {\n if (s.entry.loaded || s.ctl) this.load(key, \"reload\");\n continue;\n }\n const read = s.entry.read;\n const { file } = change;\n if (!read) continue;\n // Viewer URLs and responses from an earlier scope require a fresh read.\n if (!s.options.editor || s.scope !== scope) {\n this.load(key, \"reload\");\n continue;\n }\n if (s.options.prefix && !file.path.startsWith(s.options.prefix)) continue;\n this.update(s, { read: withUpload(read, file.path, file) });\n // A read in flight may predate the processing.\n if (s.ctl) this.load(key, \"reload\");\n }\n }\n\n // A new entry per change: useSyncExternalStore compares snapshots by identity.\n private update(s: Slot, patch: Partial<ReadEntry>): void {\n s.entry = { ...s.entry, ...patch };\n for (const l of s.listeners.keys()) l();\n }\n}\n\n/** Some upload processing in before is done (or failed) in after. */\nfunction finished(before: ReadResult | null, after: ReadResult): boolean {\n if (!before) return false;\n const now = new Map(after.files.filter((f) => f.upload).map((f) => [f.path, f]));\n return before.files.some((f) => isProcessing(f) && now.has(f.path) && !isProcessing(now.get(f.path)!));\n}\n\nconst stores = new WeakMap<ContentKitClient, ReadStore>();\n\n/** The client's shared read store. */\nexport function storeFor(client: ContentKitClient): ReadStore {\n let s = stores.get(client);\n if (!s) stores.set(client, (s = new ReadStore(client)));\n return s;\n}\n"],"mappings":"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"}
|
package/dist/react/upload.d.ts
CHANGED
|
@@ -21,8 +21,9 @@ export interface UploadQueueOptions extends QueueOptions {
|
|
|
21
21
|
client?: ContentKitClient;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* A file queue for one item
|
|
25
|
-
*
|
|
24
|
+
* A file queue for one item: another ref gets a fresh queue and the previous
|
|
25
|
+
* one pauses (the other options are read once per queue). Unmounting pauses
|
|
26
|
+
* running uploads.
|
|
26
27
|
*/
|
|
27
28
|
export declare function useUploadQueue({ client: own, ...options }: UploadQueueOptions): UseUploadQueue;
|
|
28
29
|
export interface UploadStatus {
|
package/dist/react/upload.js
CHANGED
|
@@ -7,21 +7,26 @@ import { useContentKitClient as u } from "./context.js";
|
|
|
7
7
|
import { useCallback as d, useEffect as f, useMemo as p, useRef as m, useState as h, useSyncExternalStore as g } from "react";
|
|
8
8
|
//#region src/react/upload.ts
|
|
9
9
|
function _({ client: e, ...t }) {
|
|
10
|
-
let n = u(e),
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
10
|
+
let n = u(e), r = `${t.ref.kind}/${t.ref.id}`, i = () => ({
|
|
11
|
+
key: r,
|
|
12
|
+
queue: new l(n.media, {
|
|
13
|
+
...t,
|
|
14
|
+
autoStart: !1
|
|
15
|
+
})
|
|
16
|
+
}), [a, o] = h(i);
|
|
17
|
+
a.key !== r && o(i());
|
|
18
|
+
let { queue: s } = a, c = t.autoStart ?? !0;
|
|
19
|
+
return f(() => (c && s.start(), () => s.pause()), [s, c]), {
|
|
20
|
+
...g(s.subscribe, s.getSnapshot, s.getSnapshot),
|
|
21
|
+
queue: s,
|
|
22
|
+
add: s.add.bind(s),
|
|
23
|
+
move: s.move.bind(s),
|
|
24
|
+
update: s.update.bind(s),
|
|
25
|
+
remove: s.remove.bind(s),
|
|
26
|
+
retry: s.retry.bind(s),
|
|
27
|
+
start: s.start.bind(s),
|
|
28
|
+
pause: s.pause.bind(s),
|
|
29
|
+
commit: s.commit.bind(s)
|
|
25
30
|
};
|
|
26
31
|
}
|
|
27
32
|
function v({ client: t } = {}) {
|
package/dist/react/upload.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.js","names":[],"sources":["../../src/react/upload.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { ratio, type AspectRatio } from \"../client/aspect.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { centeredCrop, constrainCrop, editOf, rotation, sameEdit, toOriginal, type Rotation, type Size } from \"../client/crop.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Crop, Edit, EncodeProgress, FileInfo } from \"../client/generated/wire.js\";\nimport type { Progress, PutOptions, UploadedFile, UploadOptions } from \"../client/media/client.js\";\nimport { encodeRemaining } from \"../client/media/encode.js\";\nimport { UploadQueue, type QueueOptions, type QueueSnapshot } from \"../client/media/queue.js\";\nimport { useContentKitClient } from \"./context.js\";\n\nexport interface UseUploadQueue extends QueueSnapshot {\n queue: UploadQueue;\n add: UploadQueue[\"add\"];\n move: UploadQueue[\"move\"];\n update: UploadQueue[\"update\"];\n remove: UploadQueue[\"remove\"];\n retry: UploadQueue[\"retry\"];\n start: UploadQueue[\"start\"];\n pause: UploadQueue[\"pause\"];\n commit: UploadQueue[\"commit\"];\n}\n\nexport interface UploadQueueOptions extends QueueOptions {\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\n/**\n * A file queue for one item (options are read once; key the component by\n * ref to switch items). Unmounting pauses running uploads.\n */\nexport function useUploadQueue({ client: own, ...options }: UploadQueueOptions): UseUploadQueue {\n const client = useContentKitClient(own);\n const [queue] = useState(() => new UploadQueue(client.media, { ...options, autoStart: false }));\n const autoStart = options.autoStart ?? true;\n useEffect(() => {\n if (autoStart) queue.start();\n return () => queue.pause();\n }, [queue, autoStart]);\n const snap = useSyncExternalStore(queue.subscribe, queue.getSnapshot, queue.getSnapshot);\n return {\n ...snap,\n queue,\n add: queue.add.bind(queue),\n move: queue.move.bind(queue),\n update: queue.update.bind(queue),\n remove: queue.remove.bind(queue),\n retry: queue.retry.bind(queue),\n start: queue.start.bind(queue),\n pause: queue.pause.bind(queue),\n commit: queue.commit.bind(queue),\n };\n}\n\nexport interface UploadStatus {\n status: \"idle\" | \"uploading\" | \"done\" | \"error\";\n progress?: Progress;\n error?: ContentKitError;\n result?: UploadedFile & { file?: FileInfo };\n}\n\nexport interface UseUpload extends UploadStatus {\n /**\n * Uploads one file; with put it also commits it to its path (put's edit,\n * meta and index) and waits until it is processed (result.file).\n */\n upload: (file: File, opts: Omit<UploadOptions, \"signal\" | \"onProgress\"> & { put?: PutOptions }) => Promise<UploadedFile & { file?: FileInfo }>;\n cancel: () => void;\n}\n\n/** One upload at a time (a cover, an inline image); a new upload cancels the previous. */\nexport function useUpload({ client: own }: { client?: ContentKitClient } = {}): UseUpload {\n const { media } = useContentKitClient(own);\n const [s, set] = useState<UploadStatus>({ status: \"idle\" });\n const ctl = useRef<AbortController | null>(null);\n useEffect(() => () => ctl.current?.abort(), []);\n\n const upload = useCallback<UseUpload[\"upload\"]>(\n async (file, { put, ...opts }) => {\n ctl.current?.abort();\n const c = (ctl.current = new AbortController());\n set({ status: \"uploading\" });\n const o: UploadOptions = {\n ...opts,\n signal: c.signal,\n onProgress: (progress) => c === ctl.current && set({ status: \"uploading\", progress }),\n };\n try {\n let result: UploadedFile & { file?: FileInfo };\n if (put) {\n let up: UploadedFile | undefined;\n const f = await media.put(file, { ...o, ...put, onUploaded: (u) => (up = u) });\n result = { ...up!, file: f };\n } else result = await media.upload(file, o);\n if (c === ctl.current) set((prev) => ({ status: \"done\", progress: prev.progress, result }));\n return result;\n } catch (e) {\n const error = toContentKitError(e);\n if (c === ctl.current) set(error.code === \"aborted\" ? { status: \"idle\" } : { status: \"error\", error });\n throw error;\n }\n },\n [media],\n );\n const cancel = useCallback(() => ctl.current?.abort(), []);\n return { ...s, upload, cancel };\n}\n\nexport interface UseCropOptions {\n /** The upload's size (an editor read's w and h). */\n source: Size;\n /** The edited image's \"W:H\", e.g. the public preset's aspect; \"\" or omitted is free. */\n aspect?: AspectRatio;\n /** The current edit to start from. */\n initial?: Edit | null;\n}\n\nexport interface UseCrop {\n /** In original pixels, inside the source and at the aspect. */\n crop: Crop;\n rotate: Rotation;\n /** Ready for an edit or put op; null when nothing changes. Its identity changes only with its value. */\n edit: Edit | null;\n /** A rect in original pixels. */\n setCrop: (rect: Crop) => void;\n /** A rect on the image as displayed (after the rotation) at display size: a cropper's output. */\n setFromDisplay: (rect: Crop, display: Size) => void;\n /** Turns clockwise by deg (a multiple of 90); the crop keeps the aspect. */\n rotateBy: (deg: number) => void;\n reset: () => void;\n}\n\n/** Headless crop state for any cropper UI: a rect in original pixels, a rotation and the resulting edit. */\nexport function useCrop({ source: given, aspect: shape, initial }: UseCropOptions): UseCrop {\n const aspect = ratio(shape);\n const { width, height } = given;\n const source = useMemo(() => ({ width, height }), [width, height]);\n const start = (): [Crop, Rotation] => {\n const rotate = rotation(initial?.rotate ?? 0);\n const c = initial?.crop;\n return [c ? constrainCrop(c, source, aspect, rotate) : centeredCrop(source, aspect, rotate), rotate];\n };\n const [state, setState] = useState(start);\n const [crop, rotate] = state;\n const set = useCallback(\n (next: [Crop, Rotation] | ((s: [Crop, Rotation]) => [Crop, Rotation])) =>\n setState((s) => {\n const n = typeof next === \"function\" ? next(s) : next;\n return sameEdit({ crop: n[0], rotate: n[1] }, { crop: s[0], rotate: s[1] }) ? s : n;\n }),\n [],\n );\n const setCrop = useCallback((r: Crop) => set(([, rot]) => [constrainCrop(r, source, aspect, rot), rot]), [set, source, aspect]);\n const setFromDisplay = useCallback(\n (r: Crop, display: Size) => set(([, rot]) => [constrainCrop(toOriginal(r, display, source, rot), source, aspect, rot), rot]),\n [set, source, aspect],\n );\n const rotateBy = useCallback(\n (deg: number) =>\n set(([c, rot]) => {\n const next = rotation(rot + deg);\n return [constrainCrop(c, source, aspect, next), next];\n }),\n [set, source, aspect],\n );\n const reset = useCallback(() => set([centeredCrop(source, aspect), 0]), [set, source, aspect]);\n const last = useRef<Edit | null>(null);\n const edit = useMemo(() => {\n const e = editOf(crop, source, rotate);\n return sameEdit(e, last.current) ? last.current : (last.current = e);\n }, [crop, source, rotate]);\n return { crop, rotate, edit, setCrop, setFromDisplay, rotateBy, reset };\n}\n\nexport interface UseEncodeProgress {\n progress?: EncodeProgress;\n /** Seconds left, counting down each second between reports; undefined while unknown. */\n remaining?: number;\n}\n\n/**\n * A pending video file's encode progress from the read API (`files[i].progress`),\n * with the ETA counted down locally between polls.\n */\nexport function useEncodeProgress(progress?: EncodeProgress | null): UseEncodeProgress {\n const [base, setBase] = useState(() => ({ at: progress?.at, received: Date.now() }));\n if (progress?.at !== base.at) setBase({ at: progress?.at, received: Date.now() });\n const [now, setNow] = useState(() => Date.now());\n const ticking = !!progress?.eta && !progress.stalled;\n useEffect(() => {\n if (!ticking) return;\n setNow(Date.now());\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, [ticking, base]);\n return { progress: progress ?? undefined, remaining: encodeRemaining(progress, base.received, Math.max(now, base.received)) };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"upload.js","names":[],"sources":["../../src/react/upload.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from \"react\";\nimport { ratio, type AspectRatio } from \"../client/aspect.js\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { centeredCrop, constrainCrop, editOf, rotation, sameEdit, toOriginal, type Rotation, type Size } from \"../client/crop.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { Crop, Edit, EncodeProgress, FileInfo } from \"../client/generated/wire.js\";\nimport type { Progress, PutOptions, UploadedFile, UploadOptions } from \"../client/media/client.js\";\nimport { encodeRemaining } from \"../client/media/encode.js\";\nimport { UploadQueue, type QueueOptions, type QueueSnapshot } from \"../client/media/queue.js\";\nimport { useContentKitClient } from \"./context.js\";\n\nexport interface UseUploadQueue extends QueueSnapshot {\n queue: UploadQueue;\n add: UploadQueue[\"add\"];\n move: UploadQueue[\"move\"];\n update: UploadQueue[\"update\"];\n remove: UploadQueue[\"remove\"];\n retry: UploadQueue[\"retry\"];\n start: UploadQueue[\"start\"];\n pause: UploadQueue[\"pause\"];\n commit: UploadQueue[\"commit\"];\n}\n\nexport interface UploadQueueOptions extends QueueOptions {\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\n/**\n * A file queue for one item: another ref gets a fresh queue and the previous\n * one pauses (the other options are read once per queue). Unmounting pauses\n * running uploads.\n */\nexport function useUploadQueue({ client: own, ...options }: UploadQueueOptions): UseUploadQueue {\n const client = useContentKitClient(own);\n const key = `${options.ref.kind}/${options.ref.id}`;\n const open = () => ({ key, queue: new UploadQueue(client.media, { ...options, autoStart: false }) });\n const [current, setCurrent] = useState(open);\n if (current.key !== key) setCurrent(open());\n const { queue } = current;\n const autoStart = options.autoStart ?? true;\n useEffect(() => {\n if (autoStart) queue.start();\n return () => queue.pause();\n }, [queue, autoStart]);\n const snap = useSyncExternalStore(queue.subscribe, queue.getSnapshot, queue.getSnapshot);\n return {\n ...snap,\n queue,\n add: queue.add.bind(queue),\n move: queue.move.bind(queue),\n update: queue.update.bind(queue),\n remove: queue.remove.bind(queue),\n retry: queue.retry.bind(queue),\n start: queue.start.bind(queue),\n pause: queue.pause.bind(queue),\n commit: queue.commit.bind(queue),\n };\n}\n\nexport interface UploadStatus {\n status: \"idle\" | \"uploading\" | \"done\" | \"error\";\n progress?: Progress;\n error?: ContentKitError;\n result?: UploadedFile & { file?: FileInfo };\n}\n\nexport interface UseUpload extends UploadStatus {\n /**\n * Uploads one file; with put it also commits it to its path (put's edit,\n * meta and index) and waits until it is processed (result.file).\n */\n upload: (file: File, opts: Omit<UploadOptions, \"signal\" | \"onProgress\"> & { put?: PutOptions }) => Promise<UploadedFile & { file?: FileInfo }>;\n cancel: () => void;\n}\n\n/** One upload at a time (a cover, an inline image); a new upload cancels the previous. */\nexport function useUpload({ client: own }: { client?: ContentKitClient } = {}): UseUpload {\n const { media } = useContentKitClient(own);\n const [s, set] = useState<UploadStatus>({ status: \"idle\" });\n const ctl = useRef<AbortController | null>(null);\n useEffect(() => () => ctl.current?.abort(), []);\n\n const upload = useCallback<UseUpload[\"upload\"]>(\n async (file, { put, ...opts }) => {\n ctl.current?.abort();\n const c = (ctl.current = new AbortController());\n set({ status: \"uploading\" });\n const o: UploadOptions = {\n ...opts,\n signal: c.signal,\n onProgress: (progress) => c === ctl.current && set({ status: \"uploading\", progress }),\n };\n try {\n let result: UploadedFile & { file?: FileInfo };\n if (put) {\n let up: UploadedFile | undefined;\n const f = await media.put(file, { ...o, ...put, onUploaded: (u) => (up = u) });\n result = { ...up!, file: f };\n } else result = await media.upload(file, o);\n if (c === ctl.current) set((prev) => ({ status: \"done\", progress: prev.progress, result }));\n return result;\n } catch (e) {\n const error = toContentKitError(e);\n if (c === ctl.current) set(error.code === \"aborted\" ? { status: \"idle\" } : { status: \"error\", error });\n throw error;\n }\n },\n [media],\n );\n const cancel = useCallback(() => ctl.current?.abort(), []);\n return { ...s, upload, cancel };\n}\n\nexport interface UseCropOptions {\n /** The upload's size (an editor read's w and h). */\n source: Size;\n /** The edited image's \"W:H\", e.g. the public preset's aspect; \"\" or omitted is free. */\n aspect?: AspectRatio;\n /** The current edit to start from. */\n initial?: Edit | null;\n}\n\nexport interface UseCrop {\n /** In original pixels, inside the source and at the aspect. */\n crop: Crop;\n rotate: Rotation;\n /** Ready for an edit or put op; null when nothing changes. Its identity changes only with its value. */\n edit: Edit | null;\n /** A rect in original pixels. */\n setCrop: (rect: Crop) => void;\n /** A rect on the image as displayed (after the rotation) at display size: a cropper's output. */\n setFromDisplay: (rect: Crop, display: Size) => void;\n /** Turns clockwise by deg (a multiple of 90); the crop keeps the aspect. */\n rotateBy: (deg: number) => void;\n reset: () => void;\n}\n\n/** Headless crop state for any cropper UI: a rect in original pixels, a rotation and the resulting edit. */\nexport function useCrop({ source: given, aspect: shape, initial }: UseCropOptions): UseCrop {\n const aspect = ratio(shape);\n const { width, height } = given;\n const source = useMemo(() => ({ width, height }), [width, height]);\n const start = (): [Crop, Rotation] => {\n const rotate = rotation(initial?.rotate ?? 0);\n const c = initial?.crop;\n return [c ? constrainCrop(c, source, aspect, rotate) : centeredCrop(source, aspect, rotate), rotate];\n };\n const [state, setState] = useState(start);\n const [crop, rotate] = state;\n const set = useCallback(\n (next: [Crop, Rotation] | ((s: [Crop, Rotation]) => [Crop, Rotation])) =>\n setState((s) => {\n const n = typeof next === \"function\" ? next(s) : next;\n return sameEdit({ crop: n[0], rotate: n[1] }, { crop: s[0], rotate: s[1] }) ? s : n;\n }),\n [],\n );\n const setCrop = useCallback((r: Crop) => set(([, rot]) => [constrainCrop(r, source, aspect, rot), rot]), [set, source, aspect]);\n const setFromDisplay = useCallback(\n (r: Crop, display: Size) => set(([, rot]) => [constrainCrop(toOriginal(r, display, source, rot), source, aspect, rot), rot]),\n [set, source, aspect],\n );\n const rotateBy = useCallback(\n (deg: number) =>\n set(([c, rot]) => {\n const next = rotation(rot + deg);\n return [constrainCrop(c, source, aspect, next), next];\n }),\n [set, source, aspect],\n );\n const reset = useCallback(() => set([centeredCrop(source, aspect), 0]), [set, source, aspect]);\n const last = useRef<Edit | null>(null);\n const edit = useMemo(() => {\n const e = editOf(crop, source, rotate);\n return sameEdit(e, last.current) ? last.current : (last.current = e);\n }, [crop, source, rotate]);\n return { crop, rotate, edit, setCrop, setFromDisplay, rotateBy, reset };\n}\n\nexport interface UseEncodeProgress {\n progress?: EncodeProgress;\n /** Seconds left, counting down each second between reports; undefined while unknown. */\n remaining?: number;\n}\n\n/**\n * A pending video file's encode progress from the read API (`files[i].progress`),\n * with the ETA counted down locally between polls.\n */\nexport function useEncodeProgress(progress?: EncodeProgress | null): UseEncodeProgress {\n const [base, setBase] = useState(() => ({ at: progress?.at, received: Date.now() }));\n if (progress?.at !== base.at) setBase({ at: progress?.at, received: Date.now() });\n const [now, setNow] = useState(() => Date.now());\n const ticking = !!progress?.eta && !progress.stalled;\n useEffect(() => {\n if (!ticking) return;\n setNow(Date.now());\n const id = setInterval(() => setNow(Date.now()), 1000);\n return () => clearInterval(id);\n }, [ticking, base]);\n return { progress: progress ?? undefined, remaining: encodeRemaining(progress, base.received, Math.max(now, base.received)) };\n}\n"],"mappings":"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"}
|
|
@@ -5,6 +5,7 @@ import { Fetcher, Page, Resource, ResourceStore, Tag } from './resources.js';
|
|
|
5
5
|
export declare function useContentScope(own?: ContentKitClient | null): {
|
|
6
6
|
client: ContentKitClient;
|
|
7
7
|
store: ResourceStore;
|
|
8
|
+
scope: string;
|
|
8
9
|
viewer: string | null | undefined;
|
|
9
10
|
onSignIn: (() => void) | undefined;
|
|
10
11
|
};
|
|
@@ -1,42 +1,50 @@
|
|
|
1
|
-
import { ContentKitContext as e, useContentKitClient as t } from "./context.js";
|
|
2
|
-
import { resourcesFor as
|
|
3
|
-
import { useCallback as
|
|
1
|
+
import { ContentKitContext as e, useContentKitClient as t, useReadScope as n } from "./context.js";
|
|
2
|
+
import { resourcesFor as r } from "./resources.js";
|
|
3
|
+
import { useCallback as i, useContext as a, useEffect as o, useRef as s, useSyncExternalStore as c } from "react";
|
|
4
4
|
//#region src/react/use-resource.ts
|
|
5
|
-
function
|
|
6
|
-
let
|
|
5
|
+
function l(i) {
|
|
6
|
+
let o = a(e), s = t(i);
|
|
7
7
|
return {
|
|
8
|
-
client:
|
|
9
|
-
store:
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
client: s,
|
|
9
|
+
store: r(s),
|
|
10
|
+
scope: n(),
|
|
11
|
+
viewer: o?.viewer,
|
|
12
|
+
onSignIn: o?.onSignIn
|
|
12
13
|
};
|
|
13
14
|
}
|
|
14
|
-
var
|
|
15
|
+
var u = {
|
|
15
16
|
data: void 0,
|
|
16
17
|
loading: !1,
|
|
17
18
|
loaded: !1
|
|
18
|
-
},
|
|
19
|
-
function
|
|
20
|
-
let
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
}, d = () => () => {};
|
|
20
|
+
function f(e, t, r, a, l = {}) {
|
|
21
|
+
let f = n(), p = s(a), m = s(l.more), h = s(l.initial);
|
|
22
|
+
o(() => {
|
|
23
|
+
p.current = a, m.current = l.more, h.current = l.initial;
|
|
23
24
|
});
|
|
24
|
-
let
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
let g = i((n) => t ? e.subscribe(t, {
|
|
26
|
+
...r,
|
|
27
|
+
readScope: f
|
|
28
|
+
}, n) : d(), [
|
|
29
|
+
e,
|
|
30
|
+
t,
|
|
31
|
+
f
|
|
32
|
+
]), _ = i(() => t ? e.snapshot(t) : u, [e, t]), v = c(g, _, _);
|
|
33
|
+
o(() => {
|
|
34
|
+
t && (e.bind(t, (e) => p.current(e), m.current && ((e, t) => m.current(e, t))), h.current !== void 0 && !e.snapshot(t).loaded && !e.snapshot(t).loading ? e.set(t, h.current) : e.load(t));
|
|
27
35
|
}, [e, t]);
|
|
28
|
-
let
|
|
29
|
-
return !
|
|
30
|
-
...
|
|
31
|
-
data:
|
|
32
|
-
reload:
|
|
36
|
+
let y = i(() => t && e.load(t, !0), [e, t]);
|
|
37
|
+
return !v.loaded && l.initial !== void 0 ? {
|
|
38
|
+
...v,
|
|
39
|
+
data: l.initial,
|
|
40
|
+
reload: y
|
|
33
41
|
} : {
|
|
34
|
-
...
|
|
35
|
-
reload:
|
|
42
|
+
...v,
|
|
43
|
+
reload: y
|
|
36
44
|
};
|
|
37
45
|
}
|
|
38
|
-
function
|
|
39
|
-
let a =
|
|
46
|
+
function p(e, t, n, r) {
|
|
47
|
+
let a = f(e, t, n, (e) => r(void 0, e), { more: (e, t) => r(e, t) }), o = i(() => t ? e.loadMore(t) : Promise.resolve(), [e, t]);
|
|
40
48
|
return {
|
|
41
49
|
items: a.data?.items ?? [],
|
|
42
50
|
loading: a.loading || !!t && !a.loaded,
|
|
@@ -47,7 +55,7 @@ function f(e, t, n, i) {
|
|
|
47
55
|
reload: a.reload
|
|
48
56
|
};
|
|
49
57
|
}
|
|
50
|
-
function
|
|
58
|
+
function m(e, t, n = !1) {
|
|
51
59
|
return async (r, i) => {
|
|
52
60
|
let a = typeof r == "number" ? r : 0, o = await t({
|
|
53
61
|
limit: e,
|
|
@@ -59,8 +67,8 @@ function p(e, t, n = !1) {
|
|
|
59
67
|
};
|
|
60
68
|
};
|
|
61
69
|
}
|
|
62
|
-
var
|
|
70
|
+
var h = (...e) => JSON.stringify(e);
|
|
63
71
|
//#endregion
|
|
64
|
-
export {
|
|
72
|
+
export { h as keyOf, m as offsetPages, l as useContentScope, p as useList, f as useResource };
|
|
65
73
|
|
|
66
74
|
//# sourceMappingURL=use-resource.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-resource.js","names":[],"sources":["../../src/react/use-resource.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport { ContentKitContext, useContentKitClient } from \"./context.js\";\nimport { resourcesFor, type Fetcher, type Page, type Resource, type ResourceStore, type Tag } from \"./resources.js\";\n\n/** The client, its shared store, and the provider's viewer and sign-in. */\nexport function useContentScope(own?: ContentKitClient | null) {\n const ctx = useContext(ContentKitContext);\n const client = useContentKitClient(own);\n return { client, store: resourcesFor(client), viewer: ctx?.viewer, onSignIn: ctx?.onSignIn };\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\nconst none = () => () => {};\n\n/** A shared read: fetched once per key while shown; initial is shown (and seeds the store) until it loads. */\nexport function useResource<T>(\n store: ResourceStore,\n key: string | null,\n tag: Tag,\n fetcher: Fetcher<T>,\n o: { initial?: T; more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>> } = {},\n): Resource<T> & { reload: () => void } {\n const fetch = useRef(fetcher);\n const more = useRef(o.more);\n const initial = useRef(o.initial);\n useEffect(() => {\n fetch.current = fetcher;\n more.current = o.more;\n });\n const subscribe = useCallback(\n (l: () => void) => (key ? store.subscribe(key, tag, l) : none()),\n // The tag is part of the key.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [store, key],\n );\n const snapshot = useCallback(() => (key ? store.snapshot<T>(key) : IDLE), [store, key]);\n const entry = useSyncExternalStore(subscribe, snapshot, snapshot);\n useEffect(() => {\n if (!key) return;\n store.bind(key, (s) => fetch.current(s), more.current && ((c, s) => more.current!(c, s)));\n if (initial.current !== undefined && !store.snapshot(key).loaded && !store.snapshot(key).loading) store.set(key, initial.current);\n else store.load(key);\n }, [store, key]);\n const reload = useCallback(() => key && store.load(key, true), [store, key]);\n if (!entry.loaded && o.initial !== undefined) return { ...entry, data: o.initial, reload };\n return { ...entry, reload };\n}\n\n/** A shared list, a page at a time. */\nexport interface UseList<T> {\n items: T[];\n /** The first page is loading (also on reload). */\n loading: boolean;\n loadingMore: boolean;\n error?: ContentKitError;\n hasMore: boolean;\n loadMore: () => Promise<void>;\n reload: () => void;\n}\n\nexport type PageFetcher<T> = (cursor: string | number | undefined, signal: AbortSignal) => Promise<Page<T>>;\n\nexport function useList<T extends { id: string }>(store: ResourceStore, key: string | null, tag: Tag, page: PageFetcher<T>): UseList<T> {\n const r = useResource<Page<T>>(store, key, tag, (s) => page(undefined, s), { more: (c, s) => page(c, s) });\n const loadMore = useCallback(() => (key ? store.loadMore(key) : Promise.resolve()), [store, key]);\n return {\n items: r.data?.items ?? [],\n loading: r.loading || (!!key && !r.loaded),\n loadingMore: !!r.loadingMore,\n error: r.error,\n hasMore: r.data ? r.data.next !== null : false,\n loadMore,\n reload: r.reload,\n };\n}\n\n/**\n * A limit/offset list as pages. A full page means more may follow; with\n * underfill (the latest feed drops items the caller may not see) only an\n * empty page ends it.\n */\nexport function offsetPages<T>(limit: number, list: (q: { limit: number; offset: number }, signal: AbortSignal) => Promise<T[]>, underfill = false): PageFetcher<T> {\n return async (cursor, signal) => {\n const offset = typeof cursor === \"number\" ? cursor : 0;\n const items = await list({ limit, offset }, signal);\n const end = underfill ? items.length === 0 : items.length < limit;\n return { items, next: end ? null : offset + limit };\n };\n}\n\n/** A stable store key. */\nexport const keyOf = (...parts: unknown[]) => JSON.stringify(parts);\n"],"mappings":";;;;AAOA,SAAgB,EAAgB,GAA+B;CAC7D,IAAM,IAAM,EAAW,CAAiB,GAClC,IAAS,EAAoB,CAAG;CACtC,OAAO;EAAE;EAAQ,OAAO,EAAa,CAAM;EAAG,QAAQ,GAAK;EAAQ,UAAU,GAAK;CAAS;
|
|
1
|
+
{"version":3,"file":"use-resource.js","names":[],"sources":["../../src/react/use-resource.ts"],"sourcesContent":["import { useCallback, useContext, useEffect, useRef, useSyncExternalStore } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport type { ContentKitError } from \"../client/errors.js\";\nimport { ContentKitContext, useContentKitClient, useReadScope } from \"./context.js\";\nimport { resourcesFor, type Fetcher, type Page, type Resource, type ResourceStore, type Tag } from \"./resources.js\";\n\n/** The client, its shared store, and the provider's viewer and sign-in. */\nexport function useContentScope(own?: ContentKitClient | null) {\n const ctx = useContext(ContentKitContext);\n const client = useContentKitClient(own);\n return { client, store: resourcesFor(client), scope: useReadScope(), viewer: ctx?.viewer, onSignIn: ctx?.onSignIn };\n}\n\nconst IDLE: Resource<never> = { data: undefined, loading: false, loaded: false };\nconst none = () => () => {};\n\n/** A shared read: fetched once per key while shown; initial is shown (and seeds the store) until it loads. */\nexport function useResource<T>(\n store: ResourceStore,\n key: string | null,\n tag: Tag,\n fetcher: Fetcher<T>,\n o: { initial?: T; more?: (cursor: string | number, signal: AbortSignal) => Promise<Page<unknown>> } = {},\n): Resource<T> & { reload: () => void } {\n const scope = useReadScope();\n const fetch = useRef(fetcher);\n const more = useRef(o.more);\n const initial = useRef(o.initial);\n useEffect(() => {\n fetch.current = fetcher;\n more.current = o.more;\n initial.current = o.initial;\n });\n const subscribe = useCallback(\n (l: () => void) => (key ? store.subscribe(key, { ...tag, readScope: scope }, l) : none()),\n // The tag is part of the key.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [store, key, scope],\n );\n const snapshot = useCallback(() => (key ? store.snapshot<T>(key) : IDLE), [store, key]);\n const entry = useSyncExternalStore(subscribe, snapshot, snapshot);\n useEffect(() => {\n if (!key) return;\n store.bind(key, (s) => fetch.current(s), more.current && ((c, s) => more.current!(c, s)));\n if (initial.current !== undefined && !store.snapshot(key).loaded && !store.snapshot(key).loading) store.set(key, initial.current);\n else store.load(key);\n }, [store, key]);\n const reload = useCallback(() => key && store.load(key, true), [store, key]);\n if (!entry.loaded && o.initial !== undefined) return { ...entry, data: o.initial, reload };\n return { ...entry, reload };\n}\n\n/** A shared list, a page at a time. */\nexport interface UseList<T> {\n items: T[];\n /** The first page is loading (also on reload). */\n loading: boolean;\n loadingMore: boolean;\n error?: ContentKitError;\n hasMore: boolean;\n loadMore: () => Promise<void>;\n reload: () => void;\n}\n\nexport type PageFetcher<T> = (cursor: string | number | undefined, signal: AbortSignal) => Promise<Page<T>>;\n\nexport function useList<T extends { id: string }>(store: ResourceStore, key: string | null, tag: Tag, page: PageFetcher<T>): UseList<T> {\n const r = useResource<Page<T>>(store, key, tag, (s) => page(undefined, s), { more: (c, s) => page(c, s) });\n const loadMore = useCallback(() => (key ? store.loadMore(key) : Promise.resolve()), [store, key]);\n return {\n items: r.data?.items ?? [],\n loading: r.loading || (!!key && !r.loaded),\n loadingMore: !!r.loadingMore,\n error: r.error,\n hasMore: r.data ? r.data.next !== null : false,\n loadMore,\n reload: r.reload,\n };\n}\n\n/**\n * A limit/offset list as pages. A full page means more may follow; with\n * underfill (the latest feed drops items the caller may not see) only an\n * empty page ends it.\n */\nexport function offsetPages<T>(limit: number, list: (q: { limit: number; offset: number }, signal: AbortSignal) => Promise<T[]>, underfill = false): PageFetcher<T> {\n return async (cursor, signal) => {\n const offset = typeof cursor === \"number\" ? cursor : 0;\n const items = await list({ limit, offset }, signal);\n const end = underfill ? items.length === 0 : items.length < limit;\n return { items, next: end ? null : offset + limit };\n };\n}\n\n/** A stable store key. */\nexport const keyOf = (...parts: unknown[]) => JSON.stringify(parts);\n"],"mappings":";;;;AAOA,SAAgB,EAAgB,GAA+B;CAC7D,IAAM,IAAM,EAAW,CAAiB,GAClC,IAAS,EAAoB,CAAG;CACtC,OAAO;EAAE;EAAQ,OAAO,EAAa,CAAM;EAAG,OAAO,EAAa;EAAG,QAAQ,GAAK;EAAQ,UAAU,GAAK;CAAS;AACpH;AAEA,IAAM,IAAwB;CAAE,MAAM,KAAA;CAAW,SAAS;CAAO,QAAQ;AAAM,GACzE,gBAAmB,CAAC;AAG1B,SAAgB,EACd,GACA,GACA,GACA,GACA,IAAsG,CAAC,GACjE;CACtC,IAAM,IAAQ,EAAa,GACrB,IAAQ,EAAO,CAAO,GACtB,IAAO,EAAO,EAAE,IAAI,GACpB,IAAU,EAAO,EAAE,OAAO;CAChC,QAAgB;EAGd,AAFA,EAAM,UAAU,GAChB,EAAK,UAAU,EAAE,MACjB,EAAQ,UAAU,EAAE;CACtB,CAAC;CACD,IAAM,IAAY,GACf,MAAmB,IAAM,EAAM,UAAU,GAAK;EAAE,GAAG;EAAK,WAAW;CAAM,GAAG,CAAC,IAAI,EAAK,GAGvF;EAAC;EAAO;EAAK;CAAK,CACpB,GACM,IAAW,QAAmB,IAAM,EAAM,SAAY,CAAG,IAAI,GAAO,CAAC,GAAO,CAAG,CAAC,GAChF,IAAQ,EAAqB,GAAW,GAAU,CAAQ;CAChE,QAAgB;EACT,MACL,EAAM,KAAK,IAAM,MAAM,EAAM,QAAQ,CAAC,GAAG,EAAK,aAAa,GAAG,MAAM,EAAK,QAAS,GAAG,CAAC,EAAE,GACpF,EAAQ,YAAY,KAAA,KAAa,CAAC,EAAM,SAAS,CAAG,CAAC,CAAC,UAAU,CAAC,EAAM,SAAS,CAAG,CAAC,CAAC,UAAS,EAAM,IAAI,GAAK,EAAQ,OAAO,IAC3H,EAAM,KAAK,CAAG;CACrB,GAAG,CAAC,GAAO,CAAG,CAAC;CACf,IAAM,IAAS,QAAkB,KAAO,EAAM,KAAK,GAAK,EAAI,GAAG,CAAC,GAAO,CAAG,CAAC;CAE3E,OADI,CAAC,EAAM,UAAU,EAAE,YAAY,KAAA,IAAkB;EAAE,GAAG;EAAO,MAAM,EAAE;EAAS;CAAO,IAClF;EAAE,GAAG;EAAO;CAAO;AAC5B;AAgBA,SAAgB,EAAkC,GAAsB,GAAoB,GAAU,GAAkC;CACtI,IAAM,IAAI,EAAqB,GAAO,GAAK,IAAM,MAAM,EAAK,KAAA,GAAW,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,EAAK,GAAG,CAAC,EAAE,CAAC,GACnG,IAAW,QAAmB,IAAM,EAAM,SAAS,CAAG,IAAI,QAAQ,QAAQ,GAAI,CAAC,GAAO,CAAG,CAAC;CAChG,OAAO;EACL,OAAO,EAAE,MAAM,SAAS,CAAC;EACzB,SAAS,EAAE,WAAY,CAAC,CAAC,KAAO,CAAC,EAAE;EACnC,aAAa,CAAC,CAAC,EAAE;EACjB,OAAO,EAAE;EACT,SAAS,EAAE,OAAO,EAAE,KAAK,SAAS,OAAO;EACzC;EACA,QAAQ,EAAE;CACZ;AACF;AAOA,SAAgB,EAAe,GAAe,GAAmF,IAAY,IAAuB;CAClK,OAAO,OAAO,GAAQ,MAAW;EAC/B,IAAM,IAAS,OAAO,KAAW,WAAW,IAAS,GAC/C,IAAQ,MAAM,EAAK;GAAE;GAAO;EAAO,GAAG,CAAM;EAElD,OAAO;GAAE;GAAO,OADJ,IAAY,EAAM,WAAW,IAAI,EAAM,SAAS,KAChC,OAAO,IAAS;EAAM;CACpD;AACF;AAGA,IAAa,KAAS,GAAG,MAAqB,KAAK,UAAU,CAAK"}
|
package/dist/react.js
CHANGED
|
@@ -2,20 +2,20 @@ import { HOVER_DELAY as e, InlinePreviewContext as t, useInlinePreview as n, use
|
|
|
2
2
|
import { useContentKit as i, useContentKitClient as a, useContentURLs as o, useErrorReporter as s, useOptionalContentKitClient as c } from "./react/context.js";
|
|
3
3
|
import { useCrop as l, useEncodeProgress as u, useUpload as d, useUploadQueue as f } from "./react/upload.js";
|
|
4
4
|
import { useMediaRead as p } from "./react/read.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
5
|
+
import { usePresets as m, usePublicImage as h } from "./react/public.js";
|
|
6
|
+
import { editOutput as g, useSlotCrop as _, useSlotImage as v } from "./react/slot.js";
|
|
7
|
+
import { round3 as y, useFrameStrip as b, useVideoFrame as x, useVideoImages as S, useVideoPoster as C } from "./react/video.js";
|
|
8
|
+
import { GALLERY_VIEW_KEY as w, PLAYER_QUALITY_KEY as T, useCarousel as E, useGalleryView as D, useHlsPlayer as O, useRefreshBeforeExpiry as k } from "./react/gallery.js";
|
|
9
|
+
import { useEditorCrop as A } from "./react/crop.js";
|
|
10
|
+
import { nameIn as j, thumbnailOf as M, useMediaFolder as N } from "./react/folder.js";
|
|
11
|
+
import { useNearViewport as P } from "./react/viewport.js";
|
|
12
|
+
import { ResourceStore as F, resourcesFor as I } from "./react/resources.js";
|
|
13
|
+
import { useContentScope as L } from "./react/use-resource.js";
|
|
14
|
+
import { useAdminComments as R, useCanComment as z, useCommentBans as B, useCommentReplies as V, useComments as H, useLatestComments as U, useModerationQueue as W, withReaction as G } from "./react/comments.js";
|
|
15
|
+
import { useContentConfig as K } from "./react/config.js";
|
|
16
|
+
import { useFavorite as q, useReaction as J } from "./react/engagement.js";
|
|
17
|
+
import { usePoll as Y, usePollEditor as X, usePolls as Z, withVote as Q } from "./react/polls.js";
|
|
18
|
+
import { ContentKitProvider as $ } from "./react/provider.js";
|
|
19
19
|
import { useCanonicalContent as ee } from "./react/canonical.js";
|
|
20
20
|
import { isPublished as te, usePost as ne, usePosts as re } from "./react/posts.js";
|
|
21
|
-
export {
|
|
21
|
+
export { $ as ContentKitProvider, w as GALLERY_VIEW_KEY, e as HOVER_DELAY, t as InlinePreviewContext, T as PLAYER_QUALITY_KEY, F as ResourceStore, g as editOutput, te as isPublished, j as nameIn, I as resourcesFor, y as round3, M as thumbnailOf, R as useAdminComments, z as useCanComment, ee as useCanonicalContent, E as useCarousel, B as useCommentBans, V as useCommentReplies, H as useComments, K as useContentConfig, i as useContentKit, a as useContentKitClient, L as useContentScope, o as useContentURLs, l as useCrop, A as useEditorCrop, u as useEncodeProgress, s as useErrorReporter, q as useFavorite, b as useFrameStrip, D as useGalleryView, O as useHlsPlayer, n as useInlinePreview, U as useLatestComments, N as useMediaFolder, p as useMediaRead, W as useModerationQueue, P as useNearViewport, c as useOptionalContentKitClient, Y as usePoll, X as usePollEditor, Z as usePolls, ne as usePost, re as usePosts, m as usePresets, h as usePublicImage, J as useReaction, r as useReducedMotion, k as useRefreshBeforeExpiry, _ as useSlotCrop, v as useSlotImage, d as useUpload, f as useUploadQueue, x as useVideoFrame, S as useVideoImages, C as useVideoPoster, G as withReaction, Q as withVote };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openrails/contentkit-ui",
|
|
3
|
-
"version": "0.68.
|
|
3
|
+
"version": "0.68.1",
|
|
4
4
|
"description": "ContentKit in the browser: client, React hooks and styled components for media, uploads and content URLs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@eslint/js": "^10.0.1",
|
|
84
|
+
"@openrails/auth-ui": "1.15.0",
|
|
84
85
|
"@playwright/test": "1.60.0",
|
|
85
86
|
"@tailwindcss/vite": "4.3.3",
|
|
86
87
|
"@testing-library/dom": "10.4.2",
|
|
@@ -113,9 +114,10 @@
|
|
|
113
114
|
"contract:check": "pnpm contract && git diff --exit-code -- src/client/generated ../../api ../../docs/api && test -z \"$(git status --porcelain -- src/client/generated ../../api ../../docs/api)\"",
|
|
114
115
|
"lint": "eslint .",
|
|
115
116
|
"test": "vitest run --project unit",
|
|
116
|
-
"test:integration": "vitest run --project integration",
|
|
117
|
-
"demo": "
|
|
118
|
-
"
|
|
119
|
-
"
|
|
117
|
+
"test:integration": "vitest run --project integration && vitest run --project integration-dom",
|
|
118
|
+
"demo": "node e2e/support/up.ts",
|
|
119
|
+
"check": "pnpm typecheck && pnpm lint && pnpm test && pnpm build",
|
|
120
|
+
"test:browser": "playwright test",
|
|
121
|
+
"test:e2e": "node e2e/support/run.ts"
|
|
120
122
|
}
|
|
121
123
|
}
|