@lenorin/dsh-tauri-launcher 1.0.4 → 1.0.6
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/launcher/README.md +57 -56
- package/launcher/bin/dsh-launcher.exe +0 -0
- package/lib/client.js +576 -252
- package/lib/index.js +488 -478
- package/package.json +9 -1
package/lib/client.js
CHANGED
|
@@ -1,252 +1,576 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-tauri-launcher — browser half.
|
|
3
|
-
* 在设置面板注册「桌面启动」分区:桌面端启动开关(含快速确认与乐观反馈)、
|
|
4
|
-
* 添加快捷方式按钮、关闭确认弹窗、诊断信息(仅出错时显示)。
|
|
5
|
-
* 与宿主通信走 /api/dsh-tauri-launcher/*(同源 fetch,仅回环)。
|
|
6
|
-
*/
|
|
7
|
-
window.__ModuleLoader__.load({
|
|
8
|
-
id: '@lenorin/dsh-tauri-launcher',
|
|
9
|
-
factory: (require) => {
|
|
10
|
-
const module = { exports: {} }
|
|
11
|
-
const exports = module.exports
|
|
12
|
-
const React = require('react')
|
|
13
|
-
|
|
14
|
-
const CSS = `
|
|
15
|
-
.tauri-dsl-root { display: flex; flex-direction: column; gap: 12px; padding: 4px 0 8px; max-width: 560px; }
|
|
16
|
-
.tauri-dsl-desc { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
17
|
-
.tauri-dsl-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; background: var(--dsw-alias-bg-layer-2); cursor: pointer; }
|
|
18
|
-
.tauri-dsl-left { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
|
19
|
-
.tauri-dsl-label { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
20
|
-
.tauri-dsl-state { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--dsw-alias-label-secondary); }
|
|
21
|
-
.tauri-dsl-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--dsw-alias-label-secondary); }
|
|
22
|
-
.tauri-dsl-dot-on { background: var(--dsw-alias-state-success-primary); box-shadow: 0 0 6px var(--dsw-alias-state-success-primary); }
|
|
23
|
-
.tauri-dsl-dot-unknown { background: var(--dsw-alias-state-warn-primary); }
|
|
24
|
-
.tauri-dsl-dot-pending { background: var(--dsw-alias-state-warn-primary); animation: tauri-dsl-pulse 1s ease-in-out infinite; }
|
|
25
|
-
@keyframes tauri-dsl-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
|
|
26
|
-
.tauri-dsl-switch { position: relative; width: 42px; height: 24px; flex-shrink: 0; display: inline-block; }
|
|
27
|
-
.tauri-dsl-switch input { opacity: 0; width: 0; height: 0; position: absolute; }
|
|
28
|
-
.tauri-dsl-track { position: absolute; inset: 0; border-radius: 999px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2); transition: background 0.15s ease, border-color 0.15s ease; }
|
|
29
|
-
.tauri-dsl-track::before { content: ''; position: absolute; width: 16px; height: 16px; left: 3px; top: 3px; border-radius: 50%; background: var(--dsw-alias-label-secondary); transition: transform 0.15s ease, background 0.15s ease; }
|
|
30
|
-
.tauri-dsl-switch input:checked + .tauri-dsl-track { background: var(--dsw-alias-brand-primary); border-color: transparent; }
|
|
31
|
-
.tauri-dsl-switch input:checked + .tauri-dsl-track::before { transform: translateX(18px); background: var(--dsw-alias-label-primary-inverted); }
|
|
32
|
-
.tauri-dsl-switch input:disabled + .tauri-dsl-track { opacity: 0.55; }
|
|
33
|
-
.tauri-dsl-row:has(input:disabled) { cursor: wait; }
|
|
34
|
-
.tauri-dsl-linkbtn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-size: 12px; cursor: pointer; }
|
|
35
|
-
.tauri-dsl-linkbtn:disabled { opacity: 0.55; cursor: default; }
|
|
36
|
-
.tauri-dsl-overlay { position: fixed; inset: 0; z-index: 9999; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.45); }
|
|
37
|
-
.tauri-dsl-dialog { width: min(340px, calc(100vw - 48px)); padding: 18px 18px 14px; border-radius: 14px; background: var(--dsw-alias-bg-overlay); border: 1px solid var(--dsw-alias-border-l2); box-shadow: 0 18px 50px rgba(0, 0, 0, 0.4); display: flex; flex-direction: column; gap: 10px; }
|
|
38
|
-
.tauri-dsl-dialog-title { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
39
|
-
.tauri-dsl-dialog-body { font-size: 13px; line-height: 1.7; color: var(--dsw-alias-label-secondary); }
|
|
40
|
-
.tauri-dsl-dialog-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 4px; }
|
|
41
|
-
.tauri-dsl-btn-ghost { padding: 7px 16px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; cursor: pointer; }
|
|
42
|
-
.tauri-dsl-btn-ghost:hover { background: var(--dsw-alias-bg-layer-2); }
|
|
43
|
-
.tauri-dsl-btn-primary { padding: 7px 16px; border-radius: 8px; border: none; background: var(--dsw-alias-brand-primary); color: var(--dsw-alias-label-primary-inverted); font-size: 13px; font-weight: 600; cursor: pointer; }
|
|
44
|
-
.tauri-dsl-btn-primary:hover { filter: brightness(1.08); }
|
|
45
|
-
.tauri-dsl-error { font-size: 12px; color: var(--dsw-alias-state-error-primary); white-space: pre-wrap; word-break: break-all; }
|
|
46
|
-
.tauri-dsl-details { font-size: 12px; color: var(--dsw-alias-label-secondary); }
|
|
47
|
-
.tauri-dsl-details summary { cursor: pointer; user-select: none; }
|
|
48
|
-
.tauri-dsl-diag { margin: 6px 0 0; padding: 8px 10px; max-height: 220px; overflow: auto; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; font-family: Consolas, 'Cascadia Mono', monospace; font-size: 11px; line-height: 1.5; color: var(--dsw-alias-label-secondary); white-space: pre-wrap; word-break: break-all; }
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
opacity:
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
async
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
React.createElement('
|
|
232
|
-
React.createElement('
|
|
233
|
-
React.createElement('
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
),
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* dsh-tauri-launcher — browser half.
|
|
3
|
+
* 在设置面板注册「桌面启动」分区:桌面端启动开关(含快速确认与乐观反馈)、
|
|
4
|
+
* 添加快捷方式按钮、关闭确认弹窗、诊断信息(仅出错时显示)。
|
|
5
|
+
* 与宿主通信走 /api/dsh-tauri-launcher/*(同源 fetch,仅回环)。
|
|
6
|
+
*/
|
|
7
|
+
window.__ModuleLoader__.load({
|
|
8
|
+
id: '@lenorin/dsh-tauri-launcher',
|
|
9
|
+
factory: (require) => {
|
|
10
|
+
const module = { exports: {} }
|
|
11
|
+
const exports = module.exports
|
|
12
|
+
const React = require('react')
|
|
13
|
+
|
|
14
|
+
const CSS = `
|
|
15
|
+
.tauri-dsl-root { display: flex; flex-direction: column; gap: 12px; padding: 4px 0 8px; max-width: 560px; }
|
|
16
|
+
.tauri-dsl-desc { font-size: 12px; line-height: 1.6; color: var(--dsw-alias-label-secondary); }
|
|
17
|
+
.tauri-dsl-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; border: 1px solid var(--dsw-alias-border-l1); border-radius: 12px; background: var(--dsw-alias-bg-layer-2); cursor: pointer; }
|
|
18
|
+
.tauri-dsl-left { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
|
|
19
|
+
.tauri-dsl-label { font-size: 13px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
20
|
+
.tauri-dsl-state { display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--dsw-alias-label-secondary); }
|
|
21
|
+
.tauri-dsl-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; background: var(--dsw-alias-label-secondary); }
|
|
22
|
+
.tauri-dsl-dot-on { background: var(--dsw-alias-state-success-primary); box-shadow: 0 0 6px var(--dsw-alias-state-success-primary); }
|
|
23
|
+
.tauri-dsl-dot-unknown { background: var(--dsw-alias-state-warn-primary); }
|
|
24
|
+
.tauri-dsl-dot-pending { background: var(--dsw-alias-state-warn-primary); animation: tauri-dsl-pulse 1s ease-in-out infinite; }
|
|
25
|
+
@keyframes tauri-dsl-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }
|
|
26
|
+
.tauri-dsl-switch { position: relative; width: 42px; height: 24px; flex-shrink: 0; display: inline-block; }
|
|
27
|
+
.tauri-dsl-switch input { opacity: 0; width: 0; height: 0; position: absolute; }
|
|
28
|
+
.tauri-dsl-track { position: absolute; inset: 0; border-radius: 999px; background: var(--dsw-alias-bg-layer-1); border: 1px solid var(--dsw-alias-border-l2); transition: background 0.15s ease, border-color 0.15s ease; }
|
|
29
|
+
.tauri-dsl-track::before { content: ''; position: absolute; width: 16px; height: 16px; left: 3px; top: 3px; border-radius: 50%; background: var(--dsw-alias-label-secondary); transition: transform 0.15s ease, background 0.15s ease; }
|
|
30
|
+
.tauri-dsl-switch input:checked + .tauri-dsl-track { background: var(--dsw-alias-brand-primary); border-color: transparent; }
|
|
31
|
+
.tauri-dsl-switch input:checked + .tauri-dsl-track::before { transform: translateX(18px); background: var(--dsw-alias-label-primary-inverted); }
|
|
32
|
+
.tauri-dsl-switch input:disabled + .tauri-dsl-track { opacity: 0.55; }
|
|
33
|
+
.tauri-dsl-row:has(input:disabled) { cursor: wait; }
|
|
34
|
+
.tauri-dsl-linkbtn { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: var(--dsw-alias-bg-layer-2); color: var(--dsw-alias-label-primary); font-size: 12px; cursor: pointer; }
|
|
35
|
+
.tauri-dsl-linkbtn:disabled { opacity: 0.55; cursor: default; }
|
|
36
|
+
.tauri-dsl-overlay { position: fixed; inset: 0; z-index: 9999; display: flex; align-items: center; justify-content: center; background: rgba(0, 0, 0, 0.45); }
|
|
37
|
+
.tauri-dsl-dialog { width: min(340px, calc(100vw - 48px)); padding: 18px 18px 14px; border-radius: 14px; background: var(--dsw-alias-bg-overlay); border: 1px solid var(--dsw-alias-border-l2); box-shadow: 0 18px 50px rgba(0, 0, 0, 0.4); display: flex; flex-direction: column; gap: 10px; }
|
|
38
|
+
.tauri-dsl-dialog-title { font-size: 14px; font-weight: 600; color: var(--dsw-alias-label-primary); }
|
|
39
|
+
.tauri-dsl-dialog-body { font-size: 13px; line-height: 1.7; color: var(--dsw-alias-label-secondary); }
|
|
40
|
+
.tauri-dsl-dialog-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 4px; }
|
|
41
|
+
.tauri-dsl-btn-ghost { padding: 7px 16px; border-radius: 8px; border: 1px solid var(--dsw-alias-border-l2); background: transparent; color: var(--dsw-alias-label-primary); font-size: 13px; cursor: pointer; }
|
|
42
|
+
.tauri-dsl-btn-ghost:hover { background: var(--dsw-alias-bg-layer-2); }
|
|
43
|
+
.tauri-dsl-btn-primary { padding: 7px 16px; border-radius: 8px; border: none; background: var(--dsw-alias-brand-primary); color: var(--dsw-alias-label-primary-inverted); font-size: 13px; font-weight: 600; cursor: pointer; }
|
|
44
|
+
.tauri-dsl-btn-primary:hover { filter: brightness(1.08); }
|
|
45
|
+
.tauri-dsl-error { font-size: 12px; color: var(--dsw-alias-state-error-primary); white-space: pre-wrap; word-break: break-all; }
|
|
46
|
+
.tauri-dsl-details { font-size: 12px; color: var(--dsw-alias-label-secondary); }
|
|
47
|
+
.tauri-dsl-details summary { cursor: pointer; user-select: none; }
|
|
48
|
+
.tauri-dsl-diag { margin: 6px 0 0; padding: 8px 10px; max-height: 220px; overflow: auto; background: var(--dsw-alias-bg-layer-2); border: 1px solid var(--dsw-alias-border-l1); border-radius: 8px; font-family: Consolas, 'Cascadia Mono', monospace; font-size: 11px; line-height: 1.5; color: var(--dsw-alias-label-secondary); white-space: pre-wrap; word-break: break-all; }
|
|
49
|
+
/* ⚠️ 外壳集成 hack(脆弱,已知会随外壳升级失效):
|
|
50
|
+
设置导航图标替换依赖 DSH 外壳 bundle 的 CSS Modules hash 类名(.VOzbGW_*)与
|
|
51
|
+
分区序号(nth-child(5)),二者任一变化该规则即失效;失效时仅回退为默认齿轮,
|
|
52
|
+
无功能影响。若外壳未来提供按分区 id 注入图标的能力,应迁移到该机制。 */
|
|
53
|
+
.VOzbGW_navList button:nth-child(5) svg { display: none; }
|
|
54
|
+
.VOzbGW_navList button:nth-child(5)::before {
|
|
55
|
+
content: '';
|
|
56
|
+
width: 16px;
|
|
57
|
+
height: 16px;
|
|
58
|
+
flex-shrink: 0;
|
|
59
|
+
background-color: currentColor;
|
|
60
|
+
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect width='20' height='14' x='2' y='3' rx='2'/%3E%3Cline x1='8' x2='16' y1='21' y2='21'/%3E%3Cline x1='12' x2='12' y1='17' y2='21'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
|
|
61
|
+
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect width='20' height='14' x='2' y='3' rx='2'/%3E%3Cline x1='8' x2='16' y1='21' y2='21'/%3E%3Cline x1='12' x2='12' y1='17' y2='21'/%3E%3C/svg%3E") center / 16px 16px no-repeat;
|
|
62
|
+
opacity: 0.85;
|
|
63
|
+
}
|
|
64
|
+
.VOzbGW_navList button:nth-child(5)[aria-current='true']::before {
|
|
65
|
+
background-color: var(--dsw-alias-brand-primary);
|
|
66
|
+
opacity: 1;
|
|
67
|
+
}
|
|
68
|
+
/* 布局框架定制:body 前缀保证特异性 (0,1,1) 压过产品的 (0,1,0),不依赖样式注入顺序。
|
|
69
|
+
全部使用官方 DOM 原生标记(CSS modules 的 hash_原名 class 子串匹配),不依赖
|
|
70
|
+
@linxin666/dsh-web-ui-all 兼容层挂的 data-dsh-frame / data-pane 语义标记。
|
|
71
|
+
注意不能用裸 [class*='frame']:官方另有 user-questions/attachment/subagent
|
|
72
|
+
四个 *_frame 组件会被误伤;AppFrame 是唯一内部含 sidebarCol 的 frame,用 :has() 收紧。 */
|
|
73
|
+
/* 框架底色与侧栏一致:sidebar 与页面空白融为一体,conversation 借边框勾勒边界 */
|
|
74
|
+
body [class*='frame']:has([class*='sidebarCol']) {
|
|
75
|
+
background: var(--dsw-specific-sidebar-fill);
|
|
76
|
+
}
|
|
77
|
+
/* sidebar 隐藏右边框,分隔线职责移交 conversation 左边框 */
|
|
78
|
+
body [class*='sidebarCol'] {
|
|
79
|
+
border-right: none;
|
|
80
|
+
}
|
|
81
|
+
/* conversation 列:顶部分隔线 + 左边框 + 左上角 10px 圆角(仅裁切形状,不改背景;
|
|
82
|
+
产品对 centerCol 自带 overflow:hidden,圆角裁切天然成立) */
|
|
83
|
+
body [class*='centerCol'] {
|
|
84
|
+
border-top: 1px solid var(--dsw-alias-border-l1);
|
|
85
|
+
border-left: 1px solid var(--dsw-alias-border-l1);
|
|
86
|
+
border-top-left-radius: 10px;
|
|
87
|
+
}
|
|
88
|
+
`
|
|
89
|
+
|
|
90
|
+
const API_BASE = '/api/dsh-tauri-launcher'
|
|
91
|
+
/** 自定义请求头:同源 fetch 可带,跨站 form POST 无法伪造(CSRF 防线)。 */
|
|
92
|
+
const CSRF_HEADERS = { 'x-requested-with': 'dsh-tauri-launcher' }
|
|
93
|
+
|
|
94
|
+
const api = {
|
|
95
|
+
async state() {
|
|
96
|
+
const response = await fetch(API_BASE + '/state', { headers: CSRF_HEADERS })
|
|
97
|
+
return response.json()
|
|
98
|
+
},
|
|
99
|
+
async setDesktop(enabled) {
|
|
100
|
+
const response = await fetch(API_BASE + '/set-desktop', {
|
|
101
|
+
method: 'POST',
|
|
102
|
+
headers: { ...CSRF_HEADERS, 'content-type': 'application/json' },
|
|
103
|
+
body: JSON.stringify({ enabled }),
|
|
104
|
+
})
|
|
105
|
+
return response.json()
|
|
106
|
+
},
|
|
107
|
+
async setShortcut() {
|
|
108
|
+
const response = await fetch(API_BASE + '/set-shortcut', { method: 'POST', headers: CSRF_HEADERS })
|
|
109
|
+
return response.json()
|
|
110
|
+
},
|
|
111
|
+
async diagnose() {
|
|
112
|
+
const response = await fetch(API_BASE + '/diagnose', { headers: CSRF_HEADERS })
|
|
113
|
+
return response.json()
|
|
114
|
+
},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
exports.inject = ['slots']
|
|
118
|
+
|
|
119
|
+
exports.apply = function (ctx) {
|
|
120
|
+
const slots = ctx.get('slots')
|
|
121
|
+
if (slots === undefined) return
|
|
122
|
+
|
|
123
|
+
const styleEl = document.createElement('style')
|
|
124
|
+
styleEl.dataset.plugin = '@lenorin/dsh-tauri-launcher'
|
|
125
|
+
styleEl.textContent = CSS
|
|
126
|
+
document.head.appendChild(styleEl)
|
|
127
|
+
ctx.effect(() => () => styleEl.remove(), 'dsh-tauri-launcher: css')
|
|
128
|
+
|
|
129
|
+
function errText(error) {
|
|
130
|
+
if (typeof error === 'string') return error
|
|
131
|
+
if (error && error.message) return error.message
|
|
132
|
+
try { return JSON.stringify(error) } catch { return String(error) }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function fetchState() {
|
|
136
|
+
try {
|
|
137
|
+
const state = await api.state()
|
|
138
|
+
return { desktop: state.desktop, shortcut: Boolean(state.shortcut), error: '', diag: '' }
|
|
139
|
+
} catch (error) {
|
|
140
|
+
let diag = ''
|
|
141
|
+
try {
|
|
142
|
+
const diagnosed = await api.diagnose()
|
|
143
|
+
diag = (diagnosed && diagnosed.diag) || ''
|
|
144
|
+
} catch { /* 诊断失败不影响错误展示 */ }
|
|
145
|
+
return { desktop: null, shortcut: false, error: '读取状态失败:' + errText(error), diag }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function DesktopSection() {
|
|
150
|
+
const [snap, setSnap] = React.useState(null)
|
|
151
|
+
const [busy, setBusy] = React.useState(false)
|
|
152
|
+
const [pending, setPending] = React.useState(null)
|
|
153
|
+
const [linkBusy, setLinkBusy] = React.useState(false)
|
|
154
|
+
const [confirmOpen, setConfirmOpen] = React.useState(false)
|
|
155
|
+
|
|
156
|
+
React.useEffect(() => {
|
|
157
|
+
let alive = true
|
|
158
|
+
const merge = (s) => setSnap((prev) => (prev && prev.error ? { ...s, error: prev.error } : s))
|
|
159
|
+
fetchState().then((s) => { if (alive) merge(s) })
|
|
160
|
+
const timer = setInterval(() => { fetchState().then((s) => { if (alive) merge(s) }) }, 10000)
|
|
161
|
+
return () => { alive = false; clearInterval(timer) }
|
|
162
|
+
}, [])
|
|
163
|
+
|
|
164
|
+
const performToggle = async (target) => {
|
|
165
|
+
setBusy(true)
|
|
166
|
+
setPending(target ? 'on' : 'off')
|
|
167
|
+
try {
|
|
168
|
+
const result = await api.setDesktop(target)
|
|
169
|
+
if (result && result.ok) {
|
|
170
|
+
setSnap((prev) => ({
|
|
171
|
+
desktop: result.desktop,
|
|
172
|
+
shortcut: typeof result.shortcut === 'boolean' ? result.shortcut : (prev ? prev.shortcut : false),
|
|
173
|
+
error: '',
|
|
174
|
+
diag: result.diag || '',
|
|
175
|
+
}))
|
|
176
|
+
} else {
|
|
177
|
+
setSnap((prev) => ({
|
|
178
|
+
desktop: typeof result.desktop === 'boolean' ? result.desktop : (prev ? prev.desktop : null),
|
|
179
|
+
shortcut: prev ? prev.shortcut : false,
|
|
180
|
+
error: (result && result.error) || '操作失败',
|
|
181
|
+
diag: (result && result.diag) || (prev ? prev.diag : ''),
|
|
182
|
+
}))
|
|
183
|
+
}
|
|
184
|
+
} catch (error) {
|
|
185
|
+
setSnap((prev) => ({ desktop: prev ? prev.desktop : null, shortcut: prev ? prev.shortcut : false, error: '操作失败:' + errText(error), diag: prev ? prev.diag : '' }))
|
|
186
|
+
} finally {
|
|
187
|
+
setBusy(false)
|
|
188
|
+
setPending(null)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const toggle = async () => {
|
|
193
|
+
if (busy) return
|
|
194
|
+
const target = !(snap && snap.desktop === true)
|
|
195
|
+
if (target === false) {
|
|
196
|
+
setConfirmOpen(true)
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
await performToggle(true)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const addLink = async () => {
|
|
203
|
+
if (linkBusy) return
|
|
204
|
+
setLinkBusy(true)
|
|
205
|
+
try {
|
|
206
|
+
const result = await api.setShortcut()
|
|
207
|
+
if (result && result.ok) {
|
|
208
|
+
setSnap((prev) => ({ ...(prev || { desktop: null, shortcut: false, diag: '' }), shortcut: true, error: '', diag: result.diag || (prev ? prev.diag : '') }))
|
|
209
|
+
} else {
|
|
210
|
+
setSnap((prev) => ({ ...(prev || { desktop: null, shortcut: false, diag: '' }), error: (result && result.error) || '添加快捷方式失败', diag: (result && result.diag) || (prev ? prev.diag : '') }))
|
|
211
|
+
}
|
|
212
|
+
} catch (error) {
|
|
213
|
+
setSnap((prev) => ({ ...(prev || { desktop: null, shortcut: false, diag: '' }), error: '添加快捷方式失败:' + errText(error) }))
|
|
214
|
+
} finally {
|
|
215
|
+
setLinkBusy(false)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const desktop = snap ? snap.desktop : null
|
|
220
|
+
const shortcut = snap ? snap.shortcut : false
|
|
221
|
+
const checked = pending ? pending === 'on' : desktop === true
|
|
222
|
+
const stateText = pending
|
|
223
|
+
? (pending === 'on' ? '正在启动…' : '正在退出…')
|
|
224
|
+
: desktop === true ? '运行中' : desktop === false ? '已停止' : '状态未知'
|
|
225
|
+
const dotClass = 'tauri-dsl-dot' + (pending ? ' tauri-dsl-dot-pending' : desktop === true ? ' tauri-dsl-dot-on' : desktop === null ? ' tauri-dsl-dot-unknown' : '')
|
|
226
|
+
|
|
227
|
+
return React.createElement('div', { className: 'tauri-dsl-root' },
|
|
228
|
+
React.createElement('div', { className: 'tauri-dsl-desc' }, '启动或退出本机桌面应用(DeepSeek Harness Tauri)。开启时自动创建桌面快捷方式,关闭时自动删除;开机启动与全局快捷键请在桌面应用托盘 → 设置中管理。'),
|
|
229
|
+
React.createElement('label', { className: 'tauri-dsl-row' },
|
|
230
|
+
React.createElement('div', { className: 'tauri-dsl-left' },
|
|
231
|
+
React.createElement('span', { className: 'tauri-dsl-label' }, '桌面端启动'),
|
|
232
|
+
React.createElement('span', { className: 'tauri-dsl-state' },
|
|
233
|
+
React.createElement('span', { className: dotClass }),
|
|
234
|
+
stateText,
|
|
235
|
+
),
|
|
236
|
+
),
|
|
237
|
+
React.createElement('span', { className: 'tauri-dsl-switch' },
|
|
238
|
+
React.createElement('input', { type: 'checkbox', checked, onChange: toggle, disabled: busy }),
|
|
239
|
+
React.createElement('span', { className: 'tauri-dsl-track' }),
|
|
240
|
+
),
|
|
241
|
+
),
|
|
242
|
+
desktop === true && !pending ? React.createElement('button', {
|
|
243
|
+
className: 'tauri-dsl-linkbtn',
|
|
244
|
+
onClick: addLink,
|
|
245
|
+
disabled: linkBusy || shortcut,
|
|
246
|
+
}, shortcut ? '已添加快捷方式' : '添加快捷方式') : null,
|
|
247
|
+
snap && snap.error ? React.createElement('div', { className: 'tauri-dsl-error' }, snap.error) : null,
|
|
248
|
+
snap && snap.error && snap.diag ? React.createElement('details', { className: 'tauri-dsl-details' },
|
|
249
|
+
React.createElement('summary', null, '诊断信息'),
|
|
250
|
+
React.createElement('pre', { className: 'tauri-dsl-diag' }, snap.diag),
|
|
251
|
+
) : null,
|
|
252
|
+
confirmOpen ? React.createElement('div', { className: 'tauri-dsl-overlay', onClick: () => setConfirmOpen(false) },
|
|
253
|
+
React.createElement('div', { className: 'tauri-dsl-dialog', onClick: (event) => event.stopPropagation() },
|
|
254
|
+
React.createElement('div', { className: 'tauri-dsl-dialog-title' }, '确定关闭桌面端启动?'),
|
|
255
|
+
React.createElement('div', { className: 'tauri-dsl-dialog-body' }, '桌面应用将退出,同时删除桌面快捷方式。之后可随时在设置中重新开启,或通过命令行启动。'),
|
|
256
|
+
React.createElement('div', { className: 'tauri-dsl-dialog-actions' },
|
|
257
|
+
React.createElement('button', { className: 'tauri-dsl-btn-ghost', onClick: () => setConfirmOpen(false) }, '✗ 取消'),
|
|
258
|
+
React.createElement('button', {
|
|
259
|
+
className: 'tauri-dsl-btn-primary',
|
|
260
|
+
onClick: () => { setConfirmOpen(false); void performToggle(false) },
|
|
261
|
+
}, '✓ 确认关闭'),
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
) : null,
|
|
265
|
+
)
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
slots.inject('settings.section', () => slots.register(
|
|
269
|
+
{ name: 'settings.section', id: 'desktop-launch', order: 30, label: '桌面启动' },
|
|
270
|
+
() => React.createElement(DesktopSection, null),
|
|
271
|
+
))
|
|
272
|
+
|
|
273
|
+
// ---------- 标题栏主题中继 ----------
|
|
274
|
+
// 桌面端把 DSH 嵌在 iframe 里;这里读取官方主题服务解析出的浅/深方案,
|
|
275
|
+
// 并把 DSH 页面真实生效的设计 token(计算样式)发给启动器外壳,
|
|
276
|
+
// 让标题栏与 DSH 配色逐像素一致。仅当本页面被嵌入(iframe)时才有意义,
|
|
277
|
+
// 普通浏览器标签(window.parent === window)直接跳过。
|
|
278
|
+
// 注意:内置主题的快照 tokens 是空表,真实颜色在 CSS 变量里
|
|
279
|
+
// (body[data-ds-dark-theme] 切换),必须经 getComputedStyle 取。
|
|
280
|
+
const readVar = (name) => {
|
|
281
|
+
try {
|
|
282
|
+
const style = getComputedStyle(document.body)
|
|
283
|
+
let value = style.getPropertyValue(name).trim()
|
|
284
|
+
const ref = value.match(/^var\((--[a-zA-Z0-9-]+)\)$/)
|
|
285
|
+
if (ref) {
|
|
286
|
+
const inner = style.getPropertyValue(ref[1]).trim()
|
|
287
|
+
if (inner) value = inner
|
|
288
|
+
}
|
|
289
|
+
return value || null
|
|
290
|
+
} catch {
|
|
291
|
+
return null
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let themeSent = false
|
|
296
|
+
const postTheme = () => {
|
|
297
|
+
if (window.parent === window) return
|
|
298
|
+
let scheme = null
|
|
299
|
+
try {
|
|
300
|
+
const theme = ctx.get('theme')
|
|
301
|
+
if (theme && typeof theme.getTheme === 'function') {
|
|
302
|
+
const snap = theme.getTheme()
|
|
303
|
+
const s = snap && snap.active && snap.active.colorScheme
|
|
304
|
+
if (s === 'light' || s === 'dark') scheme = s
|
|
305
|
+
}
|
|
306
|
+
} catch { /* 主题服务异常:退化到 DOM 推断 */ }
|
|
307
|
+
if (!scheme) {
|
|
308
|
+
scheme = document.body.hasAttribute('data-ds-dark-theme') ? 'dark' : 'light'
|
|
309
|
+
}
|
|
310
|
+
const payload = {
|
|
311
|
+
__dshLauncherTheme: 1,
|
|
312
|
+
scheme,
|
|
313
|
+
bg: readVar('--dsw-specific-sidebar-fill'),
|
|
314
|
+
fg: readVar('--dsw-alias-label-primary'),
|
|
315
|
+
menuBg: readVar('--dsw-alias-bg-overlay'),
|
|
316
|
+
menuBorder: readVar('--dsw-alias-border-l2'),
|
|
317
|
+
sep: readVar('--dsw-alias-border-l1'),
|
|
318
|
+
danger: readVar('--dsw-alias-state-error-primary'),
|
|
319
|
+
}
|
|
320
|
+
themeSent = true
|
|
321
|
+
window.parent.postMessage(payload, '*')
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
ctx.on('theme/change', postTheme)
|
|
326
|
+
} catch { /* 事件不可用时依赖下面的重试梯子 */ }
|
|
327
|
+
|
|
328
|
+
// 主题服务可能在本插件之后才就绪,且 preference 等于默认值时不会发 theme/change:
|
|
329
|
+
// 用一个短重试梯子保证初始状态一定送达(成功或 6 秒后停止)。
|
|
330
|
+
let themeTries = 0
|
|
331
|
+
const themeTimer = setInterval(() => {
|
|
332
|
+
themeTries += 1
|
|
333
|
+
if (themeSent || themeTries >= 12) {
|
|
334
|
+
clearInterval(themeTimer)
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
postTheme()
|
|
338
|
+
}, 500)
|
|
339
|
+
ctx.effect(() => () => clearInterval(themeTimer), 'dsh-tauri-launcher: theme relay timer')
|
|
340
|
+
|
|
341
|
+
// ---------- 会话导航(应用层会话栈,等价浏览器前进/后退) ----------
|
|
342
|
+
// DSH 是 React SPA:切会话不产生浏览器历史条目,history.back() 无效。
|
|
343
|
+
// 这里维护"最近访问会话"栈:非本插件发起的 current 变化 → 上一个会话压
|
|
344
|
+
// backStack、清空 forwardStack;◀ 弹 backStack 跳转(当前压 forwardStack),
|
|
345
|
+
// ▶ 反向。跳转前设 pendingJump 锁,防止自身触发的变化被重复压栈,超时兜底解锁。
|
|
346
|
+
// sessions 服务存在就绪时序:一次性 ctx.get 可能拿到 undefined(静默失效),
|
|
347
|
+
// 故用重试梯子初始化;外壳每 3 秒 ping 一次,状态周期性回报实现自愈。
|
|
348
|
+
if (window.parent !== window) {
|
|
349
|
+
let navDispose = null
|
|
350
|
+
let navReady = false
|
|
351
|
+
|
|
352
|
+
// 父窗口 origin:跨源读不到 window.parent.location,用 document.referrer 推导
|
|
353
|
+
let parentOrigin = 'http://tauri.localhost'
|
|
354
|
+
try {
|
|
355
|
+
if (document.referrer) parentOrigin = new URL(document.referrer).origin
|
|
356
|
+
} catch { /* 兜底 http://tauri.localhost */ }
|
|
357
|
+
|
|
358
|
+
// ---------- 桌面端系统主题跟随 ----------
|
|
359
|
+
// WebView2 页面里的 prefers-color-scheme 只有宿主显式设置
|
|
360
|
+
// PreferredColorScheme 才会随 Windows 变化,而 tauri/wry 未暴露该能力
|
|
361
|
+
// (wry#806),DSH 的 `system` 偏好会被冻结在启动时的取值。启动器轮询
|
|
362
|
+
// 到系统主题变化后经外壳 postMessage 转发到这里:偏好为 `system` 时把
|
|
363
|
+
// ThemeRuntime 持有的 MediaQueryList.matches(冻结值)覆盖为真实系统值
|
|
364
|
+
// 并触发重发布——偏好保持「跟随系统」不变,界面随真实系统主题切换;
|
|
365
|
+
// 用户手动选了固定主题时不干预。浏览器直开不进入本块(Chromium 原生跟随)。
|
|
366
|
+
// ⚠️ 兼容层 hack:依赖 ThemeRuntime 的 media/publish 运行时成员(无真
|
|
367
|
+
// 私有化),外壳升级若重命名则静默退化为现状(跟随失效,无副作用)。
|
|
368
|
+
const applySystemTheme = (scheme) => {
|
|
369
|
+
try {
|
|
370
|
+
const theme = ctx.get('theme')
|
|
371
|
+
if (!theme || !(theme.media instanceof MediaQueryList)) return
|
|
372
|
+
if (typeof theme.publish !== 'function') return
|
|
373
|
+
const snap = typeof theme.getTheme === 'function' ? theme.getTheme() : null
|
|
374
|
+
if (!snap || snap.preference !== 'system') return
|
|
375
|
+
if (snap.active && snap.active.colorScheme === scheme) return
|
|
376
|
+
const desc = Object.getOwnPropertyDescriptor(MediaQueryList.prototype, 'matches')
|
|
377
|
+
if (!desc || typeof desc.get !== 'function') return
|
|
378
|
+
const wantDark = scheme === 'dark'
|
|
379
|
+
Object.defineProperty(theme.media, 'matches', {
|
|
380
|
+
configurable: true,
|
|
381
|
+
get() { return wantDark }
|
|
382
|
+
})
|
|
383
|
+
theme.publish()
|
|
384
|
+
} catch { /* 主题服务异常:忽略,等待下次通知 */ }
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const onSystemThemeMessage = (event) => {
|
|
388
|
+
const data = event.data || {}
|
|
389
|
+
if (event.source !== window.parent) return
|
|
390
|
+
if (event.origin !== parentOrigin) return
|
|
391
|
+
const scheme = data.__tbSystemTheme
|
|
392
|
+
if (scheme === 'light' || scheme === 'dark') applySystemTheme(scheme)
|
|
393
|
+
}
|
|
394
|
+
window.addEventListener('message', onSystemThemeMessage)
|
|
395
|
+
ctx.effect(() => () => window.removeEventListener('message', onSystemThemeMessage), 'dsh-tauri-launcher: system theme follow')
|
|
396
|
+
|
|
397
|
+
const initNav = () => {
|
|
398
|
+
const sessions = ctx.get('sessions')
|
|
399
|
+
const store = sessions && sessions.list
|
|
400
|
+
if (!store || typeof store.subscribe !== 'function') {
|
|
401
|
+
return false
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const CAP = 50
|
|
405
|
+
let backStack = []
|
|
406
|
+
let forwardStack = []
|
|
407
|
+
let currentId = undefined
|
|
408
|
+
let currentAddress = undefined
|
|
409
|
+
let pendingJump = null
|
|
410
|
+
let jumpTimer = null
|
|
411
|
+
|
|
412
|
+
const snapCurrent = () => {
|
|
413
|
+
const snap = store.getSnapshot()
|
|
414
|
+
return { id: snap.current, address: snap.currentAddress }
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const sendStatus = () => {
|
|
418
|
+
if (window.parent === window) return
|
|
419
|
+
const payload = { __tbNavStatus: { back: backStack.length > 0, forward: forwardStack.length > 0 } }
|
|
420
|
+
try {
|
|
421
|
+
// 用 '*' 投递:WebView2 下对 http://tauri.localhost 虚拟主机的
|
|
422
|
+
// 精确 targetOrigin 匹配有丢弃嫌疑;外壳侧会校验 event.origin。
|
|
423
|
+
window.parent.postMessage(payload, '*')
|
|
424
|
+
} catch { /* 忽略 */ }
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const pushStack = (stack, entry) => {
|
|
428
|
+
const top = stack[stack.length - 1]
|
|
429
|
+
if (top && top.id === entry.id && top.address === entry.address) return
|
|
430
|
+
stack.push(entry)
|
|
431
|
+
if (stack.length > CAP) stack.shift()
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const jumpTo = (entry) => {
|
|
435
|
+
pendingJump = { id: entry.id, address: entry.address }
|
|
436
|
+
currentId = entry.id
|
|
437
|
+
currentAddress = entry.address
|
|
438
|
+
clearTimeout(jumpTimer)
|
|
439
|
+
jumpTimer = setTimeout(() => {
|
|
440
|
+
pendingJump = null
|
|
441
|
+
const snap = snapCurrent()
|
|
442
|
+
currentId = snap.id
|
|
443
|
+
currentAddress = snap.address
|
|
444
|
+
sendStatus()
|
|
445
|
+
}, 1500)
|
|
446
|
+
try {
|
|
447
|
+
if (entry.address) sessions.openSubagent(entry.address)
|
|
448
|
+
else sessions.open(entry.id)
|
|
449
|
+
} catch {
|
|
450
|
+
pendingJump = null
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const goBack = () => {
|
|
455
|
+
if (backStack.length === 0) return
|
|
456
|
+
const target = backStack.pop()
|
|
457
|
+
if (currentId !== undefined) pushStack(forwardStack, { id: currentId, address: currentAddress })
|
|
458
|
+
sendStatus()
|
|
459
|
+
jumpTo(target)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const goForward = () => {
|
|
463
|
+
if (forwardStack.length === 0) return
|
|
464
|
+
const target = forwardStack.pop()
|
|
465
|
+
if (currentId !== undefined) pushStack(backStack, { id: currentId, address: currentAddress })
|
|
466
|
+
sendStatus()
|
|
467
|
+
jumpTo(target)
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const onListChange = () => {
|
|
471
|
+
const snap = snapCurrent()
|
|
472
|
+
if (pendingJump) {
|
|
473
|
+
if (snap.id === pendingJump.id || snap.id !== undefined && snap.id !== currentId) {
|
|
474
|
+
pendingJump = null
|
|
475
|
+
clearTimeout(jumpTimer)
|
|
476
|
+
currentId = snap.id
|
|
477
|
+
currentAddress = snap.address
|
|
478
|
+
sendStatus()
|
|
479
|
+
}
|
|
480
|
+
return
|
|
481
|
+
}
|
|
482
|
+
if (snap.id === undefined) {
|
|
483
|
+
currentAddress = undefined
|
|
484
|
+
sendStatus()
|
|
485
|
+
return
|
|
486
|
+
}
|
|
487
|
+
if (snap.id === currentId) {
|
|
488
|
+
if (snap.address !== undefined) currentAddress = snap.address
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
if (currentId !== undefined) {
|
|
492
|
+
pushStack(backStack, { id: currentId, address: currentAddress })
|
|
493
|
+
forwardStack = []
|
|
494
|
+
}
|
|
495
|
+
currentId = snap.id
|
|
496
|
+
currentAddress = snap.address
|
|
497
|
+
sendStatus()
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const onNavMessage = (event) => {
|
|
501
|
+
const data = event.data || {}
|
|
502
|
+
if (event.source !== window.parent) return
|
|
503
|
+
if (event.origin !== parentOrigin) return
|
|
504
|
+
const dir = data.__tbNav
|
|
505
|
+
if (dir === 'back') goBack()
|
|
506
|
+
else if (dir === 'forward') goForward()
|
|
507
|
+
else if (dir === 'ping') sendStatus()
|
|
508
|
+
}
|
|
509
|
+
window.addEventListener('message', onNavMessage)
|
|
510
|
+
|
|
511
|
+
const onKeydown = (event) => {
|
|
512
|
+
if (!event.altKey || event.ctrlKey || event.shiftKey) return
|
|
513
|
+
if (event.key === 'ArrowLeft') {
|
|
514
|
+
event.preventDefault()
|
|
515
|
+
goBack()
|
|
516
|
+
} else if (event.key === 'ArrowRight') {
|
|
517
|
+
event.preventDefault()
|
|
518
|
+
goForward()
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
window.addEventListener('keydown', onKeydown, true)
|
|
522
|
+
|
|
523
|
+
// 鼠标侧键:DOM button 3 = 后退(XButton1)、4 = 前进(XButton2)
|
|
524
|
+
const onMouseup = (event) => {
|
|
525
|
+
if (event.button === 3) {
|
|
526
|
+
event.preventDefault()
|
|
527
|
+
goBack()
|
|
528
|
+
} else if (event.button === 4) {
|
|
529
|
+
event.preventDefault()
|
|
530
|
+
goForward()
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
window.addEventListener('mouseup', onMouseup, true)
|
|
534
|
+
|
|
535
|
+
const unsubscribe = store.subscribe(onListChange)
|
|
536
|
+
const init = snapCurrent()
|
|
537
|
+
currentId = init.id
|
|
538
|
+
currentAddress = init.address
|
|
539
|
+
sendStatus()
|
|
540
|
+
|
|
541
|
+
navDispose = () => {
|
|
542
|
+
if (typeof unsubscribe === 'function') unsubscribe()
|
|
543
|
+
clearTimeout(jumpTimer)
|
|
544
|
+
window.removeEventListener('message', onNavMessage)
|
|
545
|
+
window.removeEventListener('keydown', onKeydown, true)
|
|
546
|
+
window.removeEventListener('mouseup', onMouseup, true)
|
|
547
|
+
}
|
|
548
|
+
return true
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// 立即尝试一次,再走重试梯子(服务可能在之后才就绪)
|
|
552
|
+
let navTries = 0
|
|
553
|
+
try {
|
|
554
|
+
navReady = initNav()
|
|
555
|
+
} catch { /* 服务未就绪,走重试 */ }
|
|
556
|
+
const navTimer = setInterval(() => {
|
|
557
|
+
navTries += 1
|
|
558
|
+
if (navReady || navTries >= 12) {
|
|
559
|
+
clearInterval(navTimer)
|
|
560
|
+
return
|
|
561
|
+
}
|
|
562
|
+
try {
|
|
563
|
+
navReady = initNav()
|
|
564
|
+
} catch { /* 下一轮重试 */ }
|
|
565
|
+
}, 500)
|
|
566
|
+
|
|
567
|
+
ctx.effect(() => () => {
|
|
568
|
+
clearInterval(navTimer)
|
|
569
|
+
if (navDispose) navDispose()
|
|
570
|
+
}, 'dsh-tauri-launcher: session nav')
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return module.exports
|
|
575
|
+
},
|
|
576
|
+
})
|