@eclipse-lyra/extension-howto-system 0.0.0
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/contributions/ai-setup-howto-contributions.d.ts +2 -0
- package/dist/contributions/ai-setup-howto-contributions.d.ts.map +1 -0
- package/dist/contributions/onboarding-howto-contributions.d.ts +2 -0
- package/dist/contributions/onboarding-howto-contributions.d.ts.map +1 -0
- package/dist/howto-contribution.d.ts +78 -0
- package/dist/howto-contribution.d.ts.map +1 -0
- package/dist/howto-extension-8r07TyoY.js +1375 -0
- package/dist/howto-extension-8r07TyoY.js.map +1 -0
- package/dist/howto-extension.d.ts +17 -0
- package/dist/howto-extension.d.ts.map +1 -0
- package/dist/howto-panel.d.ts +56 -0
- package/dist/howto-panel.d.ts.map +1 -0
- package/dist/howto-service.d.ts +45 -0
- package/dist/howto-service.d.ts.map +1 -0
- package/dist/i18n.json.d.ts +13 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/package.json +29 -0
|
@@ -0,0 +1,1375 @@
|
|
|
1
|
+
import { subscribe, TOPIC_CONTRIBUTEIONS_CHANGED, contributionRegistry, createLogger, TOPIC_WORKSPACE_CHANGED, TOPIC_WORKSPACE_CONNECTED, watchSignal, activeEditorSignal, partDirtySignal, workspaceService, File, appLoaderService, appSettings, LyraWidget, toastError, commandRegistry, rootContext, registerAll, publish, TOOLBAR_BOTTOM_END } from "@eclipse-lyra/core";
|
|
2
|
+
import { TOPIC_AICONFIG_CHANGED, KEY_AI_CONFIG } from "@eclipse-lyra/extension-ai-system/api";
|
|
3
|
+
import { css, nothing, html } from "lit";
|
|
4
|
+
import { state, customElement } from "lit/decorators.js";
|
|
5
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
6
|
+
import { createRef, ref } from "lit/directives/ref.js";
|
|
7
|
+
const logger$1 = createLogger("HowToService");
|
|
8
|
+
const HOWTO_CONTRIBUTION_TARGET = "system.howtos";
|
|
9
|
+
class HowToService {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.contributions = /* @__PURE__ */ new Map();
|
|
12
|
+
this.loadContributions();
|
|
13
|
+
subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
|
|
14
|
+
if (event.target === HOWTO_CONTRIBUTION_TARGET) {
|
|
15
|
+
this.loadContributions();
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Load contributions from the contribution registry
|
|
21
|
+
*/
|
|
22
|
+
loadContributions() {
|
|
23
|
+
const contributions = contributionRegistry.getContributions(HOWTO_CONTRIBUTION_TARGET);
|
|
24
|
+
this.contributions.clear();
|
|
25
|
+
for (const contribution of contributions) {
|
|
26
|
+
if (!contribution.id) {
|
|
27
|
+
logger$1.warn("HowTo contribution missing id, skipping");
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (!contribution.label) {
|
|
31
|
+
const title2 = typeof contribution.title === "function" ? contribution.title() : contribution.title;
|
|
32
|
+
contribution.label = title2;
|
|
33
|
+
}
|
|
34
|
+
if (!contribution.steps || contribution.steps.length === 0) {
|
|
35
|
+
logger$1.warn(`HowTo contribution "${contribution.id}" has no steps, skipping`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
39
|
+
for (const step of contribution.steps) {
|
|
40
|
+
if (stepIds.has(step.id)) {
|
|
41
|
+
logger$1.warn(`HowTo contribution "${contribution.id}" has duplicate step ID: "${step.id}"`);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
stepIds.add(step.id);
|
|
45
|
+
}
|
|
46
|
+
this.contributions.set(contribution.id, contribution);
|
|
47
|
+
const title = typeof contribution.title === "function" ? contribution.title() : contribution.title;
|
|
48
|
+
logger$1.debug(`Loaded HowTo contribution: ${title} (${contribution.id})`);
|
|
49
|
+
}
|
|
50
|
+
logger$1.info(`Loaded ${this.contributions.size} HowTo contributions`);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get a HowTo Contribution by ID
|
|
54
|
+
*
|
|
55
|
+
* @param contributionId The ID of the contribution
|
|
56
|
+
* @returns The contribution or undefined if not found
|
|
57
|
+
*/
|
|
58
|
+
getContribution(contributionId) {
|
|
59
|
+
return this.contributions.get(contributionId);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get all registered HowTo Contributions
|
|
63
|
+
*
|
|
64
|
+
* @returns Array of all contributions
|
|
65
|
+
*/
|
|
66
|
+
getAllContributions() {
|
|
67
|
+
return Array.from(this.contributions.values());
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get HowTo Contributions by category
|
|
71
|
+
*
|
|
72
|
+
* @param category The category to filter by
|
|
73
|
+
* @returns Array of contributions in the category
|
|
74
|
+
*/
|
|
75
|
+
getContributionsByCategory(category) {
|
|
76
|
+
return Array.from(this.contributions.values()).filter((contrib) => contrib.category === category);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a contribution exists
|
|
80
|
+
*
|
|
81
|
+
* @param contributionId The ID of the contribution
|
|
82
|
+
* @returns True if the contribution exists
|
|
83
|
+
*/
|
|
84
|
+
hasContribution(contributionId) {
|
|
85
|
+
return this.contributions.has(contributionId);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const howToService = new HowToService();
|
|
89
|
+
const ONBOARDING_FILE_NAME = "welcome.txt";
|
|
90
|
+
async function getOnboardingFilePath() {
|
|
91
|
+
const workspace = await workspaceService.getWorkspace();
|
|
92
|
+
if (!workspace) return null;
|
|
93
|
+
const folders = await workspaceService.getFolders();
|
|
94
|
+
if (folders.length === 0) return null;
|
|
95
|
+
const firstFolderName = folders[0].name;
|
|
96
|
+
return `${firstFolderName}/${ONBOARDING_FILE_NAME}`;
|
|
97
|
+
}
|
|
98
|
+
function isEditorContentProvider$1(editor) {
|
|
99
|
+
return editor && typeof editor.getFilePath === "function";
|
|
100
|
+
}
|
|
101
|
+
async function isWorkspaceSelected() {
|
|
102
|
+
return workspaceService.isConnected();
|
|
103
|
+
}
|
|
104
|
+
async function onboardingFileExists() {
|
|
105
|
+
const path = await getOnboardingFilePath();
|
|
106
|
+
if (!path) return false;
|
|
107
|
+
const workspace = await workspaceService.getWorkspace();
|
|
108
|
+
if (!workspace) return false;
|
|
109
|
+
try {
|
|
110
|
+
const resource = await workspace.getResource(path);
|
|
111
|
+
return resource instanceof File;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function isOnboardingFileOpen() {
|
|
117
|
+
const activeEditor = activeEditorSignal.get();
|
|
118
|
+
if (!activeEditor || !isEditorContentProvider$1(activeEditor)) {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
const filePath = activeEditor.getFilePath();
|
|
122
|
+
return filePath === ONBOARDING_FILE_NAME || filePath?.endsWith("/" + ONBOARDING_FILE_NAME) === true;
|
|
123
|
+
}
|
|
124
|
+
function isActiveEditorClean() {
|
|
125
|
+
if (!isOnboardingFileOpen()) return false;
|
|
126
|
+
const activeEditor = activeEditorSignal.get();
|
|
127
|
+
if (!activeEditor) return false;
|
|
128
|
+
return activeEditor.isDirty() === false;
|
|
129
|
+
}
|
|
130
|
+
function isOnboardingFileClosed() {
|
|
131
|
+
return !isOnboardingFileOpen();
|
|
132
|
+
}
|
|
133
|
+
function getAppName$1() {
|
|
134
|
+
const currentApp = appLoaderService.getCurrentApp();
|
|
135
|
+
return currentApp?.name || "AppSpace";
|
|
136
|
+
}
|
|
137
|
+
const onboardingContribution = {
|
|
138
|
+
id: "appspace.onboarding",
|
|
139
|
+
title: () => `Welcome to ${getAppName$1()}`,
|
|
140
|
+
description: () => `Get started with ${getAppName$1()} by learning the basics of workspace and file management`,
|
|
141
|
+
icon: "graduation-cap",
|
|
142
|
+
// label will be set from title in howto-service.ts
|
|
143
|
+
label: "",
|
|
144
|
+
category: "Getting Started",
|
|
145
|
+
initialize: (context) => {
|
|
146
|
+
const cleanups = [];
|
|
147
|
+
subscribe(TOPIC_WORKSPACE_CHANGED, () => {
|
|
148
|
+
context.requestUpdate();
|
|
149
|
+
});
|
|
150
|
+
subscribe(TOPIC_WORKSPACE_CONNECTED, () => {
|
|
151
|
+
context.requestUpdate();
|
|
152
|
+
});
|
|
153
|
+
cleanups.push(
|
|
154
|
+
watchSignal(activeEditorSignal, () => {
|
|
155
|
+
context.requestUpdate();
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
cleanups.push(
|
|
159
|
+
watchSignal(partDirtySignal, () => {
|
|
160
|
+
context.requestUpdate();
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
return () => {
|
|
164
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
steps: [
|
|
168
|
+
{
|
|
169
|
+
id: "create-text-file",
|
|
170
|
+
title: "Create welcome.txt",
|
|
171
|
+
description: `Create a new text file called "welcome.txt" in your workspace. If you don't have a workspace selected, choose one first.`,
|
|
172
|
+
preCondition: async () => {
|
|
173
|
+
return await isWorkspaceSelected();
|
|
174
|
+
},
|
|
175
|
+
postCondition: async () => {
|
|
176
|
+
return await onboardingFileExists();
|
|
177
|
+
},
|
|
178
|
+
command: "touch",
|
|
179
|
+
commandParams: async () => {
|
|
180
|
+
const path = await getOnboardingFilePath();
|
|
181
|
+
return path ? {
|
|
182
|
+
path,
|
|
183
|
+
contents: "Welcome to AppSpace!\n\nThis is your first file. You can edit it and save your changes."
|
|
184
|
+
} : {};
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: "open-text-file",
|
|
189
|
+
title: "Open welcome.txt",
|
|
190
|
+
description: 'Open the "welcome.txt" file in the editor.',
|
|
191
|
+
preCondition: async () => {
|
|
192
|
+
return await onboardingFileExists();
|
|
193
|
+
},
|
|
194
|
+
postCondition: () => {
|
|
195
|
+
return isOnboardingFileOpen();
|
|
196
|
+
},
|
|
197
|
+
command: "open_editor",
|
|
198
|
+
commandParams: async () => {
|
|
199
|
+
const path = await getOnboardingFilePath();
|
|
200
|
+
return path ? { path } : {};
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "edit-and-save",
|
|
205
|
+
title: "Type something and save",
|
|
206
|
+
description: "Type some text in the editor to modify the file, then save it using Ctrl+S or the save button.",
|
|
207
|
+
preCondition: () => {
|
|
208
|
+
return isOnboardingFileOpen();
|
|
209
|
+
},
|
|
210
|
+
postCondition: () => {
|
|
211
|
+
return isActiveEditorClean();
|
|
212
|
+
}
|
|
213
|
+
// No command - user manually edits and saves
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: "close-text-file",
|
|
217
|
+
title: "Close the file",
|
|
218
|
+
description: "Close the editor tab by clicking the X button on the tab.",
|
|
219
|
+
preCondition: () => {
|
|
220
|
+
return isOnboardingFileOpen();
|
|
221
|
+
},
|
|
222
|
+
postCondition: () => {
|
|
223
|
+
return isOnboardingFileClosed();
|
|
224
|
+
}
|
|
225
|
+
// No command - user manually closes the tab
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
};
|
|
229
|
+
contributionRegistry.registerContribution(HOWTO_CONTRIBUTION_TARGET, onboardingContribution);
|
|
230
|
+
const AI_CONFIG_EDITOR_KEY = ".system.ai-config";
|
|
231
|
+
function isEditorContentProvider(editor) {
|
|
232
|
+
return editor && typeof editor.getFilePath === "function";
|
|
233
|
+
}
|
|
234
|
+
function isAIConfigEditorOpen() {
|
|
235
|
+
const activeEditor = activeEditorSignal.get();
|
|
236
|
+
if (!activeEditor || !isEditorContentProvider(activeEditor)) {
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
const filePath = activeEditor.getFilePath();
|
|
240
|
+
return filePath === AI_CONFIG_EDITOR_KEY;
|
|
241
|
+
}
|
|
242
|
+
async function isLLMProviderConfigured() {
|
|
243
|
+
try {
|
|
244
|
+
const config = await appSettings.get(KEY_AI_CONFIG);
|
|
245
|
+
if (!config || !config.defaultProvider) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
const defaultProvider = config.providers?.find((p) => p.name === config.defaultProvider);
|
|
249
|
+
if (!defaultProvider) {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
return !!defaultProvider.apiKey && defaultProvider.apiKey.trim() !== "";
|
|
253
|
+
} catch {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
function isAIConfigEditorDirty() {
|
|
258
|
+
const activeEditor = activeEditorSignal.get();
|
|
259
|
+
if (!activeEditor || !isEditorContentProvider(activeEditor)) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
const filePath = activeEditor.getFilePath();
|
|
263
|
+
if (filePath !== AI_CONFIG_EDITOR_KEY) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return activeEditor.isDirty() === true;
|
|
267
|
+
}
|
|
268
|
+
function isAIConfigEditorSaved() {
|
|
269
|
+
if (!isAIConfigEditorOpen()) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return !isAIConfigEditorDirty();
|
|
273
|
+
}
|
|
274
|
+
function isAIConfigEditorClosed() {
|
|
275
|
+
return !isAIConfigEditorOpen();
|
|
276
|
+
}
|
|
277
|
+
async function hasTypedInChat() {
|
|
278
|
+
try {
|
|
279
|
+
const sessions = await appSettings.get("aiChatSessions");
|
|
280
|
+
if (!sessions || typeof sessions !== "object") {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
for (const sessionId in sessions) {
|
|
284
|
+
const session = sessions[sessionId];
|
|
285
|
+
if (session?.history && Array.isArray(session.history)) {
|
|
286
|
+
const hasUserMessage = session.history.some(
|
|
287
|
+
(msg) => msg.role === "user" && msg.content && msg.content.trim() !== ""
|
|
288
|
+
);
|
|
289
|
+
if (hasUserMessage) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return false;
|
|
295
|
+
} catch {
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function getAppName() {
|
|
300
|
+
const currentApp = appLoaderService.getCurrentApp();
|
|
301
|
+
return currentApp?.name || "AppSpace";
|
|
302
|
+
}
|
|
303
|
+
const aiSetupContribution = {
|
|
304
|
+
id: "appspace.ai-setup",
|
|
305
|
+
title: () => `Set up AI in ${getAppName()}`,
|
|
306
|
+
description: () => `Configure an LLM provider to enable AI chat features in ${getAppName()}`,
|
|
307
|
+
icon: "robot",
|
|
308
|
+
label: "",
|
|
309
|
+
category: "Getting Started",
|
|
310
|
+
initialize: (context) => {
|
|
311
|
+
const cleanups = [];
|
|
312
|
+
cleanups.push(
|
|
313
|
+
watchSignal(activeEditorSignal, () => {
|
|
314
|
+
context.requestUpdate();
|
|
315
|
+
})
|
|
316
|
+
);
|
|
317
|
+
cleanups.push(
|
|
318
|
+
watchSignal(partDirtySignal, () => {
|
|
319
|
+
context.requestUpdate();
|
|
320
|
+
})
|
|
321
|
+
);
|
|
322
|
+
subscribe(TOPIC_AICONFIG_CHANGED, () => {
|
|
323
|
+
context.requestUpdate();
|
|
324
|
+
});
|
|
325
|
+
return () => {
|
|
326
|
+
cleanups.forEach((cleanup) => cleanup());
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
steps: [
|
|
330
|
+
{
|
|
331
|
+
id: "open-ai-settings",
|
|
332
|
+
title: "Open AI Settings",
|
|
333
|
+
description: "Open the AI settings editor by clicking the robot icon in the toolbar or using the command palette.",
|
|
334
|
+
preCondition: () => true,
|
|
335
|
+
// Always available
|
|
336
|
+
postCondition: () => isAIConfigEditorOpen(),
|
|
337
|
+
command: "open_ai_config"
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: "configure-llm-provider",
|
|
341
|
+
title: "Configure LLM Provider",
|
|
342
|
+
description: "Select a provider as default and enter an API key. Make sure to save your changes using Ctrl+S or the save button.",
|
|
343
|
+
preCondition: () => isAIConfigEditorOpen(),
|
|
344
|
+
postCondition: async () => {
|
|
345
|
+
const configured = await isLLMProviderConfigured();
|
|
346
|
+
const saved = isAIConfigEditorSaved();
|
|
347
|
+
return configured && saved;
|
|
348
|
+
}
|
|
349
|
+
// No command - user manually configures in the editor
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: "save-and-close",
|
|
353
|
+
title: "Save and Close",
|
|
354
|
+
description: "Save your changes (if not already saved) and close the AI settings editor tab.",
|
|
355
|
+
preCondition: () => isAIConfigEditorOpen(),
|
|
356
|
+
postCondition: () => isAIConfigEditorClosed()
|
|
357
|
+
// No command - user manually saves and closes the tab
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
id: "type-in-chat",
|
|
361
|
+
title: "Type in Chat",
|
|
362
|
+
description: "Open the AI chat view (if not already open) and type a message to test your AI configuration.",
|
|
363
|
+
preCondition: async () => await isLLMProviderConfigured(),
|
|
364
|
+
postCondition: async () => await hasTypedInChat()
|
|
365
|
+
// No command - user manually types in chat
|
|
366
|
+
}
|
|
367
|
+
]
|
|
368
|
+
};
|
|
369
|
+
contributionRegistry.registerContribution(HOWTO_CONTRIBUTION_TARGET, aiSetupContribution);
|
|
370
|
+
var __defProp = Object.defineProperty;
|
|
371
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
372
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
373
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
374
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
375
|
+
if (decorator = decorators[i])
|
|
376
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
377
|
+
if (kind && result) __defProp(target, key, result);
|
|
378
|
+
return result;
|
|
379
|
+
};
|
|
380
|
+
const TOPIC_SHOW_HOWTO_PANEL = "howto/show-panel";
|
|
381
|
+
const TOPIC_TOGGLE_HOWTO_PANEL = "howto/toggle-panel";
|
|
382
|
+
const SETTINGS_KEY = "howto-panel";
|
|
383
|
+
const DEFAULT_POSITION = { x: 100, y: 100 };
|
|
384
|
+
const DEFAULT_PANEL_SIZE = { width: 400, height: 300 };
|
|
385
|
+
let LyraHowToPanel = class extends LyraWidget {
|
|
386
|
+
constructor() {
|
|
387
|
+
super(...arguments);
|
|
388
|
+
this.contributions = [];
|
|
389
|
+
this.activeContributionId = null;
|
|
390
|
+
this.stepStates = /* @__PURE__ */ new Map();
|
|
391
|
+
this.isMinimized = false;
|
|
392
|
+
this.isVisible = false;
|
|
393
|
+
this.positionX = DEFAULT_POSITION.x;
|
|
394
|
+
this.positionY = DEFAULT_POSITION.y;
|
|
395
|
+
this.isDragging = false;
|
|
396
|
+
this.dragPreviewPosition = null;
|
|
397
|
+
this.dragStartPosition = { x: 0, y: 0 };
|
|
398
|
+
this.panelRef = createRef();
|
|
399
|
+
this.handleDragStart = (e) => {
|
|
400
|
+
const target = e.target;
|
|
401
|
+
if (this.isDragTarget(target) || !this.panelRef.value) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
const rect = this.panelRef.value.getBoundingClientRect();
|
|
405
|
+
this.dragStartPosition = {
|
|
406
|
+
x: e.clientX - rect.left,
|
|
407
|
+
y: e.clientY - rect.top
|
|
408
|
+
};
|
|
409
|
+
this.isDragging = true;
|
|
410
|
+
this.dragPreviewPosition = { x: this.positionX, y: this.positionY };
|
|
411
|
+
this.requestUpdate();
|
|
412
|
+
e.preventDefault();
|
|
413
|
+
e.stopPropagation();
|
|
414
|
+
};
|
|
415
|
+
this.handleDragMove = (e) => {
|
|
416
|
+
if (!this.isDragging || !this.dragPreviewPosition) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const bounds = this.getViewportBounds();
|
|
420
|
+
const newX = Math.max(0, Math.min(e.clientX - this.dragStartPosition.x, bounds.maxX));
|
|
421
|
+
const newY = Math.max(0, Math.min(e.clientY - this.dragStartPosition.y, bounds.maxY));
|
|
422
|
+
if (this.dragPreviewPosition.x !== newX || this.dragPreviewPosition.y !== newY) {
|
|
423
|
+
this.dragPreviewPosition = { x: newX, y: newY };
|
|
424
|
+
this.requestUpdate();
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
this.handleDragEnd = async () => {
|
|
428
|
+
if (!this.isDragging || !this.dragPreviewPosition) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
this.isDragging = false;
|
|
432
|
+
const previewPos = this.dragPreviewPosition;
|
|
433
|
+
this.dragPreviewPosition = null;
|
|
434
|
+
this.positionX = previewPos.x;
|
|
435
|
+
this.positionY = previewPos.y;
|
|
436
|
+
await this.saveSettings();
|
|
437
|
+
this.requestUpdate();
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
async doBeforeUI() {
|
|
441
|
+
this.loadContributions();
|
|
442
|
+
subscribe(TOPIC_CONTRIBUTEIONS_CHANGED, (event) => {
|
|
443
|
+
if (event.target === HOWTO_CONTRIBUTION_TARGET) {
|
|
444
|
+
this.loadContributions();
|
|
445
|
+
this.requestUpdate();
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
subscribe(TOPIC_SHOW_HOWTO_PANEL, () => this.showPanel());
|
|
449
|
+
subscribe(TOPIC_TOGGLE_HOWTO_PANEL, () => this.toggleVisibility());
|
|
450
|
+
await this.loadSettings();
|
|
451
|
+
const hasWorkspace = workspaceService.isConnected();
|
|
452
|
+
if (!hasWorkspace && this.isVisible === false) {
|
|
453
|
+
const settings = await appSettings.getDialogSetting(SETTINGS_KEY);
|
|
454
|
+
if (!settings || settings.visible === void 0) {
|
|
455
|
+
this.isVisible = true;
|
|
456
|
+
await this.saveSettings();
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
subscribe(TOPIC_WORKSPACE_CONNECTED, () => {
|
|
460
|
+
const checkAutoHide = async () => {
|
|
461
|
+
const settings = await appSettings.getDialogSetting(SETTINGS_KEY);
|
|
462
|
+
if (!settings || settings.visible === void 0) ;
|
|
463
|
+
};
|
|
464
|
+
checkAutoHide();
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
doInitUI() {
|
|
468
|
+
this.boundHandleDragMove = this.handleDragMove.bind(this);
|
|
469
|
+
this.boundHandleDragEnd = this.handleDragEnd.bind(this);
|
|
470
|
+
document.addEventListener("mousemove", this.boundHandleDragMove);
|
|
471
|
+
document.addEventListener("mouseup", this.boundHandleDragEnd);
|
|
472
|
+
}
|
|
473
|
+
firstUpdated(_changedProperties) {
|
|
474
|
+
super.firstUpdated(_changedProperties);
|
|
475
|
+
}
|
|
476
|
+
loadContributions() {
|
|
477
|
+
this.contributions = howToService.getAllContributions();
|
|
478
|
+
}
|
|
479
|
+
async loadSettings() {
|
|
480
|
+
const settings = await appSettings.getDialogSetting(SETTINGS_KEY);
|
|
481
|
+
if (settings) {
|
|
482
|
+
if (settings.position) {
|
|
483
|
+
this.positionX = settings.position.x || DEFAULT_POSITION.x;
|
|
484
|
+
this.positionY = settings.position.y || DEFAULT_POSITION.y;
|
|
485
|
+
}
|
|
486
|
+
if (settings.visible !== void 0) {
|
|
487
|
+
this.isVisible = settings.visible;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
async saveSettings() {
|
|
492
|
+
await appSettings.setDialogSetting(SETTINGS_KEY, {
|
|
493
|
+
position: { x: this.positionX, y: this.positionY },
|
|
494
|
+
visible: this.isVisible
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
isDragTarget(target) {
|
|
498
|
+
return !!(target.closest(".header-actions") || target.closest("wa-button"));
|
|
499
|
+
}
|
|
500
|
+
getViewportBounds() {
|
|
501
|
+
const panelWidth = this.panelRef.value?.offsetWidth || DEFAULT_PANEL_SIZE.width;
|
|
502
|
+
const panelHeight = this.panelRef.value?.offsetHeight || DEFAULT_PANEL_SIZE.height;
|
|
503
|
+
return {
|
|
504
|
+
maxX: window.innerWidth - panelWidth,
|
|
505
|
+
maxY: window.innerHeight - panelHeight
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
async startHowTo(contributionId) {
|
|
509
|
+
const contribution = howToService.getContribution(contributionId);
|
|
510
|
+
if (!contribution) {
|
|
511
|
+
toastError(`HowTo "${contributionId}" not found`);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
this.cleanupHowTo();
|
|
515
|
+
this.activeContributionId = contributionId;
|
|
516
|
+
this.isMinimized = false;
|
|
517
|
+
const states = contribution.steps.map((step) => ({
|
|
518
|
+
step,
|
|
519
|
+
status: "pending"
|
|
520
|
+
}));
|
|
521
|
+
this.stepStates.set(contributionId, states);
|
|
522
|
+
if (contribution.initialize) {
|
|
523
|
+
const context = {
|
|
524
|
+
requestUpdate: () => this.recheckActiveStepConditions(),
|
|
525
|
+
contributionId
|
|
526
|
+
};
|
|
527
|
+
this.howtoCleanup = contribution.initialize(context) || void 0;
|
|
528
|
+
}
|
|
529
|
+
this.requestUpdate();
|
|
530
|
+
await this.checkPreConditions(contributionId, 0);
|
|
531
|
+
}
|
|
532
|
+
cleanupHowTo() {
|
|
533
|
+
if (this.howtoCleanup) {
|
|
534
|
+
this.howtoCleanup();
|
|
535
|
+
this.howtoCleanup = void 0;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
getStepState(contributionId, stepIndex) {
|
|
539
|
+
const states = this.stepStates.get(contributionId);
|
|
540
|
+
return states && stepIndex < states.length ? states[stepIndex] : null;
|
|
541
|
+
}
|
|
542
|
+
async checkPreConditions(contributionId, stepIndex) {
|
|
543
|
+
const state2 = this.getStepState(contributionId, stepIndex);
|
|
544
|
+
if (!state2) return;
|
|
545
|
+
if (!state2.step.preCondition) {
|
|
546
|
+
state2.preConditionMet = true;
|
|
547
|
+
this.requestUpdate();
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
try {
|
|
551
|
+
state2.preConditionMet = await state2.step.preCondition();
|
|
552
|
+
this.requestUpdate();
|
|
553
|
+
} catch (error) {
|
|
554
|
+
console.error(`Pre-condition check failed for step ${state2.step.id}:`, error);
|
|
555
|
+
state2.preConditionMet = false;
|
|
556
|
+
this.requestUpdate();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async checkPostConditions(contributionId, stepIndex) {
|
|
560
|
+
const state2 = this.getStepState(contributionId, stepIndex);
|
|
561
|
+
if (!state2) return;
|
|
562
|
+
if (!state2.step.postCondition) {
|
|
563
|
+
this.completeStep(contributionId, stepIndex);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
try {
|
|
567
|
+
const result = await state2.step.postCondition();
|
|
568
|
+
state2.postConditionMet = result;
|
|
569
|
+
state2.status = result ? "completed" : "failed";
|
|
570
|
+
if (result) {
|
|
571
|
+
this.activateNextStep(contributionId, stepIndex);
|
|
572
|
+
}
|
|
573
|
+
this.requestUpdate();
|
|
574
|
+
} catch (error) {
|
|
575
|
+
console.error(`Post-condition check failed for step ${state2.step.id}:`, error);
|
|
576
|
+
state2.postConditionMet = false;
|
|
577
|
+
state2.status = "failed";
|
|
578
|
+
this.requestUpdate();
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
completeStep(contributionId, stepIndex) {
|
|
582
|
+
const state2 = this.getStepState(contributionId, stepIndex);
|
|
583
|
+
if (!state2) return;
|
|
584
|
+
state2.status = "completed";
|
|
585
|
+
this.activateNextStep(contributionId, stepIndex);
|
|
586
|
+
this.requestUpdate();
|
|
587
|
+
}
|
|
588
|
+
async activateNextStep(contributionId, stepIndex) {
|
|
589
|
+
const states = this.stepStates.get(contributionId);
|
|
590
|
+
if (!states || stepIndex + 1 >= states.length) return;
|
|
591
|
+
const nextState = states[stepIndex + 1];
|
|
592
|
+
nextState.status = "active";
|
|
593
|
+
await this.checkPreConditions(contributionId, stepIndex + 1);
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Re-checks conditions for active and pending steps when workspace or editor state changes
|
|
597
|
+
*/
|
|
598
|
+
async recheckActiveStepConditions() {
|
|
599
|
+
if (!this.activeContributionId) return;
|
|
600
|
+
const states = this.stepStates.get(this.activeContributionId);
|
|
601
|
+
if (!states) return;
|
|
602
|
+
const activeStepIndex = states.findIndex((state2) => state2.status === "active");
|
|
603
|
+
if (activeStepIndex !== -1) {
|
|
604
|
+
const activeState = states[activeStepIndex];
|
|
605
|
+
const step = activeState.step;
|
|
606
|
+
if (step.postCondition) {
|
|
607
|
+
try {
|
|
608
|
+
const result = await step.postCondition();
|
|
609
|
+
if (result && activeState.status === "active") {
|
|
610
|
+
await this.checkPostConditions(this.activeContributionId, activeStepIndex);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
} catch (error) {
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
for (let i = 0; i < states.length; i++) {
|
|
618
|
+
const state2 = states[i];
|
|
619
|
+
if (state2.status === "pending" && state2.step.preCondition) {
|
|
620
|
+
await this.checkPreConditions(this.activeContributionId, i);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
this.requestUpdate();
|
|
624
|
+
}
|
|
625
|
+
async executeStep(contributionId, stepIndex) {
|
|
626
|
+
const state2 = this.getStepState(contributionId, stepIndex);
|
|
627
|
+
if (!state2) return;
|
|
628
|
+
if (!await this.validatePreConditions(state2, contributionId, stepIndex)) {
|
|
629
|
+
return;
|
|
630
|
+
}
|
|
631
|
+
state2.status = "active";
|
|
632
|
+
this.requestUpdate();
|
|
633
|
+
if (state2.step.command && !await this.executeStepCommand(state2)) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
await this.checkPostConditions(contributionId, stepIndex);
|
|
637
|
+
}
|
|
638
|
+
async validatePreConditions(state2, contributionId, stepIndex) {
|
|
639
|
+
if (!state2.step.preCondition) {
|
|
640
|
+
return true;
|
|
641
|
+
}
|
|
642
|
+
if (state2.preConditionMet === void 0 || state2.preConditionMet === false) {
|
|
643
|
+
await this.checkPreConditions(contributionId, stepIndex);
|
|
644
|
+
}
|
|
645
|
+
if (state2.preConditionMet !== true) {
|
|
646
|
+
toastError(`Pre-conditions not met for step: ${state2.step.title}`);
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
return true;
|
|
650
|
+
}
|
|
651
|
+
async executeStepCommand(state2) {
|
|
652
|
+
if (!state2.step.command) {
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
try {
|
|
656
|
+
const rawParams = state2.step.commandParams;
|
|
657
|
+
const params = typeof rawParams === "function" ? await rawParams() : rawParams || {};
|
|
658
|
+
const execContext = commandRegistry.createExecutionContext(params);
|
|
659
|
+
await commandRegistry.execute(state2.step.command, execContext);
|
|
660
|
+
return true;
|
|
661
|
+
} catch (error) {
|
|
662
|
+
console.error(`Failed to execute command for step ${state2.step.id}:`, error);
|
|
663
|
+
toastError(`Failed to execute step: ${state2.step.title}`);
|
|
664
|
+
state2.status = "failed";
|
|
665
|
+
this.requestUpdate();
|
|
666
|
+
return false;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
async runStepCommand(contributionId, stepIndex) {
|
|
670
|
+
const state2 = this.getStepState(contributionId, stepIndex);
|
|
671
|
+
if (!state2 || !state2.step.command) {
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (state2.step.preCondition) {
|
|
675
|
+
const preConditionMet = await state2.step.preCondition();
|
|
676
|
+
if (!preConditionMet) {
|
|
677
|
+
toastError(`Pre-conditions not met for step: ${state2.step.title}`);
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const success = await this.executeStepCommand(state2);
|
|
682
|
+
if (success) {
|
|
683
|
+
await this.checkPostConditions(contributionId, stepIndex);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
skipStep(contributionId, stepIndex) {
|
|
687
|
+
const state2 = this.getStepState(contributionId, stepIndex);
|
|
688
|
+
if (!state2 || !state2.step.optional) return;
|
|
689
|
+
state2.status = "skipped";
|
|
690
|
+
this.activateNextStep(contributionId, stepIndex);
|
|
691
|
+
this.requestUpdate();
|
|
692
|
+
}
|
|
693
|
+
closeHowTo() {
|
|
694
|
+
this.cleanupHowTo();
|
|
695
|
+
this.activeContributionId = null;
|
|
696
|
+
this.stepStates.clear();
|
|
697
|
+
this.requestUpdate();
|
|
698
|
+
}
|
|
699
|
+
toggleMinimize() {
|
|
700
|
+
this.isMinimized = !this.isMinimized;
|
|
701
|
+
this.requestUpdate();
|
|
702
|
+
}
|
|
703
|
+
async showPanel() {
|
|
704
|
+
this.isVisible = true;
|
|
705
|
+
this.isMinimized = false;
|
|
706
|
+
await this.saveSettings();
|
|
707
|
+
this.requestUpdate();
|
|
708
|
+
}
|
|
709
|
+
async hidePanel() {
|
|
710
|
+
this.isVisible = false;
|
|
711
|
+
await this.saveSettings();
|
|
712
|
+
this.requestUpdate();
|
|
713
|
+
}
|
|
714
|
+
async toggleVisibility() {
|
|
715
|
+
if (this.isVisible) {
|
|
716
|
+
await this.hidePanel();
|
|
717
|
+
} else {
|
|
718
|
+
await this.showPanel();
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
renderStep(state2, index, contributionId) {
|
|
722
|
+
const { step, status, preConditionMet, postConditionMet } = state2;
|
|
723
|
+
const isActive = status === "active";
|
|
724
|
+
const isCompleted = status === "completed";
|
|
725
|
+
const isFailed = status === "failed";
|
|
726
|
+
const isPending = status === "pending";
|
|
727
|
+
const isSkipped = status === "skipped";
|
|
728
|
+
return html`
|
|
729
|
+
<div class="step ${status}" ?data-active=${isActive}>
|
|
730
|
+
<div class="step-header">
|
|
731
|
+
<div class="step-number">${index + 1}</div>
|
|
732
|
+
<div class="step-title">${step.title}</div>
|
|
733
|
+
<div class="step-status">
|
|
734
|
+
${step.command ? html`
|
|
735
|
+
<wa-button
|
|
736
|
+
size="small"
|
|
737
|
+
appearance="plain"
|
|
738
|
+
@click=${(e) => {
|
|
739
|
+
e.stopPropagation();
|
|
740
|
+
this.runStepCommand(contributionId, index);
|
|
741
|
+
}}
|
|
742
|
+
title="Run step command"
|
|
743
|
+
>
|
|
744
|
+
<wa-icon name="play"></wa-icon>
|
|
745
|
+
</wa-button>
|
|
746
|
+
` : nothing}
|
|
747
|
+
${isCompleted ? html`<wa-icon name="check-circle" class="status-icon completed"></wa-icon>` : nothing}
|
|
748
|
+
${isFailed ? html`<wa-icon name="xmark-circle" class="status-icon failed"></wa-icon>` : nothing}
|
|
749
|
+
${isSkipped ? html`<wa-icon name="minus-circle" class="status-icon skipped"></wa-icon>` : nothing}
|
|
750
|
+
${isPending ? html`<wa-icon name="circle" class="status-icon pending"></wa-icon>` : nothing}
|
|
751
|
+
${isActive ? html`<wa-icon name="play-circle" class="status-icon active"></wa-icon>` : nothing}
|
|
752
|
+
</div>
|
|
753
|
+
</div>
|
|
754
|
+
<div class="step-description">${step.description}</div>
|
|
755
|
+
${step.preCondition && preConditionMet !== void 0 ? html`
|
|
756
|
+
<div class="condition pre-condition ${preConditionMet ? "met" : "not-met"}">
|
|
757
|
+
<wa-icon name="${preConditionMet ? "check" : "xmark"}"></wa-icon>
|
|
758
|
+
<span>Pre-condition: ${preConditionMet ? "Met" : "Not met"}</span>
|
|
759
|
+
</div>
|
|
760
|
+
` : nothing}
|
|
761
|
+
${step.postCondition && postConditionMet !== void 0 ? html`
|
|
762
|
+
<div class="condition post-condition ${postConditionMet ? "met" : "not-met"}">
|
|
763
|
+
<wa-icon name="${postConditionMet ? "check" : "xmark"}"></wa-icon>
|
|
764
|
+
<span>Post-condition: ${postConditionMet ? "Met" : "Not met"}</span>
|
|
765
|
+
</div>
|
|
766
|
+
` : nothing}
|
|
767
|
+
${isActive && step.optional ? html`
|
|
768
|
+
<div class="step-actions">
|
|
769
|
+
<wa-button size="small" appearance="outline" @click=${() => this.skipStep(contributionId, index)}>
|
|
770
|
+
<wa-icon name="forward"></wa-icon>
|
|
771
|
+
Skip
|
|
772
|
+
</wa-button>
|
|
773
|
+
</div>
|
|
774
|
+
` : nothing}
|
|
775
|
+
</div>
|
|
776
|
+
`;
|
|
777
|
+
}
|
|
778
|
+
render() {
|
|
779
|
+
if (!this.isVisible) {
|
|
780
|
+
return nothing;
|
|
781
|
+
}
|
|
782
|
+
const activeContribution = this.activeContributionId ? howToService.getContribution(this.activeContributionId) : null;
|
|
783
|
+
const activeStepStates = this.activeContributionId ? this.stepStates.get(this.activeContributionId) || [] : [];
|
|
784
|
+
return html`
|
|
785
|
+
${this.dragPreviewPosition ? html`
|
|
786
|
+
<div
|
|
787
|
+
class="howto-panel-drag-preview"
|
|
788
|
+
style=${styleMap({
|
|
789
|
+
left: `${this.dragPreviewPosition.x}px`,
|
|
790
|
+
top: `${this.dragPreviewPosition.y}px`,
|
|
791
|
+
width: `${this.panelRef.value?.offsetWidth || DEFAULT_PANEL_SIZE.width}px`,
|
|
792
|
+
height: `${this.panelRef.value?.offsetHeight || DEFAULT_PANEL_SIZE.height}px`,
|
|
793
|
+
display: "block",
|
|
794
|
+
visibility: "visible"
|
|
795
|
+
})}
|
|
796
|
+
></div>
|
|
797
|
+
` : nothing}
|
|
798
|
+
<div
|
|
799
|
+
class="howto-panel ${this.isMinimized ? "minimized" : ""} ${this.dragPreviewPosition ? "dragging" : ""}"
|
|
800
|
+
style=${styleMap({
|
|
801
|
+
left: `${this.positionX}px`,
|
|
802
|
+
top: `${this.positionY}px`,
|
|
803
|
+
transform: "translateZ(0)"
|
|
804
|
+
})}
|
|
805
|
+
${ref(this.panelRef)}
|
|
806
|
+
>
|
|
807
|
+
<div class="panel-header" @mousedown=${this.handleDragStart}>
|
|
808
|
+
<div class="header-title">
|
|
809
|
+
<wa-icon name="list-check"></wa-icon>
|
|
810
|
+
<span>HowTo Workflows</span>
|
|
811
|
+
</div>
|
|
812
|
+
<div class="header-actions" @mousedown=${(e) => e.stopPropagation()}>
|
|
813
|
+
<wa-button
|
|
814
|
+
size="small"
|
|
815
|
+
appearance="plain"
|
|
816
|
+
@click=${this.toggleMinimize}
|
|
817
|
+
title="${this.isMinimized ? "Expand" : "Minimize"}"
|
|
818
|
+
>
|
|
819
|
+
<wa-icon name="${this.isMinimized ? "chevron-up" : "chevron-down"}"></wa-icon>
|
|
820
|
+
</wa-button>
|
|
821
|
+
<wa-button
|
|
822
|
+
size="small"
|
|
823
|
+
appearance="plain"
|
|
824
|
+
@click=${this.hidePanel}
|
|
825
|
+
title="Hide Panel"
|
|
826
|
+
>
|
|
827
|
+
<wa-icon name="xmark"></wa-icon>
|
|
828
|
+
</wa-button>
|
|
829
|
+
</div>
|
|
830
|
+
</div>
|
|
831
|
+
|
|
832
|
+
${!this.isMinimized ? html`
|
|
833
|
+
<div class="panel-content">
|
|
834
|
+
${activeContribution ? html`
|
|
835
|
+
<div class="active-workflow">
|
|
836
|
+
<div class="workflow-header">
|
|
837
|
+
<div class="workflow-title-section">
|
|
838
|
+
<h3>${typeof activeContribution.title === "function" ? activeContribution.title() : activeContribution.title}</h3>
|
|
839
|
+
${activeContribution.description ? html`
|
|
840
|
+
<p class="workflow-description">${typeof activeContribution.description === "function" ? activeContribution.description() : activeContribution.description}</p>
|
|
841
|
+
` : nothing}
|
|
842
|
+
</div>
|
|
843
|
+
<wa-button
|
|
844
|
+
size="small"
|
|
845
|
+
appearance="plain"
|
|
846
|
+
@click=${this.closeHowTo}
|
|
847
|
+
title="Close HowTo"
|
|
848
|
+
>
|
|
849
|
+
<wa-icon name="xmark"></wa-icon>
|
|
850
|
+
</wa-button>
|
|
851
|
+
</div>
|
|
852
|
+
<div class="steps-list">
|
|
853
|
+
${activeStepStates.map(
|
|
854
|
+
(state2, index) => this.renderStep(state2, index, this.activeContributionId)
|
|
855
|
+
)}
|
|
856
|
+
</div>
|
|
857
|
+
</div>
|
|
858
|
+
` : html`
|
|
859
|
+
<div class="workflows-list">
|
|
860
|
+
<h3>Available Workflows</h3>
|
|
861
|
+
${this.contributions.length === 0 ? html`
|
|
862
|
+
<div class="empty-state">
|
|
863
|
+
<wa-icon name="list-check" style="font-size: 2em; opacity: 0.5; margin-bottom: 12px;"></wa-icon>
|
|
864
|
+
<p>No HowTo workflows available yet.</p>
|
|
865
|
+
<p style="font-size: 0.9em; opacity: 0.7;">Extensions can register workflows via the contribution registry.</p>
|
|
866
|
+
</div>
|
|
867
|
+
` : this.contributions.map((contrib) => {
|
|
868
|
+
const title = typeof contrib.title === "function" ? contrib.title() : contrib.title;
|
|
869
|
+
const description = contrib.description ? typeof contrib.description === "function" ? contrib.description() : contrib.description : null;
|
|
870
|
+
return html`
|
|
871
|
+
<div class="workflow-item" @click=${() => this.startHowTo(contrib.id)}>
|
|
872
|
+
${contrib.icon ? html`
|
|
873
|
+
<wa-icon name="${contrib.icon}"></wa-icon>
|
|
874
|
+
` : html`
|
|
875
|
+
<wa-icon name="list-check"></wa-icon>
|
|
876
|
+
`}
|
|
877
|
+
<div class="workflow-info">
|
|
878
|
+
<div class="workflow-title">${title}</div>
|
|
879
|
+
${description ? html`
|
|
880
|
+
<div class="workflow-desc">${description}</div>
|
|
881
|
+
` : nothing}
|
|
882
|
+
<div class="workflow-meta">${contrib.steps.length} step${contrib.steps.length !== 1 ? "s" : ""}</div>
|
|
883
|
+
</div>
|
|
884
|
+
</div>
|
|
885
|
+
`;
|
|
886
|
+
})}
|
|
887
|
+
</div>
|
|
888
|
+
`}
|
|
889
|
+
</div>
|
|
890
|
+
` : nothing}
|
|
891
|
+
</div>
|
|
892
|
+
`;
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
LyraHowToPanel.styles = css`
|
|
896
|
+
:host {
|
|
897
|
+
display: block;
|
|
898
|
+
position: fixed;
|
|
899
|
+
z-index: 10000;
|
|
900
|
+
pointer-events: none;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.howto-panel-drag-preview {
|
|
904
|
+
position: fixed !important;
|
|
905
|
+
border: 3px dashed var(--wa-color-primary-50, #0066cc) !important;
|
|
906
|
+
background: var(--wa-color-primary-05, rgba(0, 102, 204, 0.05)) !important;
|
|
907
|
+
border-radius: var(--wa-border-radius-medium, 8px);
|
|
908
|
+
z-index: 10001 !important;
|
|
909
|
+
pointer-events: none !important;
|
|
910
|
+
opacity: 0.8 !important;
|
|
911
|
+
box-sizing: border-box;
|
|
912
|
+
display: block !important;
|
|
913
|
+
visibility: visible !important;
|
|
914
|
+
min-width: 100px;
|
|
915
|
+
min-height: 100px;
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
:host-context(.wa-light) .howto-panel-drag-preview {
|
|
919
|
+
background: var(--wa-color-primary-95);
|
|
920
|
+
border-color: var(--wa-color-primary-50);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.howto-panel {
|
|
924
|
+
position: fixed !important;
|
|
925
|
+
width: 400px;
|
|
926
|
+
max-height: 600px;
|
|
927
|
+
background: var(--wa-color-surface-raised, var(--wa-color-neutral-05));
|
|
928
|
+
border: var(--wa-border-width-s, 1px) solid var(--wa-color-neutral-border-loud, var(--wa-color-neutral-25));
|
|
929
|
+
border-radius: var(--wa-border-radius-medium, 8px);
|
|
930
|
+
box-shadow: var(--wa-shadow-large, 0 8px 24px rgba(0, 0, 0, 0.8));
|
|
931
|
+
pointer-events: all;
|
|
932
|
+
display: flex;
|
|
933
|
+
flex-direction: column;
|
|
934
|
+
overflow: hidden;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
:host-context(.wa-light) .howto-panel {
|
|
938
|
+
background: var(--wa-color-surface-raised, var(--wa-color-neutral-95));
|
|
939
|
+
border-color: var(--wa-color-neutral-border-loud, var(--wa-color-neutral-75));
|
|
940
|
+
box-shadow: var(--wa-shadow-large, 0 8px 24px rgba(0, 0, 0, 0.2));
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.howto-panel.minimized {
|
|
944
|
+
max-height: auto;
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
.howto-panel.dragging {
|
|
948
|
+
opacity: 0.5;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
.panel-header {
|
|
952
|
+
display: flex;
|
|
953
|
+
align-items: center;
|
|
954
|
+
justify-content: space-between;
|
|
955
|
+
padding: var(--wa-spacing-medium, 12px) var(--wa-spacing-large, 16px);
|
|
956
|
+
background: var(--wa-color-surface-lowered, var(--wa-color-neutral-10));
|
|
957
|
+
border-bottom: var(--wa-border-width-s, 1px) solid var(--wa-color-neutral-border-loud, var(--wa-color-neutral-25));
|
|
958
|
+
cursor: move;
|
|
959
|
+
user-select: none;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
:host-context(.wa-light) .panel-header {
|
|
963
|
+
background: var(--wa-color-surface-lowered, var(--wa-color-neutral-90));
|
|
964
|
+
border-bottom-color: var(--wa-color-neutral-border-loud, var(--wa-color-neutral-75));
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
.header-title {
|
|
968
|
+
display: flex;
|
|
969
|
+
align-items: center;
|
|
970
|
+
gap: var(--wa-spacing-small, 8px);
|
|
971
|
+
font-weight: 600;
|
|
972
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-90));
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
:host-context(.wa-light) .header-title {
|
|
976
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-10));
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.header-actions {
|
|
980
|
+
display: flex;
|
|
981
|
+
gap: var(--wa-spacing-x-small, 4px);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.panel-content {
|
|
985
|
+
flex: 1;
|
|
986
|
+
overflow-y: auto;
|
|
987
|
+
padding: var(--wa-spacing-large, 16px);
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
.workflows-list h3 {
|
|
991
|
+
margin: 0 0 var(--wa-spacing-medium, 12px) 0;
|
|
992
|
+
font-size: var(--wa-font-size-medium, 14px);
|
|
993
|
+
font-weight: 600;
|
|
994
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-80));
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
:host-context(.wa-light) .workflows-list h3 {
|
|
998
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-20));
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.workflow-item {
|
|
1002
|
+
display: flex;
|
|
1003
|
+
align-items: flex-start;
|
|
1004
|
+
gap: var(--wa-spacing-medium, 12px);
|
|
1005
|
+
padding: var(--wa-spacing-medium, 12px);
|
|
1006
|
+
margin-bottom: var(--wa-spacing-small, 8px);
|
|
1007
|
+
background: var(--wa-color-surface-lowered, var(--wa-color-neutral-10));
|
|
1008
|
+
border: var(--wa-border-width-s, 1px) solid var(--wa-color-neutral-border-subtle, var(--wa-color-neutral-20));
|
|
1009
|
+
border-radius: var(--wa-border-radius-small, 6px);
|
|
1010
|
+
cursor: pointer;
|
|
1011
|
+
transition: all var(--wa-transition-medium, 0.2s);
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
:host-context(.wa-light) .workflow-item {
|
|
1015
|
+
background: var(--wa-color-surface-lowered, var(--wa-color-neutral-90));
|
|
1016
|
+
border-color: var(--wa-color-neutral-border-subtle, var(--wa-color-neutral-80));
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
.workflow-item:hover {
|
|
1020
|
+
background: var(--wa-color-mix-hover, var(--wa-color-neutral-15));
|
|
1021
|
+
border-color: var(--wa-color-neutral-border-loud, var(--wa-color-neutral-30));
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
:host-context(.wa-light) .workflow-item:hover {
|
|
1025
|
+
background: var(--wa-color-mix-hover, var(--wa-color-neutral-85));
|
|
1026
|
+
border-color: var(--wa-color-neutral-border-loud, var(--wa-color-neutral-70));
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.workflow-info {
|
|
1030
|
+
flex: 1;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.workflow-title {
|
|
1034
|
+
font-weight: 600;
|
|
1035
|
+
margin-bottom: var(--wa-spacing-x-small, 4px);
|
|
1036
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-90));
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
:host-context(.wa-light) .workflow-title {
|
|
1040
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-10));
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
.workflow-desc {
|
|
1044
|
+
font-size: var(--wa-font-size-small, 12px);
|
|
1045
|
+
color: var(--wa-color-text-subtle, var(--wa-color-neutral-70));
|
|
1046
|
+
margin-bottom: var(--wa-spacing-x-small, 4px);
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
:host-context(.wa-light) .workflow-desc {
|
|
1050
|
+
color: var(--wa-color-text-subtle, var(--wa-color-neutral-30));
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.workflow-meta {
|
|
1054
|
+
font-size: var(--wa-font-size-x-small, 11px);
|
|
1055
|
+
color: var(--wa-color-text-quiet, var(--wa-color-neutral-60));
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
:host-context(.wa-light) .workflow-meta {
|
|
1059
|
+
color: var(--wa-color-text-quiet, var(--wa-color-neutral-40));
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
.active-workflow {
|
|
1063
|
+
display: flex;
|
|
1064
|
+
flex-direction: column;
|
|
1065
|
+
gap: var(--wa-spacing-large, 16px);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
.workflow-header {
|
|
1069
|
+
display: flex;
|
|
1070
|
+
align-items: flex-start;
|
|
1071
|
+
justify-content: space-between;
|
|
1072
|
+
gap: var(--wa-spacing-medium, 12px);
|
|
1073
|
+
margin-bottom: var(--wa-spacing-medium, 12px);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
.workflow-title-section {
|
|
1077
|
+
flex: 1;
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
.workflow-header h3 {
|
|
1081
|
+
margin: 0 0 var(--wa-spacing-small, 8px) 0;
|
|
1082
|
+
font-size: var(--wa-font-size-large, 16px);
|
|
1083
|
+
font-weight: 600;
|
|
1084
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-90));
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
:host-context(.wa-light) .workflow-header h3 {
|
|
1088
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-10));
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
.workflow-description {
|
|
1092
|
+
margin: 0;
|
|
1093
|
+
font-size: var(--wa-font-size-medium, 13px);
|
|
1094
|
+
color: var(--wa-color-text-subtle, var(--wa-color-neutral-70));
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
:host-context(.wa-light) .workflow-description {
|
|
1098
|
+
color: var(--wa-color-text-subtle, var(--wa-color-neutral-30));
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.steps-list {
|
|
1102
|
+
display: flex;
|
|
1103
|
+
flex-direction: column;
|
|
1104
|
+
gap: var(--wa-spacing-medium, 12px);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
.step {
|
|
1108
|
+
padding: var(--wa-spacing-medium, 12px);
|
|
1109
|
+
background: var(--wa-color-surface-lowered, var(--wa-color-neutral-10));
|
|
1110
|
+
border: var(--wa-border-width-s, 1px) solid var(--wa-color-neutral-border-subtle, var(--wa-color-neutral-20));
|
|
1111
|
+
border-radius: var(--wa-border-radius-small, 6px);
|
|
1112
|
+
transition: all var(--wa-transition-medium, 0.2s);
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
:host-context(.wa-light) .step {
|
|
1116
|
+
background: var(--wa-color-surface-lowered, var(--wa-color-neutral-90));
|
|
1117
|
+
border-color: var(--wa-color-neutral-border-subtle, var(--wa-color-neutral-80));
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
.step[data-active="true"] {
|
|
1121
|
+
border-color: var(--wa-color-primary-50);
|
|
1122
|
+
background: var(--wa-color-primary-05);
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
:host-context(.wa-light) .step[data-active="true"] {
|
|
1126
|
+
background: var(--wa-color-primary-95);
|
|
1127
|
+
border-color: var(--wa-color-primary-50);
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
.step.completed {
|
|
1131
|
+
border-color: var(--wa-color-success-50);
|
|
1132
|
+
background: var(--wa-color-success-05);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
:host-context(.wa-light) .step.completed {
|
|
1136
|
+
background: var(--wa-color-success-95);
|
|
1137
|
+
border-color: var(--wa-color-success-50);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
.step.failed {
|
|
1141
|
+
border-color: var(--wa-color-danger-50);
|
|
1142
|
+
background: var(--wa-color-danger-05);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
:host-context(.wa-light) .step.failed {
|
|
1146
|
+
background: var(--wa-color-danger-95);
|
|
1147
|
+
border-color: var(--wa-color-danger-50);
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
.step-header {
|
|
1151
|
+
display: flex;
|
|
1152
|
+
align-items: center;
|
|
1153
|
+
gap: var(--wa-spacing-medium, 12px);
|
|
1154
|
+
margin-bottom: var(--wa-spacing-small, 8px);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.step-number {
|
|
1158
|
+
width: 24px;
|
|
1159
|
+
height: 24px;
|
|
1160
|
+
display: flex;
|
|
1161
|
+
align-items: center;
|
|
1162
|
+
justify-content: center;
|
|
1163
|
+
background: var(--wa-color-surface-lowered, var(--wa-color-neutral-20));
|
|
1164
|
+
border-radius: 50%;
|
|
1165
|
+
font-size: var(--wa-font-size-small, 12px);
|
|
1166
|
+
font-weight: 600;
|
|
1167
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-80));
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
:host-context(.wa-light) .step-number {
|
|
1171
|
+
background: var(--wa-color-surface-lowered, var(--wa-color-neutral-80));
|
|
1172
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-20));
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.step-title {
|
|
1176
|
+
flex: 1;
|
|
1177
|
+
font-weight: 600;
|
|
1178
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-90));
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
:host-context(.wa-light) .step-title {
|
|
1182
|
+
color: var(--wa-color-text-normal, var(--wa-color-neutral-10));
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
.step-status {
|
|
1186
|
+
display: flex;
|
|
1187
|
+
align-items: center;
|
|
1188
|
+
gap: var(--wa-spacing-small, 8px);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
.status-icon {
|
|
1192
|
+
width: 20px;
|
|
1193
|
+
height: 20px;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
.status-icon.completed {
|
|
1197
|
+
color: var(--wa-color-success-50);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
.status-icon.failed {
|
|
1201
|
+
color: var(--wa-color-danger-50);
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
.status-icon.skipped {
|
|
1205
|
+
color: var(--wa-color-neutral-50);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
.status-icon.active {
|
|
1209
|
+
color: var(--wa-color-primary-50);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
.status-icon.pending {
|
|
1213
|
+
color: var(--wa-color-neutral-50);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
.step-description {
|
|
1217
|
+
font-size: var(--wa-font-size-medium, 13px);
|
|
1218
|
+
color: var(--wa-color-text-subtle, var(--wa-color-neutral-70));
|
|
1219
|
+
margin-bottom: var(--wa-spacing-small, 8px);
|
|
1220
|
+
line-height: 1.4;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
:host-context(.wa-light) .step-description {
|
|
1224
|
+
color: var(--wa-color-text-subtle, var(--wa-color-neutral-30));
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
.condition {
|
|
1228
|
+
display: flex;
|
|
1229
|
+
align-items: center;
|
|
1230
|
+
gap: var(--wa-spacing-x-small, 6px);
|
|
1231
|
+
font-size: var(--wa-font-size-small, 12px);
|
|
1232
|
+
padding: var(--wa-spacing-x-small, 6px) var(--wa-spacing-small, 8px);
|
|
1233
|
+
border-radius: var(--wa-border-radius-x-small, 4px);
|
|
1234
|
+
margin-bottom: var(--wa-spacing-small, 8px);
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
.condition.met {
|
|
1238
|
+
background: var(--wa-color-success-10);
|
|
1239
|
+
color: var(--wa-color-success-70);
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
:host-context(.wa-light) .condition.met {
|
|
1243
|
+
background: var(--wa-color-success-90);
|
|
1244
|
+
color: var(--wa-color-success-30);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
.condition.not-met {
|
|
1248
|
+
background: var(--wa-color-danger-10);
|
|
1249
|
+
color: var(--wa-color-danger-70);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
:host-context(.wa-light) .condition.not-met {
|
|
1253
|
+
background: var(--wa-color-danger-90);
|
|
1254
|
+
color: var(--wa-color-danger-30);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
.step-actions {
|
|
1258
|
+
display: flex;
|
|
1259
|
+
gap: var(--wa-spacing-small, 8px);
|
|
1260
|
+
margin-top: var(--wa-spacing-small, 8px);
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
.empty-state {
|
|
1264
|
+
display: flex;
|
|
1265
|
+
flex-direction: column;
|
|
1266
|
+
align-items: center;
|
|
1267
|
+
justify-content: center;
|
|
1268
|
+
padding: var(--wa-spacing-x-large, 40px) var(--wa-spacing-large, 20px);
|
|
1269
|
+
text-align: center;
|
|
1270
|
+
color: var(--wa-color-text-subtle, var(--wa-color-neutral-70));
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
:host-context(.wa-light) .empty-state {
|
|
1274
|
+
color: var(--wa-color-text-subtle, var(--wa-color-neutral-30));
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.empty-state p {
|
|
1278
|
+
margin: var(--wa-spacing-small, 8px) 0;
|
|
1279
|
+
}
|
|
1280
|
+
`;
|
|
1281
|
+
__decorateClass([
|
|
1282
|
+
state()
|
|
1283
|
+
], LyraHowToPanel.prototype, "contributions", 2);
|
|
1284
|
+
__decorateClass([
|
|
1285
|
+
state()
|
|
1286
|
+
], LyraHowToPanel.prototype, "activeContributionId", 2);
|
|
1287
|
+
__decorateClass([
|
|
1288
|
+
state()
|
|
1289
|
+
], LyraHowToPanel.prototype, "stepStates", 2);
|
|
1290
|
+
__decorateClass([
|
|
1291
|
+
state()
|
|
1292
|
+
], LyraHowToPanel.prototype, "isMinimized", 2);
|
|
1293
|
+
__decorateClass([
|
|
1294
|
+
state()
|
|
1295
|
+
], LyraHowToPanel.prototype, "isVisible", 2);
|
|
1296
|
+
__decorateClass([
|
|
1297
|
+
state()
|
|
1298
|
+
], LyraHowToPanel.prototype, "positionX", 2);
|
|
1299
|
+
__decorateClass([
|
|
1300
|
+
state()
|
|
1301
|
+
], LyraHowToPanel.prototype, "positionY", 2);
|
|
1302
|
+
__decorateClass([
|
|
1303
|
+
state()
|
|
1304
|
+
], LyraHowToPanel.prototype, "dragPreviewPosition", 2);
|
|
1305
|
+
LyraHowToPanel = __decorateClass([
|
|
1306
|
+
customElement("lyra-howto-panel")
|
|
1307
|
+
], LyraHowToPanel);
|
|
1308
|
+
const logger = createLogger("HowToExtension");
|
|
1309
|
+
function howToExtension(context) {
|
|
1310
|
+
logger.info("HowTo system extension loaded");
|
|
1311
|
+
rootContext.put("howToService", howToService);
|
|
1312
|
+
const ensurePanelInDOM = () => {
|
|
1313
|
+
if (document.querySelector("lyra-howto-panel")) {
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
const panel = document.createElement("lyra-howto-panel");
|
|
1317
|
+
document.body.appendChild(panel);
|
|
1318
|
+
logger.info("HowTo panel added to DOM");
|
|
1319
|
+
};
|
|
1320
|
+
if (document.body) {
|
|
1321
|
+
requestAnimationFrame(() => {
|
|
1322
|
+
ensurePanelInDOM();
|
|
1323
|
+
});
|
|
1324
|
+
} else {
|
|
1325
|
+
const checkDOM = () => {
|
|
1326
|
+
if (document.body) {
|
|
1327
|
+
ensurePanelInDOM();
|
|
1328
|
+
} else {
|
|
1329
|
+
requestAnimationFrame(checkDOM);
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
requestAnimationFrame(checkDOM);
|
|
1333
|
+
}
|
|
1334
|
+
registerAll({
|
|
1335
|
+
command: {
|
|
1336
|
+
id: "howto.show-panel",
|
|
1337
|
+
name: "Show HowTo Panel",
|
|
1338
|
+
description: "Shows the HowTo workflows panel",
|
|
1339
|
+
icon: "list-check",
|
|
1340
|
+
parameters: []
|
|
1341
|
+
},
|
|
1342
|
+
handler: {
|
|
1343
|
+
execute: () => {
|
|
1344
|
+
publish(TOPIC_SHOW_HOWTO_PANEL, null);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
});
|
|
1348
|
+
registerAll({
|
|
1349
|
+
command: {
|
|
1350
|
+
id: "howto.toggle-panel",
|
|
1351
|
+
name: "Toggle HowTo Panel",
|
|
1352
|
+
description: "Toggles the visibility of the HowTo workflows panel",
|
|
1353
|
+
icon: "list-check",
|
|
1354
|
+
keyBinding: "CTRL+SHIFT+H",
|
|
1355
|
+
parameters: []
|
|
1356
|
+
},
|
|
1357
|
+
handler: {
|
|
1358
|
+
execute: () => {
|
|
1359
|
+
publish(TOPIC_TOGGLE_HOWTO_PANEL, null);
|
|
1360
|
+
}
|
|
1361
|
+
},
|
|
1362
|
+
contribution: {
|
|
1363
|
+
target: TOOLBAR_BOTTOM_END,
|
|
1364
|
+
icon: "list-check",
|
|
1365
|
+
label: "HowTo"
|
|
1366
|
+
}
|
|
1367
|
+
});
|
|
1368
|
+
logger.info("HowTo system extension initialized");
|
|
1369
|
+
}
|
|
1370
|
+
export {
|
|
1371
|
+
HOWTO_CONTRIBUTION_TARGET,
|
|
1372
|
+
howToExtension as default,
|
|
1373
|
+
howToService
|
|
1374
|
+
};
|
|
1375
|
+
//# sourceMappingURL=howto-extension-8r07TyoY.js.map
|