@openrails/contentkit-ui 0.103.2 → 0.104.0-rc.2
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 +111 -49
- package/dist/client/beacons.d.ts +3 -13
- package/dist/client/beacons.js +8 -46
- package/dist/client/beacons.js.map +1 -1
- package/dist/client/client.d.ts +3 -0
- package/dist/client/client.js +45 -43
- package/dist/client/client.js.map +1 -1
- package/dist/client/content/favorites.d.ts +2 -0
- package/dist/client/content/favorites.js.map +1 -1
- package/dist/client/content/types.d.ts +3 -0
- package/dist/client/content/types.js.map +1 -1
- package/dist/client/contents.d.ts +7 -1
- package/dist/client/contents.js +8 -0
- package/dist/client/contents.js.map +1 -1
- package/dist/client/discovery.d.ts +34 -5
- package/dist/client/discovery.js +23 -2
- package/dist/client/discovery.js.map +1 -1
- package/dist/client/engagement.d.ts +70 -0
- package/dist/client/engagement.js +191 -0
- package/dist/client/engagement.js.map +1 -0
- package/dist/client/generated/routes.d.ts +74 -35
- package/dist/client/generated/routes.js +119 -60
- package/dist/client/generated/routes.js.map +1 -1
- package/dist/client/generated/wire.d.ts +88 -85
- package/dist/client/generated/wire.js.map +1 -1
- package/dist/client/guest-reading-history.d.ts +40 -0
- package/dist/client/guest-reading-history.js +144 -0
- package/dist/client/guest-reading-history.js.map +1 -0
- package/dist/client/index.d.ts +5 -3
- package/dist/client/media/windows.d.ts +2 -0
- package/dist/client/media/windows.js +4 -1
- package/dist/client/media/windows.js.map +1 -1
- package/dist/client/stats.d.ts +4 -20
- package/dist/client/stats.js +0 -11
- package/dist/client/stats.js.map +1 -1
- package/dist/client/versions.d.ts +8 -6
- package/dist/client/versions.js +15 -10
- package/dist/client/versions.js.map +1 -1
- package/dist/client.js +24 -22
- package/dist/components/admin/bulk-drafts.d.ts +17 -0
- package/dist/components/admin/bulk-drafts.js +41 -0
- package/dist/components/admin/bulk-drafts.js.map +1 -0
- package/dist/components/admin/content-admin.d.ts +1 -1
- package/dist/components/admin/content-admin.js.map +1 -1
- package/dist/components/admin/content-manager.d.ts +11 -3
- package/dist/components/admin/content-manager.js +751 -394
- package/dist/components/admin/content-manager.js.map +1 -1
- package/dist/components/admin/group-manager.d.ts +8 -3
- package/dist/components/admin/group-manager.js +218 -146
- package/dist/components/admin/group-manager.js.map +1 -1
- package/dist/components/admin/page-notices.d.ts +16 -0
- package/dist/components/admin/page-notices.js +113 -0
- package/dist/components/admin/page-notices.js.map +1 -0
- package/dist/components/admin/page-picker.d.ts +38 -0
- package/dist/components/admin/page-picker.js +159 -0
- package/dist/components/admin/page-picker.js.map +1 -0
- package/dist/components/admin/quick-search.d.ts +14 -0
- package/dist/components/admin/quick-search.js +60 -0
- package/dist/components/admin/quick-search.js.map +1 -0
- package/dist/components/admin/reader-content-editor.js +382 -321
- package/dist/components/admin/reader-content-editor.js.map +1 -1
- package/dist/components/admin/reader-manifest.js +181 -179
- package/dist/components/admin/reader-manifest.js.map +1 -1
- package/dist/components/admin/shell.d.ts +10 -2
- package/dist/components/admin/shell.js +114 -42
- package/dist/components/admin/shell.js.map +1 -1
- package/dist/components/admin/stats-dashboard.d.ts +4 -9
- package/dist/components/admin/stats-dashboard.js +303 -207
- package/dist/components/admin/stats-dashboard.js.map +1 -1
- package/dist/components/admin/upload-queue.d.ts +14 -0
- package/dist/components/admin/upload-queue.js +115 -98
- package/dist/components/admin/upload-queue.js.map +1 -1
- package/dist/components/content/card.d.ts +31 -3
- package/dist/components/content/card.js +324 -158
- package/dist/components/content/card.js.map +1 -1
- package/dist/components/content/daily-releases.js +144 -190
- package/dist/components/content/daily-releases.js.map +1 -1
- package/dist/components/content/editor-save.js +27 -25
- package/dist/components/content/editor-save.js.map +1 -1
- package/dist/components/content/editors.js +274 -253
- package/dist/components/content/editors.js.map +1 -1
- package/dist/components/content/favorites.d.ts +6 -0
- package/dist/components/content/favorites.js +47 -0
- package/dist/components/content/favorites.js.map +1 -0
- package/dist/components/content/filter-dialog.js +19 -15
- package/dist/components/content/filter-dialog.js.map +1 -1
- package/dist/components/content/filters.d.ts +2 -0
- package/dist/components/content/filters.js +5 -1
- package/dist/components/content/filters.js.map +1 -1
- package/dist/components/content/grid.d.ts +44 -16
- package/dist/components/content/grid.js +308 -270
- package/dist/components/content/grid.js.map +1 -1
- package/dist/components/content/history.d.ts +6 -0
- package/dist/components/content/history.js +111 -0
- package/dist/components/content/history.js.map +1 -0
- package/dist/components/content/index.d.ts +6 -3
- package/dist/components/content/listing.d.ts +14 -3
- package/dist/components/content/listing.js +65 -56
- package/dist/components/content/listing.js.map +1 -1
- package/dist/components/content/offer-fields.js +6 -6
- package/dist/components/content/paged-grid.d.ts +10 -0
- package/dist/components/content/paged-grid.js +116 -0
- package/dist/components/content/paged-grid.js.map +1 -0
- package/dist/components/content/pagination.d.ts +19 -0
- package/dist/components/content/pagination.js +97 -0
- package/dist/components/content/pagination.js.map +1 -0
- package/dist/components/content/paywall.d.ts +9 -0
- package/dist/components/content/paywall.js +27 -3
- package/dist/components/content/paywall.js.map +1 -1
- package/dist/components/content/post-list.d.ts +37 -0
- package/dist/components/content/post-list.js +203 -0
- package/dist/components/content/post-list.js.map +1 -0
- package/dist/components/content/reader-settings.d.ts +2 -0
- package/dist/components/content/reader-settings.js +1 -1
- package/dist/components/content/reader-settings.js.map +1 -1
- package/dist/components/content/search.d.ts +11 -4
- package/dist/components/content/search.js +22 -11
- package/dist/components/content/search.js.map +1 -1
- package/dist/components/content/summary-tile.d.ts +14 -0
- package/dist/components/content/summary-tile.js +63 -0
- package/dist/components/content/summary-tile.js.map +1 -0
- package/dist/components/groups/banner.d.ts +6 -2
- package/dist/components/groups/banner.js +79 -61
- package/dist/components/groups/banner.js.map +1 -1
- package/dist/components/groups/directory.d.ts +12 -2
- package/dist/components/groups/directory.js +168 -105
- package/dist/components/groups/directory.js.map +1 -1
- package/dist/components/groups/editor.js +10 -10
- package/dist/components/groups/editor.js.map +1 -1
- package/dist/components/groups/entity-chips.d.ts +61 -0
- package/dist/components/groups/entity-chips.js +382 -0
- package/dist/components/groups/entity-chips.js.map +1 -0
- package/dist/components/groups/group-members.d.ts +29 -0
- package/dist/components/groups/group-members.js +474 -0
- package/dist/components/groups/group-members.js.map +1 -0
- package/dist/components/groups/header.js +53 -45
- package/dist/components/groups/header.js.map +1 -1
- package/dist/components/groups/index.d.ts +3 -1
- package/dist/components/groups/memberships.d.ts +3 -36
- package/dist/components/groups/memberships.js +105 -480
- package/dist/components/groups/memberships.js.map +1 -1
- package/dist/components/groups/scoped.d.ts +23 -0
- package/dist/components/groups/scoped.js +92 -0
- package/dist/components/groups/scoped.js.map +1 -0
- package/dist/components/media-folder-editor.d.ts +5 -0
- package/dist/components/media-folder-editor.js +419 -231
- package/dist/components/media-folder-editor.js.map +1 -1
- package/dist/components/sortable-list.d.ts +5 -1
- package/dist/components/sortable-list.js +97 -81
- package/dist/components/sortable-list.js.map +1 -1
- package/dist/components/video-mini-player.d.ts +4 -1
- package/dist/components/video-mini-player.js +77 -71
- package/dist/components/video-mini-player.js.map +1 -1
- package/dist/components/video-player.d.ts +4 -1
- package/dist/components/video-player.js +280 -276
- package/dist/components/video-player.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +41 -34
- package/dist/locales/admin/de.js +80 -47
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +74 -41
- package/dist/locales/admin/en.js +80 -47
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +80 -47
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +80 -47
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +80 -47
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +80 -47
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/de.js +1 -0
- package/dist/locales/de.js.map +1 -1
- package/dist/locales/discovery/de.js +25 -2
- package/dist/locales/discovery/de.js.map +1 -1
- package/dist/locales/discovery/en.d.ts +24 -1
- package/dist/locales/discovery/en.js +25 -2
- package/dist/locales/discovery/en.js.map +1 -1
- package/dist/locales/discovery/es.js +25 -2
- package/dist/locales/discovery/es.js.map +1 -1
- package/dist/locales/discovery/ja.js +25 -2
- package/dist/locales/discovery/ja.js.map +1 -1
- package/dist/locales/discovery/ko.js +25 -2
- package/dist/locales/discovery/ko.js.map +1 -1
- package/dist/locales/discovery/zh.js +25 -2
- package/dist/locales/discovery/zh.js.map +1 -1
- package/dist/locales/en.d.ts +119 -44
- package/dist/locales/en.js +1 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +1 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/locales/groups/de.js +22 -4
- package/dist/locales/groups/de.js.map +1 -1
- package/dist/locales/groups/en.d.ts +20 -2
- package/dist/locales/groups/en.js +22 -4
- package/dist/locales/groups/en.js.map +1 -1
- package/dist/locales/groups/es.js +22 -4
- package/dist/locales/groups/es.js.map +1 -1
- package/dist/locales/groups/ja.js +22 -4
- package/dist/locales/groups/ja.js.map +1 -1
- package/dist/locales/groups/ko.js +22 -4
- package/dist/locales/groups/ko.js.map +1 -1
- package/dist/locales/groups/zh.js +22 -4
- package/dist/locales/groups/zh.js.map +1 -1
- package/dist/locales/ja.js +1 -0
- package/dist/locales/ja.js.map +1 -1
- package/dist/locales/ko.js +1 -0
- package/dist/locales/ko.js.map +1 -1
- package/dist/locales/zh.js +1 -0
- package/dist/locales/zh.js.map +1 -1
- package/dist/react/discovery.d.ts +36 -17
- package/dist/react/discovery.js +305 -221
- package/dist/react/discovery.js.map +1 -1
- package/dist/react/groups.d.ts +19 -3
- package/dist/react/groups.js +49 -22
- package/dist/react/groups.js.map +1 -1
- package/dist/react/index.d.ts +7 -4
- package/dist/react/page-notices.d.ts +13 -0
- package/dist/react/page-notices.js +38 -0
- package/dist/react/page-notices.js.map +1 -0
- package/dist/react/post-reads.d.ts +23 -0
- package/dist/react/post-reads.js +90 -0
- package/dist/react/post-reads.js.map +1 -0
- package/dist/react/remembered-sort.d.ts +5 -0
- package/dist/react/remembered-sort.js +29 -0
- package/dist/react/remembered-sort.js.map +1 -0
- package/dist/react/resources.d.ts +3 -1
- package/dist/react/resources.js +6 -2
- package/dist/react/resources.js.map +1 -1
- package/dist/react/stats.d.ts +3 -7
- package/dist/react/stats.js +2 -6
- package/dist/react/stats.js.map +1 -1
- package/dist/react/use-engagement.d.ts +28 -0
- package/dist/react/use-engagement.js +226 -0
- package/dist/react/use-engagement.js.map +1 -0
- package/dist/react/use-resource.d.ts +4 -3
- package/dist/react/use-resource.js +2 -1
- package/dist/react/use-resource.js.map +1 -1
- package/dist/react.js +29 -26
- package/dist/styles.css +1 -1
- package/dist/ui/dialog.d.ts +2 -1
- package/dist/ui/dialog.js +23 -20
- package/dist/ui/dialog.js.map +1 -1
- package/dist/urls/index.d.ts +1 -1
- package/dist/urls.js +2 -2
- package/dist/urls.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/admin/page-review.d.ts +0 -13
- package/dist/components/admin/page-review.js +0 -225
- package/dist/components/admin/page-review.js.map +0 -1
- package/dist/react/page-review.d.ts +0 -14
- package/dist/react/page-review.js +0 -37
- package/dist/react/page-review.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stats-dashboard.js","names":[],"sources":["../../../src/components/admin/stats-dashboard.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentKitError } from \"../../client/errors.js\";\nimport type { Period } from \"../../client/discovery.js\";\nimport type { RankedContent } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useActivity, useTopContents } from \"../../react/stats.js\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\ntype Metric = \"views\" | \"comments\";\n\nexport interface StatsDashboardProps extends AdminFlowProps {\n /** Content kinds offered: codes, or codes with labels; the first shows first. */\n kinds: readonly Choice[];\n /** A ranked item's link; default its path. */\n contentHref?: (content: RankedContent) => string | undefined;\n /** Rows in the ranking. Default 10. */\n limit?: number;\n}\n\nconst choices = (list: readonly Choice[]) => list.map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst DAYS = [7, 30, 90] as const;\nconst METRICS: readonly Metric[] = [\"views\", \"comments\"];\nconst PERIODS: readonly Period[] = [\"7d\", \"30d\", \"90d\", \"365d\", \"all\"];\nconst refused = (e?: ContentKitError) => e?.code === \"forbidden\" || e?.code === \"unauthorized\";\n\n/**\n * Content statistics for staff (root:content:view-restricted): a kind's views\n * per day (view sessions, distinct and signed-in viewers) and its content\n * ranked by views or comments over a period. Views need the signal plane;\n * without it the dashboard says so and still ranks by comments.\n */\nexport function StatsDashboard({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: StatsDashboardProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"stats-dashboard\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Dashboard {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<StatsDashboardProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Dashboard({ kinds, contentHref, limit = 10, client }: Inner) {\n const { t } = useMessages();\n const options = choices(kinds);\n const [kind, setKind] = useState(options[0]?.value ?? \"\");\n const [days, setDays] = useState<number>(7);\n const [metric, setMetric] = useState<Metric>(\"views\");\n const [period, setPeriod] = useState<Period>(\"30d\");\n const activity = useActivity(kind ? { kind, days } : null, { client });\n const top = useTopContents(kind ? { kind, metric, period, limit } : null, { client });\n if (refused(activity.error) || refused(top.error)) return <NoAccess>{t(\"stats.noAccess\")}</NoAccess>;\n const rows = activity.data?.days ?? [];\n const most = Math.max(1, ...rows.map((d) => d.views));\n const total = rows.reduce((n, d) => n + d.views, 0);\n return (\n <>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"stats.title\")}</h2>\n {options.length > 1 && (\n <Pick label={t(\"stats.kind\")} value={kind} onChange={setKind} options={options} />\n )}\n </div>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-activity\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"font-medium\">{t(\"stats.activity\")}</h3>\n <Pick label={t(\"stats.days\")} value={String(days)} onChange={(v) => setDays(Number(v))} options={DAYS.map((d) => ({ value: String(d), label: t(\"stats.lastDays\", { count: d }) }))} />\n </div>\n {activity.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : activity.error ? (\n <LoadFailed error={activity.error} message={t(\"stats.loadFailed\")} retry={activity.reload} />\n ) : activity.loading && !activity.data ? (\n <Rows count={2} />\n ) : !rows.length ? (\n <p className=\"text-muted-foreground\">{t(\"stats.noActivity\")}</p>\n ) : (\n <table className=\"w-full text-left tabular-nums\" aria-busy={activity.loading}>\n <thead className=\"text-xs text-muted-foreground\">\n <tr>\n <th className=\"py-1 font-medium\">{t(\"stats.date\")}</th>\n <th className=\"py-1 font-medium\">{t(\"stats.views\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.viewers\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.users\")}</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((d) => (\n <tr key={d.date} className=\"border-t border-border\" data-ckui=\"stats-day\">\n <td className=\"py-1.5 pe-3 whitespace-nowrap\">{d.date}</td>\n <td className=\"w-1/2 py-1.5 pe-3\">\n <span className=\"flex items-center gap-2\">\n <span aria-hidden className=\"h-2 rounded-full bg-primary\" style={{ width: `${Math.max(2, (d.views / most) * 100)}%` }} />\n {d.views}\n </span>\n </td>\n <td className=\"py-1.5 text-end\">{d.viewers}</td>\n <td className=\"py-1.5 text-end\">{d.users}</td>\n </tr>\n ))}\n </tbody>\n <tfoot>\n <tr className=\"border-t border-border font-medium\">\n <td className=\"py-1.5\">{t(\"stats.total\")}</td>\n <td className=\"py-1.5\" data-ckui=\"stats-total\">\n {total}\n </td>\n <td />\n <td />\n </tr>\n </tfoot>\n </table>\n )}\n </section>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-top\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h3 className=\"font-medium\">{t(\"stats.top\")}</h3>\n <div className=\"flex flex-wrap gap-2\">\n <Pick label={t(\"stats.metric\")} value={metric} onChange={(v) => setMetric(v as Metric)} options={METRICS.map((x) => ({ value: x, label: t(`stats.metric_${x}`) }))} />\n <Pick label={t(\"stats.period\")} value={period} onChange={(v) => setPeriod(v as Period)} options={PERIODS.map((x) => ({ value: x, label: t(`stats.period_${x}`) }))} />\n </div>\n </div>\n {top.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : top.error ? (\n <LoadFailed error={top.error} message={t(\"stats.loadFailed\")} retry={top.reload} />\n ) : top.loading && !top.data ? (\n <Rows count={3} />\n ) : !top.data?.contents.length ? (\n <p className=\"text-muted-foreground\">{t(\"stats.noTop\")}</p>\n ) : (\n <ol className=\"grid divide-y divide-border rounded-lg border border-border\" aria-busy={top.loading}>\n {top.data.contents.map((c, i) => {\n const href = contentHref ? contentHref(c) : c.path;\n return (\n <li key={c.id} className=\"flex items-center gap-3 p-2.5\" data-ckui=\"stats-ranked\">\n <span className=\"w-6 text-end text-muted-foreground tabular-nums\">{i + 1}</span>\n {href ? (\n <a className=\"min-w-0 flex-1 break-words hover:underline\" href={href}>\n {c.title}\n </a>\n ) : (\n <span className=\"min-w-0 flex-1 break-words\">{c.title}</span>\n )}\n <span className=\"tabular-nums\" data-ckui=\"stats-value\">\n {c.value}\n </span>\n </li>\n );\n })}\n </ol>\n )}\n </section>\n </>\n );\n}\n\nfunction Pick({ label, value, onChange, options }: { label: string; value: string; onChange: (v: string) => void; options: { value: string; label: string }[] }) {\n return (\n <Select value={value} onValueChange={(v) => onChange(String(v))}>\n <SelectTrigger size=\"sm\" aria-label={label}>\n <SelectValue>{(v: string) => options.find((o) => o.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"stats-dashboard.js","names":[],"sources":["../../../src/components/admin/stats-dashboard.tsx"],"sourcesContent":["import { cn } from \"cn\";\nimport { useState } from \"react\";\nimport type { ContentKitClient } from \"../../client/client.js\";\nimport type { ContentKitError } from \"../../client/errors.js\";\nimport { formatDuration } from \"../../client/gallery.js\";\nimport type { ActivityDay } from \"../../client/generated/wire.js\";\nimport { useMessages } from \"../../i18n/context.js\";\nimport { useFlowClient } from \"../../react/admin.js\";\nimport { useActivity } from \"../../react/stats.js\";\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from \"#ckui/ui/select\";\nimport { AdminRoot, LoadFailed, NoAccess, Rows, type AdminFlowProps } from \"./shell.js\";\n\ntype Choice = string | { value: string; label: string };\n\nexport interface StatsDashboardProps extends AdminFlowProps {\n /** Content kinds offered: codes, or codes with labels; the first shows first. */\n kinds: readonly Choice[];\n}\n\nconst choices = (list: readonly Choice[]) => list.map((c) => (typeof c === \"string\" ? { value: c, label: c } : c));\nconst DAYS = [10, 30, 90] as const;\nconst refused = (e?: ContentKitError) => e?.code === \"forbidden\" || e?.code === \"unauthorized\";\n\n// Validated categorical slots (light / dark steps), stacked bottom to top.\nconst AUDIENCES = [\n { key: \"anon\", fill: \"bg-[#2a78d6] dark:bg-[#3987e5]\" },\n { key: \"free\", fill: \"bg-[#eb6834] dark:bg-[#d95926]\" },\n { key: \"premium\", fill: \"bg-[#1baf7a] dark:bg-[#199e70]\" },\n] as const;\n\n/**\n * Staff statistics from measured attention: distinct people per UTC day who\n * read or played at least one item, by audience (anonymous, free, premium).\n * People are distinct within a day only, so days are never summed into a\n * period-unique total.\n */\nexport function StatsDashboard({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: StatsDashboardProps) {\n const client = useFlowClient({ client: own, baseUrl, auth });\n return (\n <AdminRoot flow=\"stats-dashboard\" appearance={appearance} className={className} language={language} messages={messages} guard={guard}>\n <Dashboard {...p} client={client} />\n </AdminRoot>\n );\n}\n\ntype Inner = Omit<StatsDashboardProps, \"client\" | \"baseUrl\" | \"auth\" | \"guard\" | \"appearance\" | \"className\" | \"language\" | \"messages\"> & { client: ContentKitClient };\n\nfunction Dashboard({ kinds, client }: Inner) {\n const { t } = useMessages();\n const options = choices(kinds);\n const [kind, setKind] = useState(options[0]?.value ?? \"\");\n const [days, setDays] = useState<number>(DAYS[0]);\n const activity = useActivity(kind ? { kind, days } : null, { client });\n if (refused(activity.error)) return <NoAccess>{t(\"stats.noAccess\")}</NoAccess>;\n // Newest first from the server; the chart reads oldest to newest.\n const rows = [...(activity.data?.days ?? [])].reverse();\n return (\n <>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <h2 className=\"text-base font-semibold\">{t(\"stats.title\")}</h2>\n {options.length > 1 && <Pick label={t(\"stats.kind\")} value={kind} onChange={setKind} options={options} />}\n </div>\n\n <section className=\"grid gap-3\" data-ckui=\"stats-activity\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"grid gap-0.5\">\n <h3 className=\"font-medium\">{t(\"stats.activity\")}</h3>\n <p className=\"text-xs text-muted-foreground\">{t(\"stats.visitorsNote\")}</p>\n </div>\n <Pick\n label={t(\"stats.days\")}\n value={String(days)}\n onChange={(v) => setDays(Number(v))}\n options={DAYS.map((d) => ({ value: String(d), label: t(\"stats.lastDays\", { count: d }) }))}\n />\n </div>\n {activity.unavailable ? (\n <p className=\"text-muted-foreground\" role=\"status\">\n {t(\"stats.unavailable\")}\n </p>\n ) : activity.error ? (\n <LoadFailed error={activity.error} message={t(\"stats.loadFailed\")} retry={activity.reload} />\n ) : activity.loading && !activity.data ? (\n <Rows count={3} />\n ) : (\n <div className=\"grid gap-4\" aria-busy={activity.loading}>\n <Today day={rows.at(-1)} />\n <Chart rows={rows} />\n <DayTable rows={rows} />\n </div>\n )}\n </section>\n </>\n );\n}\n\nfunction Today({ day }: { day?: ActivityDay }) {\n const { t } = useMessages();\n const tiles = [\n { label: t(\"stats.visitors\"), value: day?.readers ?? 0, fill: undefined },\n ...AUDIENCES.map((a) => ({ label: t(`stats.${a.key}`), value: day?.[a.key] ?? 0, fill: a.fill })),\n ];\n return (\n <div className=\"grid gap-2\" data-ckui=\"stats-today\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {t(\"stats.today\")}\n {day && day.active_ms > 0 && (\n <span className=\"font-normal\">\n {\" · \"}\n {t(\"stats.activeTime\")} {formatDuration(Math.floor(day.active_ms / 1000))}\n </span>\n )}\n </p>\n <dl className=\"grid grid-cols-2 gap-2 sm:grid-cols-4\">\n {tiles.map((tile) => (\n <div key={tile.label} className=\"grid gap-0.5 rounded-lg border border-border p-3\">\n <dt className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n {tile.fill && <span aria-hidden className={cn(\"size-2.5 rounded-sm\", tile.fill)} />}\n {tile.label}\n </dt>\n <dd className=\"text-xl font-semibold tabular-nums\">{tile.value}</dd>\n </div>\n ))}\n </dl>\n </div>\n );\n}\n\nfunction Chart({ rows }: { rows: ActivityDay[] }) {\n const m = useMessages();\n const { t } = m;\n const [active, setActive] = useState<number | null>(null);\n const most = Math.max(0, ...rows.map((d) => d.readers));\n const scale = most || 1;\n const every = Math.ceil(rows.length / 10);\n const short = new Intl.DateTimeFormat(m.language, { month: \"short\", day: \"numeric\", timeZone: \"UTC\" });\n const long = new Intl.DateTimeFormat(m.language, { dateStyle: \"medium\", timeZone: \"UTC\" });\n const at = (date: string) => new Date(`${date}T00:00:00Z`);\n const shown = active === null ? undefined : rows[active];\n return (\n <figure className=\"grid gap-2\" data-ckui=\"stats-chart\">\n <figcaption className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-muted-foreground\">\n {AUDIENCES.map((a) => (\n <span key={a.key} className=\"flex items-center gap-1.5\">\n <span aria-hidden className={cn(\"size-2.5 rounded-sm\", a.fill)} />\n {t(`stats.${a.key}`)}\n </span>\n ))}\n <span className=\"ms-auto tabular-nums\">{t(\"stats.peak\", { count: most })}</span>\n </figcaption>\n <div className=\"relative\">\n <div className=\"pointer-events-none absolute inset-x-0 top-0 border-t border-dashed border-border\" aria-hidden />\n <div className=\"pointer-events-none absolute inset-x-0 top-1/2 border-t border-dashed border-border/60\" aria-hidden />\n <ol className=\"relative flex h-48 items-end gap-1 border-b border-border\" onMouseLeave={() => setActive(null)}>\n {rows.map((d, i) => (\n <li\n key={d.date}\n tabIndex={0}\n data-ckui=\"stats-day\"\n aria-label={`${long.format(at(d.date))}: ${t(\"stats.visitors\")} ${d.readers}, ${t(\"stats.anon\")} ${d.anon}, ${t(\"stats.free\")} ${d.free}, ${t(\"stats.premium\")} ${d.premium}`}\n className=\"flex h-full min-w-0 flex-1 cursor-default flex-col justify-end rounded-sm outline-none hover:bg-muted/40 focus-visible:bg-muted/40 focus-visible:ring-1 focus-visible:ring-ring\"\n onMouseEnter={() => setActive(i)}\n onFocus={() => setActive(i)}\n onBlur={() => setActive(null)}\n >\n {/* 2px surface gaps between segments; the top segment carries the 4px rounded end. */}\n <span className=\"mx-auto flex w-full max-w-10 flex-col-reverse gap-[2px]\" style={{ height: `${(d.readers / scale) * 100}%` }}>\n {AUDIENCES.map((a, j) => {\n const value = d[a.key];\n if (!value) return null;\n const top = AUDIENCES.slice(j + 1).every((b) => !d[b.key]);\n return <span key={a.key} className={cn(\"min-h-[2px] w-full\", a.fill, top && \"rounded-t-[4px]\")} style={{ flexGrow: value, flexBasis: 0 }} />;\n })}\n </span>\n </li>\n ))}\n </ol>\n {shown && active !== null && (\n <div\n role=\"tooltip\"\n className=\"pointer-events-none absolute -top-2 z-10 grid min-w-36 -translate-y-full gap-1 rounded-md border border-border bg-popover p-2 text-xs text-popover-foreground shadow-md\"\n style={{ left: `clamp(0%, ${((active + 0.5) / rows.length) * 100}% - 4.5rem, calc(100% - 9rem))` }}\n >\n <p className=\"font-medium\">{long.format(at(shown.date))}</p>\n <p className=\"flex justify-between gap-3 tabular-nums\">\n <span>{t(\"stats.visitors\")}</span>\n <span className=\"font-semibold\">{shown.readers}</span>\n </p>\n {[...AUDIENCES].reverse().map((a) => (\n <p key={a.key} className=\"flex items-center justify-between gap-3 tabular-nums text-muted-foreground\">\n <span className=\"flex items-center gap-1.5\">\n <span aria-hidden className={cn(\"size-2 rounded-sm\", a.fill)} />\n {t(`stats.${a.key}`)}\n </span>\n <span className=\"text-popover-foreground\">{shown[a.key]}</span>\n </p>\n ))}\n </div>\n )}\n </div>\n <ol aria-hidden className=\"flex gap-1 text-[11px] text-muted-foreground tabular-nums\">\n {rows.map((d, i) => (\n <li key={d.date} className=\"min-w-0 flex-1 overflow-visible text-center whitespace-nowrap\">\n {(rows.length - 1 - i) % every === 0 ? short.format(at(d.date)) : \"\"}\n </li>\n ))}\n </ol>\n {most === 0 && <p className=\"text-muted-foreground\">{t(\"stats.noActivity\")}</p>}\n </figure>\n );\n}\n\nfunction DayTable({ rows }: { rows: ActivityDay[] }) {\n const { t } = useMessages();\n return (\n <details className=\"text-sm\">\n <summary className=\"cursor-pointer text-xs text-muted-foreground\">{t(\"stats.showTable\")}</summary>\n <table className=\"mt-2 w-full text-left tabular-nums\">\n <thead className=\"text-xs text-muted-foreground\">\n <tr>\n <th className=\"py-1 font-medium\">{t(\"stats.date\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.visitors\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.anon\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.free\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.premium\")}</th>\n <th className=\"py-1 text-end font-medium\">{t(\"stats.activeTime\")}</th>\n </tr>\n </thead>\n <tbody>\n {[...rows].reverse().map((d) => (\n <tr key={d.date} className=\"border-t border-border\">\n <td className=\"py-1.5 pe-3 whitespace-nowrap\">{d.date}</td>\n <td className=\"py-1.5 text-end\">{d.readers}</td>\n <td className=\"py-1.5 text-end\">{d.anon}</td>\n <td className=\"py-1.5 text-end\">{d.free}</td>\n <td className=\"py-1.5 text-end\">{d.premium}</td>\n <td className=\"py-1.5 text-end\">{formatDuration(Math.floor(d.active_ms / 1000))}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </details>\n );\n}\n\nfunction Pick({ label, value, onChange, options }: { label: string; value: string; onChange: (v: string) => void; options: { value: string; label: string }[] }) {\n return (\n <Select value={value} onValueChange={(v) => onChange(String(v))}>\n <SelectTrigger size=\"sm\" aria-label={label}>\n <SelectValue>{(v: string) => options.find((o) => o.value === v)?.label ?? v}</SelectValue>\n </SelectTrigger>\n <SelectContent>\n {options.map((o) => (\n <SelectItem key={o.value} value={o.value}>\n {o.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ContentKitClient } from '../../client/client.js';
|
|
1
2
|
import { FileInfo, RefBody } from '../../client/generated/wire.js';
|
|
2
3
|
import { AdminFlowProps } from './shell.js';
|
|
3
4
|
export interface UploadQueueProps extends AdminFlowProps {
|
|
@@ -5,6 +6,13 @@ export interface UploadQueueProps extends AdminFlowProps {
|
|
|
5
6
|
createItem: (file: File) => Promise<RefBody>;
|
|
6
7
|
/** The upload path in each item. Default "source". */
|
|
7
8
|
path?: string;
|
|
9
|
+
/** Where a file uploads in its new item (path and media type), in place of path. */
|
|
10
|
+
target?: (ref: RefBody, file: File, signal: AbortSignal) => Promise<{
|
|
11
|
+
path: string;
|
|
12
|
+
type?: string;
|
|
13
|
+
}>;
|
|
14
|
+
/** Opens a file's item: an Open button once it has one. */
|
|
15
|
+
onOpen?: (ref: RefBody) => void;
|
|
8
16
|
/** The file picker's accept, e.g. "video/*". */
|
|
9
17
|
accept?: string;
|
|
10
18
|
/** Files uploading at once. Default 2. */
|
|
@@ -21,3 +29,9 @@ export interface UploadQueueProps extends AdminFlowProps {
|
|
|
21
29
|
* the same item. Leaving the page while files upload asks first.
|
|
22
30
|
*/
|
|
23
31
|
export declare function UploadQueue({ client: own, baseUrl, auth, guard, appearance, className, language, messages, ...p }: UploadQueueProps): import('react').JSX.Element;
|
|
32
|
+
type Inner = Omit<UploadQueueProps, "client" | "baseUrl" | "auth" | "guard" | "appearance" | "className" | "language" | "messages"> & {
|
|
33
|
+
client: ContentKitClient;
|
|
34
|
+
};
|
|
35
|
+
/** The queue inside an admin flow that already has its root (ContentManager's bulk upload). */
|
|
36
|
+
export declare function UploadQueueBody(p: Inner): import('react').JSX.Element;
|
|
37
|
+
export {};
|
|
@@ -43,133 +43,144 @@ function w({ client: e, baseUrl: t, auth: n, guard: r, appearance: i, className:
|
|
|
43
43
|
language: o,
|
|
44
44
|
messages: s,
|
|
45
45
|
guard: r,
|
|
46
|
-
children: /* @__PURE__ */ g(
|
|
46
|
+
children: /* @__PURE__ */ g(E, {
|
|
47
47
|
...c,
|
|
48
48
|
client: d
|
|
49
49
|
})
|
|
50
50
|
});
|
|
51
51
|
}
|
|
52
|
-
function T(
|
|
53
|
-
|
|
52
|
+
function T(e) {
|
|
53
|
+
return /* @__PURE__ */ g(E, { ...e });
|
|
54
|
+
}
|
|
55
|
+
function E({ client: l, createItem: u, path: w = "source", target: T, accept: E, concurrency: D = 2, onUploaded: O, onOpen: k, storageKey: A = "ckui.uploads", onError: j }) {
|
|
56
|
+
let M = r(), { t: N } = M, P = i(j), F = a(), [I, L] = m([]), [R, z] = m(() => Object.values(S(A))), [B, V] = m(!1), H = p(null), U = p(/* @__PURE__ */ new Map()), W = p({
|
|
54
57
|
createItem: u,
|
|
55
|
-
onUploaded:
|
|
58
|
+
onUploaded: O,
|
|
59
|
+
place: T
|
|
56
60
|
});
|
|
57
61
|
f(() => {
|
|
58
|
-
|
|
62
|
+
W.current = {
|
|
59
63
|
createItem: u,
|
|
60
|
-
onUploaded:
|
|
64
|
+
onUploaded: O,
|
|
65
|
+
place: T
|
|
61
66
|
};
|
|
62
67
|
});
|
|
63
|
-
let
|
|
68
|
+
let G = d((e, t) => L((n) => n.map((n) => n.id === e ? {
|
|
64
69
|
...n,
|
|
65
70
|
...t
|
|
66
|
-
} : n)), []),
|
|
71
|
+
} : n)), []), K = d(async (n) => {
|
|
67
72
|
let r = new AbortController();
|
|
68
|
-
|
|
69
|
-
let i = S(
|
|
73
|
+
U.current.set(n.id, r);
|
|
74
|
+
let i = S(A)[n.key];
|
|
70
75
|
try {
|
|
71
76
|
let a = n.ref ?? i?.ref;
|
|
72
77
|
if (!a) {
|
|
73
|
-
|
|
78
|
+
G(n.id, {
|
|
74
79
|
status: "creating",
|
|
75
80
|
error: void 0
|
|
76
|
-
}), a = await
|
|
81
|
+
}), a = await W.current.createItem(n.file);
|
|
77
82
|
let e = a;
|
|
78
|
-
C(
|
|
83
|
+
C(A, (t) => void (t[n.key] = {
|
|
79
84
|
ref: e,
|
|
80
85
|
name: n.file.name,
|
|
81
86
|
size: n.file.size
|
|
82
87
|
}));
|
|
83
88
|
}
|
|
84
89
|
let o = a;
|
|
85
|
-
|
|
86
|
-
status: "uploading",
|
|
90
|
+
G(n.id, {
|
|
87
91
|
ref: a,
|
|
88
92
|
error: void 0
|
|
89
93
|
});
|
|
90
|
-
let s = {
|
|
91
|
-
ref: a,
|
|
94
|
+
let s = W.current.place, c = s ? await F(() => s(o, n.file, r.signal)) : {
|
|
92
95
|
path: w,
|
|
96
|
+
type: void 0
|
|
97
|
+
};
|
|
98
|
+
G(n.id, { status: "uploading" });
|
|
99
|
+
let u = {
|
|
100
|
+
ref: a,
|
|
101
|
+
path: c.path,
|
|
102
|
+
type: c.type,
|
|
93
103
|
signal: r.signal,
|
|
94
|
-
onProgress: (e) =>
|
|
95
|
-
onState: (e) => C(
|
|
104
|
+
onProgress: (e) => G(n.id, { progress: e }),
|
|
105
|
+
onState: (e) => C(A, (t) => void (t[n.key] = {
|
|
96
106
|
ref: o,
|
|
97
107
|
name: n.file.name,
|
|
98
108
|
size: n.file.size,
|
|
99
109
|
state: e ?? void 0
|
|
100
110
|
}))
|
|
101
|
-
},
|
|
111
|
+
}, d = await F(async () => {
|
|
102
112
|
try {
|
|
103
113
|
return await l.media.upload(n.file, {
|
|
104
|
-
...
|
|
114
|
+
...u,
|
|
105
115
|
resume: i?.state
|
|
106
116
|
});
|
|
107
117
|
} catch (t) {
|
|
108
|
-
if (i?.state && e(t).code === "resume_mismatch") return l.media.upload(n.file,
|
|
118
|
+
if (i?.state && e(t).code === "resume_mismatch") return l.media.upload(n.file, u);
|
|
109
119
|
throw t;
|
|
110
120
|
}
|
|
111
121
|
});
|
|
112
|
-
|
|
113
|
-
let
|
|
122
|
+
G(n.id, { status: "committing" });
|
|
123
|
+
let f = await F(() => l.media.commit(o, [{
|
|
114
124
|
op: "put",
|
|
115
|
-
path:
|
|
116
|
-
|
|
125
|
+
path: d.path,
|
|
126
|
+
source_name: d.sourceName,
|
|
127
|
+
blob: d.blob
|
|
117
128
|
}], { signal: r.signal }));
|
|
118
|
-
C(
|
|
129
|
+
C(A, (e) => void delete e[n.key]), G(n.id, {
|
|
119
130
|
status: "done",
|
|
120
131
|
progress: void 0
|
|
121
132
|
});
|
|
122
|
-
let
|
|
123
|
-
|
|
133
|
+
let p = f.find((e) => t(e.path, d.path));
|
|
134
|
+
p && W.current.onUploaded?.(o, p);
|
|
124
135
|
} catch (t) {
|
|
125
136
|
let r = e(t);
|
|
126
|
-
r.code === "aborted" ?
|
|
137
|
+
r.code === "aborted" ? G(n.id, { status: "paused" }) : (G(n.id, {
|
|
127
138
|
status: "failed",
|
|
128
139
|
error: r
|
|
129
|
-
}),
|
|
140
|
+
}), P(r, "upload", n.file.name));
|
|
130
141
|
} finally {
|
|
131
|
-
|
|
142
|
+
U.current.delete(n.id);
|
|
132
143
|
}
|
|
133
144
|
}, [
|
|
134
145
|
l,
|
|
135
146
|
w,
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
147
|
+
A,
|
|
148
|
+
F,
|
|
149
|
+
G,
|
|
150
|
+
P
|
|
140
151
|
]);
|
|
141
152
|
f(() => {
|
|
142
|
-
let e =
|
|
153
|
+
let e = I.filter((e) => y.includes(e.status)).length, t = I.filter((e) => e.status === "queued").slice(0, Math.max(0, D - e));
|
|
143
154
|
if (t.length) {
|
|
144
|
-
|
|
155
|
+
L((e) => e.map((e) => t.some((t) => t.id === e.id) ? {
|
|
145
156
|
...e,
|
|
146
157
|
status: "creating"
|
|
147
158
|
} : e));
|
|
148
|
-
for (let e of t)
|
|
159
|
+
for (let e of t) K(e);
|
|
149
160
|
}
|
|
150
161
|
}, [
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
I,
|
|
163
|
+
D,
|
|
164
|
+
K
|
|
154
165
|
]);
|
|
155
|
-
let
|
|
166
|
+
let q = I.some((e) => y.includes(e.status) || e.status === "queued");
|
|
156
167
|
f(() => {
|
|
157
|
-
if (!
|
|
168
|
+
if (!q) return;
|
|
158
169
|
let e = (e) => {
|
|
159
170
|
e.preventDefault(), e.returnValue = "";
|
|
160
171
|
};
|
|
161
172
|
return addEventListener("beforeunload", e), () => removeEventListener("beforeunload", e);
|
|
162
|
-
}, [
|
|
163
|
-
let e =
|
|
173
|
+
}, [q]), f(() => {
|
|
174
|
+
let e = U.current;
|
|
164
175
|
return () => {
|
|
165
176
|
for (let t of e.values()) t.abort();
|
|
166
177
|
};
|
|
167
178
|
}, []);
|
|
168
|
-
let
|
|
169
|
-
let t = S(
|
|
179
|
+
let J = (e) => {
|
|
180
|
+
let t = S(A), n = [];
|
|
170
181
|
for (let r of e) {
|
|
171
182
|
let e = b(r);
|
|
172
|
-
|
|
183
|
+
I.some((t) => t.key === e && t.status !== "done") || n.some((t) => t.key === e) || n.push({
|
|
173
184
|
id: `u${++x}`,
|
|
174
185
|
file: r,
|
|
175
186
|
key: e,
|
|
@@ -178,70 +189,70 @@ function T({ client: l, createItem: u, path: w = "source", accept: T, concurrenc
|
|
|
178
189
|
resumed: !!t[e]
|
|
179
190
|
});
|
|
180
191
|
}
|
|
181
|
-
n.length && (
|
|
182
|
-
},
|
|
183
|
-
for (let [t, n] of
|
|
184
|
-
|
|
192
|
+
n.length && (z((e) => e.filter((e) => !n.some((t) => t.file.name === e.name && t.file.size === e.size))), L((e) => [...e, ...n]));
|
|
193
|
+
}, Y = (e) => {
|
|
194
|
+
for (let [t, n] of U.current) (!e || t === e) && n.abort();
|
|
195
|
+
L((t) => t.map((t) => t.status === "queued" && (!e || t.id === e) ? {
|
|
185
196
|
...t,
|
|
186
197
|
status: "paused"
|
|
187
198
|
} : t));
|
|
188
|
-
},
|
|
199
|
+
}, X = (e) => L((t) => t.map((t) => (t.status === "paused" || t.status === "failed") && (!e || t.id === e) ? {
|
|
189
200
|
...t,
|
|
190
201
|
status: "queued",
|
|
191
202
|
error: void 0
|
|
192
|
-
} : t)),
|
|
193
|
-
|
|
194
|
-
},
|
|
195
|
-
C(
|
|
203
|
+
} : t)), Z = (e) => {
|
|
204
|
+
U.current.get(e.id)?.abort(), e.status !== "done" && C(A, (t) => void delete t[e.key]), L((t) => t.filter((t) => t.id !== e.id));
|
|
205
|
+
}, Q = (e) => {
|
|
206
|
+
C(A, (t) => {
|
|
196
207
|
for (let [n, r] of Object.entries(t)) r.name === e.name && r.size === e.size && delete t[n];
|
|
197
|
-
}),
|
|
198
|
-
},
|
|
208
|
+
}), z((t) => t.filter((t) => t !== e));
|
|
209
|
+
}, ee = I.filter((e) => e.status === "done").length, te = I.some((e) => y.includes(e.status) || e.status === "queued"), ne = I.some((e) => e.status === "paused" || e.status === "failed"), $ = (e) => e.progress?.total ? Math.round(e.progress.loaded / e.progress.total * 100) : 0;
|
|
199
210
|
return /* @__PURE__ */ _(h, { children: [
|
|
200
211
|
/* @__PURE__ */ _("div", {
|
|
201
|
-
className: v("grid justify-items-center gap-2 rounded-lg border-2 border-dashed border-border p-6 text-center transition-colors",
|
|
212
|
+
className: v("grid justify-items-center gap-2 rounded-lg border-2 border-dashed border-border p-6 text-center transition-colors", B && "border-primary bg-accent"),
|
|
202
213
|
"data-ckui": "upload-drop",
|
|
203
214
|
onDragOver: (e) => {
|
|
204
|
-
e.preventDefault(),
|
|
215
|
+
e.preventDefault(), V(!0);
|
|
205
216
|
},
|
|
206
|
-
onDragLeave: () =>
|
|
217
|
+
onDragLeave: () => V(!1),
|
|
207
218
|
onDrop: (e) => {
|
|
208
|
-
e.preventDefault(),
|
|
219
|
+
e.preventDefault(), V(!1), J(e.dataTransfer.files);
|
|
209
220
|
},
|
|
210
221
|
children: [
|
|
211
222
|
/* @__PURE__ */ g("p", {
|
|
212
223
|
className: "text-muted-foreground",
|
|
213
|
-
children:
|
|
224
|
+
children: N("uploadQueue.drop")
|
|
214
225
|
}),
|
|
215
226
|
/* @__PURE__ */ g(o, {
|
|
216
227
|
size: "sm",
|
|
217
|
-
onClick: () =>
|
|
218
|
-
children:
|
|
228
|
+
onClick: () => H.current?.click(),
|
|
229
|
+
children: N("uploadQueue.add")
|
|
219
230
|
}),
|
|
220
231
|
/* @__PURE__ */ g("input", {
|
|
221
|
-
ref:
|
|
232
|
+
ref: H,
|
|
222
233
|
type: "file",
|
|
223
234
|
multiple: !0,
|
|
224
235
|
hidden: !0,
|
|
225
|
-
accept:
|
|
226
|
-
"aria-label":
|
|
236
|
+
accept: E,
|
|
237
|
+
"aria-label": N("uploadQueue.add"),
|
|
227
238
|
onChange: (e) => {
|
|
228
|
-
|
|
239
|
+
J(e.target.files ?? []), e.target.value = "";
|
|
229
240
|
}
|
|
230
241
|
})
|
|
231
242
|
]
|
|
232
243
|
}),
|
|
233
|
-
|
|
244
|
+
R.length > 0 && /* @__PURE__ */ _("div", {
|
|
234
245
|
className: "grid gap-2 rounded-lg border border-border p-3",
|
|
235
246
|
"data-ckui": "upload-waiting",
|
|
236
247
|
role: "status",
|
|
237
248
|
children: [
|
|
238
249
|
/* @__PURE__ */ g("p", {
|
|
239
250
|
className: "font-medium",
|
|
240
|
-
children:
|
|
251
|
+
children: N("uploadQueue.unfinished")
|
|
241
252
|
}),
|
|
242
253
|
/* @__PURE__ */ g("ul", {
|
|
243
254
|
className: "grid gap-1",
|
|
244
|
-
children:
|
|
255
|
+
children: R.map((e) => /* @__PURE__ */ _("li", {
|
|
245
256
|
className: "flex flex-wrap items-center gap-2",
|
|
246
257
|
children: [
|
|
247
258
|
/* @__PURE__ */ g("span", {
|
|
@@ -255,44 +266,44 @@ function T({ client: l, createItem: u, path: w = "source", accept: T, concurrenc
|
|
|
255
266
|
/* @__PURE__ */ g(o, {
|
|
256
267
|
size: "xs",
|
|
257
268
|
variant: "ghost",
|
|
258
|
-
onClick: () =>
|
|
259
|
-
children:
|
|
269
|
+
onClick: () => Q(e),
|
|
270
|
+
children: N("uploadQueue.forget")
|
|
260
271
|
})
|
|
261
272
|
]
|
|
262
273
|
}, `${e.name}:${e.size}`))
|
|
263
274
|
}),
|
|
264
275
|
/* @__PURE__ */ g("p", {
|
|
265
276
|
className: "text-xs text-muted-foreground",
|
|
266
|
-
children:
|
|
277
|
+
children: N("uploadQueue.unfinishedHint")
|
|
267
278
|
})
|
|
268
279
|
]
|
|
269
280
|
}),
|
|
270
|
-
|
|
281
|
+
I.length > 0 && /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ _("div", {
|
|
271
282
|
className: "flex flex-wrap items-center gap-2",
|
|
272
283
|
children: [/* @__PURE__ */ g("p", {
|
|
273
284
|
className: "text-muted-foreground",
|
|
274
285
|
"aria-live": "polite",
|
|
275
|
-
children:
|
|
276
|
-
done:
|
|
277
|
-
total:
|
|
286
|
+
children: N("uploadQueue.summary", {
|
|
287
|
+
done: ee,
|
|
288
|
+
total: I.length
|
|
278
289
|
})
|
|
279
290
|
}), /* @__PURE__ */ _("div", {
|
|
280
291
|
className: "ms-auto flex gap-2",
|
|
281
|
-
children: [
|
|
292
|
+
children: [te && /* @__PURE__ */ g(o, {
|
|
282
293
|
size: "sm",
|
|
283
294
|
variant: "outline",
|
|
284
|
-
onClick: () =>
|
|
285
|
-
children:
|
|
286
|
-
}),
|
|
295
|
+
onClick: () => Y(),
|
|
296
|
+
children: N("uploadQueue.pauseAll")
|
|
297
|
+
}), ne && /* @__PURE__ */ g(o, {
|
|
287
298
|
size: "sm",
|
|
288
299
|
variant: "outline",
|
|
289
|
-
onClick: () =>
|
|
290
|
-
children:
|
|
300
|
+
onClick: () => X(),
|
|
301
|
+
children: N("uploadQueue.resumeAll")
|
|
291
302
|
})]
|
|
292
303
|
})]
|
|
293
304
|
}), /* @__PURE__ */ g("ul", {
|
|
294
305
|
className: "grid divide-y divide-border rounded-lg border border-border",
|
|
295
|
-
children:
|
|
306
|
+
children: I.map((e) => /* @__PURE__ */ _("li", {
|
|
296
307
|
className: "grid gap-1.5 p-3",
|
|
297
308
|
"data-ckui": "upload-entry",
|
|
298
309
|
"data-status": e.status,
|
|
@@ -310,11 +321,11 @@ function T({ client: l, createItem: u, path: w = "source", accept: T, concurrenc
|
|
|
310
321
|
}),
|
|
311
322
|
e.resumed && e.status !== "done" && /* @__PURE__ */ g(s, {
|
|
312
323
|
variant: "secondary",
|
|
313
|
-
children:
|
|
324
|
+
children: N("uploadQueue.resumed")
|
|
314
325
|
}),
|
|
315
326
|
/* @__PURE__ */ g(s, {
|
|
316
327
|
variant: e.status === "failed" ? "destructive" : e.status === "done" ? "outline" : "secondary",
|
|
317
|
-
children:
|
|
328
|
+
children: N(`uploadQueue.${e.status}`)
|
|
318
329
|
})
|
|
319
330
|
]
|
|
320
331
|
}),
|
|
@@ -330,27 +341,33 @@ function T({ client: l, createItem: u, path: w = "source", accept: T, concurrenc
|
|
|
330
341
|
style: { width: `${$(e)}%` }
|
|
331
342
|
})
|
|
332
343
|
}),
|
|
333
|
-
e.error && /* @__PURE__ */ g(c, { children:
|
|
344
|
+
e.error && /* @__PURE__ */ g(c, { children: M.error(e.error) }),
|
|
334
345
|
/* @__PURE__ */ _("div", {
|
|
335
346
|
className: "flex flex-wrap gap-2",
|
|
336
347
|
children: [
|
|
337
348
|
(y.includes(e.status) || e.status === "queued") && /* @__PURE__ */ g(o, {
|
|
338
349
|
size: "xs",
|
|
339
350
|
variant: "ghost",
|
|
340
|
-
onClick: () =>
|
|
341
|
-
children:
|
|
351
|
+
onClick: () => Y(e.id),
|
|
352
|
+
children: N("uploadQueue.pause")
|
|
342
353
|
}),
|
|
343
354
|
(e.status === "paused" || e.status === "failed") && /* @__PURE__ */ g(o, {
|
|
344
355
|
size: "xs",
|
|
345
356
|
variant: "outline",
|
|
346
|
-
onClick: () =>
|
|
347
|
-
children:
|
|
357
|
+
onClick: () => X(e.id),
|
|
358
|
+
children: N(e.status === "failed" ? "common.retry" : "uploadQueue.resume")
|
|
359
|
+
}),
|
|
360
|
+
k && e.ref && (e.status === "done" || e.status === "failed") && /* @__PURE__ */ g(o, {
|
|
361
|
+
size: "xs",
|
|
362
|
+
variant: "outline",
|
|
363
|
+
onClick: () => k(e.ref),
|
|
364
|
+
children: N("uploadQueue.open")
|
|
348
365
|
}),
|
|
349
366
|
e.status !== "done" && /* @__PURE__ */ g(o, {
|
|
350
367
|
size: "xs",
|
|
351
368
|
variant: "ghost",
|
|
352
|
-
onClick: () =>
|
|
353
|
-
children:
|
|
369
|
+
onClick: () => Z(e),
|
|
370
|
+
children: N("common.remove")
|
|
354
371
|
})
|
|
355
372
|
]
|
|
356
373
|
})
|
|
@@ -360,6 +377,6 @@ function T({ client: l, createItem: u, path: w = "source", accept: T, concurrenc
|
|
|
360
377
|
] });
|
|
361
378
|
}
|
|
362
379
|
//#endregion
|
|
363
|
-
export { w as UploadQueue };
|
|
380
|
+
export { w as UploadQueue, T as UploadQueueBody };
|
|
364
381
|
|
|
365
382
|
//# sourceMappingURL=upload-queue.js.map
|