@linxin666/dsh-client-ui-skin-center 0.1.12 → 0.1.13
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 +6 -0
- package/README.md +67 -106
- package/README.zh.md +106 -0
- package/lib/client.js +65 -44
- package/lib/client.js.map +1 -1
- package/lib/index.js +286 -31
- package/package.json +1 -1
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# node scripts/verify-docs.mjs --write <dir>
|
|
5
|
+
README.md: d76c6efe9b4b484a23911b70ca34f5e790d6a0f0
|
|
6
|
+
README.zh.md: db039c777f5ab54e27f54f278e24c9af27a6407e
|
package/README.md
CHANGED
|
@@ -1,145 +1,106 @@
|
|
|
1
|
-
# Skin Center
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
标题、body 内联样式全部恢复。「官方默认」也可试穿:点一下皮肤立即收回、回到官方外观预览。
|
|
16
|
-
- 互斥:试穿期间会按配方暂时收回当前激活皮肤的视觉写面(body 属性、背景内联样式、
|
|
17
|
-
chrome 子节点、xp 的 footer taskbar),退出后原样恢复;同一时刻页面上只有一套皮肤。
|
|
18
|
-
- 应用:host 半区(`src/index.ts` + `src/routes.ts`)暴露 `/api/skin-center/apply` 与
|
|
19
|
-
`/api/skin-center/bundle/<id>`(按需提供皮肤 bundle),
|
|
20
|
-
点击「Apply / 恢复默认」即在服务端执行 `dsh-skin use <name>`(或 `use official`),
|
|
21
|
-
写入 `~/.dsh/cordis.patch.yml` 后由 DSH 配置 watcher 秒级热载入,页面自动刷新生效——
|
|
22
|
-
**无需重启 dsh web,无需复制命令**。应用失败时错误提示里附带终端兜底命令。
|
|
23
|
-
host 依赖 `dsh-skin` CLI 在 PATH(`~/.local/bin/dsh-skin`,仓库 `scripts/dsh-skin`)。
|
|
24
|
-
Windows 兼容性:`dsh-skin` 的 harness home 取 `$DSH_HOME`(缺省 `~/.dsh`),仓库根目录从
|
|
25
|
-
脚本自身位置推导(可用 `DSH_SKIN_REPO` 覆盖),不依赖 `$HOME` 与固定路径;Windows 用户
|
|
26
|
-
自建 `dsh-skin.cmd` 包装(内容 `node scripts/dsh-skin %*`)放入 PATH 目录即可。host 端在
|
|
27
|
-
Windows 上经 shell 调用该命令——`execFile` 无法直接启动裸名 `.cmd`。
|
|
28
|
-
|
|
29
|
-
## 安装(官方 plugin bundle 方式)
|
|
30
|
-
|
|
31
|
-
推荐先装皮肤全家桶聚合包 `@linxin666/dsh-skins` 一次到位(含全部皮肤与皮肤中心);
|
|
32
|
-
只装本包时用下列 link 命令。
|
|
1
|
+
# Skin Center (in-GUI embedded skin center)
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
`@linxin666/dsh-client-ui-skin-center` (cordis plugin id `ui-skin-center`) embeds the skin list / try-on / apply into the plugin configuration page of the real dsh Web GUI, as a card in the "Web UI plugins" group (settings → plugin config → Web UI plugins → 皮肤中心 / Skin Center), sharing the same slot (`web-ui.plugin.item`) as the family plugins such as task-board / pet / live-stats, without taking a top-level settings nav item.
|
|
6
|
+
|
|
7
|
+
- List: shows "官方默认" (official default) plus every skin in the repo (qq98 / ths / xp / blue-fantasy / dragon-heir / minecraft) with its name, tagline, and accent color; the currently active target carries the Active marker.
|
|
8
|
+
- Try-on: clicking "Try on" loads that skin's client bundle on demand — the host route `/api/skin-center/bundle/<id>` serves `lib/client.js` as a same-origin script (the same mechanism the core uses to load plugins), the factory registers with the page's own `window.__ModuleLoader__`, and `window.__DSH_MODULES__.import` materializes it (a real loader, not a simulator and no eval); the chrome takes effect immediately; light/dark switching rides the official theme service; "Exit try-on" restores everything — the current skin's styles, DOM, favicon, title, and body inline styles are all restored. "官方默认" (official default) can also be tried on: one click immediately withdraws the skin and returns to the official look preview.
|
|
9
|
+
- Mutual exclusion: during try-on, the active skin's visual writes (body attribute, background inline styles, chrome child nodes, xp's footer taskbar) are temporarily withdrawn per the prescription, and restored verbatim on exit; only one skin is on the page at a time.
|
|
10
|
+
- Apply: the host half (`src/index.ts` + `src/routes.ts`) exposes `/api/skin-center/apply` and `/api/skin-center/bundle/<id>` (serves skin bundles on demand); clicking "Apply / 恢复默认" (Apply / Restore default) runs the embedded in-process `dsh-skin use` port (`src/skin-switch.ts`) server-side, writes `<harness-home>/cordis.patch.yml`, and the DSH config watcher hot-loads it within seconds and the page auto-refreshes — **no dsh web restart, no copying a command, no `dsh-skin` binary on PATH**. On failure the error message includes a terminal fallback command. Harness home follows the dsh launcher: an injected HOME maps to `<home>/.dsh`, else a trimmed non-empty `$DSH_HOME` is used directly, else `~/.dsh`. The target profile resolves as: explicit option, then `$DSH_SKIN_PROFILE`, then `$DSH_PROFILE`, then `process.cwd()` when it is a directory directly under `<harness-home>/profiles/<name>`, then `web`. Windows compatibility: the same resolution rules apply with no `$HOME` or fixed paths, and profile links fall back to directory junctions when symlink privileges are missing.
|
|
11
|
+
|
|
12
|
+
## Install (official plugin bundle)
|
|
13
|
+
|
|
14
|
+
Install the family skin aggregate package `@linxin666/dsh-skins` first (all skins plus the skin center in one); for this package alone use the `link:` commands below.
|
|
33
15
|
|
|
34
16
|
```sh
|
|
35
|
-
#
|
|
17
|
+
# All skins (recommended)
|
|
36
18
|
dsh plugin --profile web add @linxin666/dsh-skins
|
|
37
|
-
#
|
|
19
|
+
# Or just the skin center
|
|
38
20
|
dsh plugin --profile web add @linxin666/dsh-client-ui-skin-center
|
|
39
|
-
#
|
|
21
|
+
# From the repo (dev): dsh plugin --profile web add link:$(pwd)/packages/skins/skin-center
|
|
40
22
|
```
|
|
41
23
|
|
|
42
|
-
`$(pwd)`
|
|
24
|
+
`$(pwd)` is your clone of the dsh-web-ui monorepo.
|
|
43
25
|
|
|
44
|
-
skin-center
|
|
45
|
-
`cordis.patch.yml`、`prepare` 用专用 tsdown 配置自包含构建,无项目引用、无类型检查),
|
|
46
|
-
也可经 git 安装:`dsh plugin --profile web add github:<org>/dsh-web-ui#<sha>`
|
|
47
|
-
(`prepare` 会原地构建 `lib/`)。
|
|
26
|
+
skin-center is a self-contained bundle meeting the official DSH plugin standard (`dsh.bundle.patch` points to `cordis.patch.yml`; `prepare` uses a dedicated tsdown config for a self-contained build with no project references or type checking); it can also be installed via git: `dsh plugin --profile web add github:<org>/dsh-web-ui#<sha>` (the `prepare` script builds `lib/` in place).
|
|
48
27
|
|
|
49
|
-
|
|
50
|
-
`pnpm install && pnpm -r build` 后再 link 安装。
|
|
28
|
+
A local `link:` install needs built artifacts first — `lib/` is git-ignored and not committed, so run `pnpm install && pnpm -r build` in the monorepo before linking.
|
|
51
29
|
|
|
52
|
-
pnpm ≥10
|
|
30
|
+
pnpm ≥10 requires authorizing `allowBuilds` before installing a git dependency (the `prepare` script builds in place); a local `link:` install has no such requirement.
|
|
53
31
|
|
|
54
|
-
|
|
55
|
-
完整列出 / 试穿全部皮肤;skin-center 本身无互斥要求。
|
|
32
|
+
The skin plugins (qq98 / ths / xp / blue-fantasy) must resolve in the host for skin-center to list / try on the full set; skin-center itself has no mutual-exclusion requirement.
|
|
56
33
|
|
|
57
|
-
##
|
|
34
|
+
## Directory structure
|
|
58
35
|
|
|
59
36
|
```
|
|
60
37
|
skins/skin-center/
|
|
61
|
-
package.json / tsdown.config.ts / tsconfig.json # checkout
|
|
62
|
-
src/index.ts # host
|
|
63
|
-
src/routes.ts # host
|
|
64
|
-
src/invariant.ts # invariant
|
|
65
|
-
src/client/index.ts # apply
|
|
66
|
-
src/client/SkinCenter.tsx #
|
|
67
|
-
src/client/try-on.ts #
|
|
68
|
-
src/client/locales.ts # en/zh
|
|
69
|
-
src/client/skin-center.module.css #
|
|
70
|
-
src/client/generated/skins.ts #
|
|
38
|
+
package.json / tsdown.config.ts / tsconfig.json # metadata for building within the checkout
|
|
39
|
+
src/index.ts # host side: registers /api/skin-center/* routes
|
|
40
|
+
src/routes.ts # host routes (proxy to the dsh-skin CLI)
|
|
41
|
+
src/invariant.ts # invariant companion plugin (no assertions)
|
|
42
|
+
src/client/index.ts # apply: registers the Web UI plugin-group card + body scope
|
|
43
|
+
src/client/SkinCenter.tsx # card component (official default + list/try-on/light-dark/one-click apply)
|
|
44
|
+
src/client/try-on.ts # try-on engine (real loader + mutual-exclusion restore, incl. official try-on)
|
|
45
|
+
src/client/locales.ts # en/zh copy
|
|
46
|
+
src/client/skin-center.module.css # panel styles (--dsw-* tokens, adapting to the skin)
|
|
47
|
+
src/client/generated/skins.ts # generated: skin registry (metadata only, do not hand-edit)
|
|
71
48
|
```
|
|
72
49
|
|
|
73
|
-
##
|
|
74
|
-
|
|
75
|
-
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
-
|
|
85
|
-
script 的 error 事件触发,试穿报通用错误并完整还原激活皮肤;加载与还原之间不会留下半套
|
|
86
|
-
皮肤(tryOn 的 catch 分支负责恢复)。
|
|
87
|
-
- 退出还原:先跑皮肤的 disposer(属性/chrome/favicon/标题/背景全撤回),再
|
|
88
|
-
`invalidate(package)` + 删 style 标签,最后把激活皮肤的视觉快照原样恢复。
|
|
89
|
-
官方默认试穿 = 同一套收回配方但不挂载任何皮肤,退出同样原样恢复。
|
|
90
|
-
- 激活皮肤检测:`window.__DSH_BOOT__.entries` 只含启用条目,与注册表 package 比对;
|
|
91
|
-
无匹配即官方默认。
|
|
92
|
-
- 一键应用:host `/api/skin-center/apply` 代理 `dsh-skin use <name>` / `use official`
|
|
93
|
-
(CLI 是 managed 区段与 symlink 的唯一权威)。DSH 长驻表面自带配置 watcher
|
|
94
|
-
(`watchUserPatches` + config-only HMR),patch 写入后数秒热载入、无需重启;
|
|
95
|
-
浏览器刷新页面取新 boot 图即生效(client 插件图行增删不在 `dsh-client-hmr` 语义内)。
|
|
96
|
-
|
|
97
|
-
## 构建(仓库内 tsdown,无需 DSH checkout)
|
|
98
|
-
|
|
99
|
-
皮肤中心与皮肤一样,用仓库内共享 tsdown 预设构建(`shared/tsdown.client.ts`
|
|
100
|
-
处理 CSS Modules 注入与平台外部化;类型来自官方 NPM SDK devDependencies):
|
|
50
|
+
## Mechanism notes
|
|
51
|
+
|
|
52
|
+
- Skin enumeration: `generated/skins.ts` is produced by `scripts/skin-center-bundles` (reads `skins/<name>/skin.json`, validates that `lib/client.js` exists). **It contains metadata only, no embedded bundle text**: cold start does not parse the ~700KB base64 art assets, and the generated file is reproducible across machines (no build-machine absolute paths).
|
|
53
|
+
- Try-on loading: the host route `/api/skin-center/bundle/<id>` serves `lib/client.js` on demand (same-origin script, loaded via a `<script>` tag — the same mechanism as the core's `defaultLoadBundle`); the bundle body calls `window.__ModuleLoader__.load` to only register a factory; `window.__DSH_MODULES__.import(package)` materializes the module (CSS `<style data-plugin>` auto-injected); `surface.apply(miniCtx)` mounts with a miniCtx that only implements `effect(cb)` (the skin's only dependency). It does not rely on eval, so no CSP `unsafe-eval` pass is required — only same-origin script loading (as the page itself does when loading its plugin bundles).
|
|
54
|
+
- Failure semantics: when the bundle route 404s (skin not installed / `lib/client.js` not built) or the network fails, the script's error event fires, try-on reports a generic error and fully restores the active skin; no half skin is left between loading and restoring (the tryOn catch branch handles recovery).
|
|
55
|
+
- Exit restore: first run the skin's disposer (withdraws attribute/chrome/favicon/title/background entirely), then `invalidate(package)` + remove the style tag, then restore the active skin's visual snapshot verbatim. Official-default try-on = the same withdrawal prescription but without mounting any skin; exit restores identically.
|
|
56
|
+
- Active-skin detection: `window.__DSH_BOOT__.entries` only contains enabled entries, compared against the registry packages; no match means official default.
|
|
57
|
+
- One-click apply: host `/api/skin-center/apply` runs the embedded port of `dsh-skin use <name>` / `use official` (the port is the sole authority for the managed section and the symlink). Paths are `<harness-home>/cordis.patch.yml` and `<harness-home>/profiles/<profile>/node_modules` with the home/profile resolution described above. When the active skin is itself installed as a bundle — a real profile package dir whose own `cordis.patch.yml` inserts the same loader id, or a registry `bundleWired` skin — the home layer writes only the mutual-exclusion `disabled: true` rows and leaves the insert to the bundle patch; the bundled-carrier symlink layout keeps the home insert row. The DSH daemon surface ships its own config watcher (`watchUserPatches` + config-only HMR); after the patch is written it hot-loads in seconds without a restart; a browser refresh picks up the new boot graph (client plugin-graph row changes are not within `dsh-client-hmr` semantics).
|
|
58
|
+
|
|
59
|
+
## Build (in-repo tsdown, no DSH checkout)
|
|
60
|
+
|
|
61
|
+
Like the skins, the skin center builds with the in-repo shared tsdown preset (`shared/tsdown.client.ts` handles CSS-Module injection and platform externalization; types come from the official NPM SDK devDependencies):
|
|
101
62
|
|
|
102
63
|
```sh
|
|
103
|
-
# 1.
|
|
64
|
+
# 1. Regenerate the registry (re-run after skin metadata changes; bundle text rides the host route on demand, no regen needed)
|
|
104
65
|
node scripts/skin-center-bundles
|
|
105
|
-
#
|
|
66
|
+
# A skin bundle change itself only needs that skin rebuilt (tsdown); the GUI picks up the new text on the next try-on
|
|
106
67
|
|
|
107
|
-
# 2.
|
|
108
|
-
cd ~/code/dsh-web-ui && export NPM_TOKEN='<token>' #
|
|
68
|
+
# 2. Build in the repo
|
|
69
|
+
cd ~/code/dsh-web-ui && export NPM_TOKEN='<token>' # if private-scope auth is still required
|
|
109
70
|
pnpm --filter @linxin666/dsh-client-ui-skin-center run bundle
|
|
110
71
|
```
|
|
111
72
|
|
|
112
|
-
##
|
|
73
|
+
## Install (personal environment wiring, not committed to the checkout)
|
|
113
74
|
|
|
114
75
|
```sh
|
|
115
|
-
# 1. profile symlink
|
|
76
|
+
# 1. profile symlink (same as qq98/blue-fantasy)
|
|
116
77
|
ln -sfn ~/code/dsh-web-ui/packages/skins/skin-center \
|
|
117
78
|
~/.dsh/profiles/node_modules/@linxin666/dsh-client-ui-skin-center
|
|
118
79
|
|
|
119
|
-
# 2. ~/.dsh/cordis.patch.yml
|
|
80
|
+
# 2. add to ~/.dsh/cordis.patch.yml (outside the dsh-skin managed section, do not touch that section):
|
|
120
81
|
# - insert:
|
|
121
82
|
# - id: ui-skin-center
|
|
122
83
|
# name: '@linxin666/dsh-client-ui-skin-center'
|
|
123
84
|
|
|
124
|
-
# 3.
|
|
85
|
+
# 3. the config watcher hot-loads in seconds; refresh the page to see the skin-center card in 插件配置 → Web UI 插件
|
|
125
86
|
```
|
|
126
87
|
|
|
127
|
-
##
|
|
88
|
+
## Try-on mutual-exclusion restore prescription (try-on.ts)
|
|
128
89
|
|
|
129
|
-
|
|
|
90
|
+
| Skin | body attribute | extra handling |
|
|
130
91
|
| --- | --- | --- |
|
|
131
|
-
|
|
|
132
|
-
| xp |
|
|
92
|
+
| All | withdraw `bodyAttr` (CSS deactivated) | snapshot/clear the body background inline style (blue-fantasy whale background); remove chrome among the body's direct child nodes that are not `#root` (measured: only skin chrome); neutralize observers to prevent ghost write-backs |
|
|
93
|
+
| xp | same | additionally inject neutralizer CSS to hide the sidebar footer's taskbar/start button (its rules are not scoped by attribute) |
|
|
133
94
|
|
|
134
|
-
|
|
95
|
+
Exit try-on = try-on skin disposer (real code path) → module invalidate + style cleanup → restore the active skin snapshot verbatim.
|
|
135
96
|
|
|
136
|
-
##
|
|
97
|
+
## Acceptance checklist (top-level README contract)
|
|
137
98
|
|
|
138
|
-
- [x] 插件配置 → Web UI 插件
|
|
139
|
-
- [x]
|
|
140
|
-
- [x]
|
|
141
|
-
- [x]
|
|
142
|
-
- [x]
|
|
143
|
-
- [x]
|
|
144
|
-
- [x]
|
|
145
|
-
- [x] e2e
|
|
99
|
+
- [x] The skin-center card appears in 插件配置 → Web UI 插件 without console errors
|
|
100
|
+
- [x] The list contains the official default plus all skins; the currently active one is marked
|
|
101
|
+
- [x] Try-on really takes effect (chrome/background/title/favicon); light/dark correct; the official default can be tried on
|
|
102
|
+
- [x] Exit fully restores; mutual exclusion (no two title bars)
|
|
103
|
+
- [x] One-click apply: the host API runs `dsh-skin use`, the watcher hot-loads, the page auto-refreshes (no restart); failure carries a command fallback
|
|
104
|
+
- [x] Regression: the dsh-skin CLI (incl. `use official`), the web gallery, and the official GUI are unaffected
|
|
105
|
+
- [x] On-demand loading: cold start does not parse the ~700KB embedded base64 (`generated/skins.ts` is only ~5KB of metadata); try-on fetches the bundle on demand; no eval (CSP needs no `unsafe-eval`)
|
|
106
|
+
- [x] e2e screenshots live in `docs/e2e/skin-center/`
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Skin Center(GUI 内嵌皮肤中心)
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
`@linxin666/dsh-client-ui-skin-center`(cordis 插件 id `ui-skin-center`)把皮肤列表/试穿/应用内嵌进真实 dsh Web GUI 的插件配置页,作为「Web UI 插件」组里的一张卡片(设置 → 插件配置 → Web UI 插件 → 皮肤中心),与 task-board / pet / live-stats 等全家桶插件同一套槽位(`web-ui.plugin.item`),不占设置页一级导航。
|
|
6
|
+
|
|
7
|
+
- 列表:展示「官方默认」+ 仓库里全部皮肤(qq98 / ths / xp / blue-fantasy / dragon-heir / minecraft)的名称、tagline、强调色;当前激活的目标带 Active 标记。
|
|
8
|
+
- 试穿:点击「Try on」后按需加载该皮肤的 client bundle——host 路由 `/api/skin-center/bundle/<id>` 以同源 script 提供 `lib/client.js`(内核加载插件的同一机制),factory 注册到页面自己的 `window.__ModuleLoader__`,`window.__DSH_MODULES__.import` 物化(不是模拟器、不用 eval),chrome 立即生效;亮/暗切换走官方 theme 服务;「Exit try-on」完全还原——当前皮肤的样式、DOM、favicon、标题、body 内联样式全部恢复。「官方默认」也可试穿:点一下皮肤立即收回、回到官方外观预览。
|
|
9
|
+
- 互斥:试穿期间会按配方暂时收回当前激活皮肤的视觉写面(body 属性、背景内联样式、chrome 子节点、xp 的 footer taskbar),退出后原样恢复;同一时刻页面上只有一套皮肤。
|
|
10
|
+
- 应用:host 半区(`src/index.ts` + `src/routes.ts`)暴露 `/api/skin-center/apply` 与 `/api/skin-center/bundle/<id>`(按需提供皮肤 bundle),点击「Apply / 恢复默认」即在服务端执行内嵌的 `dsh-skin use` 进程内移植版(`src/skin-switch.ts`),写入 `<harness-home>/cordis.patch.yml` 后由 DSH 配置 watcher 秒级热载入,页面自动刷新生效——**无需重启 dsh web,无需复制命令,也不要求 PATH 上有 `dsh-skin` 二进制**。应用失败时错误提示里附带终端兜底命令。harness home 与 dsh 启动器一致:注入的 HOME 映射为 `<home>/.dsh`,否则优先使用去除首尾空白后非空的 `$DSH_HOME`(直接使用,不再追加后缀),最后回退到 `~/.dsh`。目标 profile 依次取:显式选项、`$DSH_SKIN_PROFILE`、`$DSH_PROFILE`、`process.cwd()` 直接位于 `<harness-home>/profiles/<name>` 下时的 `<name>`,最后 `web`。Windows 兼容性:同一套解析规则不依赖 `$HOME` 与固定路径,符号链接权限不足时 profile 链接回退为目录 junction。
|
|
11
|
+
|
|
12
|
+
## 安装(官方 plugin bundle 方式)
|
|
13
|
+
|
|
14
|
+
推荐先装皮肤全家桶聚合包 `@linxin666/dsh-skins` 一次到位(含全部皮肤与皮肤中心);只装本包时用下列 link 命令。
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
# 装全部皮肤(推荐)
|
|
18
|
+
dsh plugin --profile web add @linxin666/dsh-skins
|
|
19
|
+
# 或单独装皮肤中心
|
|
20
|
+
dsh plugin --profile web add @linxin666/dsh-client-ui-skin-center
|
|
21
|
+
# 从仓库安装(开发调试):dsh plugin --profile web add link:$(pwd)/packages/skins/skin-center
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`$(pwd)` 指克隆全家桶仓库后的目录。
|
|
25
|
+
|
|
26
|
+
skin-center 是符合 DSH 官方插件标准的自包含 bundle(`dsh.bundle.patch` 指向 `cordis.patch.yml`、`prepare` 用专用 tsdown 配置自包含构建,无项目引用、无类型检查),也可经 git 安装:`dsh plugin --profile web add github:<org>/dsh-web-ui#<sha>`(`prepare` 会原地构建 `lib/`)。
|
|
27
|
+
|
|
28
|
+
本地 link 安装前需先在全家桶仓库内构建产物(`lib/` 被 git 忽略、不随仓库提交):`pnpm install && pnpm -r build` 后再 link 安装。
|
|
29
|
+
|
|
30
|
+
pnpm ≥10 安装 git 依赖前需先授权 `allowBuilds`(`prepare` 会原地构建),本地 link 安装则无此要求。
|
|
31
|
+
|
|
32
|
+
需要皮肤插件们(qq98 / ths / xp / blue-fantasy)在宿主里也可解析时,skin-center 才能完整列出 / 试穿全部皮肤;skin-center 本身无互斥要求。
|
|
33
|
+
|
|
34
|
+
## 目录结构
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
skins/skin-center/
|
|
38
|
+
package.json / tsdown.config.ts / tsconfig.json # checkout 内构建所需的元数据
|
|
39
|
+
src/index.ts # host 侧:注册 /api/skin-center/* 路由
|
|
40
|
+
src/routes.ts # host 路由(代理 dsh-skin CLI)
|
|
41
|
+
src/invariant.ts # invariant 伴随插件(无断言)
|
|
42
|
+
src/client/index.ts # apply:注册 Web UI 插件组卡片 + body 作用域
|
|
43
|
+
src/client/SkinCenter.tsx # 卡片组件(官方默认 + 列表/试穿/亮暗/一键应用)
|
|
44
|
+
src/client/try-on.ts # 试穿引擎(真实 loader + 互斥还原,含官方试穿)
|
|
45
|
+
src/client/locales.ts # en/zh 文案
|
|
46
|
+
src/client/skin-center.module.css # 面板样式(--dsw-* token,随皮肤自适应)
|
|
47
|
+
src/client/generated/skins.ts # 生成:皮肤注册表(仅元数据,勿手改)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## 机制要点
|
|
51
|
+
|
|
52
|
+
- 皮肤枚举:`generated/skins.ts` 由 `scripts/skin-center-bundles` 生成(读 `skins/<name>/skin.json`,校验 `lib/client.js` 存在)。**只含元数据,不内嵌 bundle 文本**:冷启动不解析 ~700KB 的 base64 美术资源,且生成文件跨机器可复现(无构建机绝对路径)。
|
|
53
|
+
- 试穿加载:host 路由 `/api/skin-center/bundle/<id>` 按需提供 `lib/client.js`(同源 script,`<script>` 标签加载——与内核 `defaultLoadBundle` 同一机制),bundle 体调用 `window.__ModuleLoader__.load` 只注册 factory;`window.__DSH_MODULES__.import(package)` 物化模块(CSS `<style data-plugin>` 自动注入);`surface.apply(miniCtx)` 挂载,miniCtx 只实现 `effect(cb)`(皮肤唯一依赖)。不依赖 eval,因此不要求 CSP 放行 `unsafe-eval`——只要求同源 script 可加载(页面自身加载插件 bundle 亦然)。
|
|
54
|
+
- 失败语义:bundle 路由 404(皮肤未安装 / `lib/client.js` 未构建)或网络失败时,script 的 error 事件触发,试穿报通用错误并完整还原激活皮肤;加载与还原之间不会留下半套皮肤(tryOn 的 catch 分支负责恢复)。
|
|
55
|
+
- 退出还原:先跑皮肤的 disposer(属性/chrome/favicon/标题/背景全撤回),再 `invalidate(package)` + 删 style 标签,最后把激活皮肤的视觉快照原样恢复。官方默认试穿 = 同一套收回配方但不挂载任何皮肤,退出同样原样恢复。
|
|
56
|
+
- 激活皮肤检测:`window.__DSH_BOOT__.entries` 只含启用条目,与注册表 package 比对;无匹配即官方默认。
|
|
57
|
+
- 一键应用:host `/api/skin-center/apply` 执行内嵌的 `dsh-skin use <name>` / `use official` 移植版(该移植版是 managed 区段与 symlink 的唯一权威)。路径为 `<harness-home>/cordis.patch.yml` 与 `<harness-home>/profiles/<profile>/node_modules`,home/profile 按上文规则解析。当激活皮肤自身已作为 bundle 安装——profile 包目录是真实目录且其自身 `cordis.patch.yml` 插入了同一 loader id,或注册表标记 `bundleWired`——home 层只写互斥的 `disabled: true` 行,insert 留给 bundle patch;聚合包载体 symlink 布局仍保留 home 层 insert 行。DSH 长驻表面自带配置 watcher(`watchUserPatches` + config-only HMR),patch 写入后数秒热载入、无需重启;浏览器刷新页面取新 boot 图即生效(client 插件图行增删不在 `dsh-client-hmr` 语义内)。
|
|
58
|
+
|
|
59
|
+
## 构建(仓库内 tsdown,无需 DSH checkout)
|
|
60
|
+
|
|
61
|
+
皮肤中心与皮肤一样,用仓库内共享 tsdown 预设构建(`shared/tsdown.client.ts` 处理 CSS Modules 注入与平台外部化;类型来自官方 NPM SDK devDependencies):
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
# 1. 重新生成注册表(皮肤元数据变化后重跑;bundle 文本按需走 host 路由,无需重生成)
|
|
65
|
+
node scripts/skin-center-bundles
|
|
66
|
+
# 皮肤 bundle 自身变化只需重建对应皮肤(tsdown),GUI 下次试穿即取到新文本
|
|
67
|
+
|
|
68
|
+
# 2. 在仓库内构建
|
|
69
|
+
cd ~/code/dsh-web-ui && export NPM_TOKEN='<token>' # 若仍使用私有 scope 认证
|
|
70
|
+
pnpm --filter @linxin666/dsh-client-ui-skin-center run bundle
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 安装(个人环境接线,不在 checkout 提交)
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
# 1. profile symlink(与 qq98/blue-fantasy 同款)
|
|
77
|
+
ln -sfn ~/code/dsh-web-ui/packages/skins/skin-center \
|
|
78
|
+
~/.dsh/profiles/node_modules/@linxin666/dsh-client-ui-skin-center
|
|
79
|
+
|
|
80
|
+
# 2. ~/.dsh/cordis.patch.yml 增加(放在 dsh-skin managed 段之外,勿动该段):
|
|
81
|
+
# - insert:
|
|
82
|
+
# - id: ui-skin-center
|
|
83
|
+
# name: '@linxin666/dsh-client-ui-skin-center'
|
|
84
|
+
|
|
85
|
+
# 3. 配置 watcher 秒级热载入;刷新页面即在 插件配置 → Web UI 插件 组里看到皮肤中心卡片
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 试穿互斥的还原配方(try-on.ts)
|
|
89
|
+
|
|
90
|
+
| 皮肤 | body 属性 | 额外处理 |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| 全部 | 收回 `bodyAttr`(CSS 失活) | 快照/清空 body 背景内联样式(blue-fantasy 鲸鱼背景);摘除 body 直接子节点中非 `#root` 的 chrome(实测仅皮肤 chrome);中性化观察器防幽灵写回 |
|
|
93
|
+
| xp | 同上 | 额外注入 neutralizer CSS 隐藏 sidebar footer 的 taskbar/开始按钮(其规则未按属性作用域) |
|
|
94
|
+
|
|
95
|
+
退出试穿 = 试穿皮肤 disposer(真实代码路径)→ 模块 invalidate + 样式清理 → 激活皮肤快照原样恢复。
|
|
96
|
+
|
|
97
|
+
## 验收对照(README 顶层契约)
|
|
98
|
+
|
|
99
|
+
- [x] 插件配置 → Web UI 插件 组里出现皮肤中心卡片,无 console 报错
|
|
100
|
+
- [x] 列表含官方默认 + 全部皮肤,当前激活有标记
|
|
101
|
+
- [x] 试穿真实生效(chrome/背景/标题/favicon),亮/暗正确;官方默认可试穿
|
|
102
|
+
- [x] 退出完全还原;互斥(不出现两套标题栏)
|
|
103
|
+
- [x] 一键应用:host API 执行 `dsh-skin use`,watcher 热载入,页面自动刷新生效(无重启);失败附命令兜底
|
|
104
|
+
- [x] 回归:dsh-skin CLI(含 `use official`)、网页 Gallery、官方 GUI 不受影响
|
|
105
|
+
- [x] 按需加载:冷启动不解析 ~700KB 内嵌 base64(`generated/skins.ts` 仅 5KB 元数据),试穿按需取 bundle;无 eval(CSP 无需 `unsafe-eval`)
|
|
106
|
+
- [x] e2e 截图见 `docs/e2e/skin-center/`
|
package/lib/client.js
CHANGED
|
@@ -464,8 +464,8 @@ window.__ModuleLoader__.load({
|
|
|
464
464
|
}
|
|
465
465
|
};
|
|
466
466
|
//#endregion
|
|
467
|
-
//#region \0dsh-css
|
|
468
|
-
const css = "body[data-dsh-skin-center] .
|
|
467
|
+
//#region \0dsh-css:packages/skins/skin-center/src/client/skin-center.module.css.mjs
|
|
468
|
+
const css = "body[data-dsh-skin-center] .eDzMgW_pluginCard{border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;list-style:none;transition:border-color .16s,background .16s}body[data-dsh-skin-center] .eDzMgW_pluginCard:hover{border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .eDzMgW_pluginCardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}body[data-dsh-skin-center] .eDzMgW_cardHeader{appearance:none;width:100%;color:inherit;font:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:14px 16px;display:flex}body[data-dsh-skin-center] .eDzMgW_cardHeader:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}body[data-dsh-skin-center] .eDzMgW_headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}body[data-dsh-skin-center] .eDzMgW_pluginName{color:var(--dsw-alias-label-primary);align-items:baseline;gap:8px;font-size:15px;font-weight:600;line-height:1.4;display:flex}body[data-dsh-skin-center] .eDzMgW_cardDescription{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.5}body[data-dsh-skin-center] .eDzMgW_chevron,body[data-dsh-skin-center] .eDzMgW_chevronOpen{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}body[data-dsh-skin-center] .eDzMgW_chevronOpen{transform:rotate(180deg)}body[data-dsh-skin-center] .eDzMgW_cardBody{border-top:1px solid var(--dsw-alias-border-l2);flex-direction:column;gap:12px;margin:0 16px;padding:12px 0 8px;display:flex}body[data-dsh-skin-center] .eDzMgW_head{flex-direction:column;gap:6px;display:flex}body[data-dsh-skin-center] .eDzMgW_titleBadge{color:var(--dsw-alias-label-secondary,#6b7280);font-size:11px;font-weight:500}body[data-dsh-skin-center] .eDzMgW_intro{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12.5px;line-height:1.55}body[data-dsh-skin-center] .eDzMgW_themeRow{align-items:center;gap:8px;margin-top:2px;display:flex}body[data-dsh-skin-center] .eDzMgW_themeLabel{color:var(--dsw-alias-label-secondary,#6b7280);margin-right:2px;font-size:12px}body[data-dsh-skin-center] .eDzMgW_themeButton{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#172a45);cursor:pointer;border-radius:6px;padding:5px 10px;font-size:12px;line-height:1;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .eDzMgW_themeButton:hover{border-color:var(--dsw-alias-border-l4,#94a3b8)}body[data-dsh-skin-center] .eDzMgW_themeButton:active{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_themeButton:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_themeButtonActive{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_list{flex-direction:column;gap:10px;display:flex}body[data-dsh-skin-center] .eDzMgW_card{border:1px solid var(--dsw-alias-border-l1,#e2e8f0);background:var(--dsw-alias-bg-layer-2,#fff);border-radius:10px;flex-direction:column;gap:8px;padding:12px 14px;display:flex}body[data-dsh-skin-center] .eDzMgW_cardHead{align-items:center;gap:10px;min-width:0;display:flex}body[data-dsh-skin-center] .eDzMgW_swatch{width:14px;height:14px;box-shadow:inset 0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);border-radius:50%;flex:none}body[data-dsh-skin-center] .eDzMgW_cardName{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:13.5px;font-weight:600;overflow:hidden}body[data-dsh-skin-center] .eDzMgW_cardTagline{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.45}body[data-dsh-skin-center] .eDzMgW_badge{letter-spacing:.02em;border-radius:999px;flex:none;min-width:0;margin-left:auto;padding:2px 8px;font-size:11px;font-weight:600}body[data-dsh-skin-center] .eDzMgW_badgeActive{color:var(--dsw-alias-state-success-primary,#0f6b3a);background:var(--dsw-alias-state-success-tertiary,#dcf3e5)}body[data-dsh-skin-center] .eDzMgW_badgeTrying{color:var(--dsw-alias-brand-primary,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e2edfc)}body[data-dsh-skin-center] .eDzMgW_actions{flex-wrap:wrap;align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .eDzMgW_button{border:1px solid var(--dsw-alias-border-l3,#cbd5e1);background:var(--dsw-alias-bg-layer-2,#fff);color:var(--dsw-alias-label-primary,#172a45);cursor:pointer;border-radius:7px;padding:6px 12px;font-size:12px;line-height:1;transition:background .12s,border-color .12s,color .12s}body[data-dsh-skin-center] .eDzMgW_button:hover:not(:disabled){border-color:var(--dsw-alias-brand-primary,#2b7cd9);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_button:active:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-dimmed,#e8f1fc);color:var(--dsw-alias-brand-primary,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_buttonPrimary{border-color:var(--dsw-alias-brand-primary,#2b7cd9);background:var(--dsw-alias-button-primary-fill,#2b7cd9);color:var(--dsw-alias-label-primary-foreground,#fff)}body[data-dsh-skin-center] .eDzMgW_buttonPrimary:hover:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-hover,#1e63b8);color:var(--dsw-alias-label-primary-foreground,#fff)}body[data-dsh-skin-center] .eDzMgW_buttonPrimary:active:not(:disabled),body[data-dsh-skin-center] .eDzMgW_buttonPrimary:focus-visible:not(:disabled){border-color:var(--dsw-alias-button-primary-hover,#1e63b8);background:var(--dsw-alias-button-primary-hover,#1e63b8)}body[data-dsh-skin-center] .eDzMgW_buttonGhost{background:0 0;border-color:#0000}body[data-dsh-skin-center] .eDzMgW_button:disabled{opacity:.55;cursor:default}body[data-dsh-skin-center] .eDzMgW_error{color:var(--dsw-alias-state-error-primary,#b42318);font-size:12px}body[data-dsh-skin-center] .eDzMgW_backgroundRow{flex-direction:column;gap:6px;padding:8px 0;display:flex}body[data-dsh-skin-center] .eDzMgW_backgroundHead{align-items:center;gap:8px;display:flex}body[data-dsh-skin-center] .eDzMgW_backgroundLabel{color:var(--dsw-alias-label-primary,#172a45);font-size:12.5px;font-weight:600}body[data-dsh-skin-center] .eDzMgW_backgroundValue{font-variant-numeric:tabular-nums;color:var(--dsw-alias-brand-primary,#2b7cd9);flex:none;margin-left:auto;font-size:12px}body[data-dsh-skin-center] .eDzMgW_backgroundRange{background:var(--dsw-alias-bg-layer-3,#e2e8f0);-webkit-appearance:none;appearance:none;cursor:pointer;border-radius:999px;width:100%;height:4px;margin:0}body[data-dsh-skin-center] .eDzMgW_backgroundRange::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:14px;height:14px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%}body[data-dsh-skin-center] .eDzMgW_backgroundRange::-moz-range-thumb{border:2px solid var(--dsw-alias-label-primary-foreground,#fff);background:var(--dsw-alias-brand-primary,#2b7cd9);width:12px;height:12px;box-shadow:0 0 0 1px var(--dsw-alias-border-l4,#0f172a1f);cursor:pointer;border-radius:50%}body[data-dsh-skin-center] .eDzMgW_backgroundRange:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#2b7cd9);outline-offset:2px}body[data-dsh-skin-center] .eDzMgW_backgroundHint{color:var(--dsw-alias-label-secondary,#6b7280);font-size:12px;line-height:1.5}body[data-dsh-skin-center] .eDzMgW_backgroundHintMuted{color:var(--dsw-alias-label-tertiary,#9aa4b5);font-size:12px;line-height:1.5}@media (prefers-reduced-motion:reduce){body[data-dsh-skin-center] .eDzMgW_pluginCard,body[data-dsh-skin-center] .eDzMgW_cardHeader,body[data-dsh-skin-center] .eDzMgW_themeButton,body[data-dsh-skin-center] .eDzMgW_button,body[data-dsh-skin-center] .eDzMgW_chevron,body[data-dsh-skin-center] .eDzMgW_chevronOpen{transition:none}}";
|
|
469
469
|
const tagId = "@linxin666/dsh-client-ui-skin-center/skin-center.module.css";
|
|
470
470
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
471
471
|
const tag = document.createElement("style");
|
|
@@ -475,42 +475,43 @@ window.__ModuleLoader__.load({
|
|
|
475
475
|
document.head.appendChild(tag);
|
|
476
476
|
}
|
|
477
477
|
var skin_center_module_css_default = {
|
|
478
|
-
"actions": "
|
|
479
|
-
"backgroundHead": "
|
|
480
|
-
"backgroundHint": "
|
|
481
|
-
"backgroundHintMuted": "
|
|
482
|
-
"backgroundLabel": "
|
|
483
|
-
"backgroundRange": "
|
|
484
|
-
"backgroundRow": "
|
|
485
|
-
"backgroundValue": "
|
|
486
|
-
"badge": "
|
|
487
|
-
"badgeActive": "
|
|
488
|
-
"badgeTrying": "
|
|
489
|
-
"button": "
|
|
490
|
-
"buttonGhost": "
|
|
491
|
-
"buttonPrimary": "
|
|
492
|
-
"card": "
|
|
493
|
-
"cardBody": "
|
|
494
|
-
"cardDescription": "
|
|
495
|
-
"cardHead": "
|
|
496
|
-
"cardHeader": "
|
|
497
|
-
"cardName": "
|
|
498
|
-
"cardTagline": "
|
|
499
|
-
"chevron": "
|
|
500
|
-
"chevronOpen": "
|
|
501
|
-
"error": "
|
|
502
|
-
"head": "
|
|
503
|
-
"headText": "
|
|
504
|
-
"intro": "
|
|
505
|
-
"list": "
|
|
506
|
-
"pluginCard": "
|
|
507
|
-
"
|
|
508
|
-
"
|
|
509
|
-
"
|
|
510
|
-
"
|
|
511
|
-
"
|
|
512
|
-
"
|
|
513
|
-
"
|
|
478
|
+
"actions": "eDzMgW_actions",
|
|
479
|
+
"backgroundHead": "eDzMgW_backgroundHead",
|
|
480
|
+
"backgroundHint": "eDzMgW_backgroundHint",
|
|
481
|
+
"backgroundHintMuted": "eDzMgW_backgroundHintMuted",
|
|
482
|
+
"backgroundLabel": "eDzMgW_backgroundLabel",
|
|
483
|
+
"backgroundRange": "eDzMgW_backgroundRange",
|
|
484
|
+
"backgroundRow": "eDzMgW_backgroundRow",
|
|
485
|
+
"backgroundValue": "eDzMgW_backgroundValue",
|
|
486
|
+
"badge": "eDzMgW_badge",
|
|
487
|
+
"badgeActive": "eDzMgW_badgeActive",
|
|
488
|
+
"badgeTrying": "eDzMgW_badgeTrying",
|
|
489
|
+
"button": "eDzMgW_button",
|
|
490
|
+
"buttonGhost": "eDzMgW_buttonGhost",
|
|
491
|
+
"buttonPrimary": "eDzMgW_buttonPrimary",
|
|
492
|
+
"card": "eDzMgW_card",
|
|
493
|
+
"cardBody": "eDzMgW_cardBody",
|
|
494
|
+
"cardDescription": "eDzMgW_cardDescription",
|
|
495
|
+
"cardHead": "eDzMgW_cardHead",
|
|
496
|
+
"cardHeader": "eDzMgW_cardHeader",
|
|
497
|
+
"cardName": "eDzMgW_cardName",
|
|
498
|
+
"cardTagline": "eDzMgW_cardTagline",
|
|
499
|
+
"chevron": "eDzMgW_chevron",
|
|
500
|
+
"chevronOpen": "eDzMgW_chevronOpen",
|
|
501
|
+
"error": "eDzMgW_error",
|
|
502
|
+
"head": "eDzMgW_head",
|
|
503
|
+
"headText": "eDzMgW_headText",
|
|
504
|
+
"intro": "eDzMgW_intro",
|
|
505
|
+
"list": "eDzMgW_list",
|
|
506
|
+
"pluginCard": "eDzMgW_pluginCard",
|
|
507
|
+
"pluginCardOpen": "eDzMgW_pluginCardOpen",
|
|
508
|
+
"pluginName": "eDzMgW_pluginName",
|
|
509
|
+
"swatch": "eDzMgW_swatch",
|
|
510
|
+
"themeButton": "eDzMgW_themeButton",
|
|
511
|
+
"themeButtonActive": "eDzMgW_themeButtonActive",
|
|
512
|
+
"themeLabel": "eDzMgW_themeLabel",
|
|
513
|
+
"themeRow": "eDzMgW_themeRow",
|
|
514
|
+
"titleBadge": "eDzMgW_titleBadge"
|
|
514
515
|
};
|
|
515
516
|
//#endregion
|
|
516
517
|
//#region src/client/SkinCenter.tsx
|
|
@@ -546,6 +547,13 @@ window.__ModuleLoader__.load({
|
|
|
546
547
|
const [tryingOfficial, setTryingOfficial] = (0, react.useState)(false);
|
|
547
548
|
const [applying, setApplying] = (0, react.useState)(null);
|
|
548
549
|
const [error, setError] = (0, react.useState)(null);
|
|
550
|
+
const mounted = (0, react.useRef)(false);
|
|
551
|
+
(0, react.useEffect)(() => {
|
|
552
|
+
mounted.current = true;
|
|
553
|
+
return () => {
|
|
554
|
+
mounted.current = false;
|
|
555
|
+
};
|
|
556
|
+
}, []);
|
|
549
557
|
const tryOn = (entry) => {
|
|
550
558
|
setError(null);
|
|
551
559
|
controller.tryOn(entry).then(() => {
|
|
@@ -584,6 +592,10 @@ window.__ModuleLoader__.load({
|
|
|
584
592
|
const expected = target === OFFICIAL ? "none" : target;
|
|
585
593
|
let tries = 0;
|
|
586
594
|
const tick = () => {
|
|
595
|
+
if (!mounted.current) {
|
|
596
|
+
resolve(false);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
587
599
|
tries += 1;
|
|
588
600
|
fetch("/api/skin-center/state").then(async (response) => {
|
|
589
601
|
const payload = await response.json().catch(() => null);
|
|
@@ -591,10 +603,10 @@ window.__ModuleLoader__.load({
|
|
|
591
603
|
resolve(true);
|
|
592
604
|
return;
|
|
593
605
|
}
|
|
594
|
-
if (tries >= 20) resolve(false);
|
|
606
|
+
if (tries >= 20 || !mounted.current) resolve(false);
|
|
595
607
|
else window.setTimeout(tick, 250);
|
|
596
608
|
}).catch(() => {
|
|
597
|
-
if (tries >= 20) resolve(false);
|
|
609
|
+
if (tries >= 20 || !mounted.current) resolve(false);
|
|
598
610
|
else window.setTimeout(tick, 250);
|
|
599
611
|
});
|
|
600
612
|
};
|
|
@@ -618,6 +630,7 @@ window.__ModuleLoader__.load({
|
|
|
618
630
|
if (!response.ok || payload?.ok !== true) throw new Error(payload?.error ?? `HTTP ${response.status}`);
|
|
619
631
|
setApplying(null);
|
|
620
632
|
confirmActive(target).then((confirmed) => {
|
|
633
|
+
if (!mounted.current) return;
|
|
621
634
|
if (confirmed) window.location.reload();
|
|
622
635
|
else {
|
|
623
636
|
const command = target === OFFICIAL ? "dsh-skin use official" : `dsh-skin use ${target}`;
|
|
@@ -661,7 +674,7 @@ window.__ModuleLoader__.load({
|
|
|
661
674
|
})]
|
|
662
675
|
});
|
|
663
676
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
664
|
-
className: skin_center_module_css_default.pluginCard,
|
|
677
|
+
className: open ? `${skin_center_module_css_default.pluginCard} ${skin_center_module_css_default.pluginCardOpen}` : skin_center_module_css_default.pluginCard,
|
|
665
678
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
666
679
|
type: "button",
|
|
667
680
|
className: skin_center_module_css_default.cardHeader,
|
|
@@ -683,9 +696,17 @@ window.__ModuleLoader__.load({
|
|
|
683
696
|
title: t("cardDescription"),
|
|
684
697
|
children: t("cardDescription")
|
|
685
698
|
})]
|
|
686
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
687
|
-
|
|
688
|
-
|
|
699
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
700
|
+
width: "14",
|
|
701
|
+
height: "14",
|
|
702
|
+
viewBox: "0 0 14 14",
|
|
703
|
+
fill: "none",
|
|
704
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
705
|
+
className: open ? `${skin_center_module_css_default.chevron} ${skin_center_module_css_default.chevronOpen}` : skin_center_module_css_default.chevron,
|
|
706
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
707
|
+
d: "M11.8486 5.5L11.4238 5.92383L8.69727 8.65137C8.44157 8.90706 8.21562 9.13382 8.01172 9.29785C7.79912 9.46883 7.55595 9.61756 7.25 9.66602C7.08435 9.69222 6.91565 9.69222 6.75 9.66602C6.44405 9.61756 6.20088 9.46883 5.98828 9.29785C5.78438 9.13382 5.55843 8.90706 5.30273 8.65137L2.57617 5.92383L2.15137 5.5L3 4.65137L3.42383 5.07617L6.15137 7.80273C6.42595 8.07732 6.59876 8.24849 6.74023 8.3623C6.87291 8.46904 6.92272 8.47813 6.9375 8.48047C6.97895 8.48703 7.02105 8.48703 7.0625 8.48047C7.07728 8.47813 7.12709 8.46904 7.25977 8.3623C7.40124 8.24849 7.57405 8.07732 7.84863 7.80273L10.5762 5.07617L11 4.65137L11.8486 5.5Z",
|
|
708
|
+
fill: "currentColor"
|
|
709
|
+
})
|
|
689
710
|
})]
|
|
690
711
|
}), open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
691
712
|
className: skin_center_module_css_default.cardBody,
|