@wonderwhy-er/desktop-commander 0.2.39 → 0.2.40
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/dist/server.js +1 -1
- package/dist/ui/file-preview/preview-runtime.js +204 -153
- package/dist/ui/file-preview/src/markdown/controller.d.ts +7 -1
- package/dist/ui/file-preview/src/markdown/controller.js +135 -16
- package/dist/ui/file-preview/src/markdown/editor.d.ts +97 -1
- package/dist/ui/file-preview/src/markdown/editor.js +814 -26
- package/dist/ui/file-preview/src/model.d.ts +2 -1
- package/dist/utils/capture.js +1 -1
- package/dist/utils/toolHistory.d.ts +13 -0
- package/dist/utils/toolHistory.js +65 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -1
- package/dist/ui/config-editor/app.js +0 -840
- package/dist/ui/config-editor/array-modal.d.ts +0 -19
- package/dist/ui/config-editor/array-modal.js +0 -185
- package/dist/ui/config-editor/main.d.ts +0 -1
- package/dist/ui/config-editor/main.js +0 -2
- package/dist/ui/config-editor/src/App.d.ts +0 -43
- package/dist/ui/config-editor/src/components/layout.d.ts +0 -4
- package/dist/ui/config-editor/src/components/layout.js +0 -83
- package/dist/ui/config-editor/src/components/toolbar.d.ts +0 -1
- package/dist/ui/config-editor/src/components/toolbar.js +0 -21
- package/dist/ui/config-editor/src/config-values.d.ts +0 -6
- package/dist/ui/config-editor/src/config-values.js +0 -61
- package/dist/ui/config-editor/src/contracts.d.ts +0 -14
- package/dist/ui/config-editor/src/contracts.js +0 -3
- package/dist/ui/config-editor/src/directory-browser.d.ts +0 -6
- package/dist/ui/config-editor/src/directory-browser.js +0 -71
- package/dist/ui/config-editor/src/layout.d.ts +0 -5
- package/dist/ui/config-editor/src/layout.js +0 -90
- package/dist/ui/config-editor/src/parsing.d.ts +0 -5
- package/dist/ui/config-editor/src/parsing.js +0 -50
- package/dist/ui/config-editor/src/toolbar.d.ts +0 -1
- package/dist/ui/config-editor/src/toolbar.js +0 -18
- package/dist/ui/config-editor/src/types.d.ts +0 -17
- package/dist/ui/config-editor/src/types.js +0 -3
- package/dist/ui/config-editor/src/utils/config-values.d.ts +0 -9
- package/dist/ui/config-editor/src/utils/config-values.js +0 -61
- package/dist/ui/config-editor/src/utils/directory-browser.d.ts +0 -31
- package/dist/ui/config-editor/src/utils/directory-browser.js +0 -201
- package/dist/ui/config-editor/src/utils/parsing.d.ts +0 -8
- package/dist/ui/config-editor/src/utils/parsing.js +0 -50
- package/dist/ui/file-preview/app.d.ts +0 -8
- package/dist/ui/file-preview/app.js +0 -2020
- package/dist/ui/file-preview/components/code-viewer.d.ts +0 -6
- package/dist/ui/file-preview/components/code-viewer.js +0 -73
- package/dist/ui/file-preview/components/highlighting.d.ts +0 -2
- package/dist/ui/file-preview/components/highlighting.js +0 -54
- package/dist/ui/file-preview/components/html-renderer.d.ts +0 -5
- package/dist/ui/file-preview/components/html-renderer.js +0 -47
- package/dist/ui/file-preview/components/markdown-renderer.d.ts +0 -1
- package/dist/ui/file-preview/components/markdown-renderer.js +0 -67
- package/dist/ui/file-preview/components/toolbar.d.ts +0 -6
- package/dist/ui/file-preview/components/toolbar.js +0 -75
- package/dist/ui/file-preview/image-preview.d.ts +0 -3
- package/dist/ui/file-preview/image-preview.js +0 -21
- package/dist/ui/file-preview/main.d.ts +0 -1
- package/dist/ui/file-preview/main.js +0 -5
- package/dist/ui/file-preview/markdown/editor.d.ts +0 -36
- package/dist/ui/file-preview/markdown/editor.js +0 -643
- package/dist/ui/file-preview/markdown/linking.d.ts +0 -9
- package/dist/ui/file-preview/markdown/linking.js +0 -210
- package/dist/ui/file-preview/markdown/outline.d.ts +0 -7
- package/dist/ui/file-preview/markdown/outline.js +0 -40
- package/dist/ui/file-preview/markdown/preview.d.ts +0 -8
- package/dist/ui/file-preview/markdown/preview.js +0 -33
- package/dist/ui/file-preview/markdown/slugify.d.ts +0 -3
- package/dist/ui/file-preview/markdown/slugify.js +0 -31
- package/dist/ui/file-preview/markdown/toc.d.ts +0 -11
- package/dist/ui/file-preview/markdown/toc.js +0 -75
- package/dist/ui/file-preview/markdown/utils.d.ts +0 -1
- package/dist/ui/file-preview/markdown/utils.js +0 -15
- package/dist/ui/file-preview/markdown/workspace-controller.d.ts +0 -25
- package/dist/ui/file-preview/markdown/workspace-controller.js +0 -40
- package/dist/ui/file-preview/src/components/CodeViewer.d.ts +0 -6
- package/dist/ui/file-preview/src/components/CodeViewer.js +0 -60
- package/dist/ui/file-preview/src/components/HtmlRenderer.d.ts +0 -8
- package/dist/ui/file-preview/src/components/HtmlRenderer.js +0 -45
- package/dist/ui/file-preview/src/components/MarkdownRenderer.d.ts +0 -1
- package/dist/ui/file-preview/src/components/MarkdownRenderer.js +0 -15
- package/dist/ui/file-preview/src/components/Toolbar.d.ts +0 -6
- package/dist/ui/file-preview/src/components/Toolbar.js +0 -75
- package/dist/ui/file-preview/src/components/editor-toolbar.d.ts +0 -15
- package/dist/ui/file-preview/src/components/editor-toolbar.js +0 -384
- package/dist/ui/file-preview/src/components/markdown-editor.d.ts +0 -29
- package/dist/ui/file-preview/src/components/markdown-editor.js +0 -535
- package/dist/ui/file-preview/src/markdown/block-merge.d.ts +0 -25
- package/dist/ui/file-preview/src/markdown/block-merge.js +0 -86
- package/dist/ui/file-preview/src/markdown/link-modal.d.ts +0 -13
- package/dist/ui/file-preview/src/markdown/link-modal.js +0 -213
- package/dist/ui/file-preview/src/markdown/raw-editor.d.ts +0 -8
- package/dist/ui/file-preview/src/markdown/raw-editor.js +0 -61
- package/dist/ui/file-preview/src/markdown/selection-toolbar.d.ts +0 -14
- package/dist/ui/file-preview/src/markdown/selection-toolbar.js +0 -128
- package/dist/ui/file-preview/src/markdown/toc.d.ts +0 -11
- package/dist/ui/file-preview/src/markdown/toc.js +0 -75
- package/dist/ui/file-preview/src/markdown-workspace/editor.d.ts +0 -36
- package/dist/ui/file-preview/src/markdown-workspace/editor.js +0 -643
- package/dist/ui/file-preview/src/markdown-workspace/linking.d.ts +0 -9
- package/dist/ui/file-preview/src/markdown-workspace/linking.js +0 -210
- package/dist/ui/file-preview/src/markdown-workspace/outline.d.ts +0 -7
- package/dist/ui/file-preview/src/markdown-workspace/outline.js +0 -40
- package/dist/ui/file-preview/src/markdown-workspace/preview.d.ts +0 -8
- package/dist/ui/file-preview/src/markdown-workspace/preview.js +0 -33
- package/dist/ui/file-preview/src/markdown-workspace/slugify.d.ts +0 -3
- package/dist/ui/file-preview/src/markdown-workspace/slugify.js +0 -31
- package/dist/ui/file-preview/src/markdown-workspace/toc.d.ts +0 -11
- package/dist/ui/file-preview/src/markdown-workspace/toc.js +0 -75
- package/dist/ui/file-preview/src/markdown-workspace/utils.d.ts +0 -1
- package/dist/ui/file-preview/src/markdown-workspace/utils.js +0 -15
- package/dist/ui/file-preview/src/markdown-workspace/workspace-controller.d.ts +0 -25
- package/dist/ui/file-preview/src/markdown-workspace/workspace-controller.js +0 -40
- package/dist/ui/file-preview/types.d.ts +0 -1
- package/dist/ui/file-preview/types.js +0 -1
- package/dist/ui/server-integration.d.ts +0 -13
- package/dist/ui/server-integration.js +0 -31
- package/dist/ui/shared/ToolHeader.d.ts +0 -9
- package/dist/ui/shared/ToolHeader.js +0 -29
- package/dist/ui/shared/app-bootstrap.d.ts +0 -9
- package/dist/ui/shared/app-bootstrap.js +0 -15
- package/dist/ui/shared/guards.d.ts +0 -1
- package/dist/ui/shared/guards.js +0 -3
- package/dist/ui/shared/host-lifecycle.d.ts +0 -17
- package/dist/ui/shared/host-lifecycle.js +0 -41
- package/dist/ui/shared/rpc-client.d.ts +0 -14
- package/dist/ui/shared/rpc-client.js +0 -72
- package/dist/ui/shared/theme-adaptation.d.ts +0 -10
- package/dist/ui/shared/theme-adaptation.js +0 -118
- package/dist/ui/shared/tool-header.d.ts +0 -9
- package/dist/ui/shared/tool-header.js +0 -25
- package/dist/utils/ui-call-context.d.ts +0 -8
- package/dist/utils/ui-call-context.js +0 -72
- /package/dist/ui/config-editor/{app.d.ts → src/app.d.ts} +0 -0
- /package/dist/ui/config-editor/src/{App.js → app.js} +0 -0
- /package/dist/ui/file-preview/src/{App.d.ts → app.d.ts} +0 -0
- /package/dist/ui/file-preview/src/{App.js → app.js} +0 -0
|
@@ -1,840 +0,0 @@
|
|
|
1
|
-
import { App } from '@modelcontextprotocol/ext-apps';
|
|
2
|
-
import { createToolBridge } from '../shared/tool-bridge.js';
|
|
3
|
-
import { createCompactRowShellController } from '../shared/tool-shell.js';
|
|
4
|
-
import { renderCompactRow } from '../shared/compact-row.js';
|
|
5
|
-
import { escapeHtml } from '../shared/escape-html.js';
|
|
6
|
-
import { createWidgetStateStorage } from '../shared/widget-state.js';
|
|
7
|
-
import { connectWithSharedHostContext, isObjectRecord } from '../shared/host-context.js';
|
|
8
|
-
import { createUiEventTracker } from '../shared/ui-event-tracker.js';
|
|
9
|
-
import { createArrayModalController, renderArrayModalMarkup } from './array-modal.js';
|
|
10
|
-
import { CONFIG_FIELD_DEFINITIONS, isConfigFieldKey } from '../../config-field-definitions.js';
|
|
11
|
-
let shellController;
|
|
12
|
-
const CONFIG_EDITOR_COMPONENT = 'config_editor';
|
|
13
|
-
const GET_CONFIG_TOOL_NAME = 'get_config';
|
|
14
|
-
const MAX_TELEMETRY_MESSAGE_LENGTH = 180;
|
|
15
|
-
function sanitizeTelemetryErrorMessage(message) {
|
|
16
|
-
// Keep error signal useful while removing path-like data and bounding payload size.
|
|
17
|
-
const collapsed = message.replace(/\s+/g, ' ').trim();
|
|
18
|
-
const withoutPaths = collapsed
|
|
19
|
-
.replace(/(?:\/|\\)[\w\d_.\-/\\]+/g, '[PATH]')
|
|
20
|
-
.replace(/[A-Za-z]:\\[\w\d_.\-/\\]+/g, '[PATH]');
|
|
21
|
-
if (withoutPaths.length === 0) {
|
|
22
|
-
return 'Unknown error';
|
|
23
|
-
}
|
|
24
|
-
if (withoutPaths.length <= MAX_TELEMETRY_MESSAGE_LENGTH) {
|
|
25
|
-
return withoutPaths;
|
|
26
|
-
}
|
|
27
|
-
return `${withoutPaths.slice(0, MAX_TELEMETRY_MESSAGE_LENGTH - 3)}...`;
|
|
28
|
-
}
|
|
29
|
-
function buildConfigUpdateTelemetryParams(args) {
|
|
30
|
-
const base = {
|
|
31
|
-
config_key: args.configKey,
|
|
32
|
-
value_type: args.valueType,
|
|
33
|
-
};
|
|
34
|
-
if (args.errorStage) {
|
|
35
|
-
base.error_stage = args.errorStage;
|
|
36
|
-
}
|
|
37
|
-
if (args.errorMessage) {
|
|
38
|
-
base.error_message = sanitizeTelemetryErrorMessage(args.errorMessage);
|
|
39
|
-
}
|
|
40
|
-
return base;
|
|
41
|
-
}
|
|
42
|
-
function isConfigEditorPayload(value) {
|
|
43
|
-
return isObjectRecord(value) && Array.isArray(value.entries);
|
|
44
|
-
}
|
|
45
|
-
function stringifyValueForInput(value) {
|
|
46
|
-
if (Array.isArray(value)) {
|
|
47
|
-
return value.map((item) => String(item)).join('\n');
|
|
48
|
-
}
|
|
49
|
-
if (typeof value === 'string') {
|
|
50
|
-
return value;
|
|
51
|
-
}
|
|
52
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
53
|
-
return String(value);
|
|
54
|
-
}
|
|
55
|
-
if (value === null) {
|
|
56
|
-
return 'null';
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
return JSON.stringify(value);
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return String(value);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function formatKeyLabel(key) {
|
|
66
|
-
return key
|
|
67
|
-
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
68
|
-
.replace(/[_-]+/g, ' ')
|
|
69
|
-
.trim()
|
|
70
|
-
.split(/\s+/)
|
|
71
|
-
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
72
|
-
.join(' ');
|
|
73
|
-
}
|
|
74
|
-
function getConfigFieldMetadata(key) {
|
|
75
|
-
if (!isConfigFieldKey(key)) {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
return {
|
|
79
|
-
label: CONFIG_FIELD_DEFINITIONS[key].label,
|
|
80
|
-
description: CONFIG_FIELD_DEFINITIONS[key].description,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function extractPayload(result) {
|
|
84
|
-
if (!isObjectRecord(result)) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
const structured = result.structuredContent;
|
|
88
|
-
if (!isObjectRecord(structured) || !Array.isArray(structured.entries)) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
const entries = structured.entries
|
|
92
|
-
.filter((entry) => isObjectRecord(entry))
|
|
93
|
-
.filter((entry) => typeof entry.key === 'string')
|
|
94
|
-
.map((entry) => {
|
|
95
|
-
const key = entry.key;
|
|
96
|
-
const metadata = getConfigFieldMetadata(key);
|
|
97
|
-
return {
|
|
98
|
-
key,
|
|
99
|
-
label: metadata?.label,
|
|
100
|
-
description: metadata?.description,
|
|
101
|
-
value: entry.value,
|
|
102
|
-
valueType: typeof entry.valueType === 'string' ? entry.valueType : 'string',
|
|
103
|
-
editable: entry.editable !== false,
|
|
104
|
-
};
|
|
105
|
-
});
|
|
106
|
-
return {
|
|
107
|
-
config: isObjectRecord(structured.config) ? structured.config : undefined,
|
|
108
|
-
uiHints: isObjectRecord(structured.uiHints)
|
|
109
|
-
? {
|
|
110
|
-
availableShells: Array.isArray(structured.uiHints.availableShells)
|
|
111
|
-
? structured.uiHints.availableShells.filter((value) => typeof value === 'string')
|
|
112
|
-
: undefined,
|
|
113
|
-
}
|
|
114
|
-
: undefined,
|
|
115
|
-
entries,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
function extractToolText(result) {
|
|
119
|
-
if (!isObjectRecord(result) || !Array.isArray(result.content)) {
|
|
120
|
-
return undefined;
|
|
121
|
-
}
|
|
122
|
-
for (const item of result.content) {
|
|
123
|
-
if (!isObjectRecord(item)) {
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
if (item.type === 'text' && typeof item.text === 'string' && item.text.trim().length > 0) {
|
|
127
|
-
return item.text;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return undefined;
|
|
131
|
-
}
|
|
132
|
-
function isToolErrorResult(result) {
|
|
133
|
-
return isObjectRecord(result) && result.isError === true;
|
|
134
|
-
}
|
|
135
|
-
function parseDraftValue(rawValue, valueType) {
|
|
136
|
-
if (valueType === 'string') {
|
|
137
|
-
return { ok: true, value: rawValue.replace(/\r?\n/g, ' ') };
|
|
138
|
-
}
|
|
139
|
-
if (valueType === 'number') {
|
|
140
|
-
if (rawValue.trim() === '') {
|
|
141
|
-
return { ok: false, message: 'Enter a valid number.' };
|
|
142
|
-
}
|
|
143
|
-
const numeric = Number(rawValue);
|
|
144
|
-
if (!Number.isFinite(numeric)) {
|
|
145
|
-
return { ok: false, message: 'Enter a valid number.' };
|
|
146
|
-
}
|
|
147
|
-
return { ok: true, value: numeric };
|
|
148
|
-
}
|
|
149
|
-
if (valueType === 'boolean') {
|
|
150
|
-
const normalized = rawValue.trim().toLowerCase();
|
|
151
|
-
if (normalized !== 'true' && normalized !== 'false') {
|
|
152
|
-
return { ok: false, message: 'Boolean values must be true or false.' };
|
|
153
|
-
}
|
|
154
|
-
return { ok: true, value: normalized === 'true' };
|
|
155
|
-
}
|
|
156
|
-
if (valueType === 'null') {
|
|
157
|
-
if (rawValue.trim().toLowerCase() !== 'null') {
|
|
158
|
-
return { ok: false, message: 'Null values must be exactly null.' };
|
|
159
|
-
}
|
|
160
|
-
return { ok: true, value: null };
|
|
161
|
-
}
|
|
162
|
-
if (valueType === 'array') {
|
|
163
|
-
const trimmed = rawValue.trim();
|
|
164
|
-
if (trimmed.length === 0) {
|
|
165
|
-
return { ok: true, value: [] };
|
|
166
|
-
}
|
|
167
|
-
if (!trimmed.startsWith('[')) {
|
|
168
|
-
const items = rawValue
|
|
169
|
-
.split(/\r?\n/)
|
|
170
|
-
.map((item) => item.trim())
|
|
171
|
-
.filter((item) => item.length > 0);
|
|
172
|
-
return { ok: true, value: items };
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
const parsed = JSON.parse(trimmed);
|
|
176
|
-
if (!Array.isArray(parsed)) {
|
|
177
|
-
return { ok: false, message: 'Array values must be valid JSON arrays.' };
|
|
178
|
-
}
|
|
179
|
-
if (!parsed.every((item) => typeof item === 'string')) {
|
|
180
|
-
return { ok: false, message: 'Array values must contain only strings.' };
|
|
181
|
-
}
|
|
182
|
-
return { ok: true, value: parsed };
|
|
183
|
-
}
|
|
184
|
-
catch {
|
|
185
|
-
return { ok: false, message: 'Array values must be valid JSON arrays.' };
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return { ok: true, value: rawValue };
|
|
189
|
-
}
|
|
190
|
-
function areConfigValuesEqual(left, right) {
|
|
191
|
-
if (Object.is(left, right)) {
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
if (Array.isArray(left) && Array.isArray(right)) {
|
|
195
|
-
if (left.length !== right.length) {
|
|
196
|
-
return false;
|
|
197
|
-
}
|
|
198
|
-
for (let index = 0; index < left.length; index += 1) {
|
|
199
|
-
if (!Object.is(left[index], right[index])) {
|
|
200
|
-
return false;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return true;
|
|
204
|
-
}
|
|
205
|
-
return false;
|
|
206
|
-
}
|
|
207
|
-
function parseDraftArrayValues(draft) {
|
|
208
|
-
const trimmed = draft.trim();
|
|
209
|
-
if (!trimmed) {
|
|
210
|
-
return [];
|
|
211
|
-
}
|
|
212
|
-
if (trimmed.startsWith('[')) {
|
|
213
|
-
try {
|
|
214
|
-
const parsed = JSON.parse(trimmed);
|
|
215
|
-
if (Array.isArray(parsed)) {
|
|
216
|
-
return parsed.filter((item) => typeof item === 'string');
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
catch {
|
|
220
|
-
// fallback below
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return draft
|
|
224
|
-
.split(/\r?\n/)
|
|
225
|
-
.map((line) => line.trim())
|
|
226
|
-
.filter((line) => line.length > 0);
|
|
227
|
-
}
|
|
228
|
-
function getSettingSummary(entry) {
|
|
229
|
-
if (entry.key !== 'blockedCommands' && entry.key !== 'allowedDirectories') {
|
|
230
|
-
return null;
|
|
231
|
-
}
|
|
232
|
-
const values = Array.isArray(entry.value)
|
|
233
|
-
? entry.value.filter((item) => typeof item === 'string' && item.trim().length > 0)
|
|
234
|
-
: [];
|
|
235
|
-
const count = values.length;
|
|
236
|
-
if (entry.key === 'blockedCommands') {
|
|
237
|
-
return `${count} command${count === 1 ? '' : 's'} blocked`;
|
|
238
|
-
}
|
|
239
|
-
if (count === 0) {
|
|
240
|
-
return 'All folders allowed (no restriction)';
|
|
241
|
-
}
|
|
242
|
-
return `${count} folder${count === 1 ? '' : 's'} allowed`;
|
|
243
|
-
}
|
|
244
|
-
function getShellOptions(payload, currentShell) {
|
|
245
|
-
const hintedShells = Array.isArray(payload?.uiHints?.availableShells)
|
|
246
|
-
? payload.uiHints.availableShells.filter((shell) => typeof shell === 'string' && shell.trim().length > 0)
|
|
247
|
-
: [];
|
|
248
|
-
const config = payload?.config;
|
|
249
|
-
const systemInfo = isObjectRecord(config?.systemInfo) ? config.systemInfo : null;
|
|
250
|
-
const isWindows = Boolean(systemInfo && systemInfo.isWindows === true);
|
|
251
|
-
const isMacOS = Boolean(systemInfo && systemInfo.isMacOS === true);
|
|
252
|
-
const baseOptions = hintedShells.length > 0
|
|
253
|
-
? hintedShells
|
|
254
|
-
: isWindows
|
|
255
|
-
? ['powershell.exe', 'pwsh.exe', 'cmd.exe', 'bash.exe']
|
|
256
|
-
: isMacOS
|
|
257
|
-
? ['/bin/zsh', '/bin/bash', '/bin/sh', '/usr/bin/fish', 'zsh', 'bash', 'sh', 'fish']
|
|
258
|
-
: ['/bin/bash', '/bin/sh', '/usr/bin/fish', '/bin/zsh', 'bash', 'sh', 'fish', 'zsh'];
|
|
259
|
-
const options = new Set();
|
|
260
|
-
for (const shell of baseOptions) {
|
|
261
|
-
options.add(shell);
|
|
262
|
-
}
|
|
263
|
-
if (currentShell.trim().length > 0) {
|
|
264
|
-
options.add(currentShell);
|
|
265
|
-
}
|
|
266
|
-
return [...options];
|
|
267
|
-
}
|
|
268
|
-
export function createConfigEditorController(callTool, trackConfigUiEvent) {
|
|
269
|
-
const state = {
|
|
270
|
-
payload: null,
|
|
271
|
-
selectedKey: null,
|
|
272
|
-
draftValue: '',
|
|
273
|
-
};
|
|
274
|
-
const getSelectedEntry = () => {
|
|
275
|
-
if (!state.payload || !state.selectedKey) {
|
|
276
|
-
return undefined;
|
|
277
|
-
}
|
|
278
|
-
return state.payload.entries.find((entry) => entry.key === state.selectedKey);
|
|
279
|
-
};
|
|
280
|
-
const setPayload = (payload) => {
|
|
281
|
-
state.payload = payload;
|
|
282
|
-
if (!payload || payload.entries.length === 0) {
|
|
283
|
-
state.selectedKey = null;
|
|
284
|
-
state.draftValue = '';
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
if (!state.selectedKey || !payload.entries.some((entry) => entry.key === state.selectedKey)) {
|
|
288
|
-
state.selectedKey = payload.entries[0].key;
|
|
289
|
-
}
|
|
290
|
-
const current = getSelectedEntry();
|
|
291
|
-
state.draftValue = current ? stringifyValueForInput(current.value) : '';
|
|
292
|
-
};
|
|
293
|
-
const setSelection = (key) => {
|
|
294
|
-
state.selectedKey = key;
|
|
295
|
-
const selected = getSelectedEntry();
|
|
296
|
-
state.draftValue = selected ? stringifyValueForInput(selected.value) : '';
|
|
297
|
-
};
|
|
298
|
-
const setDraftValue = (value) => {
|
|
299
|
-
state.draftValue = value;
|
|
300
|
-
};
|
|
301
|
-
const apply = async () => {
|
|
302
|
-
const selected = getSelectedEntry();
|
|
303
|
-
if (!selected) {
|
|
304
|
-
return {
|
|
305
|
-
ok: false,
|
|
306
|
-
tooltip: {
|
|
307
|
-
message: 'Select a config key before applying.',
|
|
308
|
-
tone: 'error',
|
|
309
|
-
},
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
if (!selected.editable) {
|
|
313
|
-
return {
|
|
314
|
-
ok: false,
|
|
315
|
-
tooltip: {
|
|
316
|
-
message: `The selected key (${selected.key}) is read-only.`,
|
|
317
|
-
tone: 'error',
|
|
318
|
-
},
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
const parsed = parseDraftValue(state.draftValue, selected.valueType);
|
|
322
|
-
if (!parsed.ok) {
|
|
323
|
-
return {
|
|
324
|
-
ok: false,
|
|
325
|
-
tooltip: {
|
|
326
|
-
message: parsed.message,
|
|
327
|
-
tone: 'error',
|
|
328
|
-
},
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
if (areConfigValuesEqual(parsed.value, selected.value)) {
|
|
332
|
-
state.draftValue = stringifyValueForInput(selected.value);
|
|
333
|
-
return { ok: false };
|
|
334
|
-
}
|
|
335
|
-
try {
|
|
336
|
-
const setResult = await callTool('set_config_value', {
|
|
337
|
-
key: selected.key,
|
|
338
|
-
value: parsed.value,
|
|
339
|
-
origin: 'ui',
|
|
340
|
-
});
|
|
341
|
-
if (isToolErrorResult(setResult)) {
|
|
342
|
-
const errorMessage = extractToolText(setResult) ?? `Failed to update ${selected.key}.`;
|
|
343
|
-
trackConfigUiEvent?.('config_update_failed', {
|
|
344
|
-
tool_name: 'set_config_value',
|
|
345
|
-
...buildConfigUpdateTelemetryParams({
|
|
346
|
-
configKey: selected.key,
|
|
347
|
-
valueType: selected.valueType,
|
|
348
|
-
errorMessage,
|
|
349
|
-
errorStage: 'set_config_value',
|
|
350
|
-
}),
|
|
351
|
-
});
|
|
352
|
-
return {
|
|
353
|
-
ok: false,
|
|
354
|
-
tooltip: {
|
|
355
|
-
message: errorMessage,
|
|
356
|
-
tone: 'error',
|
|
357
|
-
},
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
trackConfigUiEvent?.('config_update_success', {
|
|
361
|
-
tool_name: 'set_config_value',
|
|
362
|
-
...buildConfigUpdateTelemetryParams({
|
|
363
|
-
configKey: selected.key,
|
|
364
|
-
valueType: selected.valueType,
|
|
365
|
-
}),
|
|
366
|
-
});
|
|
367
|
-
const refreshed = await callTool('get_config', {});
|
|
368
|
-
if (isToolErrorResult(refreshed)) {
|
|
369
|
-
const errorMessage = extractToolText(refreshed) ?? 'Value was updated but config refresh failed.';
|
|
370
|
-
return {
|
|
371
|
-
ok: false,
|
|
372
|
-
tooltip: {
|
|
373
|
-
message: errorMessage,
|
|
374
|
-
tone: 'error',
|
|
375
|
-
},
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
const refreshedPayload = extractPayload(refreshed);
|
|
379
|
-
if (refreshedPayload) {
|
|
380
|
-
setPayload(refreshedPayload);
|
|
381
|
-
if (state.selectedKey === selected.key) {
|
|
382
|
-
const updatedSelected = getSelectedEntry();
|
|
383
|
-
state.draftValue = updatedSelected
|
|
384
|
-
? stringifyValueForInput(updatedSelected.value)
|
|
385
|
-
: state.draftValue;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return {
|
|
389
|
-
ok: true,
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
catch (error) {
|
|
393
|
-
const errorMessage = `Failed to apply value: ${error instanceof Error ? error.message : String(error)}`;
|
|
394
|
-
trackConfigUiEvent?.('config_update_failed', {
|
|
395
|
-
tool_name: 'set_config_value',
|
|
396
|
-
...buildConfigUpdateTelemetryParams({
|
|
397
|
-
configKey: selected.key,
|
|
398
|
-
valueType: selected.valueType,
|
|
399
|
-
errorMessage,
|
|
400
|
-
errorStage: 'transport',
|
|
401
|
-
}),
|
|
402
|
-
});
|
|
403
|
-
return {
|
|
404
|
-
ok: false,
|
|
405
|
-
tooltip: {
|
|
406
|
-
message: errorMessage,
|
|
407
|
-
tone: 'error',
|
|
408
|
-
},
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
return {
|
|
413
|
-
state,
|
|
414
|
-
callTool,
|
|
415
|
-
extractPayload,
|
|
416
|
-
setPayload,
|
|
417
|
-
setSelection,
|
|
418
|
-
setDraftValue,
|
|
419
|
-
apply,
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
function render(container, controller, chrome, hooks = {}) {
|
|
423
|
-
shellController?.dispose();
|
|
424
|
-
shellController = undefined;
|
|
425
|
-
const { state } = controller;
|
|
426
|
-
const entries = state.payload?.entries ?? [];
|
|
427
|
-
const shellClasses = [
|
|
428
|
-
'shell',
|
|
429
|
-
'tool-shell',
|
|
430
|
-
chrome.expanded ? 'expanded' : 'collapsed',
|
|
431
|
-
'config-shell',
|
|
432
|
-
chrome.hideSummaryRow ? 'host-framed' : '',
|
|
433
|
-
chrome.compact ? 'compact' : '',
|
|
434
|
-
].filter(Boolean).join(' ');
|
|
435
|
-
const settingsHtml = entries.map((entry, index) => {
|
|
436
|
-
const keyTitle = entry.label ?? formatKeyLabel(entry.key);
|
|
437
|
-
const description = entry.description ?? '';
|
|
438
|
-
const summary = getSettingSummary(entry);
|
|
439
|
-
let controlHtml;
|
|
440
|
-
if (entry.key === 'defaultShell') {
|
|
441
|
-
const currentShell = String(entry.value ?? '');
|
|
442
|
-
const shellOptions = getShellOptions(state.payload, currentShell);
|
|
443
|
-
const isCustomShell = !shellOptions.includes(currentShell);
|
|
444
|
-
const options = shellOptions
|
|
445
|
-
.map((shell) => `<option value="${escapeHtml(shell)}" ${shell === currentShell ? 'selected' : ''}>${escapeHtml(shell)}</option>`)
|
|
446
|
-
.join('');
|
|
447
|
-
controlHtml = `
|
|
448
|
-
<div class="setting-shell-control">
|
|
449
|
-
<select class="setting-inline-select" data-action="shell-select" data-key-index="${index}">
|
|
450
|
-
${options}
|
|
451
|
-
<option value="__custom__" ${isCustomShell ? 'selected' : ''}>Custom...</option>
|
|
452
|
-
</select>
|
|
453
|
-
<input class="setting-inline-input setting-shell-custom${isCustomShell ? '' : ' hidden'}" data-action="shell-custom" data-key-index="${index}" type="text" value="${escapeHtml(currentShell)}" placeholder="Type custom shell path"/>
|
|
454
|
-
</div>
|
|
455
|
-
`;
|
|
456
|
-
}
|
|
457
|
-
else if (entry.valueType === 'boolean') {
|
|
458
|
-
const checked = String(entry.value) === 'true' ? 'checked' : '';
|
|
459
|
-
controlHtml = `<label class="setting-switch"><input type="checkbox" data-action="toggle-boolean" data-key-index="${index}" ${checked}/><span class="config-boolean-slider"></span></label>`;
|
|
460
|
-
}
|
|
461
|
-
else if (entry.valueType === 'number') {
|
|
462
|
-
controlHtml = `<input class="setting-inline-input" data-action="edit-number" data-key-index="${index}" type="number" step="any" value="${escapeHtml(String(entry.value ?? ''))}"/>`;
|
|
463
|
-
}
|
|
464
|
-
else if (entry.valueType === 'array') {
|
|
465
|
-
controlHtml = `<button class="setting-inline-action" data-action="open-array" data-key-index="${index}">Edit</button>`;
|
|
466
|
-
}
|
|
467
|
-
else if (entry.valueType === 'null') {
|
|
468
|
-
controlHtml = `<span class="setting-inline-static">null</span>`;
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
controlHtml = `<input class="setting-inline-input" data-action="edit-string" data-key-index="${index}" type="text" value="${escapeHtml(String(entry.value ?? ''))}"/>`;
|
|
472
|
-
}
|
|
473
|
-
return `
|
|
474
|
-
<section class="setting-row">
|
|
475
|
-
<div class="setting-info">
|
|
476
|
-
<h3>${escapeHtml(keyTitle)}</h3>
|
|
477
|
-
${description ? `<p>${escapeHtml(description)}</p>` : ''}
|
|
478
|
-
<p class="setting-summary${summary ? '' : ' hidden'}" data-setting-summary-key="${escapeHtml(entry.key)}">${summary ? escapeHtml(summary) : ''}</p>
|
|
479
|
-
</div>
|
|
480
|
-
<div class="setting-control">${controlHtml}</div>
|
|
481
|
-
</section>
|
|
482
|
-
`;
|
|
483
|
-
}).join('');
|
|
484
|
-
container.innerHTML = `
|
|
485
|
-
<main id="tool-shell" class="${shellClasses}">
|
|
486
|
-
${renderCompactRow({ id: 'compact-toggle', label: 'View config', filename: 'Desktop Commander', variant: 'ready', expandable: true, expanded: chrome.expanded, interactive: true })}
|
|
487
|
-
|
|
488
|
-
<section class="panel config-card">
|
|
489
|
-
<div class="panel-content-wrapper">
|
|
490
|
-
<div class="settings-stack" aria-label="Desktop Commander settings">${settingsHtml}</div>
|
|
491
|
-
</div>
|
|
492
|
-
</section>
|
|
493
|
-
|
|
494
|
-
${renderArrayModalMarkup('List Setting')}
|
|
495
|
-
</main>
|
|
496
|
-
<div id="config-tooltip" class="config-tooltip" role="status" aria-live="polite" hidden></div>
|
|
497
|
-
`;
|
|
498
|
-
const toolShell = container.querySelector('#tool-shell');
|
|
499
|
-
const compactRow = container.querySelector('.compact-row--ready');
|
|
500
|
-
const getUpdatedEntryByKey = (key) => {
|
|
501
|
-
return controller.state.payload?.entries.find((item) => item.key === key);
|
|
502
|
-
};
|
|
503
|
-
const refreshSettingSummary = (key) => {
|
|
504
|
-
const summaryElement = container.querySelector(`[data-setting-summary-key="${key}"]`);
|
|
505
|
-
if (!summaryElement) {
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
const updatedEntry = getUpdatedEntryByKey(key);
|
|
509
|
-
const summary = updatedEntry ? getSettingSummary(updatedEntry) : null;
|
|
510
|
-
summaryElement.textContent = summary ?? '';
|
|
511
|
-
summaryElement.classList.toggle('hidden', !summary);
|
|
512
|
-
};
|
|
513
|
-
const emitConfigChanged = (key, fallbackValue) => {
|
|
514
|
-
const updatedEntry = getUpdatedEntryByKey(key);
|
|
515
|
-
refreshSettingSummary(key);
|
|
516
|
-
hooks.onConfigChanged?.({
|
|
517
|
-
key,
|
|
518
|
-
value: updatedEntry ? updatedEntry.value : fallbackValue,
|
|
519
|
-
});
|
|
520
|
-
};
|
|
521
|
-
const emitTooltip = (result) => {
|
|
522
|
-
if (result.tooltip) {
|
|
523
|
-
hooks.onTooltip?.(result.tooltip);
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
const arrayModal = createArrayModalController({
|
|
527
|
-
container,
|
|
528
|
-
parseEntryItems: (entry) => parseDraftArrayValues(stringifyValueForInput(entry.value)),
|
|
529
|
-
formatEntryTitle: (entry) => entry.label ?? formatKeyLabel(entry.key),
|
|
530
|
-
onSave: async (changedKey, items) => {
|
|
531
|
-
controller.setSelection(changedKey);
|
|
532
|
-
controller.setDraftValue(items.join('\n'));
|
|
533
|
-
const result = await controller.apply();
|
|
534
|
-
emitTooltip(result);
|
|
535
|
-
if (result.ok) {
|
|
536
|
-
emitConfigChanged(changedKey, items);
|
|
537
|
-
}
|
|
538
|
-
},
|
|
539
|
-
});
|
|
540
|
-
container.querySelectorAll('[data-action]').forEach((element) => {
|
|
541
|
-
const target = element;
|
|
542
|
-
const action = target.dataset.action;
|
|
543
|
-
const keyIndex = Number(target.dataset.keyIndex);
|
|
544
|
-
const entry = entries[keyIndex];
|
|
545
|
-
if (!entry) {
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
if (action === 'toggle-boolean') {
|
|
549
|
-
target.addEventListener('change', async () => {
|
|
550
|
-
const input = target;
|
|
551
|
-
const previousChecked = input.checked;
|
|
552
|
-
controller.setSelection(entry.key);
|
|
553
|
-
controller.setDraftValue(input.checked ? 'true' : 'false');
|
|
554
|
-
const result = await controller.apply();
|
|
555
|
-
emitTooltip(result);
|
|
556
|
-
const updatedEntry = getUpdatedEntryByKey(entry.key);
|
|
557
|
-
if (updatedEntry && typeof updatedEntry.value === 'boolean') {
|
|
558
|
-
input.checked = updatedEntry.value;
|
|
559
|
-
}
|
|
560
|
-
else if (!result.ok) {
|
|
561
|
-
input.checked = !previousChecked;
|
|
562
|
-
}
|
|
563
|
-
if (result.ok) {
|
|
564
|
-
emitConfigChanged(entry.key, input.checked);
|
|
565
|
-
}
|
|
566
|
-
});
|
|
567
|
-
return;
|
|
568
|
-
}
|
|
569
|
-
if (action === 'edit-number') {
|
|
570
|
-
target.addEventListener('blur', async () => {
|
|
571
|
-
const input = target;
|
|
572
|
-
controller.setSelection(entry.key);
|
|
573
|
-
controller.setDraftValue(input.value);
|
|
574
|
-
const result = await controller.apply();
|
|
575
|
-
emitTooltip(result);
|
|
576
|
-
const updatedEntry = getUpdatedEntryByKey(entry.key);
|
|
577
|
-
input.value = String(updatedEntry?.value ?? controller.state.draftValue);
|
|
578
|
-
if (result.ok) {
|
|
579
|
-
emitConfigChanged(entry.key, input.value);
|
|
580
|
-
}
|
|
581
|
-
});
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
if (action === 'shell-select') {
|
|
585
|
-
target.addEventListener('change', async () => {
|
|
586
|
-
const select = target;
|
|
587
|
-
const customInput = container.querySelector(`input[data-action="shell-custom"][data-key-index="${keyIndex}"]`);
|
|
588
|
-
if (select.value === '__custom__') {
|
|
589
|
-
customInput?.classList.remove('hidden');
|
|
590
|
-
customInput?.focus();
|
|
591
|
-
return;
|
|
592
|
-
}
|
|
593
|
-
customInput?.classList.add('hidden');
|
|
594
|
-
controller.setSelection(entry.key);
|
|
595
|
-
controller.setDraftValue(select.value);
|
|
596
|
-
const result = await controller.apply();
|
|
597
|
-
emitTooltip(result);
|
|
598
|
-
const updatedEntry = getUpdatedEntryByKey(entry.key);
|
|
599
|
-
const shellValue = String(updatedEntry?.value ?? select.value);
|
|
600
|
-
const shellCustomInput = container.querySelector(`input[data-action="shell-custom"][data-key-index="${keyIndex}"]`);
|
|
601
|
-
if (shellCustomInput) {
|
|
602
|
-
shellCustomInput.value = shellValue;
|
|
603
|
-
}
|
|
604
|
-
if (result.ok) {
|
|
605
|
-
emitConfigChanged(entry.key, shellValue);
|
|
606
|
-
}
|
|
607
|
-
});
|
|
608
|
-
return;
|
|
609
|
-
}
|
|
610
|
-
if (action === 'shell-custom') {
|
|
611
|
-
const commitCustomShell = async () => {
|
|
612
|
-
const input = target;
|
|
613
|
-
if (!input.value.trim()) {
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
controller.setSelection(entry.key);
|
|
617
|
-
controller.setDraftValue(input.value.trim());
|
|
618
|
-
const result = await controller.apply();
|
|
619
|
-
emitTooltip(result);
|
|
620
|
-
const updatedEntry = getUpdatedEntryByKey(entry.key);
|
|
621
|
-
input.value = String(updatedEntry?.value ?? controller.state.draftValue);
|
|
622
|
-
if (result.ok) {
|
|
623
|
-
emitConfigChanged(entry.key, input.value);
|
|
624
|
-
}
|
|
625
|
-
};
|
|
626
|
-
target.addEventListener('blur', () => {
|
|
627
|
-
void commitCustomShell();
|
|
628
|
-
});
|
|
629
|
-
target.addEventListener('keydown', (event) => {
|
|
630
|
-
if (event.key !== 'Enter') {
|
|
631
|
-
return;
|
|
632
|
-
}
|
|
633
|
-
event.preventDefault();
|
|
634
|
-
void commitCustomShell();
|
|
635
|
-
});
|
|
636
|
-
return;
|
|
637
|
-
}
|
|
638
|
-
if (action === 'edit-string') {
|
|
639
|
-
target.addEventListener('keydown', async (event) => {
|
|
640
|
-
if (event.key !== 'Enter') {
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
event.preventDefault();
|
|
644
|
-
const input = target;
|
|
645
|
-
controller.setSelection(entry.key);
|
|
646
|
-
controller.setDraftValue(input.value.replace(/\r?\n/g, ' '));
|
|
647
|
-
const result = await controller.apply();
|
|
648
|
-
emitTooltip(result);
|
|
649
|
-
const updatedEntry = getUpdatedEntryByKey(entry.key);
|
|
650
|
-
input.value = String(updatedEntry?.value ?? controller.state.draftValue);
|
|
651
|
-
if (result.ok) {
|
|
652
|
-
emitConfigChanged(entry.key, input.value);
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
return;
|
|
656
|
-
}
|
|
657
|
-
if (action === 'open-array') {
|
|
658
|
-
target.addEventListener('click', () => {
|
|
659
|
-
const latestEntry = getUpdatedEntryByKey(entry.key) ?? entry;
|
|
660
|
-
arrayModal.open(latestEntry);
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
});
|
|
664
|
-
shellController = createCompactRowShellController({
|
|
665
|
-
shell: toolShell,
|
|
666
|
-
compactRow,
|
|
667
|
-
initialExpanded: chrome.expanded,
|
|
668
|
-
onToggle: (expanded) => {
|
|
669
|
-
chrome.expanded = expanded;
|
|
670
|
-
hooks.onExpandedChanged?.(expanded);
|
|
671
|
-
},
|
|
672
|
-
});
|
|
673
|
-
}
|
|
674
|
-
function markReady() {
|
|
675
|
-
document.body.classList.add('dc-ready');
|
|
676
|
-
}
|
|
677
|
-
export function bootstrapConfigEditorApp() {
|
|
678
|
-
const container = document.getElementById('app');
|
|
679
|
-
if (!container) {
|
|
680
|
-
return;
|
|
681
|
-
}
|
|
682
|
-
const bridge = createToolBridge();
|
|
683
|
-
const trackConfigUiEvent = createUiEventTracker((name, args) => bridge.callTool(name, args), {
|
|
684
|
-
component: CONFIG_EDITOR_COMPONENT,
|
|
685
|
-
baseParams: { origin: 'ui' },
|
|
686
|
-
});
|
|
687
|
-
const controller = createConfigEditorController((name, args) => bridge.callTool(name, args), trackConfigUiEvent);
|
|
688
|
-
const widgetState = createWidgetStateStorage(isConfigEditorPayload);
|
|
689
|
-
const chrome = {
|
|
690
|
-
hideSummaryRow: false,
|
|
691
|
-
compact: false,
|
|
692
|
-
expanded: true,
|
|
693
|
-
};
|
|
694
|
-
let configEditorShownEventSent = false;
|
|
695
|
-
let quietContextSupported = true;
|
|
696
|
-
let tooltipHideTimer = null;
|
|
697
|
-
const clearTooltipTimer = () => {
|
|
698
|
-
if (tooltipHideTimer !== null) {
|
|
699
|
-
window.clearTimeout(tooltipHideTimer);
|
|
700
|
-
tooltipHideTimer = null;
|
|
701
|
-
}
|
|
702
|
-
};
|
|
703
|
-
const showTooltip = (tooltip) => {
|
|
704
|
-
const tooltipElement = container.querySelector('#config-tooltip');
|
|
705
|
-
if (!tooltipElement) {
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
clearTooltipTimer();
|
|
709
|
-
tooltipElement.className = `config-tooltip config-tooltip--${tooltip.tone}`;
|
|
710
|
-
tooltipElement.textContent = tooltip.message;
|
|
711
|
-
tooltipElement.hidden = false;
|
|
712
|
-
tooltipHideTimer = window.setTimeout(() => {
|
|
713
|
-
tooltipElement.hidden = true;
|
|
714
|
-
tooltipElement.textContent = '';
|
|
715
|
-
tooltipElement.className = 'config-tooltip';
|
|
716
|
-
tooltipHideTimer = null;
|
|
717
|
-
}, 2600);
|
|
718
|
-
};
|
|
719
|
-
const syncModelContext = (reason, change) => {
|
|
720
|
-
if (!quietContextSupported || !change) {
|
|
721
|
-
return;
|
|
722
|
-
}
|
|
723
|
-
app.updateModelContext({
|
|
724
|
-
content: [{ type: 'text', text: `Updated ${change.key} to ${JSON.stringify(change.value)} (${reason}).` }],
|
|
725
|
-
structuredContent: {
|
|
726
|
-
reason,
|
|
727
|
-
changedKey: change.key,
|
|
728
|
-
changedValue: change.value,
|
|
729
|
-
},
|
|
730
|
-
}).catch(() => {
|
|
731
|
-
// Host may not support updateModelContext; avoid repeated failed calls.
|
|
732
|
-
quietContextSupported = false;
|
|
733
|
-
});
|
|
734
|
-
};
|
|
735
|
-
let renderFrameId = null;
|
|
736
|
-
const scheduleRender = () => {
|
|
737
|
-
if (renderFrameId !== null) {
|
|
738
|
-
return;
|
|
739
|
-
}
|
|
740
|
-
renderFrameId = window.requestAnimationFrame(() => {
|
|
741
|
-
renderFrameId = null;
|
|
742
|
-
render(container, controller, chrome, {
|
|
743
|
-
onConfigChanged: (change) => {
|
|
744
|
-
if (controller.state.payload) {
|
|
745
|
-
widgetState.write(controller.state.payload);
|
|
746
|
-
}
|
|
747
|
-
syncModelContext('widget-edit', change);
|
|
748
|
-
},
|
|
749
|
-
onTooltip: showTooltip,
|
|
750
|
-
onExpandedChanged: (expanded) => {
|
|
751
|
-
trackConfigUiEvent(expanded ? 'expand' : 'collapse', {
|
|
752
|
-
tool_name: GET_CONFIG_TOOL_NAME,
|
|
753
|
-
expanded,
|
|
754
|
-
});
|
|
755
|
-
},
|
|
756
|
-
});
|
|
757
|
-
markReady();
|
|
758
|
-
});
|
|
759
|
-
};
|
|
760
|
-
scheduleRender();
|
|
761
|
-
const app = new App({ name: 'Desktop Commander Config Editor', version: '1.0.0' }, {}, { autoResize: true });
|
|
762
|
-
app.onteardown = async () => {
|
|
763
|
-
shellController?.dispose();
|
|
764
|
-
if (renderFrameId !== null) {
|
|
765
|
-
window.cancelAnimationFrame(renderFrameId);
|
|
766
|
-
renderFrameId = null;
|
|
767
|
-
}
|
|
768
|
-
clearTooltipTimer();
|
|
769
|
-
return {};
|
|
770
|
-
};
|
|
771
|
-
const applyPayload = (payload) => {
|
|
772
|
-
controller.setPayload(payload);
|
|
773
|
-
widgetState.write(payload);
|
|
774
|
-
scheduleRender();
|
|
775
|
-
if (!configEditorShownEventSent) {
|
|
776
|
-
configEditorShownEventSent = true;
|
|
777
|
-
// One-shot impression event for get_config UI card visibility.
|
|
778
|
-
trackConfigUiEvent('config_editor_shown', {
|
|
779
|
-
tool_name: GET_CONFIG_TOOL_NAME,
|
|
780
|
-
entry_count: payload.entries.length,
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
};
|
|
784
|
-
const refreshConfigFromServer = async () => {
|
|
785
|
-
try {
|
|
786
|
-
const result = await bridge.callTool('get_config', {});
|
|
787
|
-
const payload = controller.extractPayload(result);
|
|
788
|
-
if (payload) {
|
|
789
|
-
applyPayload(payload);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
catch {
|
|
793
|
-
// Best-effort refresh only
|
|
794
|
-
}
|
|
795
|
-
};
|
|
796
|
-
app.ontoolresult = (result) => {
|
|
797
|
-
const payload = controller.extractPayload(result);
|
|
798
|
-
if (payload) {
|
|
799
|
-
applyPayload(payload);
|
|
800
|
-
}
|
|
801
|
-
};
|
|
802
|
-
app.ontoolcancelled = (params) => {
|
|
803
|
-
showTooltip({
|
|
804
|
-
message: params.reason ?? 'Tool was cancelled.',
|
|
805
|
-
tone: 'error',
|
|
806
|
-
});
|
|
807
|
-
};
|
|
808
|
-
void connectWithSharedHostContext({
|
|
809
|
-
app,
|
|
810
|
-
chrome,
|
|
811
|
-
onContextApplied: () => {
|
|
812
|
-
// Config editor should default to expanded in all hosts unless the
|
|
813
|
-
// host forces framed mode.
|
|
814
|
-
if (!chrome.hideSummaryRow) {
|
|
815
|
-
chrome.expanded = true;
|
|
816
|
-
}
|
|
817
|
-
scheduleRender();
|
|
818
|
-
},
|
|
819
|
-
onConnected: async () => {
|
|
820
|
-
const cachedPayload = widgetState.read();
|
|
821
|
-
if (cachedPayload) {
|
|
822
|
-
controller.setPayload(cachedPayload);
|
|
823
|
-
}
|
|
824
|
-
scheduleRender();
|
|
825
|
-
await refreshConfigFromServer();
|
|
826
|
-
},
|
|
827
|
-
}).catch(() => {
|
|
828
|
-
scheduleRender();
|
|
829
|
-
window.setTimeout(() => {
|
|
830
|
-
showTooltip({
|
|
831
|
-
message: 'Failed to connect to host.',
|
|
832
|
-
tone: 'error',
|
|
833
|
-
});
|
|
834
|
-
}, 0);
|
|
835
|
-
});
|
|
836
|
-
window.addEventListener('beforeunload', () => {
|
|
837
|
-
shellController?.dispose();
|
|
838
|
-
clearTooltipTimer();
|
|
839
|
-
}, { once: true });
|
|
840
|
-
}
|