@linxin666/dsh-remote-web-ui 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +2 -2
- package/README.md +2 -1
- package/README.zh.md +1 -1
- package/lib/client.js +25 -7
- package/lib/client.js.map +1 -1
- package/lib/types/client/UpdateEntry.d.ts.map +1 -1
- package/lib/types/client/UpdateEntry.js +8 -5
- package/lib/types/client/UpdatePanel.d.ts +3 -1
- package/lib/types/client/UpdatePanel.d.ts.map +1 -1
- package/lib/types/client/UpdatePanel.js +2 -2
- package/lib/types/client/locales.d.ts +2 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +6 -4
- package/lib/types/client/pair-api.d.ts +1 -6
- package/lib/types/client/pair-api.d.ts.map +1 -1
- package/lib/types/client/pair-api.js +1 -10
- package/package.json +1 -2
- package/src/client/UpdateEntry.tsx +9 -5
- package/src/client/UpdatePanel.tsx +10 -1
- package/src/client/locales.ts +6 -4
- package/src/client/pair-api.ts +1 -10
- package/src/mobile/mobile-theme.test.ts +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UpdateEntry.d.ts","sourceRoot":"","sources":["../../../src/client/UpdateEntry.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"UpdateEntry.d.ts","sourceRoot":"","sources":["../../../src/client/UpdateEntry.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAOnE,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;IACb,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,+BA8GxD"}
|
|
@@ -2,8 +2,9 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
/**
|
|
3
3
|
* The sidebar update seat: the download trigger beside the remote-control
|
|
4
4
|
* trigger plus the update panel modal. Owns the flow — probe the registry
|
|
5
|
-
* on open,
|
|
6
|
-
* outcome (restart hint on success,
|
|
5
|
+
* on open, show the result, and let the user start the update from the
|
|
6
|
+
* result view (#507), then report the outcome (restart hint on success,
|
|
7
|
+
* translated failure on error).
|
|
7
8
|
* Component-local state per the client stack rules.
|
|
8
9
|
*/
|
|
9
10
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
@@ -65,9 +66,11 @@ export function UpdateEntry({ wide, t }) {
|
|
|
65
66
|
setView({ kind: "result", status });
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
69
|
+
// Checking never starts an update by itself (#507): the result view
|
|
70
|
+
// carries the confirmation button that calls startUpdate.
|
|
68
71
|
setView({ kind: "result", status });
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
}, [t]);
|
|
73
|
+
const startUpdate = useCallback(async (status) => {
|
|
71
74
|
if (status.mode !== "npm" || !status.outdated)
|
|
72
75
|
return;
|
|
73
76
|
setView({ kind: "updating", status });
|
|
@@ -105,5 +108,5 @@ export function UpdateEntry({ wide, t }) {
|
|
|
105
108
|
};
|
|
106
109
|
}, [probeAvailability]);
|
|
107
110
|
const updateLabel = updateAvailable ? t("update.availableLabel") : t("update.label");
|
|
108
|
-
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.trigger, "data-wide": wide ? undefined : "rail", "data-update-available": updateAvailable ? "true" : undefined, "aria-label": updateLabel, title: updateLabel, onClick: openPanel, children: _jsx(IconDownloadOutline16, { size: wide ? 16 : 18 }) }), open && createPortal((_jsxs("div", { className: css.overlay, role: "presentation", children: [_jsx("div", { className: css.mask, "aria-hidden": "true", onClick: closePanel }), _jsx(UpdatePanel, { t: t, view: view, onClose: closePanel, onRecheck: () => { void check(); } })] })), document.body)] }));
|
|
111
|
+
return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: css.trigger, "data-wide": wide ? undefined : "rail", "data-update-available": updateAvailable ? "true" : undefined, "aria-label": updateLabel, title: updateLabel, onClick: openPanel, children: _jsx(IconDownloadOutline16, { size: wide ? 16 : 18 }) }), open && createPortal((_jsxs("div", { className: css.overlay, role: "presentation", children: [_jsx("div", { className: css.mask, "aria-hidden": "true", onClick: closePanel }), _jsx(UpdatePanel, { t: t, view: view, onClose: closePanel, onRecheck: () => { void check(); }, onStartUpdate: (status) => { void startUpdate(status); } })] })), document.body)] }));
|
|
109
112
|
}
|
|
@@ -30,11 +30,13 @@ export interface UpdatePanelProps {
|
|
|
30
30
|
onClose(): void;
|
|
31
31
|
/** Re-run the check from a terminal state. */
|
|
32
32
|
onRecheck(): void;
|
|
33
|
+
/** Start the update from the result view (the confirmation step, #507). */
|
|
34
|
+
onStartUpdate(status: UpdateStatus): void;
|
|
33
35
|
}
|
|
34
36
|
/**
|
|
35
37
|
* Render the update panel.
|
|
36
38
|
* @param props - copy, view state, and actions.
|
|
37
39
|
* @returns the panel element tree.
|
|
38
40
|
*/
|
|
39
|
-
export declare function UpdatePanel({ t, view, onClose, onRecheck }: UpdatePanelProps): import("react").JSX.Element;
|
|
41
|
+
export declare function UpdatePanel({ t, view, onClose, onRecheck, onStartUpdate }: UpdatePanelProps): import("react").JSX.Element;
|
|
40
42
|
//# sourceMappingURL=UpdatePanel.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UpdatePanel.d.ts","sourceRoot":"","sources":["../../../src/client/UpdatePanel.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAEnE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAGjE,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,IAAI,IAAI,CAAA;IACf,8CAA8C;IAC9C,SAAS,IAAI,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"UpdatePanel.d.ts","sourceRoot":"","sources":["../../../src/client/UpdatePanel.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAEnE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAGjE,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,IAAI,IAAI,CAAA;IACf,8CAA8C;IAC9C,SAAS,IAAI,IAAI,CAAA;IACjB,2EAA2E;IAC3E,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;CAC1C;AAYD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,gBAAgB,+BAiD3F"}
|
|
@@ -14,11 +14,11 @@ function anchorLatest(status) {
|
|
|
14
14
|
* @param props - copy, view state, and actions.
|
|
15
15
|
* @returns the panel element tree.
|
|
16
16
|
*/
|
|
17
|
-
export function UpdatePanel({ t, view, onClose, onRecheck }) {
|
|
17
|
+
export function UpdatePanel({ t, view, onClose, onRecheck, onStartUpdate }) {
|
|
18
18
|
const status = view.kind === "result" || view.kind === "updating" ? view.status : undefined;
|
|
19
19
|
const title = view.kind === "done" && view.result.ok ? t("update.done") : t("update.title");
|
|
20
20
|
const subtitle = subtitleOf(t, view);
|
|
21
|
-
return (_jsxs("div", { className: css.panel, role: "dialog", "aria-modal": "true", "aria-label": title, children: [_jsxs("div", { className: css.header, children: [_jsxs("div", { className: css.heading, children: [_jsx("h2", { className: css.title, children: title }), subtitle !== undefined && _jsx("p", { className: css.subtitle, children: subtitle })] }), _jsx("button", { type: "button", className: css.close, "aria-label": t("update.close"), onClick: onClose, children: _jsx(IconCloseOutline16, {}) })] }), view.kind === "checking" && _jsx("p", { className: css.updateStatus, children: t("update.checking") }), view.kind === "result" && status !== undefined && _jsx(ResultBody, { t: t, status: status }), view.kind === "updating" && status !== undefined && (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.updating", { name: anchorName(status) ?? "", version: anchorLatest(status) ?? "" }) }), _jsx("p", { className: css.updateDetail, children: t("update.cooldownNotice") }), _jsx(PackageList, { status: status })] })), view.kind === "done" && _jsx(DoneBody, { t: t, result: view.result }), view.kind === "error" && (_jsxs("div", { children: [_jsx("p", { className: css.updateError, children: view.message }), view.detail !== undefined && _jsx("pre", { className: css.updateOutput, children: view.detail })] })), (view.kind === "done" || view.kind === "error") && (_jsx("div", { className: css.updateActions, children: _jsxs("button", { type: "button", className: css.updateRetry, onClick: onRecheck, children: [_jsx(IconRefreshOutline16, {}), " ", t("update.retry")] }) }))] }));
|
|
21
|
+
return (_jsxs("div", { className: css.panel, role: "dialog", "aria-modal": "true", "aria-label": title, children: [_jsxs("div", { className: css.header, children: [_jsxs("div", { className: css.heading, children: [_jsx("h2", { className: css.title, children: title }), subtitle !== undefined && _jsx("p", { className: css.subtitle, children: subtitle })] }), _jsx("button", { type: "button", className: css.close, "aria-label": t("update.close"), onClick: onClose, children: _jsx(IconCloseOutline16, {}) })] }), view.kind === "checking" && _jsx("p", { className: css.updateStatus, children: t("update.checking") }), view.kind === "result" && status !== undefined && _jsx(ResultBody, { t: t, status: status }), view.kind === "result" && status !== undefined && status.mode === "npm" && status.outdated && status.error === undefined && (_jsx("div", { className: css.updateActions, children: _jsx("button", { type: "button", className: css.updateRetry, onClick: () => onStartUpdate(status), children: t("update.start") }) })), view.kind === "updating" && status !== undefined && (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.updating", { name: anchorName(status) ?? "", version: anchorLatest(status) ?? "" }) }), _jsx("p", { className: css.updateDetail, children: t("update.cooldownNotice") }), _jsx(PackageList, { status: status })] })), view.kind === "done" && _jsx(DoneBody, { t: t, result: view.result }), view.kind === "error" && (_jsxs("div", { children: [_jsx("p", { className: css.updateError, children: view.message }), view.detail !== undefined && _jsx("pre", { className: css.updateOutput, children: view.detail })] })), (view.kind === "done" || view.kind === "error") && (_jsx("div", { className: css.updateActions, children: _jsxs("button", { type: "button", className: css.updateRetry, onClick: onRecheck, children: [_jsx(IconRefreshOutline16, {}), " ", t("update.retry")] }) }))] }));
|
|
22
22
|
}
|
|
23
23
|
/** The subtitle copy per view state (absent on plain results). */
|
|
24
24
|
function subtitleOf(t, view) {
|
|
@@ -78,6 +78,7 @@ export declare const zh: {
|
|
|
78
78
|
'update.upToDateDetail': string;
|
|
79
79
|
'update.found': string;
|
|
80
80
|
'update.foundDetail': string;
|
|
81
|
+
'update.start': string;
|
|
81
82
|
'update.updating': string;
|
|
82
83
|
'update.cooldownNotice': string;
|
|
83
84
|
'update.updatingTitle': string;
|
|
@@ -188,6 +189,7 @@ export declare const en: {
|
|
|
188
189
|
'update.upToDateDetail': string;
|
|
189
190
|
'update.found': string;
|
|
190
191
|
'update.foundDetail': string;
|
|
192
|
+
'update.start': string;
|
|
191
193
|
'update.updating': string;
|
|
192
194
|
'update.cooldownNotice': string;
|
|
193
195
|
'update.updatingTitle': string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,mEAAmE;AACnE,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAE/E,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2GmB,CAAA;AAElC,sCAAsC;AACtC,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAA;AAEvC,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,SAAS,CAAA;AAEvC,mEAAmE;AACnE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2GsB,CAAA"}
|
|
@@ -79,10 +79,11 @@ export const zh = {
|
|
|
79
79
|
'update.upToDate': '当前已是最新版本',
|
|
80
80
|
'update.upToDateDetail': '{name} 已是最新版本 v{version}。',
|
|
81
81
|
'update.found': '发现新版本',
|
|
82
|
-
'update.foundDetail': '{name} 有新版本 v{version}
|
|
82
|
+
'update.foundDetail': '{name} 有新版本 v{version}。确认后开始更新。',
|
|
83
|
+
'update.start': '开始更新',
|
|
83
84
|
'update.updating': '正在更新 {name} 至 v{version},可能需要 1-2 分钟…',
|
|
84
85
|
'update.cooldownNotice': '本次更新由你主动触发,将跳过 pnpm 的 24 小时发布冷却期(供应链保护)直接安装所选版本。',
|
|
85
|
-
'update.updatingTitle': '
|
|
86
|
+
'update.updatingTitle': '正在更新…',
|
|
86
87
|
'update.done': '更新完成',
|
|
87
88
|
'update.doneDetail': '所有组件已更新到最新版本。',
|
|
88
89
|
'update.restartHint': '请重启 dsh web 使新版本生效:在终端按 Ctrl+C 停止后重新运行 dsh web。',
|
|
@@ -187,10 +188,11 @@ export const en = {
|
|
|
187
188
|
'update.upToDate': 'Everything is up to date',
|
|
188
189
|
'update.upToDateDetail': '{name} is at the latest version v{version}.',
|
|
189
190
|
'update.found': 'A new version is available',
|
|
190
|
-
'update.foundDetail': '{name} has a newer release v{version}
|
|
191
|
+
'update.foundDetail': '{name} has a newer release v{version}. Confirm to start the update.',
|
|
192
|
+
'update.start': 'Update now',
|
|
191
193
|
'update.updating': 'Updating {name} to v{version}; this may take 1-2 minutes…',
|
|
192
194
|
'update.cooldownNotice': 'This user-initiated update bypasses pnpm\'s 24-hour release cooldown (supply-chain protection) and installs the selected version immediately.',
|
|
193
|
-
'update.updatingTitle': 'Updating
|
|
195
|
+
'update.updatingTitle': 'Updating…',
|
|
194
196
|
'update.done': 'Update complete',
|
|
195
197
|
'update.doneDetail': 'All components are up to date.',
|
|
196
198
|
'update.restartHint': 'Restart dsh web for the new version to take effect: stop it with Ctrl+C in the terminal, then run dsh web again.',
|
|
@@ -83,12 +83,7 @@ export declare function readPairParams(search: string): {
|
|
|
83
83
|
pair?: string;
|
|
84
84
|
workspace?: string;
|
|
85
85
|
};
|
|
86
|
-
/**
|
|
87
|
-
* Strip one query parameter from the current URL without reloading.
|
|
88
|
-
* @param name - the parameter to remove.
|
|
89
|
-
* @returns the new search string ('' when empty).
|
|
90
|
-
*/
|
|
91
|
-
export declare function stripParam(name: string): string; /** Human-readable expiry clock, e.g. "10:35". */
|
|
86
|
+
/** Human-readable expiry clock, e.g. "10:35". */
|
|
92
87
|
export declare function formatClock(epochMs: number): string;
|
|
93
88
|
/**
|
|
94
89
|
* Copy text to the clipboard with a fallback for insecure contexts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pair-api.d.ts","sourceRoot":"","sources":["../../../src/client/pair-api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAwB;AACxB,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,IAAI,CAAA;IACR,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,KAAK,CAAA;IACT,IAAI,EAAE,cAAc,CAAA;CACrB;AAED,uEAAuE;AACvE,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,KAAK,CAAA;IACT,IAAI,EAAE,iBAAiB,CAAA;CACxB;AAED,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,KAAK,CAAA;IACT,IAAI,EAAE,WAAW,CAAA;CAClB;AAED,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,gBAAgB,GAAG,mBAAmB,GAAG,qBAAqB,CAAA;AAExG,8BAA8B;AAC9B,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,CAAA;CAAE,CAAA;AAEjF,sEAAsE;AACtE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAA;IACxC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,cAAc,CAAA;IAC5E,YAAY,EAAE,OAAO,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAgB9F;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,aAAa,CAAC,CAUrF;AAED,iEAAiE;AACjE,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAG9C;AAED,mFAAmF;AACnF,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnD;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAQpF;AAED
|
|
1
|
+
{"version":3,"file":"pair-api.d.ts","sourceRoot":"","sources":["../../../src/client/pair-api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAwB;AACxB,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,IAAI,CAAA;IACR,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,8DAA8D;IAC9D,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,KAAK,CAAA;IACT,IAAI,EAAE,cAAc,CAAA;CACrB;AAED,uEAAuE;AACvE,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,KAAK,CAAA;IACT,IAAI,EAAE,iBAAiB,CAAA;CACxB;AAED,qEAAqE;AACrE,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,KAAK,CAAA;IACT,IAAI,EAAE,WAAW,CAAA;CAClB;AAED,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,gBAAgB,GAAG,mBAAmB,GAAG,qBAAqB,CAAA;AAExG,8BAA8B;AAC9B,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,CAAA;CAAE,CAAA;AAEjF,sEAAsE;AACtE,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAA;IACxC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,kCAAkC;AAClC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,cAAc,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,GAAG,cAAc,CAAA;IAC5E,YAAY,EAAE,OAAO,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE,MAAM,CAAA;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,iBAAiB,CAAA;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,SAAS,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAgB9F;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG,aAAa,CAAC,CAUrF;AAED,iEAAiE;AACjE,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAG9C;AAED,mFAAmF;AACnF,wBAAsB,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC,CAEnD;AAED,+EAA+E;AAC/E,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAQpF;AAED,iDAAiD;AACjD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAKnD;AAED;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAsB7D"}
|
|
@@ -74,16 +74,7 @@ export function readPairParams(search) {
|
|
|
74
74
|
...(workspace !== null && workspace !== '' ? { workspace } : {}),
|
|
75
75
|
};
|
|
76
76
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Strip one query parameter from the current URL without reloading.
|
|
79
|
-
* @param name - the parameter to remove.
|
|
80
|
-
* @returns the new search string ('' when empty).
|
|
81
|
-
*/
|
|
82
|
-
export function stripParam(name) {
|
|
83
|
-
const url = new URL(window.location.href);
|
|
84
|
-
url.searchParams.delete(name);
|
|
85
|
-
return url.search;
|
|
86
|
-
} /** Human-readable expiry clock, e.g. "10:35". */
|
|
77
|
+
/** Human-readable expiry clock, e.g. "10:35". */
|
|
87
78
|
export function formatClock(epochMs) {
|
|
88
79
|
const date = new Date(epochMs);
|
|
89
80
|
const hours = String(date.getHours()).padStart(2, '0');
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linxin666/dsh-remote-web-ui",
|
|
3
3
|
"description": "Mobile remote control for the dsh web GUI: scan-to-pair QR entry beside the settings button, one-time pairing tokens, live device status, and revocable mobile sessions",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -78,7 +78,6 @@
|
|
|
78
78
|
"@types/react": "~18.3.1",
|
|
79
79
|
"@types/react-dom": "~18.3.0",
|
|
80
80
|
"jsdom": "29.1.1",
|
|
81
|
-
"lightningcss": "^1.32.0",
|
|
82
81
|
"react": "^18.2.0",
|
|
83
82
|
"react-dom": "^18.2.0",
|
|
84
83
|
"tsdown": "^0.22.2",
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The sidebar update seat: the download trigger beside the remote-control
|
|
3
3
|
* trigger plus the update panel modal. Owns the flow — probe the registry
|
|
4
|
-
* on open,
|
|
5
|
-
* outcome (restart hint on success,
|
|
4
|
+
* on open, show the result, and let the user start the update from the
|
|
5
|
+
* result view (#507), then report the outcome (restart hint on success,
|
|
6
|
+
* translated failure on error).
|
|
6
7
|
* Component-local state per the client stack rules.
|
|
7
8
|
*/
|
|
8
9
|
import { useCallback, useEffect, useRef, useState } from "react"
|
|
@@ -72,9 +73,12 @@ export function UpdateEntry({ wide, t }: UpdateEntryProps) {
|
|
|
72
73
|
setView({ kind: "result", status })
|
|
73
74
|
return
|
|
74
75
|
}
|
|
76
|
+
// Checking never starts an update by itself (#507): the result view
|
|
77
|
+
// carries the confirmation button that calls startUpdate.
|
|
75
78
|
setView({ kind: "result", status })
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
}, [t])
|
|
80
|
+
|
|
81
|
+
const startUpdate = useCallback(async (status: UpdateStatus): Promise<void> => {
|
|
78
82
|
if (status.mode !== "npm" || !status.outdated) return
|
|
79
83
|
setView({ kind: "updating", status })
|
|
80
84
|
const token = ++runToken.current
|
|
@@ -128,7 +132,7 @@ export function UpdateEntry({ wide, t }: UpdateEntryProps) {
|
|
|
128
132
|
{open && createPortal((
|
|
129
133
|
<div className={css.overlay} role="presentation">
|
|
130
134
|
<div className={css.mask} aria-hidden="true" onClick={closePanel} />
|
|
131
|
-
<UpdatePanel t={t} view={view} onClose={closePanel} onRecheck={() => { void check() }} />
|
|
135
|
+
<UpdatePanel t={t} view={view} onClose={closePanel} onRecheck={() => { void check() }} onStartUpdate={(status) => { void startUpdate(status) }} />
|
|
132
136
|
</div>
|
|
133
137
|
), document.body)}
|
|
134
138
|
</>
|
|
@@ -24,6 +24,8 @@ export interface UpdatePanelProps {
|
|
|
24
24
|
onClose(): void
|
|
25
25
|
/** Re-run the check from a terminal state. */
|
|
26
26
|
onRecheck(): void
|
|
27
|
+
/** Start the update from the result view (the confirmation step, #507). */
|
|
28
|
+
onStartUpdate(status: UpdateStatus): void
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
/** The anchor package name (aggregate first) for copy purposes. */
|
|
@@ -41,7 +43,7 @@ function anchorLatest(status: UpdateStatus | undefined): string | undefined {
|
|
|
41
43
|
* @param props - copy, view state, and actions.
|
|
42
44
|
* @returns the panel element tree.
|
|
43
45
|
*/
|
|
44
|
-
export function UpdatePanel({ t, view, onClose, onRecheck }: UpdatePanelProps) {
|
|
46
|
+
export function UpdatePanel({ t, view, onClose, onRecheck, onStartUpdate }: UpdatePanelProps) {
|
|
45
47
|
const status = view.kind === "result" || view.kind === "updating" ? view.status : undefined
|
|
46
48
|
const title = view.kind === "done" && view.result.ok ? t("update.done") : t("update.title")
|
|
47
49
|
const subtitle = subtitleOf(t, view)
|
|
@@ -58,6 +60,13 @@ export function UpdatePanel({ t, view, onClose, onRecheck }: UpdatePanelProps) {
|
|
|
58
60
|
</div>
|
|
59
61
|
{view.kind === "checking" && <p className={css.updateStatus}>{t("update.checking")}</p>}
|
|
60
62
|
{view.kind === "result" && status !== undefined && <ResultBody t={t} status={status} />}
|
|
63
|
+
{view.kind === "result" && status !== undefined && status.mode === "npm" && status.outdated && status.error === undefined && (
|
|
64
|
+
<div className={css.updateActions}>
|
|
65
|
+
<button type="button" className={css.updateRetry} onClick={() => onStartUpdate(status)}>
|
|
66
|
+
{t("update.start")}
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
)}
|
|
61
70
|
{view.kind === "updating" && status !== undefined && (
|
|
62
71
|
<div>
|
|
63
72
|
<p className={css.updateStatus}>
|
package/src/client/locales.ts
CHANGED
|
@@ -80,10 +80,11 @@ export const zh = {
|
|
|
80
80
|
'update.upToDate': '当前已是最新版本',
|
|
81
81
|
'update.upToDateDetail': '{name} 已是最新版本 v{version}。',
|
|
82
82
|
'update.found': '发现新版本',
|
|
83
|
-
'update.foundDetail': '{name} 有新版本 v{version}
|
|
83
|
+
'update.foundDetail': '{name} 有新版本 v{version}。确认后开始更新。',
|
|
84
|
+
'update.start': '开始更新',
|
|
84
85
|
'update.updating': '正在更新 {name} 至 v{version},可能需要 1-2 分钟…',
|
|
85
86
|
'update.cooldownNotice': '本次更新由你主动触发,将跳过 pnpm 的 24 小时发布冷却期(供应链保护)直接安装所选版本。',
|
|
86
|
-
'update.updatingTitle': '
|
|
87
|
+
'update.updatingTitle': '正在更新…',
|
|
87
88
|
'update.done': '更新完成',
|
|
88
89
|
'update.doneDetail': '所有组件已更新到最新版本。',
|
|
89
90
|
'update.restartHint': '请重启 dsh web 使新版本生效:在终端按 Ctrl+C 停止后重新运行 dsh web。',
|
|
@@ -195,10 +196,11 @@ export const en = {
|
|
|
195
196
|
'update.upToDate': 'Everything is up to date',
|
|
196
197
|
'update.upToDateDetail': '{name} is at the latest version v{version}.',
|
|
197
198
|
'update.found': 'A new version is available',
|
|
198
|
-
'update.foundDetail': '{name} has a newer release v{version}
|
|
199
|
+
'update.foundDetail': '{name} has a newer release v{version}. Confirm to start the update.',
|
|
200
|
+
'update.start': 'Update now',
|
|
199
201
|
'update.updating': 'Updating {name} to v{version}; this may take 1-2 minutes…',
|
|
200
202
|
'update.cooldownNotice': 'This user-initiated update bypasses pnpm\'s 24-hour release cooldown (supply-chain protection) and installs the selected version immediately.',
|
|
201
|
-
'update.updatingTitle': 'Updating
|
|
203
|
+
'update.updatingTitle': 'Updating…',
|
|
202
204
|
'update.done': 'Update complete',
|
|
203
205
|
'update.doneDetail': 'All components are up to date.',
|
|
204
206
|
'update.restartHint': 'Restart dsh web for the new version to take effect: stop it with Ctrl+C in the terminal, then run dsh web again.',
|
package/src/client/pair-api.ts
CHANGED
|
@@ -128,16 +128,7 @@ export function readPairParams(search: string): { pair?: string; workspace?: str
|
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
/**
|
|
132
|
-
* Strip one query parameter from the current URL without reloading.
|
|
133
|
-
* @param name - the parameter to remove.
|
|
134
|
-
* @returns the new search string ('' when empty).
|
|
135
|
-
*/
|
|
136
|
-
export function stripParam(name: string): string {
|
|
137
|
-
const url = new URL(window.location.href)
|
|
138
|
-
url.searchParams.delete(name)
|
|
139
|
-
return url.search
|
|
140
|
-
}/** Human-readable expiry clock, e.g. "10:35". */
|
|
131
|
+
/** Human-readable expiry clock, e.g. "10:35". */
|
|
141
132
|
export function formatClock(epochMs: number): string {
|
|
142
133
|
const date = new Date(epochMs)
|
|
143
134
|
const hours = String(date.getHours()).padStart(2, '0')
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/** mobile-theme: light default, persisted dark toggle, document wiring. */
|
|
3
3
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
4
4
|
import {
|
|
5
|
-
getMobileTheme,
|
|
5
|
+
getMobileTheme, setMobileTheme, subscribeMobileTheme, toggleMobileTheme,
|
|
6
6
|
} from './mobile-theme.ts'
|
|
7
7
|
|
|
8
8
|
const STORAGE_KEY = 'dsh.remote.theme'
|