@openrails/contentkit-ui 0.72.0 → 0.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/media/queue.d.ts +1 -1
- package/dist/client/media/queue.js +1 -1
- package/dist/client/media/queue.js.map +1 -1
- package/dist/client.js +2 -2
- package/dist/components/admin/media-item-panel.d.ts +27 -0
- package/dist/components/admin/media-item-panel.js +397 -0
- package/dist/components/admin/media-item-panel.js.map +1 -0
- package/dist/components/admin/upload-queue.d.ts +23 -0
- package/dist/components/admin/upload-queue.js +365 -0
- package/dist/components/admin/upload-queue.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +4 -2
- package/dist/locales/admin/de.js +51 -0
- package/dist/locales/admin/de.js.map +1 -1
- package/dist/locales/admin/en.d.ts +51 -0
- package/dist/locales/admin/en.js +51 -0
- package/dist/locales/admin/en.js.map +1 -1
- package/dist/locales/admin/es.js +51 -0
- package/dist/locales/admin/es.js.map +1 -1
- package/dist/locales/admin/ja.js +51 -0
- package/dist/locales/admin/ja.js.map +1 -1
- package/dist/locales/admin/ko.js +51 -0
- package/dist/locales/admin/ko.js.map +1 -1
- package/dist/locales/admin/zh.js +51 -0
- package/dist/locales/admin/zh.js.map +1 -1
- package/dist/locales/en.d.ts +51 -0
- package/dist/react/context.d.ts +1 -1
- package/dist/react/context.js.map +1 -1
- package/dist/react/folder.js.map +1 -1
- package/dist/react/upload.d.ts +10 -10
- package/dist/react/upload.js +1 -1
- package/dist/react/upload.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/locales/admin/zh.js
CHANGED
|
@@ -168,6 +168,57 @@ var e = {
|
|
|
168
168
|
contentsLoadFailed: "无法加载其内容。",
|
|
169
169
|
noContents: "尚未分配任何内容。",
|
|
170
170
|
proposed: "建议"
|
|
171
|
+
},
|
|
172
|
+
mediaPanel: {
|
|
173
|
+
noAccess: "你无法编辑此项目的媒体。",
|
|
174
|
+
loadFailed: "无法加载媒体。",
|
|
175
|
+
title: "视频",
|
|
176
|
+
ready: "就绪",
|
|
177
|
+
processing: "处理中",
|
|
178
|
+
failed: "失败",
|
|
179
|
+
empty: "还没有视频",
|
|
180
|
+
regenerate: "重新处理",
|
|
181
|
+
failedFile: "{name} 处理失败:{reason}",
|
|
182
|
+
source: "视频文件",
|
|
183
|
+
noSource: "尚未上传视频。",
|
|
184
|
+
replace: "替换视频",
|
|
185
|
+
upload: "上传视频",
|
|
186
|
+
phase_hashing: "正在检查文件…",
|
|
187
|
+
phase_uploading: "正在上传…",
|
|
188
|
+
phase_completing: "正在完成上传…",
|
|
189
|
+
phase_processing: "正在处理…",
|
|
190
|
+
poster: "封面",
|
|
191
|
+
choosePoster: "选择封面",
|
|
192
|
+
subtitles: "字幕",
|
|
193
|
+
noSubtitles: "没有字幕。",
|
|
194
|
+
forced: "强制",
|
|
195
|
+
subtitleFile: "字幕文件(.vtt、.srt)",
|
|
196
|
+
language: "语言",
|
|
197
|
+
label: "标签",
|
|
198
|
+
labelPlaceholder: "在播放器中显示",
|
|
199
|
+
forcedLabel: "强制字幕(仅标识和外语对白)",
|
|
200
|
+
addSubtitle: "添加字幕",
|
|
201
|
+
removeSubtitleTitle: "移除字幕“{name}”?"
|
|
202
|
+
},
|
|
203
|
+
uploadQueue: {
|
|
204
|
+
drop: "将文件拖到这里,或",
|
|
205
|
+
add: "添加文件",
|
|
206
|
+
unfinished: "未完成的上传",
|
|
207
|
+
unfinishedHint: "再次添加同一文件即可从中断处继续。",
|
|
208
|
+
forget: "忘记",
|
|
209
|
+
summary: "已上传 {done}/{total}",
|
|
210
|
+
pauseAll: "全部暂停",
|
|
211
|
+
resumeAll: "全部继续",
|
|
212
|
+
resumed: "已继续",
|
|
213
|
+
queued: "等待中",
|
|
214
|
+
creating: "正在创建",
|
|
215
|
+
uploading: "正在上传",
|
|
216
|
+
committing: "正在保存",
|
|
217
|
+
done: "已上传",
|
|
218
|
+
failed: "失败",
|
|
219
|
+
paused: "已暂停",
|
|
220
|
+
pause: "暂停",
|
|
221
|
+
resume: "继续"
|
|
171
222
|
}
|
|
172
223
|
};
|
|
173
224
|
//#endregion
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"zh.js","names":[],"sources":["../../../src/locales/admin/zh.ts"],"sourcesContent":["import type { admin as en } from \"./en.js\";\n\nexport const admin: typeof en = {\n admin: {\n noAccessTitle: \"无权访问\",\n noAccess: \"你没有使用此功能的权限。\",\n pages: \"分页\",\n previous: \"上一页\",\n next: \"下一页\",\n page: \"第 {page} 页\",\n typeToConfirm: \"输入“{word}”以确认\",\n deleteWord: \"删除\",\n },\n standing: {\n title: \"评论\",\n noAccess: \"你没有管理评论的权限。\",\n ban: \"全站评论封禁\",\n notBanned: \"未被封禁,可以评论。\",\n banLoadFailed: \"无法加载封禁信息。\",\n comments: \"最近的评论\",\n noComments: \"没有评论。\",\n },\n editor: {\n placeholder: \"写点什么…\",\n uploading: \"正在上传图片…\",\n toolbar: \"格式\",\n bold: \"粗体\",\n italic: \"斜体\",\n underline: \"下划线\",\n strike: \"删除线\",\n heading2: \"标题\",\n heading3: \"小标题\",\n bulletList: \"项目符号列表\",\n orderedList: \"编号列表\",\n quote: \"引用\",\n link: \"链接\",\n image: \"插入图片\",\n undo: \"撤销\",\n redo: \"重做\",\n linkUrl: \"链接地址\",\n applyLink: \"应用\",\n removeLink: \"移除链接\",\n },\n pollManager: {\n noAccess: \"你没有管理投票的权限。\",\n back: \"所有投票\",\n title: \"投票\",\n new: \"新建投票\",\n search: \"搜索问题\",\n allLanguages: \"所有语言\",\n state: \"状态\",\n allStates: \"所有状态\",\n live: \"进行中\",\n scheduled: \"已排期\",\n closed: \"已结束\",\n inactive: \"未启用\",\n month: \"月份\",\n date: \"日期\",\n clear: \"清除筛选\",\n loadFailed: \"无法加载投票。\",\n empty: \"没有匹配的投票。\",\n goesLive: \"{date} 上线\",\n wentLive: \"自 {date} 起进行中\",\n },\n postManager: {\n noAccess: \"你没有管理文章的权限。\",\n back: \"所有文章\",\n title: \"文章\",\n new: \"新建文章\",\n posts: \"文章\",\n deleted: \"已删除\",\n search: \"搜索文章\",\n language: \"语言\",\n status: \"状态\",\n allStatuses: \"所有状态\",\n drafts: \"草稿\",\n notDrafts: \"非草稿\",\n loadFailed: \"无法加载文章。\",\n empty: \"没有匹配的文章。\",\n noDeleted: \"没有已删除的文章。\",\n draft: \"草稿\",\n scheduled: \"已排期\",\n published: \"已发布\",\n publishedNotice: \"已发布。\",\n held: \"审核中\",\n rejected: \"已拒绝\",\n view: \"查看\",\n deleteTitle: \"删除“{title}”?\",\n deleteBody: \"它将从网站下线。你可以在“已删除”中恢复。\",\n delete: \"删除文章\",\n editTitle: \"编辑文章\",\n newTitle: \"新建文章\",\n rejectedBecause: \"审核拒绝了这篇文章:{reason}\",\n heldBecause: \"审核中:{reason}\",\n rejectedNote: \"审核拒绝了这篇文章。\",\n heldNote: \"审核中,等待管理员批准。\",\n postTitle: \"标题\",\n slug: \"短链接\",\n slugAuto: \"根据标题生成\",\n liveAt: \"上线时间(可选)\",\n excerpt: \"摘要\",\n body: \"正文\",\n bodyPlaceholder: \"撰写文章…\",\n imagesAfterCreate: \"创建草稿后即可添加封面和图片。\",\n createDraft: \"创建草稿\",\n publish: \"发布\",\n unpublish: \"撤回发布\",\n unpublishedNotice: \"已改回草稿。\",\n titleRequired: \"请输入标题。\",\n cover: \"封面\",\n noCover: \"无封面\",\n addCover: \"添加封面\",\n },\n taxonomyManager: {\n noAccess: \"你没有管理分类的权限。\",\n new: \"新建\",\n search: \"搜索名称\",\n state: \"状态\",\n active: \"有效\",\n deleted: \"已删除\",\n merged: \"已合并\",\n language: \"语言\",\n sort: \"排序\",\n sort_name: \"按名称\",\n sort_count: \"最常用\",\n sort_created: \"最新\",\n sort_updated: \"最近修改\",\n sort_oldest: \"最早\",\n minCount: \"至少\",\n loadFailed: \"无法加载此列表。\",\n empty: \"没有匹配项。\",\n count: \"内容数\",\n total_one: \"共 {count} 个\",\n total_other: \"共 {count} 个\",\n back: \"全部{kind}\",\n newTitle: \"新建:{kind}\",\n nameIn: \"{language} 名称\",\n slug: \"短链接\",\n create: \"创建\",\n nodeLoadFailed: \"无法加载。\",\n deleteTitle: \"删除 {name}?\",\n deleteBody: \"它将不再显示。分配会保留,你可以恢复它。\",\n delete: \"删除\",\n restore: \"恢复\",\n names: \"名称\",\n canonical: \"名称\",\n alias: \"别名\",\n makeCanonical: \"设为名称\",\n removeName: \"移除 {name}\",\n noNames: \"还没有名称。\",\n nameKind: \"名称类型\",\n newName: \"名称或别名\",\n addName: \"添加名称\",\n cover: \"封面\",\n mergedInto: \"已合并到\",\n mergeTitle: \"将 {from} 合并到 {into}?\",\n mergeBody: \"其内容、名称和关系将被转移,链接会重定向。此操作无法撤销。\",\n merge: \"合并\",\n mergeWord: \"合并\",\n mergeInto: \"合并到\",\n mergeDone: \"已合并;移动了 {count} 个内容。\",\n searchNodes: \"输入以搜索\",\n relations: \"关系\",\n removeRelation: \"移除 {name}\",\n addRelation: \"添加{relation}\",\n counts: \"计数\",\n noCounts: \"尚未统计。\",\n contents: \"内容\",\n contentsLoadFailed: \"无法加载其内容。\",\n noContents: \"尚未分配任何内容。\",\n proposed: \"建议\",\n },\n};\n"],"mappings":";AAEA,IAAa,IAAmB;CAC9B,OAAO;EACL,eAAe;EACf,UAAU;EACV,OAAO;EACP,UAAU;EACV,MAAM;EACN,MAAM;EACN,eAAe;EACf,YAAY;CACd;CACA,UAAU;EACR,OAAO;EACP,UAAU;EACV,KAAK;EACL,WAAW;EACX,eAAe;EACf,UAAU;EACV,YAAY;CACd;CACA,QAAQ;EACN,aAAa;EACb,WAAW;EACX,SAAS;EACT,MAAM;EACN,QAAQ;EACR,WAAW;EACX,QAAQ;EACR,UAAU;EACV,UAAU;EACV,YAAY;EACZ,aAAa;EACb,OAAO;EACP,MAAM;EACN,OAAO;EACP,MAAM;EACN,MAAM;EACN,SAAS;EACT,WAAW;EACX,YAAY;CACd;CACA,aAAa;EACX,UAAU;EACV,MAAM;EACN,OAAO;EACP,KAAK;EACL,QAAQ;EACR,cAAc;EACd,OAAO;EACP,WAAW;EACX,MAAM;EACN,WAAW;EACX,QAAQ;EACR,UAAU;EACV,OAAO;EACP,MAAM;EACN,OAAO;EACP,YAAY;EACZ,OAAO;EACP,UAAU;EACV,UAAU;CACZ;CACA,aAAa;EACX,UAAU;EACV,MAAM;EACN,OAAO;EACP,KAAK;EACL,OAAO;EACP,SAAS;EACT,QAAQ;EACR,UAAU;EACV,QAAQ;EACR,aAAa;EACb,QAAQ;EACR,WAAW;EACX,YAAY;EACZ,OAAO;EACP,WAAW;EACX,OAAO;EACP,WAAW;EACX,WAAW;EACX,iBAAiB;EACjB,MAAM;EACN,UAAU;EACV,MAAM;EACN,aAAa;EACb,YAAY;EACZ,QAAQ;EACR,WAAW;EACX,UAAU;EACV,iBAAiB;EACjB,aAAa;EACb,cAAc;EACd,UAAU;EACV,WAAW;EACX,MAAM;EACN,UAAU;EACV,QAAQ;EACR,SAAS;EACT,MAAM;EACN,iBAAiB;EACjB,mBAAmB;EACnB,aAAa;EACb,SAAS;EACT,WAAW;EACX,mBAAmB;EACnB,eAAe;EACf,OAAO;EACP,SAAS;EACT,UAAU;CACZ;CACA,iBAAiB;EACf,UAAU;EACV,KAAK;EACL,QAAQ;EACR,OAAO;EACP,QAAQ;EACR,SAAS;EACT,QAAQ;EACR,UAAU;EACV,MAAM;EACN,WAAW;EACX,YAAY;EACZ,cAAc;EACd,cAAc;EACd,aAAa;EACb,UAAU;EACV,YAAY;EACZ,OAAO;EACP,OAAO;EACP,WAAW;EACX,aAAa;EACb,MAAM;EACN,UAAU;EACV,QAAQ;EACR,MAAM;EACN,QAAQ;EACR,gBAAgB;EAChB,aAAa;EACb,YAAY;EACZ,QAAQ;EACR,SAAS;EACT,OAAO;EACP,WAAW;EACX,OAAO;EACP,eAAe;EACf,YAAY;EACZ,SAAS;EACT,UAAU;EACV,SAAS;EACT,SAAS;EACT,OAAO;EACP,YAAY;EACZ,YAAY;EACZ,WAAW;EACX,OAAO;EACP,WAAW;EACX,WAAW;EACX,WAAW;EACX,aAAa;EACb,WAAW;EACX,gBAAgB;EAChB,aAAa;EACb,QAAQ;EACR,UAAU;EACV,UAAU;EACV,oBAAoB;EACpB,YAAY;EACZ,UAAU;CACZ;AACF"}
|
|
1
|
+
{"version":3,"file":"zh.js","names":[],"sources":["../../../src/locales/admin/zh.ts"],"sourcesContent":["import type { admin as en } from \"./en.js\";\n\nexport const admin: typeof en = {\n admin: {\n noAccessTitle: \"无权访问\",\n noAccess: \"你没有使用此功能的权限。\",\n pages: \"分页\",\n previous: \"上一页\",\n next: \"下一页\",\n page: \"第 {page} 页\",\n typeToConfirm: \"输入“{word}”以确认\",\n deleteWord: \"删除\",\n },\n standing: {\n title: \"评论\",\n noAccess: \"你没有管理评论的权限。\",\n ban: \"全站评论封禁\",\n notBanned: \"未被封禁,可以评论。\",\n banLoadFailed: \"无法加载封禁信息。\",\n comments: \"最近的评论\",\n noComments: \"没有评论。\",\n },\n editor: {\n placeholder: \"写点什么…\",\n uploading: \"正在上传图片…\",\n toolbar: \"格式\",\n bold: \"粗体\",\n italic: \"斜体\",\n underline: \"下划线\",\n strike: \"删除线\",\n heading2: \"标题\",\n heading3: \"小标题\",\n bulletList: \"项目符号列表\",\n orderedList: \"编号列表\",\n quote: \"引用\",\n link: \"链接\",\n image: \"插入图片\",\n undo: \"撤销\",\n redo: \"重做\",\n linkUrl: \"链接地址\",\n applyLink: \"应用\",\n removeLink: \"移除链接\",\n },\n pollManager: {\n noAccess: \"你没有管理投票的权限。\",\n back: \"所有投票\",\n title: \"投票\",\n new: \"新建投票\",\n search: \"搜索问题\",\n allLanguages: \"所有语言\",\n state: \"状态\",\n allStates: \"所有状态\",\n live: \"进行中\",\n scheduled: \"已排期\",\n closed: \"已结束\",\n inactive: \"未启用\",\n month: \"月份\",\n date: \"日期\",\n clear: \"清除筛选\",\n loadFailed: \"无法加载投票。\",\n empty: \"没有匹配的投票。\",\n goesLive: \"{date} 上线\",\n wentLive: \"自 {date} 起进行中\",\n },\n postManager: {\n noAccess: \"你没有管理文章的权限。\",\n back: \"所有文章\",\n title: \"文章\",\n new: \"新建文章\",\n posts: \"文章\",\n deleted: \"已删除\",\n search: \"搜索文章\",\n language: \"语言\",\n status: \"状态\",\n allStatuses: \"所有状态\",\n drafts: \"草稿\",\n notDrafts: \"非草稿\",\n loadFailed: \"无法加载文章。\",\n empty: \"没有匹配的文章。\",\n noDeleted: \"没有已删除的文章。\",\n draft: \"草稿\",\n scheduled: \"已排期\",\n published: \"已发布\",\n publishedNotice: \"已发布。\",\n held: \"审核中\",\n rejected: \"已拒绝\",\n view: \"查看\",\n deleteTitle: \"删除“{title}”?\",\n deleteBody: \"它将从网站下线。你可以在“已删除”中恢复。\",\n delete: \"删除文章\",\n editTitle: \"编辑文章\",\n newTitle: \"新建文章\",\n rejectedBecause: \"审核拒绝了这篇文章:{reason}\",\n heldBecause: \"审核中:{reason}\",\n rejectedNote: \"审核拒绝了这篇文章。\",\n heldNote: \"审核中,等待管理员批准。\",\n postTitle: \"标题\",\n slug: \"短链接\",\n slugAuto: \"根据标题生成\",\n liveAt: \"上线时间(可选)\",\n excerpt: \"摘要\",\n body: \"正文\",\n bodyPlaceholder: \"撰写文章…\",\n imagesAfterCreate: \"创建草稿后即可添加封面和图片。\",\n createDraft: \"创建草稿\",\n publish: \"发布\",\n unpublish: \"撤回发布\",\n unpublishedNotice: \"已改回草稿。\",\n titleRequired: \"请输入标题。\",\n cover: \"封面\",\n noCover: \"无封面\",\n addCover: \"添加封面\",\n },\n taxonomyManager: {\n noAccess: \"你没有管理分类的权限。\",\n new: \"新建\",\n search: \"搜索名称\",\n state: \"状态\",\n active: \"有效\",\n deleted: \"已删除\",\n merged: \"已合并\",\n language: \"语言\",\n sort: \"排序\",\n sort_name: \"按名称\",\n sort_count: \"最常用\",\n sort_created: \"最新\",\n sort_updated: \"最近修改\",\n sort_oldest: \"最早\",\n minCount: \"至少\",\n loadFailed: \"无法加载此列表。\",\n empty: \"没有匹配项。\",\n count: \"内容数\",\n total_one: \"共 {count} 个\",\n total_other: \"共 {count} 个\",\n back: \"全部{kind}\",\n newTitle: \"新建:{kind}\",\n nameIn: \"{language} 名称\",\n slug: \"短链接\",\n create: \"创建\",\n nodeLoadFailed: \"无法加载。\",\n deleteTitle: \"删除 {name}?\",\n deleteBody: \"它将不再显示。分配会保留,你可以恢复它。\",\n delete: \"删除\",\n restore: \"恢复\",\n names: \"名称\",\n canonical: \"名称\",\n alias: \"别名\",\n makeCanonical: \"设为名称\",\n removeName: \"移除 {name}\",\n noNames: \"还没有名称。\",\n nameKind: \"名称类型\",\n newName: \"名称或别名\",\n addName: \"添加名称\",\n cover: \"封面\",\n mergedInto: \"已合并到\",\n mergeTitle: \"将 {from} 合并到 {into}?\",\n mergeBody: \"其内容、名称和关系将被转移,链接会重定向。此操作无法撤销。\",\n merge: \"合并\",\n mergeWord: \"合并\",\n mergeInto: \"合并到\",\n mergeDone: \"已合并;移动了 {count} 个内容。\",\n searchNodes: \"输入以搜索\",\n relations: \"关系\",\n removeRelation: \"移除 {name}\",\n addRelation: \"添加{relation}\",\n counts: \"计数\",\n noCounts: \"尚未统计。\",\n contents: \"内容\",\n contentsLoadFailed: \"无法加载其内容。\",\n noContents: \"尚未分配任何内容。\",\n proposed: \"建议\",\n },\n mediaPanel: {\n noAccess: \"你无法编辑此项目的媒体。\",\n loadFailed: \"无法加载媒体。\",\n title: \"视频\",\n ready: \"就绪\",\n processing: \"处理中\",\n failed: \"失败\",\n empty: \"还没有视频\",\n regenerate: \"重新处理\",\n failedFile: \"{name} 处理失败:{reason}\",\n source: \"视频文件\",\n noSource: \"尚未上传视频。\",\n replace: \"替换视频\",\n upload: \"上传视频\",\n phase_hashing: \"正在检查文件…\",\n phase_uploading: \"正在上传…\",\n phase_completing: \"正在完成上传…\",\n phase_processing: \"正在处理…\",\n poster: \"封面\",\n choosePoster: \"选择封面\",\n subtitles: \"字幕\",\n noSubtitles: \"没有字幕。\",\n forced: \"强制\",\n subtitleFile: \"字幕文件(.vtt、.srt)\",\n language: \"语言\",\n label: \"标签\",\n labelPlaceholder: \"在播放器中显示\",\n forcedLabel: \"强制字幕(仅标识和外语对白)\",\n addSubtitle: \"添加字幕\",\n removeSubtitleTitle: \"移除字幕“{name}”?\",\n },\n uploadQueue: {\n drop: \"将文件拖到这里,或\",\n add: \"添加文件\",\n unfinished: \"未完成的上传\",\n unfinishedHint: \"再次添加同一文件即可从中断处继续。\",\n forget: \"忘记\",\n summary: \"已上传 {done}/{total}\",\n pauseAll: \"全部暂停\",\n resumeAll: \"全部继续\",\n resumed: \"已继续\",\n queued: \"等待中\",\n creating: \"正在创建\",\n uploading: \"正在上传\",\n committing: \"正在保存\",\n done: \"已上传\",\n failed: \"失败\",\n paused: \"已暂停\",\n pause: \"暂停\",\n resume: \"继续\",\n },\n};\n"],"mappings":"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"}
|
package/dist/locales/en.d.ts
CHANGED
|
@@ -431,6 +431,57 @@ export declare const en: {
|
|
|
431
431
|
noContents: string;
|
|
432
432
|
proposed: string;
|
|
433
433
|
};
|
|
434
|
+
mediaPanel: {
|
|
435
|
+
noAccess: string;
|
|
436
|
+
loadFailed: string;
|
|
437
|
+
title: string;
|
|
438
|
+
ready: string;
|
|
439
|
+
processing: string;
|
|
440
|
+
failed: string;
|
|
441
|
+
empty: string;
|
|
442
|
+
regenerate: string;
|
|
443
|
+
failedFile: string;
|
|
444
|
+
source: string;
|
|
445
|
+
noSource: string;
|
|
446
|
+
replace: string;
|
|
447
|
+
upload: string;
|
|
448
|
+
phase_hashing: string;
|
|
449
|
+
phase_uploading: string;
|
|
450
|
+
phase_completing: string;
|
|
451
|
+
phase_processing: string;
|
|
452
|
+
poster: string;
|
|
453
|
+
choosePoster: string;
|
|
454
|
+
subtitles: string;
|
|
455
|
+
noSubtitles: string;
|
|
456
|
+
forced: string;
|
|
457
|
+
subtitleFile: string;
|
|
458
|
+
language: string;
|
|
459
|
+
label: string;
|
|
460
|
+
labelPlaceholder: string;
|
|
461
|
+
forcedLabel: string;
|
|
462
|
+
addSubtitle: string;
|
|
463
|
+
removeSubtitleTitle: string;
|
|
464
|
+
};
|
|
465
|
+
uploadQueue: {
|
|
466
|
+
drop: string;
|
|
467
|
+
add: string;
|
|
468
|
+
unfinished: string;
|
|
469
|
+
unfinishedHint: string;
|
|
470
|
+
forget: string;
|
|
471
|
+
summary: string;
|
|
472
|
+
pauseAll: string;
|
|
473
|
+
resumeAll: string;
|
|
474
|
+
resumed: string;
|
|
475
|
+
queued: string;
|
|
476
|
+
creating: string;
|
|
477
|
+
uploading: string;
|
|
478
|
+
committing: string;
|
|
479
|
+
done: string;
|
|
480
|
+
failed: string;
|
|
481
|
+
paused: string;
|
|
482
|
+
pause: string;
|
|
483
|
+
resume: string;
|
|
484
|
+
};
|
|
434
485
|
comments: {
|
|
435
486
|
title: string;
|
|
436
487
|
count_one: string;
|
package/dist/react/context.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { ReadStore } from './store.js';
|
|
|
5
5
|
/** What failed: a component's or hook's save, load or render step. */
|
|
6
6
|
export type ContentKitOperation = "poster.load" | "poster.frame" | "poster.save" | "slot.load" | "slot.decode" | "slot.save" | "slot.remove" | "upload" | "folder.load" | "folder.commit" | "folder.update" | "folder.process" | "crop.load" | "crop.save" | SocialOperation;
|
|
7
7
|
/** What failed in a content component: a load or a write. */
|
|
8
|
-
export type SocialOperation = "comment.load" | "comment.post" | "comment.edit" | "comment.delete" | "comment.react" | "comment.restore" | "ban.save" | "ban.lift" | "reaction.save" | "favorite.save" | "poll.load" | "poll.vote" | "poll.answer" | "poll.save" | "post.load" | "post.save" | "moderation.load" | "moderation.resolve" | "taxonomy.load" | "taxonomy.save";
|
|
8
|
+
export type SocialOperation = "comment.load" | "comment.post" | "comment.edit" | "comment.delete" | "comment.react" | "comment.restore" | "ban.save" | "ban.lift" | "reaction.save" | "favorite.save" | "poll.load" | "poll.vote" | "poll.answer" | "poll.save" | "post.load" | "post.save" | "moderation.load" | "moderation.resolve" | "taxonomy.load" | "taxonomy.save" | "media.save";
|
|
9
9
|
/** The failure's context: the operation and, for one file, its name. */
|
|
10
10
|
export interface ContentKitErrorInfo {
|
|
11
11
|
operation: ContentKitOperation;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","names":[],"sources":["../../src/react/context.ts"],"sourcesContent":["import { createContext, useCallback, useContext, useLayoutEffect, useRef } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { ContentURLs } from \"../urls/index.js\";\nimport type { ReadStore } from \"./store.js\";\n\n/** What failed: a component's or hook's save, load or render step. */\nexport type ContentKitOperation =\n | \"poster.load\"\n | \"poster.frame\"\n | \"poster.save\"\n | \"slot.load\"\n | \"slot.decode\"\n | \"slot.save\"\n | \"slot.remove\"\n | \"upload\"\n | \"folder.load\"\n | \"folder.commit\"\n | \"folder.update\"\n | \"folder.process\"\n | \"crop.load\"\n | \"crop.save\"\n | SocialOperation;\n\n/** What failed in a content component: a load or a write. */\nexport type SocialOperation =\n | \"comment.load\"\n | \"comment.post\"\n | \"comment.edit\"\n | \"comment.delete\"\n | \"comment.react\"\n | \"comment.restore\"\n | \"ban.save\"\n | \"ban.lift\"\n | \"reaction.save\"\n | \"favorite.save\"\n | \"poll.load\"\n | \"poll.vote\"\n | \"poll.answer\"\n | \"poll.save\"\n | \"post.load\"\n | \"post.save\"\n | \"moderation.load\"\n | \"moderation.resolve\"\n | \"taxonomy.load\"\n | \"taxonomy.save\";\n\n/** The failure's context: the operation and, for one file, its name. */\nexport interface ContentKitErrorInfo {\n operation: ContentKitOperation;\n file?: string;\n}\n\n/**\n * Every failure a component shows is also reported here (aborts excepted), so\n * the host can toast or log it. Components still show it in place.\n */\nexport type ContentKitErrorHandler = (error: ContentKitError, info: ContentKitErrorInfo) => void;\n\n/** The host router: in-app navigation, e.g. a canonical replace. */\nexport type Navigate = (to: string, options?: { replace?: boolean }) => void;\n\nexport interface ContentKitContextValue {\n client: ContentKitClient;\n /**\n * The signed-in user's id as ContentKit sees it, null when signed out,\n * undefined when the host does not say. Content reads are kept per viewer.\n */\n viewer?: string | null;\n /** Request language; keep it in sync with the client's language callback. */\n language?: string;\n /** Change when the current viewer's permissions or entitlements change. */\n accessRevision?: string | number;\n readScope: string;\n /** Asks the visitor to sign in; with it, signed-out visitors are prompted instead of acting anonymously. */\n onSignIn?: () => void;\n /** The host's content URL config (createContentURLs). */\n urls?: ContentURLs;\n navigate?: Navigate;\n onError?: ContentKitErrorHandler;\n /** The reads hooks share. */\n store: ReadStore;\n}\n\nexport const ContentKitContext = createContext<ContentKitContextValue | null>(null);\n\n/** Values that can change a read without changing its content reference. */\nexport function useReadScope(): string {\n return useContext(ContentKitContext)?.readScope ?? \"{}\";\n}\n\n/** The ContentKitProvider's value; throws outside one. */\nexport function useContentKit(): ContentKitContextValue {\n const ctx = useContext(ContentKitContext);\n if (!ctx) throw new Error(\"contentkit-ui: wrap the tree in <ContentKitProvider client>\");\n return ctx;\n}\n\n/** The client a hook or component uses: its own, else the provider's; throws without either. */\nexport function useContentKitClient(own?: ContentKitClient | null): ContentKitClient {\n const c = useOptionalContentKitClient(own);\n if (!c) throw new Error(\"contentkit-ui: pass `client` or render inside <ContentKitProvider client>\");\n return c;\n}\n\nexport function useOptionalContentKitClient(own?: ContentKitClient | null): ContentKitClient | null {\n const ctx = useContext(ContentKitContext);\n const client = own ?? ctx?.client ?? null;\n const scope = useReadScope();\n useLayoutEffect(() => { client?.setReadScope(scope); }, [client, scope]);\n return client;\n}\n\n/** The provider's content URL helpers; throws when none were given. */\nexport function useContentURLs(): ContentURLs {\n const urls = useContext(ContentKitContext)?.urls;\n if (!urls) throw new Error(\"contentkit-ui: pass `urls` to <ContentKitProvider>\");\n return urls;\n}\n\n/** A stable reporter: the component's own `onError`, else the provider's. */\nexport function useErrorReporter(own?: ContentKitErrorHandler): (e: unknown, operation: ContentKitOperation, file?: string) => void {\n const ctx = useContext(ContentKitContext)?.onError;\n const handler = useRef(own ?? ctx);\n handler.current = own ?? ctx;\n return useCallback((e: unknown, operation: ContentKitOperation, file?: string) => {\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") handler.current?.(error, file === undefined ? { operation } : { operation, file });\n }, []);\n}\n\n/** Runs a write, e.g. auth-ui's useStepUpGuard(): re-authenticates when the write asks for a recent sign-in, then retries. */\nexport type ContentKitGuard = <T>(write: () => Promise<T>) => Promise<T>;\n\n/** The admin flow's guard; hooks run their writes through it. */\nexport const GuardContext = createContext<ContentKitGuard | undefined>(undefined);\n\n/** Runs fn through the flow's guard, when the host gave one. */\nexport function useGuarded(): <T>(fn: () => Promise<T>) => Promise<T> {\n const guard = useContext(GuardContext);\n return useCallback(<T,>(fn: () => Promise<T>) => (guard ? guard(fn) : fn()), [guard]);\n}\n"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"context.js","names":[],"sources":["../../src/react/context.ts"],"sourcesContent":["import { createContext, useCallback, useContext, useLayoutEffect, useRef } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { toContentKitError, type ContentKitError } from \"../client/errors.js\";\nimport type { ContentURLs } from \"../urls/index.js\";\nimport type { ReadStore } from \"./store.js\";\n\n/** What failed: a component's or hook's save, load or render step. */\nexport type ContentKitOperation =\n | \"poster.load\"\n | \"poster.frame\"\n | \"poster.save\"\n | \"slot.load\"\n | \"slot.decode\"\n | \"slot.save\"\n | \"slot.remove\"\n | \"upload\"\n | \"folder.load\"\n | \"folder.commit\"\n | \"folder.update\"\n | \"folder.process\"\n | \"crop.load\"\n | \"crop.save\"\n | SocialOperation;\n\n/** What failed in a content component: a load or a write. */\nexport type SocialOperation =\n | \"comment.load\"\n | \"comment.post\"\n | \"comment.edit\"\n | \"comment.delete\"\n | \"comment.react\"\n | \"comment.restore\"\n | \"ban.save\"\n | \"ban.lift\"\n | \"reaction.save\"\n | \"favorite.save\"\n | \"poll.load\"\n | \"poll.vote\"\n | \"poll.answer\"\n | \"poll.save\"\n | \"post.load\"\n | \"post.save\"\n | \"moderation.load\"\n | \"moderation.resolve\"\n | \"taxonomy.load\"\n | \"taxonomy.save\"\n | \"media.save\";\n\n/** The failure's context: the operation and, for one file, its name. */\nexport interface ContentKitErrorInfo {\n operation: ContentKitOperation;\n file?: string;\n}\n\n/**\n * Every failure a component shows is also reported here (aborts excepted), so\n * the host can toast or log it. Components still show it in place.\n */\nexport type ContentKitErrorHandler = (error: ContentKitError, info: ContentKitErrorInfo) => void;\n\n/** The host router: in-app navigation, e.g. a canonical replace. */\nexport type Navigate = (to: string, options?: { replace?: boolean }) => void;\n\nexport interface ContentKitContextValue {\n client: ContentKitClient;\n /**\n * The signed-in user's id as ContentKit sees it, null when signed out,\n * undefined when the host does not say. Content reads are kept per viewer.\n */\n viewer?: string | null;\n /** Request language; keep it in sync with the client's language callback. */\n language?: string;\n /** Change when the current viewer's permissions or entitlements change. */\n accessRevision?: string | number;\n readScope: string;\n /** Asks the visitor to sign in; with it, signed-out visitors are prompted instead of acting anonymously. */\n onSignIn?: () => void;\n /** The host's content URL config (createContentURLs). */\n urls?: ContentURLs;\n navigate?: Navigate;\n onError?: ContentKitErrorHandler;\n /** The reads hooks share. */\n store: ReadStore;\n}\n\nexport const ContentKitContext = createContext<ContentKitContextValue | null>(null);\n\n/** Values that can change a read without changing its content reference. */\nexport function useReadScope(): string {\n return useContext(ContentKitContext)?.readScope ?? \"{}\";\n}\n\n/** The ContentKitProvider's value; throws outside one. */\nexport function useContentKit(): ContentKitContextValue {\n const ctx = useContext(ContentKitContext);\n if (!ctx) throw new Error(\"contentkit-ui: wrap the tree in <ContentKitProvider client>\");\n return ctx;\n}\n\n/** The client a hook or component uses: its own, else the provider's; throws without either. */\nexport function useContentKitClient(own?: ContentKitClient | null): ContentKitClient {\n const c = useOptionalContentKitClient(own);\n if (!c) throw new Error(\"contentkit-ui: pass `client` or render inside <ContentKitProvider client>\");\n return c;\n}\n\nexport function useOptionalContentKitClient(own?: ContentKitClient | null): ContentKitClient | null {\n const ctx = useContext(ContentKitContext);\n const client = own ?? ctx?.client ?? null;\n const scope = useReadScope();\n useLayoutEffect(() => { client?.setReadScope(scope); }, [client, scope]);\n return client;\n}\n\n/** The provider's content URL helpers; throws when none were given. */\nexport function useContentURLs(): ContentURLs {\n const urls = useContext(ContentKitContext)?.urls;\n if (!urls) throw new Error(\"contentkit-ui: pass `urls` to <ContentKitProvider>\");\n return urls;\n}\n\n/** A stable reporter: the component's own `onError`, else the provider's. */\nexport function useErrorReporter(own?: ContentKitErrorHandler): (e: unknown, operation: ContentKitOperation, file?: string) => void {\n const ctx = useContext(ContentKitContext)?.onError;\n const handler = useRef(own ?? ctx);\n handler.current = own ?? ctx;\n return useCallback((e: unknown, operation: ContentKitOperation, file?: string) => {\n const error = toContentKitError(e);\n if (error.code !== \"aborted\") handler.current?.(error, file === undefined ? { operation } : { operation, file });\n }, []);\n}\n\n/** Runs a write, e.g. auth-ui's useStepUpGuard(): re-authenticates when the write asks for a recent sign-in, then retries. */\nexport type ContentKitGuard = <T>(write: () => Promise<T>) => Promise<T>;\n\n/** The admin flow's guard; hooks run their writes through it. */\nexport const GuardContext = createContext<ContentKitGuard | undefined>(undefined);\n\n/** Runs fn through the flow's guard, when the host gave one. */\nexport function useGuarded(): <T>(fn: () => Promise<T>) => Promise<T> {\n const guard = useContext(GuardContext);\n return useCallback(<T,>(fn: () => Promise<T>) => (guard ? guard(fn) : fn()), [guard]);\n}\n"],"mappings":";;;AAqFA,IAAa,IAAoB,EAA6C,IAAI;AAGlF,SAAgB,IAAuB;CACrC,OAAO,EAAW,CAAiB,CAAC,EAAE,aAAa;AACrD;AAGA,SAAgB,IAAwC;CACtD,IAAM,IAAM,EAAW,CAAiB;CACxC,IAAI,CAAC,GAAK,MAAU,MAAM,6DAA6D;CACvF,OAAO;AACT;AAGA,SAAgB,EAAoB,GAAiD;CACnF,IAAM,IAAI,EAA4B,CAAG;CACzC,IAAI,CAAC,GAAG,MAAU,MAAM,2EAA2E;CACnG,OAAO;AACT;AAEA,SAAgB,EAA4B,GAAwD;CAClG,IAAM,IAAM,EAAW,CAAiB,GAClC,IAAS,KAAO,GAAK,UAAU,MAC/B,IAAQ,EAAa;CAE3B,OADA,QAAsB;EAAE,GAAQ,aAAa,CAAK;CAAG,GAAG,CAAC,GAAQ,CAAK,CAAC,GAChE;AACT;AAGA,SAAgB,IAA8B;CAC5C,IAAM,IAAO,EAAW,CAAiB,CAAC,EAAE;CAC5C,IAAI,CAAC,GAAM,MAAU,MAAM,oDAAoD;CAC/E,OAAO;AACT;AAGA,SAAgB,EAAiB,GAAmG;CAClI,IAAM,IAAM,EAAW,CAAiB,CAAC,EAAE,SACrC,IAAU,EAAO,KAAO,CAAG;CAEjC,OADA,EAAQ,UAAU,KAAO,GAClB,GAAa,GAAY,GAAgC,MAAkB;EAChF,IAAM,IAAQ,EAAkB,CAAC;EACjC,AAAI,EAAM,SAAS,aAAW,EAAQ,UAAU,GAAO,MAAS,KAAA,IAAY,EAAE,aAAU,IAAI;GAAE;GAAW;EAAK,CAAC;CACjH,GAAG,CAAC,CAAC;AACP;AAMA,IAAa,IAAe,EAA2C,KAAA,CAAS;AAGhF,SAAgB,IAAsD;CACpE,IAAM,IAAQ,EAAW,CAAY;CACrC,OAAO,GAAiB,MAA0B,IAAQ,EAAM,CAAE,IAAI,EAAG,GAAI,CAAC,CAAK,CAAC;AACtF"}
|
package/dist/react/folder.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folder.js","names":[],"sources":["../../src/react/folder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { ContentKitError, failureError, toContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, Op, ReadResult, RefBody, UploadRule } from \"../client/generated/wire.js\";\nimport { stem } from \"../client/media/client.js\";\nimport type { QueueItem } from \"../client/media/queue.js\";\nimport { filesFor, isPattern, ruleDir, ruleFor, screenFiles, uniqueName, uploadRules, type Screened } from \"../client/media/rules.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"./context.js\";\nimport { useMediaRead, type UseMediaRead } from \"./read.js\";\nimport { useUploadQueue, type UseUploadQueue } from \"./upload.js\";\n\nexport interface MediaFolderOptions {\n /**\n * The upload paths shown and added to, e.g. [\"images/{name}\",\n * \"videos/{name}\"]: one group each. Default every pattern path of the kind\n * the server does not name. A file goes to the first path whose rule takes\n * its type.\n */\n paths?: readonly string[];\n /**\n * \"manual\" (default): commit() adds the uploaded files. \"auto\": each\n * upload commits once those before it have, in the order added (a draft).\n */\n commit?: \"manual\" | \"auto\";\n /** A file's name under its path; default its own, numbered around names taken. */\n name?: (file: File) => string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Poll interval (ms) while uploads process. Default 2500. */\n poll?: number | false;\n /** Every failure (uploads, commits, updates, processing); default the provider's. */\n onError?: ContentKitErrorHandler;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface FolderGroup {\n /** The upload path, e.g. \"images/{name}\". */\n path: string;\n /** Its rules, once an editor read states them. */\n rule?: UploadRule;\n /** Its uploads in manifest order. */\n files: FileInfo[];\n}\n\nexport interface UseMediaFolder {\n /** The item; null until the host has one (files added meanwhile wait for it). */\n ref: RefBody | null;\n /** The options the folder was opened with. */\n options: MediaFolderOptions;\n /** The editor read of the item (every window, polled while processing). */\n read: UseMediaRead;\n groups: FolderGroup[];\n /** Every group's uploads. */\n uploads: FileInfo[];\n /** The upload queue; a file leaves it once the editor read lists it. */\n queue: UseUploadQueue;\n /**\n * Files added before the item and its editor read (the upload rules) were\n * known. They are screened and queued when the rules arrive, or refused\n * when the read fails or states no rules (the actor may not edit the item).\n */\n waiting: readonly File[];\n /** Screens files against the rules and queues the rest (once the rules are known); refusals land in refused. */\n add: (files: Iterable<File>) => void;\n /** The last screening's refusals. */\n refused: Screened[\"refused\"];\n dismiss: () => void;\n /** Moves an upload to index among its group's uploads. */\n move: (path: string, index: number) => Promise<void>;\n remove: (paths: readonly string[]) => Promise<void>;\n /** Renames an upload within its path; the extension stays unless name has one. */\n rename: (path: string, name: string) => Promise<void>;\n edit: (path: string, edit: Edit | null) => Promise<void>;\n /** Uploads file in place of the upload at path (same position and meta, no edit). */\n replace: (path: string, file: File) => Promise<void>;\n /** Commits the uploaded files in queue order. */\n commit: () => Promise<FileInfo[]>;\n /** Stops every upload, aborts its multipart upload and empties the queue (before deleting a draft). */\n discard: () => void;\n /** An update (move, remove, rename, edit, replace) is running. */\n updating: boolean;\n committing: boolean;\n /**\n * Work is in flight: files wait for the rules, uploads are queued or\n * running, an automatic commit is due, or a commit or update runs.\n * Leaving the page now interrupts it.\n */\n busy: boolean;\n /** The item's uploads plus the files on their way in (waiting, queued, uploading, uploaded, being added); failed uploads are not counted. */\n fileCount: number;\n /** The last update's or commit's failure; cleared by the next success. */\n error?: ContentKitError;\n}\n\nconst ALL = { start: 0, end: Number.MAX_SAFE_INTEGER, chunk: 200 };\n\n/** The upload's name under its group (\"images/a.png\" in \"images/{name}\" is \"a.png\"). */\nexport const nameIn = (group: { path: string }, path: string) => path.slice(ruleDir(group.path).length);\n\n/** Its smallest derived image with a URL: a thumbnail for an editor. */\nexport function thumbnailOf(read: ReadResult | null | undefined, upload: FileInfo): FileInfo | undefined {\n let best: FileInfo | undefined;\n for (const f of read?.files ?? []) {\n if (f.upload || f.from !== upload.path || !f.url || !f.type.startsWith(\"image/\")) continue;\n if (!best || (f.w ?? Infinity) < (best.w ?? Infinity)) best = f;\n }\n return best;\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\nconst keyOf = (ref: RefBody | null) => (ref ? `${ref.kind}/${ref.id}` : \"\");\n\nconst noRules = new WeakMap<ReadResult, ContentKitError>();\n/** The refusal for files added to an item whose read carries no upload rules (one per read). */\nfunction notEditor(read: ReadResult): ContentKitError {\n let e = noRules.get(read);\n if (!e) noRules.set(read, (e = new ContentKitError(\"forbidden\", \"the editor read states no upload rules\", { status: 403 })));\n return e;\n}\n\n/** Queue items still on their way in: not failed, and not yet listed by the read. */\nfunction inbound(items: readonly QueueItem[], listed: ReadonlySet<string>) {\n return items.filter((i) => i.status !== \"failed\" && !(i.status === \"committed\" && listed.has(i.path)));\n}\n\n/**\n * An item's folder for its editors: the editor read, an upload queue, the\n * kind's upload rules screening files before they upload, and the updates\n * (move, remove, rename, edit, replace). Files added before the editor read\n * arrives wait for its rules; with a null ref they wait for the item too (a\n * draft created on the first drop). Uploads the worker fails after the read\n * are reported once (\"folder.process\"). Another ref gets a fresh queue.\n */\nexport function useMediaFolder(ref: RefBody | null | undefined, o: MediaFolderOptions = {}): UseMediaFolder {\n const client = useContentKitClient(o.client);\n const report = useErrorReporter(o.onError);\n const item = ref ?? null;\n const key = keyOf(item);\n const target = item ?? NO_ITEM;\n // Without an item there is nothing to read: a supplied null read never fetches.\n const read = useMediaRead(target, { editor: true, window: ALL, poll: o.poll, client, read: item ? undefined : null });\n const rules = uploadRules(read.read);\n const pathsKey = o.paths?.join(\"\\n\");\n const groupPaths = useMemo(\n () => (pathsKey !== undefined ? pathsKey.split(\"\\n\").filter(Boolean) : rules.filter((r) => isPattern(r.path) && !r.named).map((r) => r.path)),\n [pathsKey, rules],\n );\n const groups = useMemo((): FolderGroup[] => {\n const known: { path: string }[] = rules.length ? rules : groupPaths.map((path) => ({ path }));\n return groupPaths.map((path) => ({ path, rule: rules.find((r) => r.path === path), files: filesFor(read.read, { path }, known) }));\n }, [groupPaths, rules, read.read]);\n const uploads = useMemo(() => groups.flatMap((g) => g.files), [groups]);\n const listed = useMemo(() => new Set(uploads.map((f) => f.path)), [uploads]);\n\n const raw = useUploadQueue({ client, ref: target, path: `${ruleDir(groupPaths[0] ?? \"{name}\")}{name}`, concurrency: o.concurrency });\n // The UploadQueue itself is stable per item; the hook's wrapper is new every render.\n const q = raw.queue;\n // A committed file stays in the queue until the read lists it, so it never drops out of view.\n const items = useMemo(() => raw.items.filter((i) => !(i.status === \"committed\" && listed.has(i.path))), [raw.items, listed]);\n const queue = { ...raw, items };\n const [refused, setRefused] = useState<Screened[\"refused\"]>([]);\n const [waiting, setWaiting] = useState<readonly File[]>([]);\n const pending = useRef<File[]>([]);\n const [updating, setUpdating] = useState(0);\n const [committing, setCommitting] = useState(false);\n const [error, setError] = useState<ContentKitError>();\n // An automatic commit that failed: no more until commit() or new files.\n const [stall, setStall] = useState<{ key: string; error: ContentKitError }>();\n const stalled = stall?.key === key ? stall.error : undefined;\n const opts = useRef(o);\n opts.current = o;\n // The rules are known once the item's editor read states them. A failed read\n // refuses what waits, and so does one without rules: the server answered a\n // viewer's read, so nothing uploads here.\n const ready = !!item && !!read.read?.uploads;\n const loadError = !item ? undefined : !read.read ? read.error : !read.read.uploads ? notEditor(read.read) : undefined;\n const live = useRef({ groups, ref: target, listed, ready, loadError });\n live.current = { groups, ref: target, listed, ready, loadError };\n\n const place = useCallback(\n (files: File[]) => {\n const { groups, listed } = live.current;\n const gs = groups.map((g) => g.rule ?? { path: g.path, types: [], max_bytes: 0 });\n const queued = inbound(q.getSnapshot().items, listed);\n const inGroup = (path: string, rule: UploadRule) => ruleFor(gs, path)?.path === rule.path;\n const s = screenFiles(files, gs, (rule) => groups.find((g) => g.path === rule.path)!.files.length + queued.filter((i) => inGroup(i.path, rule)).length);\n const taken = new Map<string, string[]>();\n const takenIn = (rule: UploadRule) => {\n let t = taken.get(rule.path);\n if (!t) {\n const g = groups.find((x) => x.path === rule.path)!;\n t = [...g.files.map((f) => nameIn(g, f.path)), ...queued.filter((i) => inGroup(i.path, rule)).map((i) => nameIn(g, i.path))];\n taken.set(rule.path, t);\n }\n return t;\n };\n const paths = new Map<File, string>();\n for (const { file, rule } of s.accepted) {\n const t = takenIn(rule);\n const name = opts.current.name?.(file) ?? uniqueName(file.name, t);\n t.push(name);\n paths.set(file, ruleDir(rule.path) + name);\n }\n if (paths.size) {\n q.add(paths.keys(), { path: (f) => paths.get(f)! });\n // New files lift a block and restart a queue discard() paused.\n q.start();\n setStall(undefined);\n }\n setRefused(s.refused);\n for (const r of s.refused) report(r.error, \"upload\", r.file.name);\n },\n [q, report],\n );\n\n // Screens what waits once the rules are known; a failed read refuses it (the read failure is reported once, as folder.load).\n const flush = useCallback(() => {\n const files = pending.current;\n const { ready, loadError } = live.current;\n if (!files.length || (!ready && !loadError)) return false;\n pending.current = [];\n setWaiting([]);\n if (ready) place(files);\n else setRefused(files.map((file) => ({ file, error: loadError! })));\n return true;\n }, [place]);\n\n const add = useCallback(\n (files: Iterable<File>) => {\n const list = [...files];\n if (!list.length) return;\n pending.current = [...pending.current, ...list];\n if (!flush()) setWaiting(pending.current);\n },\n [flush],\n );\n useEffect(() => void flush(), [flush, ready, loadError]);\n\n const update = useCallback(\n async (ops: Op[], run?: () => Promise<unknown>) => {\n setUpdating((n) => n + 1);\n try {\n if (run) await run();\n else await client.media.commit(live.current.ref, ops);\n setError(undefined);\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\") setError(err);\n report(err, \"folder.update\");\n throw err;\n } finally {\n setUpdating((n) => n - 1);\n }\n },\n [client, report],\n );\n\n const commit = useCallback(async () => {\n setCommitting(true);\n try {\n const files = await q.commit(undefined, { head: opts.current.commit === \"auto\" });\n setError(undefined);\n return files;\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"folder.commit\");\n throw err;\n } finally {\n setCommitting(false);\n }\n }, [q, report]);\n\n // Committed files leave the queue once the read lists them, or once a read lands without them.\n useEffect(() => {\n for (const i of raw.items) if (i.status === \"committed\" && (listed.has(i.path) || !read.loading)) q.remove(i.id);\n }, [raw.items, listed, read.loading, q]);\n\n // A draft commits the uploaded head of the queue as it finishes; a failed commit waits for commit().\n const auto = o.commit === \"auto\";\n const headReady = items.find((i) => i.status !== \"committed\")?.status === \"uploaded\";\n useEffect(() => {\n if (!auto || !headReady || committing || stalled) return;\n commit().catch((e: unknown) => setStall({ key, error: toContentKitError(e) }));\n }, [auto, headReady, committing, stalled, commit, key]);\n const manualCommit = useCallback(async () => {\n setStall(undefined);\n return commit();\n }, [commit]);\n\n // Each failed upload and queue-wide refusal is reported once; rows keep showing it.\n const seen = useRef(new Set<unknown>());\n useEffect(() => {\n for (const i of raw.items) {\n if (i.status !== \"failed\" || !i.error || seen.current.has(i.error)) continue;\n seen.current.add(i.error);\n report(i.error, \"upload\", i.file.name);\n }\n if (raw.blocked && !seen.current.has(raw.blocked)) {\n seen.current.add(raw.blocked);\n report(raw.blocked, \"upload\");\n }\n }, [raw.items, raw.blocked, report]);\n\n // Processing failures that appear after the item's first read.\n const failures = useRef<{ key: string; seen: Set<string> } | null>(null);\n useEffect(() => {\n if (!read.read) return;\n const failed = uploads.filter((f) => f.failed);\n if (failures.current?.key !== key) {\n failures.current = { key, seen: new Set(failed.map((f) => `${f.path}:${f.failed!.of}`)) };\n return;\n }\n for (const f of failed) {\n const id = `${f.path}:${f.failed!.of}`;\n if (failures.current.seen.has(id)) continue;\n failures.current.seen.add(id);\n report(failureError(f.failed!), \"folder.process\", f.path.split(\"/\").pop());\n }\n }, [read.read, uploads, report, key]);\n\n useEffect(() => void (read.error && report(read.error, \"folder.load\")), [read.error, report]);\n\n const busy =\n waiting.length > 0 ||\n committing ||\n updating > 0 ||\n (auto && headReady && !stalled) ||\n items.some((i) => i.status === \"uploading\" || (i.status === \"queued\" && !raw.blocked));\n const fileCount = uploads.length + waiting.length + inbound(items, listed).length;\n\n // Reads the latest groups, so callbacks stay stable.\n const groupOf = (path: string) => live.current.groups.find((g) => ruleFor([{ path: g.path }], path));\n return {\n ref: item,\n options: o,\n read,\n groups,\n uploads,\n queue,\n waiting,\n add,\n refused,\n dismiss: useCallback(() => setRefused([]), []),\n move: useCallback((path, index) => update([{ op: \"move\", path, index }]), [update]),\n remove: useCallback((paths) => update(paths.map((path): Op => ({ op: \"remove\", path }))), [update]),\n rename: useCallback(\n (path, name) => {\n const g = groupOf(path);\n const to = (g ? ruleDir(g.path) : path.slice(0, path.lastIndexOf(\"/\") + 1)) + name.trim();\n if (stem(to) === stem(path)) return Promise.resolve();\n if (g?.files.some((f) => stem(f.path) === stem(to))) {\n const err = new ContentKitError(\"conflict\", `${name} exists`, { status: 409 });\n setError(err);\n return Promise.reject(err);\n }\n return update([{ op: \"rename\", path, to }]);\n },\n [update],\n ),\n edit: useCallback((path, edit) => update([{ op: \"edit\", path, ...(edit ? { edit } : {}) }]), [update]),\n replace: useCallback(\n (path, file) => update([], () => client.media.put(file, { ref: live.current.ref, path: stem(path), wait: false })),\n [update, client],\n ),\n commit: manualCommit,\n discard: useCallback(() => {\n pending.current = [];\n setWaiting([]);\n for (const i of q.getSnapshot().items) q.remove(i.id);\n q.pause();\n }, [q]),\n updating: updating > 0,\n committing,\n busy,\n fileCount,\n error: error ?? stalled,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"folder.js","names":[],"sources":["../../src/react/folder.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { ContentKitClient } from \"../client/client.js\";\nimport { ContentKitError, failureError, toContentKitError } from \"../client/errors.js\";\nimport type { Edit, FileInfo, Op, ReadResult, RefBody, UploadRule } from \"../client/generated/wire.js\";\nimport { stem } from \"../client/media/client.js\";\nimport type { QueueItem } from \"../client/media/queue.js\";\nimport { filesFor, isPattern, ruleDir, ruleFor, screenFiles, uniqueName, uploadRules, type Screened } from \"../client/media/rules.js\";\nimport { useContentKitClient, useErrorReporter, type ContentKitErrorHandler } from \"./context.js\";\nimport { useMediaRead, type UseMediaRead } from \"./read.js\";\nimport { useUploadQueue, type UseUploadQueue } from \"./upload.js\";\n\nexport interface MediaFolderOptions {\n /**\n * The upload paths shown and added to, e.g. [\"images/{name}\",\n * \"videos/{name}\"]: one group each. Default every pattern path of the kind\n * the server does not name. A file goes to the first path whose rule takes\n * its type.\n */\n paths?: readonly string[];\n /**\n * \"manual\" (default): commit() adds the uploaded files. \"auto\": each\n * upload commits once those before it have, in the order added (a draft).\n */\n commit?: \"manual\" | \"auto\";\n /** A file's name under its path; default its own, numbered around names taken. */\n name?: (file: File) => string;\n /** Files uploading at once. Default 2. */\n concurrency?: number;\n /** Poll interval (ms) while uploads process. Default 2500. */\n poll?: number | false;\n /** Every failure (uploads, commits, updates, processing); default the provider's. */\n onError?: ContentKitErrorHandler;\n /** Overrides the provider's client. */\n client?: ContentKitClient;\n}\n\nexport interface FolderGroup {\n /** The upload path, e.g. \"images/{name}\". */\n path: string;\n /** Its rules, once an editor read states them. */\n rule?: UploadRule;\n /** Its uploads in manifest order. */\n files: FileInfo[];\n}\n\nexport interface UseMediaFolder {\n /** The item; null until the host has one (files added meanwhile wait for it). */\n ref: RefBody | null;\n /** The options the folder was opened with. */\n options: MediaFolderOptions;\n /** The editor read of the item (every window, polled while processing). */\n read: UseMediaRead;\n groups: FolderGroup[];\n /** Every group's uploads. */\n uploads: FileInfo[];\n /** The upload queue; a file leaves it once the editor read lists it. */\n queue: UseUploadQueue;\n /**\n * Files added before the item and its editor read (the upload rules) were\n * known. They are screened and queued when the rules arrive, or refused\n * when the read fails or states no rules (the actor may not edit the item).\n */\n waiting: readonly File[];\n /** Screens files against the rules and queues the rest (once the rules are known); refusals land in refused. */\n add: (files: Iterable<File>) => void;\n /** The last screening's refusals. */\n refused: Screened[\"refused\"];\n dismiss: () => void;\n /** Moves an upload to index among its group's uploads. */\n move: (path: string, index: number) => Promise<void>;\n remove: (paths: readonly string[]) => Promise<void>;\n /** Renames an upload within its path; the extension stays unless name has one. */\n rename: (path: string, name: string) => Promise<void>;\n edit: (path: string, edit: Edit | null) => Promise<void>;\n /** Uploads file in place of the upload at path (same position and meta, no edit). */\n replace: (path: string, file: File) => Promise<void>;\n /** Commits the uploaded files in queue order. */\n commit: () => Promise<FileInfo[]>;\n /** Stops every upload, aborts its multipart upload and empties the queue (before deleting a draft). */\n discard: () => void;\n /** An update (move, remove, rename, edit, replace) is running. */\n updating: boolean;\n committing: boolean;\n /**\n * Work is in flight: files wait for the rules, uploads are queued or\n * running, an automatic commit is due, or a commit or update runs.\n * Leaving the page now interrupts it.\n */\n busy: boolean;\n /** The item's uploads plus the files on their way in (waiting, queued, uploading, uploaded, being added); failed uploads are not counted. */\n fileCount: number;\n /** The last update's or commit's failure; cleared by the next success. */\n error?: ContentKitError;\n}\n\nconst ALL = { start: 0, end: Number.MAX_SAFE_INTEGER, chunk: 200 };\n\n/** The upload's name under its group (\"images/a.png\" in \"images/{name}\" is \"a.png\"). */\nexport const nameIn = (group: { path: string }, path: string) => path.slice(ruleDir(group.path).length);\n\n/** Its smallest derived image with a URL: a thumbnail for an editor. */\nexport function thumbnailOf(read: ReadResult | null | undefined, upload: FileInfo): FileInfo | undefined {\n let best: FileInfo | undefined;\n for (const f of read?.files ?? []) {\n if (f.upload || f.from !== upload.path || !f.url || !f.type.startsWith(\"image/\")) continue;\n if (!best || (f.w ?? Infinity) < (best.w ?? Infinity)) best = f;\n }\n return best;\n}\n\nconst NO_ITEM: RefBody = { kind: \"\", id: \"\" };\nconst keyOf = (ref: RefBody | null) => (ref ? `${ref.kind}/${ref.id}` : \"\");\n\nconst noRules = new WeakMap<ReadResult, ContentKitError>();\n/** The refusal for files added to an item whose read carries no upload rules (one per read). */\nfunction notEditor(read: ReadResult): ContentKitError {\n let e = noRules.get(read);\n if (!e) noRules.set(read, (e = new ContentKitError(\"forbidden\", \"the editor read states no upload rules\", { status: 403 })));\n return e;\n}\n\n/** Queue items still on their way in: not failed, and not yet listed by the read. */\nfunction inbound(items: readonly QueueItem[], listed: ReadonlySet<string>) {\n return items.filter((i) => i.status !== \"failed\" && !(i.status === \"committed\" && listed.has(i.path)));\n}\n\n/**\n * An item's folder for its editors: the editor read, an upload queue, the\n * kind's upload rules screening files before they upload, and the updates\n * (move, remove, rename, edit, replace). Files added before the editor read\n * arrives wait for its rules; with a null ref they wait for the item too (a\n * draft created on the first drop). Uploads the worker fails after the read\n * are reported once (\"folder.process\"). Another ref gets a fresh queue.\n */\nexport function useMediaFolder(ref: RefBody | null | undefined, o: MediaFolderOptions = {}): UseMediaFolder {\n const client = useContentKitClient(o.client);\n const report = useErrorReporter(o.onError);\n const item = ref ?? null;\n const key = keyOf(item);\n const target = item ?? NO_ITEM;\n // Without an item there is nothing to read: a supplied null read never fetches.\n const read = useMediaRead(target, { editor: true, window: ALL, poll: o.poll, client, read: item ? undefined : null });\n const rules = uploadRules(read.read);\n const pathsKey = o.paths?.join(\"\\n\");\n const groupPaths = useMemo(\n () => (pathsKey !== undefined ? pathsKey.split(\"\\n\").filter(Boolean) : rules.filter((r) => isPattern(r.path) && !r.named).map((r) => r.path)),\n [pathsKey, rules],\n );\n const groups = useMemo((): FolderGroup[] => {\n const known: { path: string }[] = rules.length ? rules : groupPaths.map((path) => ({ path }));\n return groupPaths.map((path) => ({ path, rule: rules.find((r) => r.path === path), files: filesFor(read.read, { path }, known) }));\n }, [groupPaths, rules, read.read]);\n const uploads = useMemo(() => groups.flatMap((g) => g.files), [groups]);\n const listed = useMemo(() => new Set(uploads.map((f) => f.path)), [uploads]);\n\n const raw = useUploadQueue({ client, ref: target, path: `${ruleDir(groupPaths[0] ?? \"{name}\")}{name}`, concurrency: o.concurrency });\n // The ItemUploadQueue itself is stable per item; the hook's wrapper is new every render.\n const q = raw.queue;\n // A committed file stays in the queue until the read lists it, so it never drops out of view.\n const items = useMemo(() => raw.items.filter((i) => !(i.status === \"committed\" && listed.has(i.path))), [raw.items, listed]);\n const queue = { ...raw, items };\n const [refused, setRefused] = useState<Screened[\"refused\"]>([]);\n const [waiting, setWaiting] = useState<readonly File[]>([]);\n const pending = useRef<File[]>([]);\n const [updating, setUpdating] = useState(0);\n const [committing, setCommitting] = useState(false);\n const [error, setError] = useState<ContentKitError>();\n // An automatic commit that failed: no more until commit() or new files.\n const [stall, setStall] = useState<{ key: string; error: ContentKitError }>();\n const stalled = stall?.key === key ? stall.error : undefined;\n const opts = useRef(o);\n opts.current = o;\n // The rules are known once the item's editor read states them. A failed read\n // refuses what waits, and so does one without rules: the server answered a\n // viewer's read, so nothing uploads here.\n const ready = !!item && !!read.read?.uploads;\n const loadError = !item ? undefined : !read.read ? read.error : !read.read.uploads ? notEditor(read.read) : undefined;\n const live = useRef({ groups, ref: target, listed, ready, loadError });\n live.current = { groups, ref: target, listed, ready, loadError };\n\n const place = useCallback(\n (files: File[]) => {\n const { groups, listed } = live.current;\n const gs = groups.map((g) => g.rule ?? { path: g.path, types: [], max_bytes: 0 });\n const queued = inbound(q.getSnapshot().items, listed);\n const inGroup = (path: string, rule: UploadRule) => ruleFor(gs, path)?.path === rule.path;\n const s = screenFiles(files, gs, (rule) => groups.find((g) => g.path === rule.path)!.files.length + queued.filter((i) => inGroup(i.path, rule)).length);\n const taken = new Map<string, string[]>();\n const takenIn = (rule: UploadRule) => {\n let t = taken.get(rule.path);\n if (!t) {\n const g = groups.find((x) => x.path === rule.path)!;\n t = [...g.files.map((f) => nameIn(g, f.path)), ...queued.filter((i) => inGroup(i.path, rule)).map((i) => nameIn(g, i.path))];\n taken.set(rule.path, t);\n }\n return t;\n };\n const paths = new Map<File, string>();\n for (const { file, rule } of s.accepted) {\n const t = takenIn(rule);\n const name = opts.current.name?.(file) ?? uniqueName(file.name, t);\n t.push(name);\n paths.set(file, ruleDir(rule.path) + name);\n }\n if (paths.size) {\n q.add(paths.keys(), { path: (f) => paths.get(f)! });\n // New files lift a block and restart a queue discard() paused.\n q.start();\n setStall(undefined);\n }\n setRefused(s.refused);\n for (const r of s.refused) report(r.error, \"upload\", r.file.name);\n },\n [q, report],\n );\n\n // Screens what waits once the rules are known; a failed read refuses it (the read failure is reported once, as folder.load).\n const flush = useCallback(() => {\n const files = pending.current;\n const { ready, loadError } = live.current;\n if (!files.length || (!ready && !loadError)) return false;\n pending.current = [];\n setWaiting([]);\n if (ready) place(files);\n else setRefused(files.map((file) => ({ file, error: loadError! })));\n return true;\n }, [place]);\n\n const add = useCallback(\n (files: Iterable<File>) => {\n const list = [...files];\n if (!list.length) return;\n pending.current = [...pending.current, ...list];\n if (!flush()) setWaiting(pending.current);\n },\n [flush],\n );\n useEffect(() => void flush(), [flush, ready, loadError]);\n\n const update = useCallback(\n async (ops: Op[], run?: () => Promise<unknown>) => {\n setUpdating((n) => n + 1);\n try {\n if (run) await run();\n else await client.media.commit(live.current.ref, ops);\n setError(undefined);\n } catch (e) {\n const err = toContentKitError(e);\n if (err.code !== \"aborted\") setError(err);\n report(err, \"folder.update\");\n throw err;\n } finally {\n setUpdating((n) => n - 1);\n }\n },\n [client, report],\n );\n\n const commit = useCallback(async () => {\n setCommitting(true);\n try {\n const files = await q.commit(undefined, { head: opts.current.commit === \"auto\" });\n setError(undefined);\n return files;\n } catch (e) {\n const err = toContentKitError(e);\n setError(err);\n report(err, \"folder.commit\");\n throw err;\n } finally {\n setCommitting(false);\n }\n }, [q, report]);\n\n // Committed files leave the queue once the read lists them, or once a read lands without them.\n useEffect(() => {\n for (const i of raw.items) if (i.status === \"committed\" && (listed.has(i.path) || !read.loading)) q.remove(i.id);\n }, [raw.items, listed, read.loading, q]);\n\n // A draft commits the uploaded head of the queue as it finishes; a failed commit waits for commit().\n const auto = o.commit === \"auto\";\n const headReady = items.find((i) => i.status !== \"committed\")?.status === \"uploaded\";\n useEffect(() => {\n if (!auto || !headReady || committing || stalled) return;\n commit().catch((e: unknown) => setStall({ key, error: toContentKitError(e) }));\n }, [auto, headReady, committing, stalled, commit, key]);\n const manualCommit = useCallback(async () => {\n setStall(undefined);\n return commit();\n }, [commit]);\n\n // Each failed upload and queue-wide refusal is reported once; rows keep showing it.\n const seen = useRef(new Set<unknown>());\n useEffect(() => {\n for (const i of raw.items) {\n if (i.status !== \"failed\" || !i.error || seen.current.has(i.error)) continue;\n seen.current.add(i.error);\n report(i.error, \"upload\", i.file.name);\n }\n if (raw.blocked && !seen.current.has(raw.blocked)) {\n seen.current.add(raw.blocked);\n report(raw.blocked, \"upload\");\n }\n }, [raw.items, raw.blocked, report]);\n\n // Processing failures that appear after the item's first read.\n const failures = useRef<{ key: string; seen: Set<string> } | null>(null);\n useEffect(() => {\n if (!read.read) return;\n const failed = uploads.filter((f) => f.failed);\n if (failures.current?.key !== key) {\n failures.current = { key, seen: new Set(failed.map((f) => `${f.path}:${f.failed!.of}`)) };\n return;\n }\n for (const f of failed) {\n const id = `${f.path}:${f.failed!.of}`;\n if (failures.current.seen.has(id)) continue;\n failures.current.seen.add(id);\n report(failureError(f.failed!), \"folder.process\", f.path.split(\"/\").pop());\n }\n }, [read.read, uploads, report, key]);\n\n useEffect(() => void (read.error && report(read.error, \"folder.load\")), [read.error, report]);\n\n const busy =\n waiting.length > 0 ||\n committing ||\n updating > 0 ||\n (auto && headReady && !stalled) ||\n items.some((i) => i.status === \"uploading\" || (i.status === \"queued\" && !raw.blocked));\n const fileCount = uploads.length + waiting.length + inbound(items, listed).length;\n\n // Reads the latest groups, so callbacks stay stable.\n const groupOf = (path: string) => live.current.groups.find((g) => ruleFor([{ path: g.path }], path));\n return {\n ref: item,\n options: o,\n read,\n groups,\n uploads,\n queue,\n waiting,\n add,\n refused,\n dismiss: useCallback(() => setRefused([]), []),\n move: useCallback((path, index) => update([{ op: \"move\", path, index }]), [update]),\n remove: useCallback((paths) => update(paths.map((path): Op => ({ op: \"remove\", path }))), [update]),\n rename: useCallback(\n (path, name) => {\n const g = groupOf(path);\n const to = (g ? ruleDir(g.path) : path.slice(0, path.lastIndexOf(\"/\") + 1)) + name.trim();\n if (stem(to) === stem(path)) return Promise.resolve();\n if (g?.files.some((f) => stem(f.path) === stem(to))) {\n const err = new ContentKitError(\"conflict\", `${name} exists`, { status: 409 });\n setError(err);\n return Promise.reject(err);\n }\n return update([{ op: \"rename\", path, to }]);\n },\n [update],\n ),\n edit: useCallback((path, edit) => update([{ op: \"edit\", path, ...(edit ? { edit } : {}) }]), [update]),\n replace: useCallback(\n (path, file) => update([], () => client.media.put(file, { ref: live.current.ref, path: stem(path), wait: false })),\n [update, client],\n ),\n commit: manualCommit,\n discard: useCallback(() => {\n pending.current = [];\n setWaiting([]);\n for (const i of q.getSnapshot().items) q.remove(i.id);\n q.pause();\n }, [q]),\n updating: updating > 0,\n committing,\n busy,\n fileCount,\n error: error ?? stalled,\n };\n}\n"],"mappings":"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"}
|
package/dist/react/upload.d.ts
CHANGED
|
@@ -4,17 +4,17 @@ import { Rotation, Size } from '../client/crop.js';
|
|
|
4
4
|
import { ContentKitError } from '../client/errors.js';
|
|
5
5
|
import { Crop, Edit, EncodeProgress, FileInfo } from '../client/generated/wire.js';
|
|
6
6
|
import { Progress, PutOptions, UploadedFile, UploadOptions } from '../client/media/client.js';
|
|
7
|
-
import {
|
|
7
|
+
import { ItemUploadQueue, QueueOptions, QueueSnapshot } from '../client/media/queue.js';
|
|
8
8
|
export interface UseUploadQueue extends QueueSnapshot {
|
|
9
|
-
queue:
|
|
10
|
-
add:
|
|
11
|
-
move:
|
|
12
|
-
update:
|
|
13
|
-
remove:
|
|
14
|
-
retry:
|
|
15
|
-
start:
|
|
16
|
-
pause:
|
|
17
|
-
commit:
|
|
9
|
+
queue: ItemUploadQueue;
|
|
10
|
+
add: ItemUploadQueue["add"];
|
|
11
|
+
move: ItemUploadQueue["move"];
|
|
12
|
+
update: ItemUploadQueue["update"];
|
|
13
|
+
remove: ItemUploadQueue["remove"];
|
|
14
|
+
retry: ItemUploadQueue["retry"];
|
|
15
|
+
start: ItemUploadQueue["start"];
|
|
16
|
+
pause: ItemUploadQueue["pause"];
|
|
17
|
+
commit: ItemUploadQueue["commit"];
|
|
18
18
|
}
|
|
19
19
|
export interface UploadQueueOptions extends QueueOptions {
|
|
20
20
|
/** Overrides the provider's client. */
|
package/dist/react/upload.js
CHANGED
|
@@ -2,7 +2,7 @@ import { toContentKitError as e } from "../client/errors.js";
|
|
|
2
2
|
import { ratio as t } from "../client/aspect.js";
|
|
3
3
|
import { centeredCrop as n, constrainCrop as r, editOf as i, rotation as a, sameEdit as o, toOriginal as s } from "../client/crop.js";
|
|
4
4
|
import { encodeRemaining as c } from "../client/media/encode.js";
|
|
5
|
-
import {
|
|
5
|
+
import { ItemUploadQueue as l } from "../client/media/queue.js";
|
|
6
6
|
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
|
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: 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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"}
|
|
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 { ItemUploadQueue, type QueueOptions, type QueueSnapshot } from \"../client/media/queue.js\";\nimport { useContentKitClient } from \"./context.js\";\n\nexport interface UseUploadQueue extends QueueSnapshot {\n queue: ItemUploadQueue;\n add: ItemUploadQueue[\"add\"];\n move: ItemUploadQueue[\"move\"];\n update: ItemUploadQueue[\"update\"];\n remove: ItemUploadQueue[\"remove\"];\n retry: ItemUploadQueue[\"retry\"];\n start: ItemUploadQueue[\"start\"];\n pause: ItemUploadQueue[\"pause\"];\n commit: ItemUploadQueue[\"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 ItemUploadQueue(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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"}
|