@iasiv5/dsh-skins 1.5.3 → 1.5.4
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.en.md +22 -8
- package/README.i18n.yaml +2 -2
- package/README.md +22 -8
- package/lib/client.js +145 -47
- package/lib/index.js +45 -8
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-skins — Skin pack for DSH
|
|
1
|
+
# dsh-skins — Skin pack for DSH
|
|
2
2
|
|
|
3
3
|
[](../../releases)
|
|
4
4
|
[](../../actions/workflows/ci.yml)
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
English · [中文](./README.md)
|
|
10
10
|
|
|
11
|
-
Hot-swappable brand skins for DeepSeek Harness Web
|
|
11
|
+
Hot-swappable brand skins for DeepSeek Harness — installable on both the Web and Desktop profiles, with a one-click path back to the official interface.
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
@@ -38,6 +38,8 @@ dsh plugin --profile web update @iasiv5/dsh-skins # update
|
|
|
38
38
|
dsh plugin --profile web remove @iasiv5/dsh-skins # uninstall
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
**DSH Desktop app**: install from the app's built-in plugin page, or run `dsh plugin --profile desktop add @iasiv5/dsh-skins`, then restart the app.
|
|
42
|
+
|
|
41
43
|
With the [dsh-m](https://github.com/iasiv5/dsh-m) plugin marketplace installed, you can also install / uninstall this plugin with one click inside the DSH Web UI.
|
|
42
44
|
|
|
43
45
|
<details>
|
|
@@ -55,7 +57,7 @@ If the install fails, send me the command output verbatim and retry at most once
|
|
|
55
57
|
|
|
56
58
|
</details>
|
|
57
59
|
|
|
58
|
-
##
|
|
60
|
+
## Three skins and the official look
|
|
59
61
|
|
|
60
62
|
This section answers: which skins exist and what each feels like.
|
|
61
63
|
|
|
@@ -105,9 +107,9 @@ Every skin's key visuals are open to adjustment. Click the gear button on a skin
|
|
|
105
107
|
|---|---|---|
|
|
106
108
|
| **Wallpaper** | Pick from the skin's built-in artwork, or upload your own images into a personal library | Each skin's default artwork |
|
|
107
109
|
| **Slogan** | The new-session guidance line, one Chinese and one English copy | The skin's factory slogan |
|
|
108
|
-
| **Translucency** | 0–100%, one value driving three visual layers: panel tint, wallpaper scrim and blur. 0% is pure, fully visible wallpaper; 100% hides it completely | meirenzhi 35
|
|
110
|
+
| **Translucency** | 0–100%, one value driving three visual layers: panel tint, wallpaper scrim and blur. 0% is pure, fully visible wallpaper; 100% hides it completely | meirenzhi: Web 35%, Desktop 55%; openbmc / uefi-harness: 55% in both |
|
|
109
111
|
|
|
110
|
-
> **
|
|
112
|
+
> **Desktop profile**: the shell is the solid skin base color and the wallpaper shows only inside the conversation area — see "The Desktop profile" below.
|
|
111
113
|
|
|
112
114
|

|
|
113
115
|
|
|
@@ -122,16 +124,25 @@ Every skin's key visuals are open to adjustment. Click the gear button on a skin
|
|
|
122
124
|
|
|
123
125
|
- Any change in the panel applies immediately and is written automatically after a brief (~0.5s) debounce.
|
|
124
126
|
- Two tabs sync within about a second; while offline, write controls disable themselves so edits are never lost silently.
|
|
125
|
-
- Reset-to-default
|
|
127
|
+
- Reset-to-default lists the settings it will reset; after confirmation it returns to factory values and persists automatically. On Desktop, destructive actions use an in-page confirmation to avoid the Electron input-focus issue after native JavaScript dialogs; Web keeps the browser's native confirm.
|
|
126
128
|
- While the panel is open, clicking another skin's card moves the panel straight to that skin's settings.
|
|
127
129
|
|
|
128
130
|
### Where settings live, and whether they survive
|
|
129
131
|
|
|
130
|
-
-
|
|
132
|
+
- Web stores configuration and the library under `$DSH_HOME/dsh-skins/`; since v1.5.4 Desktop stores them under `$DSH_HOME/dsh-skins-desktop/`. On first launch Desktop copies existing settings and images from the legacy directory once, **without removing or rewriting the old directory**; the two stores then evolve independently. Both are separate from the plugin installation directory, which upgrades replace.
|
|
131
133
|
- Only overrides are stored: untouched fields automatically follow the new version's defaults, and leftovers of retired skins or fields are cleaned up at load (the v1.4.0 tgcf removal rides this path).
|
|
132
134
|
- A damaged state file triggers recovery mode: indexes are rebuilt and bad files quarantined — your images are never wiped.
|
|
133
135
|
- Uploads pass magic-number validation and size caps; concurrent writes merge per field and never clobber each other.
|
|
134
136
|
|
|
137
|
+
## The Desktop profile
|
|
138
|
+
|
|
139
|
+
In the **DSH Desktop app**, the shell chrome — sidebar, caption strip, right panel and window buttons — is the **opaque skin base color** and does not follow the translucency knob; the wallpaper is exposed inside the conversation area (center column), where the knob drives surface tint, scrim and blur. The Web profile keeps the original fully translucent form on every surface; the two environments are independent.
|
|
140
|
+
|
|
141
|
+

|
|
142
|
+
|
|
143
|
+
- Verified with dsh Desktop `0.2.0-rc.2` (Windows); macOS / Linux Desktop currently follow the Web form and will be adapted in a later release.
|
|
144
|
+
- The right panel in fullscreen is the solid skin base color; the wallpaper is not shown through it.
|
|
145
|
+
|
|
135
146
|
## The skin switcher
|
|
136
147
|
|
|
137
148
|
This section answers: what lives in the switcher and how to use it.
|
|
@@ -164,6 +175,9 @@ Update duty has moved to the dsh-m marketplace and the DSH CLI (ADR-0008); the s
|
|
|
164
175
|
**Which DSH Web versions are supported?**
|
|
165
176
|
Verified with DSH Web `0.2.0-rc.2`. Later rc builds of the same series are expected to work, but unverified versions carry no promise.
|
|
166
177
|
|
|
178
|
+
**Is the Desktop profile supported?**
|
|
179
|
+
Yes. The DSH Desktop app and the Web profile are installed independently and do not affect each other; the desktop form (solid chrome + wallpaper in the conversation area) is described under "The Desktop profile". Verified with dsh Desktop `0.2.0-rc.2` (Windows).
|
|
180
|
+
|
|
167
181
|
**Does my appearance preference sync across browsers?**
|
|
168
182
|
In the browser on the DSH host machine (loopback), the preference is persisted by the DSH Host and shared naturally. Other remote browsers keep the preference in their own `localStorage` — independent per browser, never overwritten.
|
|
169
183
|
|
|
@@ -175,7 +189,7 @@ Yes. Configuration and the library live in the `$DSH_HOME/dsh-skins/` data direc
|
|
|
175
189
|
- Other skin plugins may also touch the body backdrop, brand slots or favicon; avoid enabling multiple visual skin plugins at the same time.
|
|
176
190
|
- The `openbmc` brand slots reuse the OpenBMC project's official logo letterforms and brand gradient, used solely for identification; all rights remain with the OpenBMC project.
|
|
177
191
|
- The `uefi-harness` mark is the UEFI Forum's official trademark (the red cube, from uefi.org's published uefi_logo_red.gif, embedded as vector paths traced via Wikimedia Commons "Logo of the UEFI Forum.svg"); it is used solely to identify the skin, and all rights remain with the UEFI Forum.
|
|
178
|
-
- Windows Desktop known limits:
|
|
192
|
+
- Windows Desktop known limits: the fullscreen right panel is the solid skin base color; the wallpaper is not shown through it.
|
|
179
193
|
|
|
180
194
|
## License
|
|
181
195
|
|
package/README.i18n.yaml
CHANGED
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
# last confirmed-consistent state. Both languages carry equal authority;
|
|
3
3
|
# after editing either side, bring the other along and re-record with:
|
|
4
4
|
# node scripts/verify-readme-pairing.mjs --write
|
|
5
|
-
README.md:
|
|
6
|
-
README.en.md:
|
|
5
|
+
README.md: 00a34bf2d930204da3aff8299779b931719ee5cd
|
|
6
|
+
README.en.md: d741fa3991323516fe07e4b81c4f6862c6800456
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# dsh-skins — DSH
|
|
1
|
+
# dsh-skins — DSH 多皮肤插件
|
|
2
2
|
|
|
3
3
|
[](../../releases)
|
|
4
4
|
[](../../actions/workflows/ci.yml)
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
[English](./README.en.md) · 中文
|
|
10
10
|
|
|
11
|
-
为 DeepSeek Harness Web
|
|
11
|
+
为 DeepSeek Harness 换上可热切换的品牌皮肤——Web 与 Desktop 两种 profile 均可安装使用,随时一键回到官方界面。
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
@@ -38,6 +38,8 @@ dsh plugin --profile web update @iasiv5/dsh-skins # 更新
|
|
|
38
38
|
dsh plugin --profile web remove @iasiv5/dsh-skins # 卸载
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
**DSH Desktop 应用**:在应用自带的插件页安装,或执行 `dsh plugin --profile desktop add @iasiv5/dsh-skins`,重启应用后生效。
|
|
42
|
+
|
|
41
43
|
安装了 [dsh-m](https://github.com/iasiv5/dsh-m) 插件市场的话,也可以直接在 DSH Web 界面里一键安装 / 卸载本插件。
|
|
42
44
|
|
|
43
45
|
<details>
|
|
@@ -55,7 +57,7 @@ dsh plugin --profile web remove @iasiv5/dsh-skins # 卸载
|
|
|
55
57
|
|
|
56
58
|
</details>
|
|
57
59
|
|
|
58
|
-
##
|
|
60
|
+
## 三款皮肤与官方界面
|
|
59
61
|
|
|
60
62
|
本节回答:有哪些皮肤、各是什么气质。
|
|
61
63
|
|
|
@@ -105,9 +107,9 @@ dsh plugin --profile web remove @iasiv5/dsh-skins # 卸载
|
|
|
105
107
|
|---|---|---|
|
|
106
108
|
| **壁纸** | 从皮肤自带的「内置精选」里挑,或上传自己的图片进「个人图库」 | 各皮肤的默认画作 |
|
|
107
109
|
| **标语** | 新会话页的引导语,中文、英文各一份 | 皮肤出厂标语 |
|
|
108
|
-
| **通透度** | 0–100% 单值联动三个视觉层:面板底色、壁纸遮罩、模糊。0% 纯壁纸完全透出,100% 完全遮蔽 |
|
|
110
|
+
| **通透度** | 0–100% 单值联动三个视觉层:面板底色、壁纸遮罩、模糊。0% 纯壁纸完全透出,100% 完全遮蔽 | 美人志:Web 35%、Desktop 55%;openbmc / uefi-harness:两端均 55% |
|
|
109
111
|
|
|
110
|
-
> **
|
|
112
|
+
> **Desktop profile**:外壳为实色皮肤本色、壁纸只在会话区显露——详见下方「Desktop profile 上的形态」。
|
|
111
113
|
|
|
112
114
|

|
|
113
115
|
|
|
@@ -122,16 +124,25 @@ dsh plugin --profile web remove @iasiv5/dsh-skins # 卸载
|
|
|
122
124
|
|
|
123
125
|
- 面板内任何修改立即生效,停顿约半秒自动写入配置。
|
|
124
126
|
- 双标签页约 1 秒内自动同步;离线时写入控件自动禁用,不会静默丢改动。
|
|
125
|
-
-
|
|
127
|
+
- 「恢复默认」会先列出将被重置的设置,确认后立即回出厂并自动保存。Desktop 的破坏性操作使用页面内确认弹窗,避免 Electron 原生确认框关闭后输入区失焦;Web 保留浏览器原生确认框。
|
|
126
128
|
- 面板展开时点击其他皮肤卡片,面板会随即切到该皮肤的设置。
|
|
127
129
|
|
|
128
130
|
### 配置存在哪、会不会丢
|
|
129
131
|
|
|
130
|
-
-
|
|
132
|
+
- Web profile 的配置与图库存于 `$DSH_HOME/dsh-skins/`;Desktop profile 自 v1.5.4 起存于 `$DSH_HOME/dsh-skins-desktop/`。Desktop 首次启动会从旧目录复制一次既有配置与图库,**不删除或改写旧目录**,此后两端独立。两处都与插件安装目录物理隔离:插件升级只替换插件安装目录。
|
|
131
133
|
- 配置只存「改过什么」:未改动的字段自动跟随新版本的默认值,已下线皮肤或字段的残留会在加载时自动清理(v1.4.0 移除 tgcf 皮肤即走这条路径)。
|
|
132
134
|
- 配置文件损坏时进入恢复模式:只重建索引、隔离坏文件,绝不清除你的图片。
|
|
133
135
|
- 上传走魔数校验与尺寸上限;并发写入按字段合并,互不覆盖。
|
|
134
136
|
|
|
137
|
+
## Desktop profile 上的形态
|
|
138
|
+
|
|
139
|
+
**DSH Desktop 应用**中,外壳——侧栏、顶栏条、右栏面板与窗口按钮——为**不透明的皮肤本色**,不随通透度变化;壁纸在会话区(内容列)显露,通透度联动会话区内的表面底色、遮罩与模糊。Web profile 维持全表面透纱的原有形态,两端互不影响。
|
|
140
|
+
|
|
141
|
+

|
|
142
|
+
|
|
143
|
+
- 已在 dsh Desktop `0.2.0-rc.2`(Windows)验证;macOS / Linux Desktop 当前跟随 Web 形态,后续版本适配。
|
|
144
|
+
- 右栏面板全屏时为实色皮肤底、不透出壁纸;面板「收起/关闭」后的空轨道行为属宿主布局。
|
|
145
|
+
|
|
135
146
|
## 皮肤切换器
|
|
136
147
|
|
|
137
148
|
本节回答:切换器里有什么、怎么用。
|
|
@@ -164,6 +175,9 @@ URL 也能切皮肤:`/?skin=official`、`/?skin=openbmc`、`/?skin=uefi-harnes
|
|
|
164
175
|
**支持哪些 DSH Web 版本?**
|
|
165
176
|
已使用 DSH Web `0.2.0-rc.2` 验证。同系列 rc 版本理论上兼容,未经验证的版本不作承诺。
|
|
166
177
|
|
|
178
|
+
**支持 Desktop profile 吗?**
|
|
179
|
+
支持。DSH Desktop 应用与 Web profile 各自独立安装、互不影响;桌面形态(实色外壳 + 会话区壁纸)见「Desktop profile 上的形态」。已使用 dsh Desktop `0.2.0-rc.2`(Windows)验证。
|
|
180
|
+
|
|
167
181
|
**外观偏好在不同的浏览器间同步吗?**
|
|
168
182
|
在 DSH 所在机器的浏览器(loopback)上,偏好由 DSH Host 持久化,天然共享。其他远程浏览器由插件将偏好存入本地 `localStorage`,各浏览器独立、互不覆盖。
|
|
169
183
|
|
|
@@ -175,7 +189,7 @@ URL 也能切皮肤:`/?skin=official`、`/?skin=openbmc`、`/?skin=uefi-harnes
|
|
|
175
189
|
- 其他皮肤插件也可能修改 body 背景、品牌位或 favicon;应避免同时启用多个视觉皮肤插件。
|
|
176
190
|
- `openbmc` 的品牌位沿用 OpenBMC 项目官方 logo 的字标字形与品牌渐变色,仅作标识用途,权利归 OpenBMC 项目所有。
|
|
177
191
|
- `uefi-harness` 的标志为 UEFI Forum 官方商标(红色立方体,源自 uefi.org 发布的 uefi_logo_red.gif,经 Wikimedia Commons「Logo of the UEFI Forum.svg」矢量描摹嵌入),仅作标识用途,权利归 UEFI Forum 所有。
|
|
178
|
-
- Windows Desktop
|
|
192
|
+
- Windows Desktop 已知边界:全屏的右栏面板为实色皮肤底,不透出壁纸。
|
|
179
193
|
|
|
180
194
|
## License
|
|
181
195
|
|
package/lib/client.js
CHANGED
|
@@ -125,17 +125,18 @@
|
|
|
125
125
|
]
|
|
126
126
|
}
|
|
127
127
|
};
|
|
128
|
-
function getSkinSchema(skinId) {
|
|
128
|
+
function getSkinSchema(skinId, profile = "web") {
|
|
129
129
|
const entry = SKINS[skinId];
|
|
130
130
|
if (entry === void 0) return null;
|
|
131
|
-
|
|
131
|
+
const fields = profile === "desktop" && skinId === "meirenzhi" ? entry.fields.map((field) => field.key === "panelOpacity" ? { ...field, default: 55 } : field) : entry.fields;
|
|
132
|
+
return { skinId, fields, builtinAssets: entry.builtinAssets };
|
|
132
133
|
}
|
|
133
134
|
function getField(skinId, key) {
|
|
134
135
|
return SKINS[skinId]?.fields.find((field) => field.key === key) ?? null;
|
|
135
136
|
}
|
|
136
|
-
function defaultsFor(skinId) {
|
|
137
|
+
function defaultsFor(skinId, profile = "web") {
|
|
137
138
|
const values = {};
|
|
138
|
-
for (const field of
|
|
139
|
+
for (const field of getSkinSchema(skinId, profile)?.fields ?? []) values[field.key] = field.default;
|
|
139
140
|
return values;
|
|
140
141
|
}
|
|
141
142
|
function isSameOverrideValue(a, b) {
|
|
@@ -245,8 +246,8 @@
|
|
|
245
246
|
})) return { ok: false, code: "BAD_SHAPE" };
|
|
246
247
|
return { ok: true };
|
|
247
248
|
}
|
|
248
|
-
function mergeValues(skinId, overrides, metaProvider) {
|
|
249
|
-
const schema = getSkinSchema(skinId);
|
|
249
|
+
function mergeValues(skinId, overrides, metaProvider, profile = "web") {
|
|
250
|
+
const schema = getSkinSchema(skinId, profile);
|
|
250
251
|
if (schema === null) return { values: {}, issues: [] };
|
|
251
252
|
const provider = typeof metaProvider === "function" ? metaProvider : void 0;
|
|
252
253
|
const values = {};
|
|
@@ -358,13 +359,13 @@
|
|
|
358
359
|
});
|
|
359
360
|
}
|
|
360
361
|
function projectSkin(skin, rawOverrides, context = {}) {
|
|
361
|
-
const schema = getSkinSchema(skin.id);
|
|
362
|
+
const schema = getSkinSchema(skin.id, context.profile);
|
|
362
363
|
if (schema === null) {
|
|
363
364
|
return { effects: null, issues: [], degraded: "failed" };
|
|
364
365
|
}
|
|
365
366
|
const project2 = skin.project;
|
|
366
367
|
const attempt = (overrides) => {
|
|
367
|
-
const { values, issues } = mergeValues(skin.id, overrides, context.metaProvider);
|
|
368
|
+
const { values, issues } = mergeValues(skin.id, overrides, context.metaProvider, context.profile);
|
|
368
369
|
const resolvedAssets = {};
|
|
369
370
|
for (const field of schema.fields) {
|
|
370
371
|
if (field.type !== "image") continue;
|
|
@@ -387,7 +388,7 @@
|
|
|
387
388
|
};
|
|
388
389
|
const first = safeAttempt(rawOverrides ?? {});
|
|
389
390
|
if (first.normalized !== null) return { effects: first.normalized, issues: first.issues, degraded: "none" };
|
|
390
|
-
const second = safeAttempt(defaultsFor(skin.id));
|
|
391
|
+
const second = safeAttempt(defaultsFor(skin.id, context.profile));
|
|
391
392
|
if (second.normalized !== null) return { effects: second.normalized, issues: first.issues, degraded: "defaults" };
|
|
392
393
|
return { effects: null, issues: first.issues, degraded: "failed" };
|
|
393
394
|
}
|
|
@@ -444,7 +445,7 @@
|
|
|
444
445
|
}
|
|
445
446
|
return lines.join("\n");
|
|
446
447
|
}
|
|
447
|
-
function createSkinRuntime() {
|
|
448
|
+
function createSkinRuntime({ profile = "web" } = {}) {
|
|
448
449
|
const skins = /* @__PURE__ */ new Map();
|
|
449
450
|
const order = [];
|
|
450
451
|
let ctx = null;
|
|
@@ -519,6 +520,7 @@
|
|
|
519
520
|
}
|
|
520
521
|
function projectionContext() {
|
|
521
522
|
return {
|
|
523
|
+
profile,
|
|
522
524
|
assetResolver: personalization?.assetResolver,
|
|
523
525
|
metaProvider: personalization?.metaProvider
|
|
524
526
|
};
|
|
@@ -866,6 +868,9 @@ ${darkSelector}{${dark.join(";")}}`;
|
|
|
866
868
|
"personalization.title": "个性化",
|
|
867
869
|
"personalization.collapse": "收起个性化面板",
|
|
868
870
|
"personalization.reset": "恢复默认",
|
|
871
|
+
"personalization.confirmTitle": "确认操作",
|
|
872
|
+
"personalization.confirm": "确认",
|
|
873
|
+
"personalization.cancel": "取消",
|
|
869
874
|
"personalization.resetJoin": "、",
|
|
870
875
|
"personalization.resetConfirm": "以下设置将恢复为出厂默认:\n{fields}\n此操作立即生效,确认继续吗?",
|
|
871
876
|
"personalization.light": "浅色",
|
|
@@ -934,6 +939,9 @@ ${darkSelector}{${dark.join(";")}}`;
|
|
|
934
939
|
"personalization.title": "Personalize",
|
|
935
940
|
"personalization.collapse": "Collapse personalization panel",
|
|
936
941
|
"personalization.reset": "Restore defaults",
|
|
942
|
+
"personalization.confirmTitle": "Confirm action",
|
|
943
|
+
"personalization.confirm": "Confirm",
|
|
944
|
+
"personalization.cancel": "Cancel",
|
|
937
945
|
"personalization.resetJoin": ", ",
|
|
938
946
|
"personalization.resetConfirm": "These settings will be reset to factory defaults:\n{fields}\nThis takes effect immediately. Continue?",
|
|
939
947
|
"personalization.light": "Light",
|
|
@@ -1098,6 +1106,13 @@ ${darkSelector}{${dark.join(";")}}`;
|
|
|
1098
1106
|
".dsh-skins-pz-danger{color:var(--dsw-alias-error-text,var(--dsw-static-red-500,#d33));border-color:var(--dsw-alias-error-text,var(--dsw-static-red-500,#d33))}",
|
|
1099
1107
|
".dsh-skins-pz-fields-locale{flex-direction:row;gap:6px}",
|
|
1100
1108
|
".dsh-skins-pz-fields-locale .dsh-skins-pz-input{flex:1;min-width:0}",
|
|
1109
|
+
// HTML <dialog>.showModal() stays inside the renderer's top layer: unlike
|
|
1110
|
+
// window.confirm(), it never opens the Windows/Electron JavaScript dialog
|
|
1111
|
+
// that can leave the Desktop window unfocused after dismissal.
|
|
1112
|
+
".dsh-skins-pz-dialog{box-sizing:border-box;width:min(480px,calc(100vw - 32px));max-height:calc(100vh - 32px);padding:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:var(--dsw-alias-bg-elevated,var(--dsw-alias-bg-overlay));color:var(--dsw-alias-label-primary);box-shadow:var(--dsw-shadow-lv3,0 8px 24px rgba(0,0,0,.25));font:inherit}",
|
|
1113
|
+
".dsh-skins-pz-dialog::backdrop{background:rgba(0,0,0,.45)}",
|
|
1114
|
+
".dsh-skins-pz-dialog-message{margin:0 0 20px;white-space:pre-line;line-height:1.6;font-size:14px}",
|
|
1115
|
+
".dsh-skins-pz-dialog-actions{display:flex;justify-content:flex-end;gap:8px}",
|
|
1101
1116
|
// Stacked (<904px) resets MUST come after the rules they override — the
|
|
1102
1117
|
// array order is the cascade order among equal-specificity selectors.
|
|
1103
1118
|
"@media (max-width:904px){.dsh-skins-pz-thumbs{grid-template-columns:repeat(4,1fr)}}"
|
|
@@ -1175,30 +1190,42 @@ ${darkSelector}{${dark.join(";")}}`;
|
|
|
1175
1190
|
}) })
|
|
1176
1191
|
] });
|
|
1177
1192
|
}
|
|
1178
|
-
function WallpaperSection({ skinId, field, value, onValue, state, disabled }) {
|
|
1193
|
+
function WallpaperSection({ skinId, field, value, onValue, state, disabled, requestConfirm }) {
|
|
1179
1194
|
const uploadRef = useRef(null);
|
|
1180
1195
|
const [message, setMessage] = useState(null);
|
|
1181
1196
|
const [deletingId, setDeletingId] = useState(null);
|
|
1197
|
+
const [clearing, setClearing] = useState(false);
|
|
1182
1198
|
const [visible, setVisible] = useState(PAGE_SIZE);
|
|
1183
1199
|
const schema = getSkinSchema(skinId);
|
|
1184
1200
|
const builtins = schema?.builtinAssets ?? {};
|
|
1185
1201
|
const liveAssets = builtinAssetsFor(skinId);
|
|
1186
1202
|
const choices = field.builtinChoices ?? [];
|
|
1187
1203
|
const library = state.library;
|
|
1188
|
-
const
|
|
1189
|
-
if (deletingId !== null) return;
|
|
1190
|
-
const references = state.references[asset.id] ?? [];
|
|
1191
|
-
const names = references.map((entry) => `${labelFor(entry.skinId)} · ${entry.key}`).join("\n");
|
|
1192
|
-
const confirmed = references.length === 0 || window.confirm(`${tr("personalization.library.deleteConfirm")}
|
|
1193
|
-
${names}`);
|
|
1194
|
-
if (!confirmed) return;
|
|
1204
|
+
const deleteAsset = async (asset) => {
|
|
1195
1205
|
setMessage(null);
|
|
1196
1206
|
setDeletingId(asset.id);
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1207
|
+
try {
|
|
1208
|
+
const result = await configClient.deleteImage(asset.id);
|
|
1209
|
+
setMessage(result.error ? tr("personalization.library.deleteFailed") : tr("personalization.library.deleted", { name: asset.displayName }));
|
|
1210
|
+
} catch {
|
|
1211
|
+
setMessage(tr("personalization.library.deleteFailed"));
|
|
1212
|
+
} finally {
|
|
1213
|
+
setDeletingId(null);
|
|
1214
|
+
}
|
|
1200
1215
|
};
|
|
1201
|
-
const
|
|
1216
|
+
const removeAsset = (asset) => {
|
|
1217
|
+
if (deletingId !== null || clearing) return;
|
|
1218
|
+
const references = state.references[asset.id] ?? [];
|
|
1219
|
+
if (references.length === 0) return deleteAsset(asset);
|
|
1220
|
+
const names = references.map((entry) => `${labelFor(entry.skinId)} · ${entry.key}`).join("\n");
|
|
1221
|
+
requestConfirm(
|
|
1222
|
+
`${tr("personalization.library.deleteConfirm")}
|
|
1223
|
+
${names}`,
|
|
1224
|
+
() => deleteAsset(asset)
|
|
1225
|
+
);
|
|
1226
|
+
};
|
|
1227
|
+
const clearLibrary = () => {
|
|
1228
|
+
if (clearing || deletingId !== null || library.length === 0) return;
|
|
1202
1229
|
const pairs = /* @__PURE__ */ new Map();
|
|
1203
1230
|
for (const asset of library) {
|
|
1204
1231
|
for (const entry of state.references[asset.id] ?? []) {
|
|
@@ -1206,19 +1233,27 @@ ${names}`);
|
|
|
1206
1233
|
}
|
|
1207
1234
|
}
|
|
1208
1235
|
const names = [...pairs.keys()].join("、");
|
|
1209
|
-
|
|
1236
|
+
requestConfirm(tr("personalization.library.clearConfirm", {
|
|
1210
1237
|
count: library.length,
|
|
1211
1238
|
names: names === "" ? "—" : names
|
|
1212
|
-
}))
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1239
|
+
}), async () => {
|
|
1240
|
+
setClearing(true);
|
|
1241
|
+
try {
|
|
1242
|
+
for (const asset of [...library]) {
|
|
1243
|
+
const result = await configClient.deleteImage(asset.id);
|
|
1244
|
+
if (result.error) {
|
|
1245
|
+
setMessage(tr("personalization.library.clearFailed"));
|
|
1246
|
+
void configClient.refetch();
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
} catch {
|
|
1217
1251
|
setMessage(tr("personalization.library.clearFailed"));
|
|
1218
|
-
configClient.refetch();
|
|
1219
|
-
|
|
1252
|
+
void configClient.refetch();
|
|
1253
|
+
} finally {
|
|
1254
|
+
setClearing(false);
|
|
1220
1255
|
}
|
|
1221
|
-
}
|
|
1256
|
+
});
|
|
1222
1257
|
};
|
|
1223
1258
|
return jsx("div", { className: "dsh-skins-pz-row", children: [
|
|
1224
1259
|
jsx("span", { className: "dsh-skins-pz-label", children: tr(field.labelKey) }),
|
|
@@ -1258,7 +1293,7 @@ ${names}`);
|
|
|
1258
1293
|
jsx("button", {
|
|
1259
1294
|
type: "button",
|
|
1260
1295
|
className: `dsh-skins-pz-del${deletingId === asset.id ? " dsh-skins-pz-del-busy" : ""}`,
|
|
1261
|
-
disabled: disabled || deletingId !== null,
|
|
1296
|
+
disabled: disabled || deletingId !== null || clearing,
|
|
1262
1297
|
"aria-label": `${tr("personalization.library.delete")}: ${asset.displayName}`,
|
|
1263
1298
|
onClick: () => removeAsset(asset),
|
|
1264
1299
|
children: deletingId === asset.id ? jsx("span", { className: "dsh-skins-spinner", "aria-hidden": "true" }) : "×"
|
|
@@ -1281,14 +1316,14 @@ ${names}`);
|
|
|
1281
1316
|
jsx("button", {
|
|
1282
1317
|
type: "button",
|
|
1283
1318
|
className: "dsh-skins-pz-btn",
|
|
1284
|
-
disabled: disabled || deletingId !== null,
|
|
1319
|
+
disabled: disabled || deletingId !== null || clearing,
|
|
1285
1320
|
onClick: () => uploadRef.current?.click(),
|
|
1286
1321
|
children: tr("personalization.library.upload")
|
|
1287
1322
|
}),
|
|
1288
1323
|
library.length > 0 ? jsx("button", {
|
|
1289
1324
|
type: "button",
|
|
1290
1325
|
className: "dsh-skins-pz-btn dsh-skins-pz-danger",
|
|
1291
|
-
disabled: disabled || deletingId !== null,
|
|
1326
|
+
disabled: disabled || deletingId !== null || clearing,
|
|
1292
1327
|
onClick: clearLibrary,
|
|
1293
1328
|
children: tr("personalization.library.clear")
|
|
1294
1329
|
}) : null
|
|
@@ -1363,8 +1398,39 @@ ${names}`);
|
|
|
1363
1398
|
}
|
|
1364
1399
|
return function PersonalizationPanel({ skinId, onCollapse }) {
|
|
1365
1400
|
const state = useConfigState();
|
|
1366
|
-
const
|
|
1401
|
+
const desktopProfile = typeof location !== "undefined" && location.protocol === "dsh-app:" && location.hostname === "app";
|
|
1402
|
+
const profile = desktopProfile ? "desktop" : "web";
|
|
1403
|
+
const schema = getSkinSchema(skinId, profile);
|
|
1367
1404
|
const headerRef = useRef(null);
|
|
1405
|
+
const dialogRef = useRef(null);
|
|
1406
|
+
const confirmationRef = useRef(null);
|
|
1407
|
+
const [confirmation, setConfirmation] = useState(null);
|
|
1408
|
+
const requestConfirm = (message, accept) => {
|
|
1409
|
+
if (!desktopProfile) {
|
|
1410
|
+
if (window.confirm(message)) void accept();
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
if (confirmationRef.current !== null) return;
|
|
1414
|
+
const next = { message, accept };
|
|
1415
|
+
confirmationRef.current = next;
|
|
1416
|
+
setConfirmation(next);
|
|
1417
|
+
};
|
|
1418
|
+
const cancelConfirm = () => {
|
|
1419
|
+
confirmationRef.current = null;
|
|
1420
|
+
setConfirmation(null);
|
|
1421
|
+
};
|
|
1422
|
+
const acceptConfirm = () => {
|
|
1423
|
+
if (confirmationRef.current !== confirmation) return;
|
|
1424
|
+
confirmationRef.current = null;
|
|
1425
|
+
setConfirmation(null);
|
|
1426
|
+
const current = configClient.getState();
|
|
1427
|
+
if (current.status === "synced" && current.mode !== "recovery") {
|
|
1428
|
+
void confirmation.accept();
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
useEffect(() => {
|
|
1432
|
+
if (confirmation !== null) dialogRef.current?.showModal?.();
|
|
1433
|
+
}, [confirmation]);
|
|
1368
1434
|
const wideLayout = typeof window !== "undefined" && window.innerWidth >= 905;
|
|
1369
1435
|
useEffect(() => {
|
|
1370
1436
|
headerRef.current?.focus?.(wideLayout ? { preventScroll: true } : void 0);
|
|
@@ -1372,7 +1438,7 @@ ${names}`);
|
|
|
1372
1438
|
if (schema === null) return null;
|
|
1373
1439
|
const writesBlocked = state.status !== "synced" || state.mode === "recovery";
|
|
1374
1440
|
const overrides = configClient.effectiveOverrides(skinId);
|
|
1375
|
-
const { values } = mergeValues(skinId, overrides);
|
|
1441
|
+
const { values } = mergeValues(skinId, overrides, void 0, profile);
|
|
1376
1442
|
const hasAnyOverride = Object.keys(overrides).length > 0;
|
|
1377
1443
|
const fieldRows = schema.fields.map((field) => {
|
|
1378
1444
|
const value = values[field.key];
|
|
@@ -1390,7 +1456,8 @@ ${names}`);
|
|
|
1390
1456
|
...common,
|
|
1391
1457
|
key: field.key,
|
|
1392
1458
|
skinId,
|
|
1393
|
-
state
|
|
1459
|
+
state,
|
|
1460
|
+
requestConfirm
|
|
1394
1461
|
});
|
|
1395
1462
|
default:
|
|
1396
1463
|
return null;
|
|
@@ -1462,12 +1529,12 @@ ${names}`);
|
|
|
1462
1529
|
disabled: writesBlocked,
|
|
1463
1530
|
onClick: () => {
|
|
1464
1531
|
const affected = schema.fields.filter((field) => overrides[field.key] !== void 0).map((field) => tr(field.labelKey));
|
|
1465
|
-
|
|
1532
|
+
requestConfirm(tr("personalization.resetConfirm", {
|
|
1466
1533
|
fields: affected.length > 0 ? affected.join(tr("personalization.resetJoin")) : "—"
|
|
1467
|
-
}))
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1534
|
+
}), () => {
|
|
1535
|
+
for (const field of schema.fields) configClient.previewReset(skinId, field.key);
|
|
1536
|
+
void configClient.flushNow();
|
|
1537
|
+
});
|
|
1471
1538
|
},
|
|
1472
1539
|
children: tr("personalization.reset")
|
|
1473
1540
|
}) : null,
|
|
@@ -1484,7 +1551,36 @@ ${names}`);
|
|
|
1484
1551
|
statusCluster.length > 0 ? jsx("div", {
|
|
1485
1552
|
className: "dsh-skins-pz-actions",
|
|
1486
1553
|
children: jsx("div", { className: "dsh-skins-pz-cluster dsh-skins-pz-cluster-status", children: statusCluster })
|
|
1487
|
-
}) : null
|
|
1554
|
+
}) : null,
|
|
1555
|
+
confirmation === null ? null : jsx("dialog", {
|
|
1556
|
+
ref: dialogRef,
|
|
1557
|
+
className: "dsh-skins-pz-dialog",
|
|
1558
|
+
"aria-labelledby": "dsh-skins-confirm-title",
|
|
1559
|
+
onCancel: (event) => {
|
|
1560
|
+
event.preventDefault();
|
|
1561
|
+
cancelConfirm();
|
|
1562
|
+
},
|
|
1563
|
+
children: [
|
|
1564
|
+
jsx("div", { id: "dsh-skins-confirm-title", className: "dsh-skins-pop-title", children: tr("personalization.confirmTitle") }),
|
|
1565
|
+
jsx("p", { className: "dsh-skins-pz-dialog-message", children: confirmation.message }),
|
|
1566
|
+
jsx("div", { className: "dsh-skins-pz-dialog-actions", children: [
|
|
1567
|
+
jsx("button", {
|
|
1568
|
+
type: "button",
|
|
1569
|
+
className: "dsh-skins-pz-btn",
|
|
1570
|
+
autoFocus: true,
|
|
1571
|
+
onClick: cancelConfirm,
|
|
1572
|
+
children: tr("personalization.cancel")
|
|
1573
|
+
}),
|
|
1574
|
+
jsx("button", {
|
|
1575
|
+
type: "button",
|
|
1576
|
+
className: "dsh-skins-pz-btn dsh-skins-pz-danger",
|
|
1577
|
+
disabled: writesBlocked,
|
|
1578
|
+
onClick: acceptConfirm,
|
|
1579
|
+
children: tr("personalization.confirm")
|
|
1580
|
+
})
|
|
1581
|
+
] })
|
|
1582
|
+
]
|
|
1583
|
+
})
|
|
1488
1584
|
] });
|
|
1489
1585
|
};
|
|
1490
1586
|
}
|
|
@@ -1781,7 +1877,7 @@ ${names}`);
|
|
|
1781
1877
|
closeShell();
|
|
1782
1878
|
};
|
|
1783
1879
|
const onKey = (event) => {
|
|
1784
|
-
if (event.key === "Escape") closeShell();
|
|
1880
|
+
if (event.key === "Escape" && !document.querySelector?.("dialog.dsh-skins-pz-dialog[open]")) closeShell();
|
|
1785
1881
|
};
|
|
1786
1882
|
document.addEventListener("pointerdown", onPointer, true);
|
|
1787
1883
|
window.addEventListener("keydown", onKey);
|
|
@@ -2006,6 +2102,7 @@ ${names}`);
|
|
|
2006
2102
|
function createConfigClient(options = {}) {
|
|
2007
2103
|
const fetchImpl = options.fetchImpl ?? (typeof fetch === "function" ? fetch : null);
|
|
2008
2104
|
const baseUrl = options.baseUrl ?? "/dsh-skins";
|
|
2105
|
+
const profile = options.profile === "desktop" ? "desktop" : "web";
|
|
2009
2106
|
const timeoutMs = options.timeoutMs ?? FETCH_TIMEOUT_MS;
|
|
2010
2107
|
const uploadTimeoutMs = options.uploadTimeoutMs ?? UPLOAD_TIMEOUT_MS;
|
|
2011
2108
|
if (typeof fetchImpl !== "function") throw new Error("config client requires a fetch implementation");
|
|
@@ -2238,7 +2335,7 @@ ${names}`);
|
|
|
2238
2335
|
const composite = `${skinId} ${key}`;
|
|
2239
2336
|
const storedOverride = snapshot.skins[skinId]?.[key];
|
|
2240
2337
|
const previewed = previews.get(composite);
|
|
2241
|
-
if (isSameOverrideValue(value, defaultsFor(skinId)[key])) {
|
|
2338
|
+
if (isSameOverrideValue(value, defaultsFor(skinId, profile)[key])) {
|
|
2242
2339
|
if (storedOverride === void 0 && previewed === void 0) return;
|
|
2243
2340
|
if (previewed === null) return;
|
|
2244
2341
|
return previewReset(skinId, key);
|
|
@@ -3460,7 +3557,8 @@ body[data-dsh-uefi-harness][data-ds-dark-theme] .lc-tip {
|
|
|
3460
3557
|
const jsxRuntime = require2("react/jsx-runtime");
|
|
3461
3558
|
const react = require2("react");
|
|
3462
3559
|
const reactDom = require2("react-dom");
|
|
3463
|
-
const
|
|
3560
|
+
const profile = window.location?.protocol === "dsh-app:" && window.location?.hostname === "app" ? "desktop" : "web";
|
|
3561
|
+
const runtime = createSkinRuntime({ profile });
|
|
3464
3562
|
const skinById = /* @__PURE__ */ new Map();
|
|
3465
3563
|
for (const factory of [createOpenBmcHarness, createUefiHarness, createMeirenzhiSkin]) {
|
|
3466
3564
|
const skin = factory(jsxRuntime);
|
|
@@ -3472,7 +3570,7 @@ body[data-dsh-uefi-harness][data-ds-dark-theme] .lc-tip {
|
|
|
3472
3570
|
skinById.set(skin.id, skin);
|
|
3473
3571
|
runtime.register(skin);
|
|
3474
3572
|
}
|
|
3475
|
-
const configClient = typeof fetch === "function" ? createConfigClient({ contextActive: () => runtime.active() }) : null;
|
|
3573
|
+
const configClient = typeof fetch === "function" ? createConfigClient({ profile, contextActive: () => runtime.active() }) : null;
|
|
3476
3574
|
if (configClient !== null) {
|
|
3477
3575
|
const assetResolver = (ref) => {
|
|
3478
3576
|
if (ref.kind === "builtin") {
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// src/index.js
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
|
-
import {
|
|
3
|
+
import { cpSync, existsSync as existsSync2, mkdtempSync, readdirSync as readdirSync2, renameSync as renameSync3, rmSync } from "node:fs";
|
|
4
|
+
import { dirname as dirname2, join as join2, resolve } from "node:path";
|
|
4
5
|
|
|
5
6
|
// src/host/personalization/store.js
|
|
6
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -277,10 +278,11 @@ var SKINS = {
|
|
|
277
278
|
]
|
|
278
279
|
}
|
|
279
280
|
};
|
|
280
|
-
function getSkinSchema(skinId) {
|
|
281
|
+
function getSkinSchema(skinId, profile = "web") {
|
|
281
282
|
const entry = SKINS[skinId];
|
|
282
283
|
if (entry === void 0) return null;
|
|
283
|
-
|
|
284
|
+
const fields = profile === "desktop" && skinId === "meirenzhi" ? entry.fields.map((field) => field.key === "panelOpacity" ? { ...field, default: 55 } : field) : entry.fields;
|
|
285
|
+
return { skinId, fields, builtinAssets: entry.builtinAssets };
|
|
284
286
|
}
|
|
285
287
|
function getField(skinId, key) {
|
|
286
288
|
return SKINS[skinId]?.fields.find((field) => field.key === key) ?? null;
|
|
@@ -288,9 +290,9 @@ function getField(skinId, key) {
|
|
|
288
290
|
function listAssetFields(skinId) {
|
|
289
291
|
return SKINS[skinId]?.fields.filter((field) => field.type === "image") ?? [];
|
|
290
292
|
}
|
|
291
|
-
function defaultsFor(skinId) {
|
|
293
|
+
function defaultsFor(skinId, profile = "web") {
|
|
292
294
|
const values = {};
|
|
293
|
-
for (const field of
|
|
295
|
+
for (const field of getSkinSchema(skinId, profile)?.fields ?? []) values[field.key] = field.default;
|
|
294
296
|
return values;
|
|
295
297
|
}
|
|
296
298
|
function isSameOverrideValue(a, b) {
|
|
@@ -483,6 +485,7 @@ function createPersonalizationStore(options = {}) {
|
|
|
483
485
|
}
|
|
484
486
|
const fs = options.fs ?? defaultFs();
|
|
485
487
|
const now = options.now ?? Date.now;
|
|
488
|
+
const profile = options.profile === "desktop" ? "desktop" : "web";
|
|
486
489
|
const stateFile = join(dataDir, STATE_FILE);
|
|
487
490
|
const assetsDir = join(dataDir, ASSETS_DIR);
|
|
488
491
|
const quarantineDir = join(dataDir, QUARANTINE_DIR);
|
|
@@ -611,7 +614,7 @@ function createPersonalizationStore(options = {}) {
|
|
|
611
614
|
continue;
|
|
612
615
|
}
|
|
613
616
|
const section = state.skins[skinId];
|
|
614
|
-
const defaults = defaultsFor(skinId);
|
|
617
|
+
const defaults = defaultsFor(skinId, profile);
|
|
615
618
|
for (const key of Object.keys(section)) {
|
|
616
619
|
if (!validateOverride(skinId, key, section[key], provider).ok) {
|
|
617
620
|
delete section[key];
|
|
@@ -1181,14 +1184,46 @@ function mountPersonalizationRoutes(host, options) {
|
|
|
1181
1184
|
|
|
1182
1185
|
// src/index.js
|
|
1183
1186
|
var name = "@iasiv5/dsh-skins";
|
|
1187
|
+
function expandHomePath(path) {
|
|
1188
|
+
if (path === "~") return homedir();
|
|
1189
|
+
if (path.startsWith("~/") || path.startsWith("~\\")) return join2(homedir(), path.slice(2));
|
|
1190
|
+
return path;
|
|
1191
|
+
}
|
|
1184
1192
|
function dshHome() {
|
|
1185
|
-
|
|
1193
|
+
const configured = process.env.DSH_HOME?.trim();
|
|
1194
|
+
return resolve(expandHomePath(configured || join2(homedir(), ".dsh")));
|
|
1195
|
+
}
|
|
1196
|
+
function hostProfileFor(root, cwd = process.cwd()) {
|
|
1197
|
+
const normalized = (path) => process.platform === "win32" ? resolve(path).toLowerCase() : resolve(path);
|
|
1198
|
+
return normalized(cwd) === normalized(join2(expandHomePath(root), "profiles", "desktop")) ? "desktop" : "web";
|
|
1199
|
+
}
|
|
1200
|
+
function dataDirForProfile(root, profile) {
|
|
1201
|
+
root = resolve(expandHomePath(root));
|
|
1202
|
+
const legacy = join2(root, "dsh-skins");
|
|
1203
|
+
if (profile !== "desktop") return legacy;
|
|
1204
|
+
const desktop = join2(root, "dsh-skins-desktop");
|
|
1205
|
+
if (existsSync2(desktop) || !existsSync2(legacy)) return desktop;
|
|
1206
|
+
const stage = mkdtempSync(join2(root, ".dsh-skins-desktop-migrate-"));
|
|
1207
|
+
try {
|
|
1208
|
+
for (const name2 of readdirSync2(legacy)) {
|
|
1209
|
+
cpSync(join2(legacy, name2), join2(stage, name2), { recursive: true, force: false, errorOnExist: true });
|
|
1210
|
+
}
|
|
1211
|
+
renameSync3(stage, desktop);
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
if (!existsSync2(desktop)) throw error;
|
|
1214
|
+
} finally {
|
|
1215
|
+
if (dirname2(resolve(stage)) !== resolve(root)) throw new Error("unsafe Desktop migration staging path");
|
|
1216
|
+
if (existsSync2(stage)) rmSync(stage, { recursive: true, force: true });
|
|
1217
|
+
}
|
|
1218
|
+
return desktop;
|
|
1186
1219
|
}
|
|
1187
1220
|
function apply(ctx) {
|
|
1188
1221
|
ctx.inject(["webServer", "webRuntime"], (hostContext) => {
|
|
1189
1222
|
const root = dshHome();
|
|
1223
|
+
const profile = hostProfileFor(root);
|
|
1190
1224
|
const personalization = createPersonalizationStore({
|
|
1191
|
-
dataDir:
|
|
1225
|
+
dataDir: dataDirForProfile(root, profile),
|
|
1226
|
+
profile
|
|
1192
1227
|
});
|
|
1193
1228
|
hostContext.effect(() => {
|
|
1194
1229
|
return mountPersonalizationRoutes(hostContext, {
|
|
@@ -1200,5 +1235,7 @@ function apply(ctx) {
|
|
|
1200
1235
|
}
|
|
1201
1236
|
export {
|
|
1202
1237
|
apply,
|
|
1238
|
+
dataDirForProfile,
|
|
1239
|
+
hostProfileFor,
|
|
1203
1240
|
name
|
|
1204
1241
|
};
|