dsh-remotion 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/docs/VALIDATION.md +2 -0
- package/docs/validation-language-2026-10-03.md +16 -0
- package/lib/client.js +21 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
[返回简介](README.md) · [使用说明](docs/USAGE.md) · [验证记录](docs/VALIDATION.md)
|
|
4
4
|
|
|
5
|
+
## 0.4.1 (2026-10-03)
|
|
6
|
+
|
|
7
|
+
- 视频工作台后台错误与任务阶段支持英文;切换语言重新展示失败引导,项目文字和原始引擎日志保留原文。
|
|
8
|
+
- 验收范围见 [中英文界面检查](docs/validation-language-2026-10-03.md)。
|
|
9
|
+
|
|
5
10
|
## 0.4.0 (2026-10-03)
|
|
6
11
|
|
|
7
12
|
- 新增视频工作台、三种模板、素材副本上传、官方 Studio 预览、本机 MP4 与可编辑 ZIP 备份。
|
package/docs/VALIDATION.md
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 0.4.1:中英文界面验收
|
|
2
|
+
|
|
3
|
+
2026-10-03,Windows / Node 24.16.0。采用 E 盘独立 DSH_HOME、合成资料与无密钥 profile;用户资料、账号、密钥、桌面窗口与 VPN 保持原状。
|
|
4
|
+
|
|
5
|
+
- 自动测试:39 项全部通过。
|
|
6
|
+
- 可见 Web 操作:英文界面超长标题保存被拒绝,提示说明 72 字符上限并保留输入;改为合法中文标题后成功保存。中文界面恢复同一修订的项目和正文。
|
|
7
|
+
- 宿主:官方 DSH 0.2.1-alpha.1 Web;官方 RC2 与 alpha SDK 同载 18 组件,110 工具、35 插件技能、19 输出样例注册与 schema 检查通过。无账号的健康结果不代表真实连接。
|
|
8
|
+
- 边界:本轮没有重新渲染 MP4,渲染和 Studio 的实际验收见 0.4.0 记录。原始引擎日志保持原文。
|
|
9
|
+
|
|
10
|
+
中英文共用同一份资料;界面翻译不会改写标题、正文、路径、引用或项目文件。系统文件选择控件和文件对话框跟随 Windows / 浏览器语言。未知技术诊断与原始引擎日志保留原文;英文界面提供操作恢复引导。
|
|
11
|
+
|
|
12
|
+
此记录不代表原生桌面窗口内的新功能已逐项操作通过。发布包完整性、GitHub CI 与公开 npm 全新安装在发布时另行核对。
|
|
13
|
+
|
|
14
|
+
## English scope
|
|
15
|
+
|
|
16
|
+
The Chinese and English UI share the same data. UI translation preserves original content, paths and project files. Visible interactions were performed in an isolated official alpha Web profile; both RC2 and alpha SDK registration/schema checks passed. Native file controls use the OS/browser language, and original diagnostic logs are retained. Browser results do not certify native desktop interaction or real-service delivery.
|
package/lib/client.js
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
window.__ModuleLoader__.load({ id: 'dsh-remotion', factory: require => {
|
|
3
3
|
const React = require('react'), { useState, useEffect, useRef } = React, h = React.createElement;
|
|
4
4
|
const engine = 'remotion', title = engine === 'hyperframes' ? 'HyperFrames' : 'Remotion', route = 'api/dsh-' + engine + '/workbench';
|
|
5
|
+
|
|
6
|
+
function mediaError(error, en) {
|
|
7
|
+
const message = typeof error === 'string' ? error : error?.message || String(error || '');
|
|
8
|
+
if (!en || !/[\u3400-\u9fff]/.test(message)) return message;
|
|
9
|
+
if (error?.status === 401) return 'Your session expired. Reopen DSH and try again.';
|
|
10
|
+
if (error?.status === 409 || /已.*更新|另一窗口|修订/.test(message)) return 'The project changed. Reopen the latest version and compare it with your preserved input.';
|
|
11
|
+
if (/标题/.test(message)) return 'Enter a title up to 72 characters.';
|
|
12
|
+
if (/时长/.test(message)) return 'Set a duration between 3 and 30 seconds.';
|
|
13
|
+
if (/正文/.test(message)) return 'Use body text up to 360 characters.';
|
|
14
|
+
if (/素材|PNG|JPEG|MP3|WAV|MP4/.test(message)) return 'Check the media format and file contents. Each asset must be ≤20 MB; a project supports 12 assets and 40 MB total, with one audio and one video background.';
|
|
15
|
+
if (/任务|官方|FFmpeg|Chrome|环境/.test(message)) return 'The official task did not finish. Open the job log and check the render environment, then retry. Your project is preserved.';
|
|
16
|
+
if (/工程记录|项目目录|文件/.test(message)) return 'The project files could not be read safely. Back them up and check their location and permissions before retrying.';
|
|
17
|
+
return 'The operation failed. Check the DSH logs and reload the project before retrying; your input is preserved.';
|
|
18
|
+
}
|
|
19
|
+
|
|
5
20
|
let cache = { project: null, fields: null, dirty: false, job: null, creating: false };
|
|
6
21
|
const empty = () => ({ template: 'title', title: '', body: '', format: 'landscape', duration: 8 });
|
|
7
22
|
const fieldsOf = p => ({ template: p.template, title: p.title, body: p.body, format: p.format, duration: p.duration });
|
|
@@ -18,7 +33,7 @@ window.__ModuleLoader__.load({ id: 'dsh-remotion', factory: require => {
|
|
|
18
33
|
const [job, setJob] = useState(cache.job), [busy, setBusy] = useState(false), [error, setError] = useState(''), [notice, setNotice] = useState(''), [pending, setPending] = useState(null), [conflict, setConflict] = useState(false);
|
|
19
34
|
const busyRef = useRef(false), fileRef = useRef(null), latest = useRef({ project, fields, dirty }); latest.current = { project, fields, dirty };
|
|
20
35
|
const t = (zh, en) => lang === 'en' ? en : zh, activeJob = job?.state === 'running', running = busy || activeJob && job.kind !== 'prepare';
|
|
21
|
-
const stage = lang === 'en' ? ({ '下载环境': 'Preparing environment', '检查 FFmpeg': 'Checking FFmpeg', '下载官方依赖': 'Downloading official dependencies', '检查官方环境': 'Checking official CLI', '检查或下载 Chrome': 'Checking or downloading Chrome', '下载专用渲染浏览器': 'Downloading the official rendering browser', '准备修订快照': 'Preparing saved revision', '启动官方预览': 'Starting official preview', '官方预览已就绪': 'Official preview is ready', '检查合成': 'Checking composition', '渲染 MP4': 'Rendering MP4', '已完成': 'Completed', '已取消': 'Cancelled', '已取消;工程已保留': 'Cancelled; project preserved', '官方任务未完成,请展开运行记录后重试。工程已保留。': 'The official task failed. Open the job log and retry; your project is preserved.', '未找到 FFmpeg,请安装并加入 PATH,重启 DSH 后重试。工程已保留。': 'FFmpeg was not found. Install it, add it to PATH, then restart DSH and retry; your project is preserved.' }[job?.stage] || job?.stage) : job?.stage;
|
|
36
|
+
const stage = lang === 'en' ? ({ '下载环境': 'Preparing environment', '检查 FFmpeg': 'Checking FFmpeg', '下载官方依赖': 'Downloading official dependencies', '检查官方环境': 'Checking official CLI', '检查或下载 Chrome': 'Checking or downloading Chrome', '下载专用渲染浏览器': 'Downloading the official rendering browser', '准备修订快照': 'Preparing saved revision', '启动官方预览': 'Starting official preview', '官方预览已就绪': 'Official preview is ready', '检查合成': 'Checking composition', '渲染 MP4': 'Rendering MP4', '已完成': 'Completed', '已取消': 'Cancelled', '已取消;工程已保留': 'Cancelled; project preserved', '官方任务未完成,请展开运行记录后重试。工程已保留。': 'The official task failed. Open the job log and retry; your project is preserved.', '未找到 FFmpeg,请安装并加入 PATH,重启 DSH 后重试。工程已保留。': 'FFmpeg was not found. Install it, add it to PATH, then restart DSH and retry; your project is preserved.' }[job?.stage] || mediaError(job?.stage, true)) : job?.stage;
|
|
22
37
|
useEffect(() => { const update = () => setLang(String(locale?.getSnapshot?.().active || '').startsWith('en') ? 'en' : 'zh'); update(); return locale?.subscribe?.(update); }, [locale]);
|
|
23
38
|
useEffect(() => { cache = { project, fields, dirty, job, creating }; }, [project, fields, dirty, job, creating]);
|
|
24
39
|
useEffect(() => { const exit = e => { if (latest.current.dirty) { e.preventDefault(); e.returnValue = ''; } }; window.addEventListener('beforeunload', exit); return () => window.removeEventListener('beforeunload', exit); }, []);
|
|
@@ -28,7 +43,7 @@ window.__ModuleLoader__.load({ id: 'dsh-remotion', factory: require => {
|
|
|
28
43
|
if (saved) { setProject(saved); setFields(fieldsOf(saved)); }
|
|
29
44
|
setJob(previous => data.jobs.find(row => row.id === previous?.id) || data.jobs.filter(row => row.state === 'running').at(-1) || data.jobs.at(-1) || null);
|
|
30
45
|
}
|
|
31
|
-
useEffect(() => { refresh().catch(e => setError(e
|
|
46
|
+
useEffect(() => { refresh().catch(e => setError(e)); }, []);
|
|
32
47
|
useEffect(() => {
|
|
33
48
|
if (!job || job.state !== 'running') return;
|
|
34
49
|
let live = true, polling = false;
|
|
@@ -39,11 +54,11 @@ window.__ModuleLoader__.load({ id: 'dsh-remotion', factory: require => {
|
|
|
39
54
|
// Refresh output before the terminal state tears down this polling effect.
|
|
40
55
|
if (next.state !== 'running') await refresh();
|
|
41
56
|
else setJob(next);
|
|
42
|
-
} catch (e) { if (live) { setError(e
|
|
57
|
+
} catch (e) { if (live) { setError(e); setJob(null); } } finally { polling = false; }
|
|
43
58
|
}, 1000);
|
|
44
59
|
return () => { live = false; clearInterval(timer); };
|
|
45
60
|
}, [job?.id, job?.state]);
|
|
46
|
-
async function perform(fn) { if (busyRef.current) return; busyRef.current = true; setBusy(true); setError(''); setNotice(''); try { return await fn(); } catch (e) { setError(e
|
|
61
|
+
async function perform(fn) { if (busyRef.current) return; busyRef.current = true; setBusy(true); setError(''); setNotice(''); try { return await fn(); } catch (e) { setError(e); setConflict(e.status === 409); } finally { busyRef.current = false; setBusy(false); } }
|
|
47
62
|
async function open(id) { const next = await api({ action: 'get', id }); setProject(next); setFields(fieldsOf(next)); setCreating(false); setDirty(false); setConflict(false); setPending(null); }
|
|
48
63
|
function navigate(target) { if (dirty) { setPending(target); return; } perform(() => move(target)); }
|
|
49
64
|
async function move(target) { if (target === 'new') { setProject(null); setFields(empty()); setCreating(true); } else if (target === 'list') { setProject(null); setCreating(false); await refresh(); } else await open(target); setDirty(false); setPending(null); }
|
|
@@ -56,10 +71,10 @@ window.__ModuleLoader__.load({ id: 'dsh-remotion', factory: require => {
|
|
|
56
71
|
async function start(action) { await perform(async () => { if (action !== 'prepare' && dirty) throw new Error(t('请先保存修改。', 'Save changes first.')); setJob(await api({ action, id: project?.id, revision: project?.revision })); }); }
|
|
57
72
|
const render = project?.renders?.[0], stale = render && render.revision !== project.revision;
|
|
58
73
|
if (!environment) return h('div', { className: 'dmw' }, h('style', null, css), h('h2', null, title + t(' 视频工作台', ' video workbench')),
|
|
59
|
-
error ? h(React.Fragment, null, h('p', { className: 'notice error', role: 'alert' }, error), h('button', { disabled: busy, onClick: () => perform(refresh) }, t('重新读取项目', 'Reload projects'))) : h('p', { role: 'status' }, t('正在读取工程与渲染环境…', 'Loading projects and render environment…')));
|
|
74
|
+
error ? h(React.Fragment, null, h('p', { className: 'notice error', role: 'alert' }, mediaError(error, lang === 'en')), h('button', { disabled: busy, onClick: () => perform(refresh) }, t('重新读取项目', 'Reload projects'))) : h('p', { role: 'status' }, t('正在读取工程与渲染环境…', 'Loading projects and render environment…')));
|
|
60
75
|
return h('div', { className: 'dmw' }, h('style', null, css), h('h2', null, title + t(' 视频工作台', ' video workbench')),
|
|
61
76
|
h('p', { className: 'muted' }, t('模板 → 素材 → 官方预览 → 本机 MP4。工程与素材保存在 DSH 本地数据目录。', 'Template → media → official preview → local MP4. Projects stay in the local DSH data folder.')),
|
|
62
|
-
error && h('div', { className: 'notice error', role: 'alert' }, error), notice && h('div', { className: 'notice', role: 'status' }, notice),
|
|
77
|
+
error && h('div', { className: 'notice error', role: 'alert' }, mediaError(error, lang === 'en')), notice && h('div', { className: 'notice', role: 'status' }, notice),
|
|
63
78
|
pending && h('div', { className: 'card' }, h('p', null, t('有未保存内容,如何继续?', 'You have unsaved changes.')),
|
|
64
79
|
h('div', { className: 'bar' }, h('button', { disabled: running, onClick: () => perform(async () => { await save(); await move(pending); }) }, t('保存后继续', 'Save and continue')), h('button', { disabled: running, onClick: () => perform(() => move(pending)) }, t('放弃修改并继续', 'Discard and continue')), h('button', { onClick: () => setPending(null) }, t('留在当前', 'Stay here')))),
|
|
65
80
|
conflict && project && h('button', { disabled: running, onClick: () => { setPending(project.id); } }, t('重新打开最新版本(先处理当前输入)', 'Reopen latest version (review your current input first)')),
|