@ibgib/space-gib 0.0.33 → 0.0.35

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.
Files changed (118) hide show
  1. package/dist/client/bootstrap.mjs +24 -51
  2. package/dist/client/chunk-FIAGRVBY.mjs +10933 -0
  3. package/dist/client/chunk-XLQ3UCQY.mjs +53 -0
  4. package/dist/client/css/activity-bar.css +435 -0
  5. package/dist/client/css/base.css +167 -0
  6. package/dist/client/css/components.css +218 -0
  7. package/dist/client/css/cosmic.css +557 -0
  8. package/dist/client/css/dev-tools.css +163 -0
  9. package/dist/client/css/landing.css +184 -0
  10. package/dist/client/css/layout.css +1017 -0
  11. package/dist/client/css/onboarding.css +181 -0
  12. package/dist/client/css/repos.css +740 -0
  13. package/dist/client/css/variables.css +64 -0
  14. package/dist/client/index.html +208 -31
  15. package/dist/client/index.mjs +22 -45
  16. package/dist/client/privacy.html +22 -22
  17. package/dist/client/script.mjs +1 -1
  18. package/dist/client/style.css +11 -1056
  19. package/dist/respec-gib.node.mjs +5 -0
  20. package/dist/server/server.mjs +6880 -3110
  21. package/package.json +6 -6
  22. package/src/client/AUTO-GENERATED-version.mts +1 -1
  23. package/src/client/cosmos/seed-cosmos-workspace.mts +83 -0
  24. package/src/client/css/activity-bar.css +435 -0
  25. package/src/client/css/base.css +167 -0
  26. package/src/client/css/components.css +218 -0
  27. package/src/client/css/cosmic.css +557 -0
  28. package/src/client/css/dev-tools.css +163 -0
  29. package/src/client/css/landing.css +184 -0
  30. package/src/client/css/layout.css +1017 -0
  31. package/src/client/css/onboarding.css +181 -0
  32. package/src/client/css/repos.css +740 -0
  33. package/src/client/css/variables.css +64 -0
  34. package/src/client/dev-tools/vcs-workspace.mts +7 -7
  35. package/src/client/index.html +208 -31
  36. package/src/client/privacy.html +22 -22
  37. package/src/client/style.css +11 -1056
  38. package/src/client/ui/component/changes/changes.css +413 -0
  39. package/src/client/ui/component/changes/changes.html +37 -0
  40. package/src/client/ui/component/changes/changes.mts +766 -0
  41. package/src/client/ui/component/changes/index.mts +9 -0
  42. package/src/client/ui/component/chat-view/IMPLEMENTATION.md +48 -0
  43. package/src/client/ui/component/chat-view/chat-view.css +381 -0
  44. package/src/client/ui/component/chat-view/chat-view.html +52 -0
  45. package/src/client/ui/component/chat-view/chat-view.mts +264 -0
  46. package/src/client/ui/component/chat-view/index.mts +9 -0
  47. package/src/client/ui/component/clone/IMPLEMENTATION.md +68 -0
  48. package/src/client/ui/component/clone/clone-constants.mts +16 -0
  49. package/src/client/ui/component/clone/clone-types.mts +60 -0
  50. package/src/client/ui/component/clone/clone.css +383 -0
  51. package/src/client/ui/component/clone/clone.html +89 -0
  52. package/src/client/ui/component/clone/clone.mts +648 -0
  53. package/src/client/ui/component/clone/index.mts +11 -0
  54. package/src/client/ui/component/code-editor/IMPLEMENTATION.md +89 -0
  55. package/src/client/ui/component/code-editor/code-editor.css +357 -0
  56. package/src/client/ui/component/code-editor/code-editor.html +67 -0
  57. package/src/client/ui/component/code-editor/code-editor.mts +643 -0
  58. package/src/client/ui/component/code-editor/index.mts +9 -0
  59. package/src/client/ui/component/cosmos-navigator/IMPLEMENTATION.md +31 -0
  60. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.css +864 -0
  61. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.html +92 -0
  62. package/src/client/ui/component/cosmos-navigator/cosmos-navigator.mts +1317 -0
  63. package/src/client/ui/component/cosmos-navigator/index.mts +18 -0
  64. package/src/client/ui/component/file-explorer/IMPLEMENTATION.md +42 -0
  65. package/src/client/ui/component/file-explorer/file-explorer.css +720 -0
  66. package/src/client/ui/component/file-explorer/file-explorer.html +55 -0
  67. package/src/client/ui/component/file-explorer/file-explorer.mts +1412 -0
  68. package/src/client/ui/component/file-explorer/index.mts +9 -0
  69. package/src/client/ui/component/nested-chat/IMPLEMENTATION.md +18 -0
  70. package/src/client/ui/component/nested-chat/chat/IMPLEMENTATION.md +14 -0
  71. package/src/client/ui/component/nested-chat/chat/chat-constants.mts +14 -0
  72. package/src/client/ui/component/nested-chat/chat/chat-helpers.mts +66 -0
  73. package/src/client/ui/component/nested-chat/chat/chat.css +654 -0
  74. package/src/client/ui/component/nested-chat/chat/chat.html +90 -0
  75. package/src/client/ui/component/nested-chat/chat/chat.mts +561 -0
  76. package/src/client/ui/component/nested-chat/chat/index.mts +7 -0
  77. package/src/client/ui/component/nested-chat/index.mts +14 -0
  78. package/src/client/ui/component/nested-chat/nested-chat-constants.mts +12 -0
  79. package/src/client/ui/component/nested-chat/nested-chat-types.mts +53 -0
  80. package/src/client/ui/component/nested-chat/nested-chat.css +142 -0
  81. package/src/client/ui/component/nested-chat/nested-chat.html +11 -0
  82. package/src/client/ui/component/nested-chat/nested-chat.mts +395 -0
  83. package/src/client/ui/component/notes-explorer/index.mts +9 -0
  84. package/src/client/ui/component/notes-explorer/notes-explorer.css +313 -0
  85. package/src/client/ui/component/notes-explorer/notes-explorer.html +136 -0
  86. package/src/client/ui/component/notes-explorer/notes-explorer.mts +240 -0
  87. package/src/client/ui/component/notes-inspector/index.mts +9 -0
  88. package/src/client/ui/component/notes-inspector/notes-inspector.css +244 -0
  89. package/src/client/ui/component/notes-inspector/notes-inspector.html +97 -0
  90. package/src/client/ui/component/notes-inspector/notes-inspector.mts +152 -0
  91. package/src/client/ui/component/pitch/IMPLEMENTATION.md +48 -0
  92. package/src/client/ui/component/pitch/index.mts +9 -0
  93. package/src/client/ui/component/pitch/pitch.css +295 -0
  94. package/src/client/ui/component/pitch/pitch.html +97 -0
  95. package/src/client/ui/component/pitch/pitch.mts +135 -0
  96. package/src/client/ui/component/timeline/IMPLEMENTATION.md +33 -0
  97. package/src/client/ui/component/timeline/index.mts +9 -0
  98. package/src/client/ui/component/timeline/timeline.css +279 -0
  99. package/src/client/ui/component/timeline/timeline.html +23 -0
  100. package/src/client/ui/component/timeline/timeline.mts +261 -0
  101. package/src/client/ui/component/timeline-item-details/IMPLEMENTATION.md +28 -0
  102. package/src/client/ui/component/timeline-item-details/index.mts +9 -0
  103. package/src/client/ui/component/timeline-item-details/timeline-item-details.css +286 -0
  104. package/src/client/ui/component/timeline-item-details/timeline-item-details.html +64 -0
  105. package/src/client/ui/component/timeline-item-details/timeline-item-details.mts +194 -0
  106. package/src/client/ui/router/index.mts +3 -0
  107. package/src/client/ui/router/space-gib-router-helpers.mts +72 -0
  108. package/src/client/ui/router/space-gib-router-helpers.respec.mts +529 -0
  109. package/src/client/ui/router/space-gib-router-service.mts +63 -0
  110. package/src/client/ui/router/space-gib-router-types.mts +49 -0
  111. package/src/client/ui/shell/cosmic-big-bang.mts +853 -0
  112. package/src/client/ui/shell/space-gib-shell-constants.mts +7 -0
  113. package/src/client/ui/shell/space-gib-shell-service.mts +2297 -32
  114. package/src/server/server.mts +20 -1
  115. package/tsconfig.test.json +5 -1
  116. package/dist/client/chunk-CRJV762I.mjs +0 -3102
  117. package/dist/client/chunk-LXRCF5OS.mjs +0 -25
  118. package/dist/client/chunk-UBDQUZZV.mjs +0 -1
@@ -0,0 +1,648 @@
1
+ import thisCss from "./clone.css";
2
+ import thisHtml from "./clone.html";
3
+
4
+ import { ROOT_ADDR } from "@ibgib/ts-gib/dist/V1/constants.mjs";
5
+ import { IbGibAddr } from "@ibgib/ts-gib/dist/types.mjs";
6
+ import { IbGib_V1 } from "@ibgib/ts-gib/dist/V1/types.mjs";
7
+ import { extractErrorMsg } from "@ibgib/helper-gib/dist/helpers/utils-helper.mjs";
8
+ import { promptForText } from "@ibgib/web-gib/dist/helpers.web.mjs";
9
+ import { validateBranchName } from "@ibgib/core-gib/dist/cosmology/branching/branch/branch-helpers.mjs";
10
+ import {
11
+ IbGibDynamicComponentMetaBase, IbGibDynamicComponentInstanceBase,
12
+ } from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs";
13
+ import {
14
+ ElementsBase, IbGibDynamicComponentInstance,
15
+ } from "@ibgib/web-gib/dist/ui/component/component-types.mjs";
16
+ import { getComponentCtorArg } from "@ibgib/web-gib/dist/app-bootstrap/init-orchestration.mjs";
17
+
18
+ import {
19
+ CLONE_COMPONENT_NAME, EVENT_SPACE_GIB_CLONE_CLOSED,
20
+ EVENT_SPACE_GIB_CLONE_EXECUTED, CLONE_SCOPE_COSMOS, CLONE_SCOPE_GALAXY,
21
+ CLONE_SCOPE_ITEM, CLONE_MODALITY_CLI, CLONE_MODALITY_ZIP,
22
+ CLONE_MODALITY_DAG,
23
+ } from "./clone-constants.mjs";
24
+ import {
25
+ CloneScope, CloneModality, CloneItemContext, CloneTreeState,
26
+ CloneModalOptions,
27
+ } from "./clone-types.mjs";
28
+
29
+ /**
30
+ * Metadata factory for space-gib-clone Web Component.
31
+ */
32
+ export class CloneComponentMeta extends IbGibDynamicComponentMetaBase {
33
+ protected lc: string = `[${CloneComponentMeta.name}]`;
34
+
35
+ routeRegExp?: RegExp = new RegExp(`^${CLONE_COMPONENT_NAME}$`);
36
+ componentName = CLONE_COMPONENT_NAME;
37
+
38
+ constructor() {
39
+ super(getComponentCtorArg());
40
+ if (!customElements.get(this.componentName)) {
41
+ customElements.define(this.componentName, CloneComponentInstance);
42
+ }
43
+ }
44
+
45
+ async createInstance({
46
+ path,
47
+ ibGibAddr
48
+ }: {
49
+ path: string;
50
+ ibGibAddr: IbGibAddr;
51
+ }): Promise<IbGibDynamicComponentInstance> {
52
+ const component = document.createElement(this.componentName) as CloneComponentInstance;
53
+ await component.initialize({
54
+ ibGibAddr: ibGibAddr || ROOT_ADDR,
55
+ meta: this,
56
+ html: thisHtml,
57
+ css: [thisCss],
58
+ });
59
+ return component;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * DOM Elements interface for CloneComponentInstance
65
+ */
66
+ export interface CloneElements extends ElementsBase {
67
+ contentEl: HTMLDivElement;
68
+ backdropEl: HTMLDivElement;
69
+ dialogEl: HTMLDivElement;
70
+ headerEl: HTMLDivElement;
71
+ btnCloseEl: HTMLButtonElement;
72
+ targetTitleEl: HTMLSpanElement;
73
+
74
+ // Scopes
75
+ scopeCosmosLabelEl: HTMLLabelElement;
76
+ scopeGalaxyLabelEl: HTMLLabelElement;
77
+ scopeItemLabelEl: HTMLLabelElement;
78
+ scopeCosmosRadioEl: HTMLInputElement;
79
+ scopeGalaxyRadioEl: HTMLInputElement;
80
+ scopeItemRadioEl: HTMLInputElement;
81
+
82
+ // Tree / Item
83
+ treeContainerEl: HTMLDivElement;
84
+ singleItemContainerEl: HTMLDivElement;
85
+ selectionSummaryEl: HTMLSpanElement;
86
+
87
+ // Modality Tabs
88
+ tabCliEl: HTMLButtonElement;
89
+ tabZipEl: HTMLButtonElement;
90
+ tabDagEl: HTMLButtonElement;
91
+
92
+ // Panels
93
+ panelCliEl: HTMLDivElement;
94
+ panelZipEl: HTMLDivElement;
95
+ panelDagEl: HTMLDivElement;
96
+
97
+ // CLI actions
98
+ cmdPreviewEl: HTMLElement;
99
+ btnCopyEl: HTMLButtonElement;
100
+
101
+ // Download actions
102
+ btnDownloadZipEl: HTMLButtonElement;
103
+ btnDownloadDagEl: HTMLButtonElement;
104
+
105
+ // Status
106
+ statusMessageEl: HTMLDivElement;
107
+ }
108
+
109
+ /**
110
+ * space-gib-clone Web Component Instance.
111
+ * Implements Cosmos, Galaxy, and Single Item cloning via CLI or direct downloads.
112
+ */
113
+ export class CloneComponentInstance
114
+ extends IbGibDynamicComponentInstanceBase<IbGib_V1, CloneElements>
115
+ implements IbGibDynamicComponentInstance<IbGib_V1, CloneElements> {
116
+ protected override lc: string = `[${CloneComponentInstance.name}]`;
117
+
118
+ get el(): CloneElements {
119
+ if (!this.elements) {
120
+ throw new Error(`elements is not defined in ${this.lc}`);
121
+ }
122
+ return this.elements;
123
+ }
124
+
125
+ protected activeScope: CloneScope = CLONE_SCOPE_COSMOS;
126
+ protected activeModality: CloneModality = CLONE_MODALITY_CLI;
127
+
128
+ // Tree state populated either from context or sample cosmos hierarchy
129
+ protected treeState: CloneTreeState = {
130
+ cosmosId: 'cosmos-1',
131
+ cosmosName: 'Cosmos 1',
132
+ selected: true,
133
+ galaxies: [
134
+ {
135
+ id: 'galaxy-code',
136
+ name: 'Code Galaxy',
137
+ icon: '</>',
138
+ type: 'code',
139
+ selected: true,
140
+ branches: [
141
+ { id: 'branch-main', name: 'main', isCanonical: true, selected: true },
142
+ { id: 'branch-feat', name: 'feature/cosmos-drift', isCanonical: false, selected: false },
143
+ ],
144
+ },
145
+ {
146
+ id: 'galaxy-notes',
147
+ name: 'Notes Galaxy',
148
+ icon: '📝',
149
+ type: 'notes',
150
+ selected: true,
151
+ branches: [
152
+ { id: 'branch-notes-main', name: 'main', isCanonical: true, selected: true },
153
+ ],
154
+ },
155
+ ],
156
+ };
157
+
158
+ override async created(): Promise<void> {
159
+ const lc = `${this.lc}[${this.created.name}]`;
160
+ try {
161
+ console.log(`${lc} created starting...`);
162
+ const root = this.shadowRoot!;
163
+
164
+ this.elements = {
165
+ contentEl: root.getElementById('clone-dialog') as HTMLDivElement,
166
+ backdropEl: root.getElementById('clone-backdrop') as HTMLDivElement,
167
+ dialogEl: root.getElementById('clone-dialog') as HTMLDivElement,
168
+ headerEl: root.getElementById('clone-header') as HTMLDivElement,
169
+ btnCloseEl: root.getElementById('btn-clone-close') as HTMLButtonElement,
170
+ targetTitleEl: root.getElementById('clone-target-title') as HTMLSpanElement,
171
+
172
+ scopeCosmosLabelEl: root.getElementById('scope-cosmos-label') as HTMLLabelElement,
173
+ scopeGalaxyLabelEl: root.getElementById('scope-galaxy-label') as HTMLLabelElement,
174
+ scopeItemLabelEl: root.getElementById('scope-item-label') as HTMLLabelElement,
175
+ scopeCosmosRadioEl: root.getElementById('scope-cosmos') as HTMLInputElement,
176
+ scopeGalaxyRadioEl: root.getElementById('scope-galaxy') as HTMLInputElement,
177
+ scopeItemRadioEl: root.getElementById('scope-item') as HTMLInputElement,
178
+
179
+ treeContainerEl: root.getElementById('clone-tree-container') as HTMLDivElement,
180
+ singleItemContainerEl: root.getElementById('clone-single-item-container') as HTMLDivElement,
181
+ selectionSummaryEl: root.getElementById('clone-selection-summary') as HTMLSpanElement,
182
+
183
+ tabCliEl: root.getElementById('tab-cli') as HTMLButtonElement,
184
+ tabZipEl: root.getElementById('tab-zip') as HTMLButtonElement,
185
+ tabDagEl: root.getElementById('tab-dag') as HTMLButtonElement,
186
+
187
+ panelCliEl: root.getElementById('panel-cli') as HTMLDivElement,
188
+ panelZipEl: root.getElementById('panel-zip') as HTMLDivElement,
189
+ panelDagEl: root.getElementById('panel-dag') as HTMLDivElement,
190
+
191
+ cmdPreviewEl: root.getElementById('clone-cmd-preview') as HTMLElement,
192
+ btnCopyEl: root.getElementById('btn-clone-copy') as HTMLButtonElement,
193
+
194
+ btnDownloadZipEl: root.getElementById('btn-clone-download-zip') as HTMLButtonElement,
195
+ btnDownloadDagEl: root.getElementById('btn-clone-download-dag') as HTMLButtonElement,
196
+
197
+ statusMessageEl: root.getElementById('clone-status-message') as HTMLDivElement,
198
+ };
199
+
200
+ this.bindEvents();
201
+ this.applyScopeRules();
202
+ this.renderTree();
203
+ this.updateCommandPreview();
204
+ } catch (error) {
205
+ console.error(`${lc} ${extractErrorMsg(error)}`);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Configure modal with options when invoked by shell service.
211
+ */
212
+ public setOptions(opts: CloneModalOptions): void {
213
+ const lc = `${this.lc}[${this.setOptions.name}]`;
214
+ console.log(`${lc} configuring options:`, opts);
215
+
216
+ if (opts.cosmosId) {
217
+ this.treeState.cosmosId = opts.cosmosId;
218
+ this.treeState.cosmosName = opts.cosmosName || opts.cosmosId;
219
+ }
220
+
221
+ if (opts.singleItem) {
222
+ this.treeState.singleItem = opts.singleItem;
223
+ this.activeScope = CLONE_SCOPE_ITEM;
224
+ } else if (opts.galaxyName) {
225
+ this.treeState.singleItem = undefined;
226
+ this.activeScope = CLONE_SCOPE_GALAXY;
227
+ } else {
228
+ this.treeState.singleItem = undefined;
229
+ this.activeScope = CLONE_SCOPE_COSMOS;
230
+ }
231
+
232
+ if (this.elements) {
233
+ this.applyScopeRules();
234
+ this.renderTree();
235
+ this.updateCommandPreview();
236
+ }
237
+ }
238
+
239
+ private bindEvents(): void {
240
+ // Close modal on close button or backdrop click
241
+ this.el.btnCloseEl?.addEventListener('click', () => this.close());
242
+ this.el.backdropEl?.addEventListener('click', (e) => {
243
+ if (e.target === this.el.backdropEl) {
244
+ this.close();
245
+ }
246
+ });
247
+
248
+ // Scope change listeners
249
+ this.el.scopeCosmosRadioEl?.addEventListener('change', () => this.setScope(CLONE_SCOPE_COSMOS));
250
+ this.el.scopeGalaxyRadioEl?.addEventListener('change', () => this.setScope(CLONE_SCOPE_GALAXY));
251
+ this.el.scopeItemRadioEl?.addEventListener('change', () => this.setScope(CLONE_SCOPE_ITEM));
252
+
253
+ // Modality tabs
254
+ this.el.tabCliEl?.addEventListener('click', () => this.setModality(CLONE_MODALITY_CLI));
255
+ this.el.tabZipEl?.addEventListener('click', () => this.setModality(CLONE_MODALITY_ZIP));
256
+ this.el.tabDagEl?.addEventListener('click', () => this.setModality(CLONE_MODALITY_DAG));
257
+
258
+ // CLI Copy button
259
+ this.el.btnCopyEl?.addEventListener('click', () => this.copyCommandToClipboard());
260
+
261
+ // Downloads
262
+ this.el.btnDownloadZipEl?.addEventListener('click', () => this.executeDownload('zip'));
263
+ this.el.btnDownloadDagEl?.addEventListener('click', () => this.executeDownload('dag'));
264
+ }
265
+
266
+ /**
267
+ * Enforces the mutual exclusion / greying-out rule:
268
+ * - If Single Item context is present: Single Item is selected; Cosmos and Galaxy are greyed out.
269
+ * - If Single Item context is NOT present: Single Item is greyed out; Cosmos and Galaxy are selectable.
270
+ */
271
+ protected applyScopeRules(): void {
272
+ const hasSingleItem = !!this.treeState.singleItem;
273
+
274
+ if (hasSingleItem) {
275
+ // Force Single Item active
276
+ this.activeScope = CLONE_SCOPE_ITEM;
277
+ this.el.scopeItemRadioEl.checked = true;
278
+
279
+ // Grey out Cosmos and Galaxy
280
+ this.el.scopeCosmosRadioEl.disabled = true;
281
+ this.el.scopeGalaxyRadioEl.disabled = true;
282
+ this.el.scopeItemRadioEl.disabled = false;
283
+
284
+ this.el.scopeCosmosLabelEl.classList.add('disabled');
285
+ this.el.scopeGalaxyLabelEl.classList.add('disabled');
286
+ this.el.scopeItemLabelEl.classList.remove('disabled');
287
+
288
+ this.el.scopeCosmosLabelEl.classList.remove('selected');
289
+ this.el.scopeGalaxyLabelEl.classList.remove('selected');
290
+ this.el.scopeItemLabelEl.classList.add('selected');
291
+
292
+ this.el.targetTitleEl.textContent = this.treeState.singleItem!.path;
293
+ } else {
294
+ // No single item: Grey out Single Item
295
+ this.el.scopeItemRadioEl.disabled = true;
296
+ this.el.scopeItemLabelEl.classList.add('disabled');
297
+ this.el.scopeItemLabelEl.classList.remove('selected');
298
+
299
+ this.el.scopeCosmosRadioEl.disabled = false;
300
+ this.el.scopeGalaxyRadioEl.disabled = false;
301
+ this.el.scopeCosmosLabelEl.classList.remove('disabled');
302
+ this.el.scopeGalaxyLabelEl.classList.remove('disabled');
303
+
304
+ if (this.activeScope === CLONE_SCOPE_ITEM) {
305
+ this.activeScope = CLONE_SCOPE_COSMOS;
306
+ }
307
+
308
+ if (this.activeScope === CLONE_SCOPE_COSMOS) {
309
+ this.el.scopeCosmosRadioEl.checked = true;
310
+ this.el.scopeCosmosLabelEl.classList.add('selected');
311
+ this.el.scopeGalaxyLabelEl.classList.remove('selected');
312
+ this.el.targetTitleEl.textContent = this.treeState.cosmosName;
313
+ } else {
314
+ this.el.scopeGalaxyRadioEl.checked = true;
315
+ this.el.scopeGalaxyLabelEl.classList.add('selected');
316
+ this.el.scopeCosmosLabelEl.classList.remove('selected');
317
+ this.el.targetTitleEl.textContent = `${this.treeState.cosmosName} (Galaxy Scope)`;
318
+ }
319
+ }
320
+ }
321
+
322
+ private setScope(scope: CloneScope): void {
323
+ this.activeScope = scope;
324
+ this.applyScopeRules();
325
+ this.renderTree();
326
+ this.updateCommandPreview();
327
+ }
328
+
329
+ private setModality(modality: CloneModality): void {
330
+ this.activeModality = modality;
331
+
332
+ this.el.tabCliEl.classList.toggle('active', modality === CLONE_MODALITY_CLI);
333
+ this.el.tabZipEl.classList.toggle('active', modality === CLONE_MODALITY_ZIP);
334
+ this.el.tabDagEl.classList.toggle('active', modality === CLONE_MODALITY_DAG);
335
+
336
+ this.el.panelCliEl.classList.toggle('active', modality === CLONE_MODALITY_CLI);
337
+ this.el.panelZipEl.classList.toggle('active', modality === CLONE_MODALITY_ZIP);
338
+ this.el.panelDagEl.classList.toggle('active', modality === CLONE_MODALITY_DAG);
339
+ }
340
+
341
+ /**
342
+ * Renders the tree hierarchy or single item details.
343
+ */
344
+ protected renderTree(): void {
345
+ if (this.activeScope === CLONE_SCOPE_ITEM && this.treeState.singleItem) {
346
+ this.el.treeContainerEl.style.display = 'none';
347
+ this.el.singleItemContainerEl.style.display = 'block';
348
+
349
+ const item = this.treeState.singleItem;
350
+ const icon = item.isDir ? '📁' : '📄';
351
+ this.el.singleItemContainerEl.innerHTML = `
352
+ <div class="single-item-row" style="display: flex; align-items: center; justify-content: space-between; gap: 8px;">
353
+ <div style="display: flex; align-items: center; gap: 8px;">
354
+ <span style="font-size: 1.2rem;">${icon}</span>
355
+ <div>
356
+ <strong style="color: #f8fafc;">${item.path}</strong>
357
+ <div style="font-size: 0.75rem; color: #94a3b8; margin-top: 2px;">
358
+ ${item.isDir ? 'Directory Subtree Workspace' : 'Isolated File Timeline Projection'}
359
+ ${item.galaxyName ? `&bull; Galaxy: ${item.galaxyName}` : ''}
360
+ ${item.branchName ? `&bull; Branch: ${item.branchName}` : ''}
361
+ </div>
362
+ </div>
363
+ </div>
364
+ <button type="button" id="btn-clone-modal-branch-item" class="btn-action" style="background: rgba(16, 185, 129, 0.15); border: 1px solid #10b981; color: #6ee7b7; padding: 4px 10px; border-radius: 5px; font-size: 0.78rem; cursor: pointer; white-space: nowrap;" title="Branch from this item (🌿+)">🌿+ Branch</button>
365
+ </div>
366
+ `;
367
+ this.bindSingleItemEvents(item);
368
+ this.el.selectionSummaryEl.textContent = 'Single item isolated projection';
369
+ return;
370
+ }
371
+
372
+ this.el.singleItemContainerEl.style.display = 'none';
373
+ this.el.treeContainerEl.style.display = 'block';
374
+
375
+ let html = '';
376
+
377
+ // Cosmos Root Node
378
+ html += `
379
+ <div class="tree-node depth-0">
380
+ <input type="checkbox" id="chk-tree-cosmos" ${this.treeState.selected ? 'checked' : ''}>
381
+ <span class="node-icon">🌌</span>
382
+ <span>${this.treeState.cosmosName}</span>
383
+ </div>
384
+ `;
385
+
386
+ // Galaxies & Branches
387
+ this.treeState.galaxies.forEach((galaxy, gIdx) => {
388
+ html += `
389
+ <div class="tree-node depth-1">
390
+ <input type="checkbox" id="chk-tree-g-${gIdx}" ${galaxy.selected ? 'checked' : ''}>
391
+ <span class="node-icon">${galaxy.icon}</span>
392
+ <span>${galaxy.name}</span>
393
+ <span class="node-badge">${galaxy.branches.length} branches</span>
394
+ </div>
395
+ `;
396
+
397
+ galaxy.branches.forEach((branch, bIdx) => {
398
+ html += `
399
+ <div class="tree-node depth-2" style="justify-content: space-between;">
400
+ <div style="display: flex; align-items: center; gap: 8px;">
401
+ <input type="checkbox" id="chk-tree-g-${gIdx}-b-${bIdx}" ${branch.selected ? 'checked' : ''}>
402
+ <span class="node-icon">🌿</span>
403
+ <span>${branch.name}</span>
404
+ ${branch.isCanonical ? '<span class="node-badge" style="background:#1e3a2f; color:#6ee7b7;">canonical</span>' : ''}
405
+ </div>
406
+ <button type="button" class="btn-tree-branch-action" data-branch="${branch.name}" data-galaxy="${galaxy.name}" title="Branch from ${branch.name} (🌿+)" style="background: transparent; border: none; color: #6ee7b7; cursor: pointer; font-size: 0.75rem; padding: 1px 6px; border-radius: 3px;">🌿+</button>
407
+ </div>
408
+ `;
409
+ });
410
+ });
411
+
412
+ this.el.treeContainerEl.innerHTML = html;
413
+ this.bindTreeEvents();
414
+ this.updateSelectionSummary();
415
+ }
416
+
417
+ private bindSingleItemEvents(item: CloneItemContext): void {
418
+ const btnBranchItem = this.el.singleItemContainerEl.querySelector('#btn-clone-modal-branch-item');
419
+ btnBranchItem?.addEventListener('click', async () => {
420
+ const filename = item.path.split('/').pop() || item.path;
421
+ let candidateName = `feature-branch-${filename.replace(/[^a-zA-Z0-9_.-]/g, '-')}`;
422
+ let promptMsg = `Enter branch name isolating and diverging from item [${item.path}]:`;
423
+ let cleanBranchName = '';
424
+
425
+ while (true) {
426
+ const newBranchName = await promptForText({
427
+ title: `Create Branch from Item`,
428
+ msg: promptMsg,
429
+ defaultValue: candidateName,
430
+ cancelable: true,
431
+ });
432
+ if (!newBranchName || !newBranchName.trim()) return;
433
+ cleanBranchName = newBranchName.trim();
434
+ const validation = validateBranchName(cleanBranchName);
435
+ if (!validation.isValid) {
436
+ candidateName = cleanBranchName;
437
+ promptMsg = `Invalid branch name: ${validation.error}\n\nPlease enter a valid branch name:`;
438
+ continue;
439
+ }
440
+ break;
441
+ }
442
+
443
+ this.showStatus(`Branch created: ${cleanBranchName}`);
444
+ window.dispatchEvent(new CustomEvent('space-gib-branch-requested', {
445
+ detail: {
446
+ galaxyName: item.galaxyName || 'Code Galaxy',
447
+ sourceBranch: item.branchName || 'main',
448
+ itemPath: item.path,
449
+ isDir: item.isDir,
450
+ newBranchName: cleanBranchName,
451
+ scope: 'item',
452
+ },
453
+ bubbles: true,
454
+ composed: true,
455
+ }));
456
+ });
457
+ }
458
+
459
+ private bindTreeEvents(): void {
460
+ const rootChk = this.el.treeContainerEl.querySelector('#chk-tree-cosmos') as HTMLInputElement | null;
461
+ rootChk?.addEventListener('change', () => {
462
+ const checked = rootChk.checked;
463
+ this.treeState.selected = checked;
464
+ this.treeState.galaxies.forEach(g => {
465
+ g.selected = checked;
466
+ g.branches.forEach(b => b.selected = checked);
467
+ });
468
+ this.renderTree();
469
+ this.updateCommandPreview();
470
+ });
471
+
472
+ this.treeState.galaxies.forEach((galaxy, gIdx) => {
473
+ const gChk = this.el.treeContainerEl.querySelector(`#chk-tree-g-${gIdx}`) as HTMLInputElement | null;
474
+ gChk?.addEventListener('change', () => {
475
+ const checked = gChk.checked;
476
+ galaxy.selected = checked;
477
+ galaxy.branches.forEach(b => b.selected = checked);
478
+ this.syncParentState();
479
+ this.renderTree();
480
+ this.updateCommandPreview();
481
+ });
482
+
483
+ galaxy.branches.forEach((branch, bIdx) => {
484
+ const bChk = this.el.treeContainerEl.querySelector(`#chk-tree-g-${gIdx}-b-${bIdx}`) as HTMLInputElement | null;
485
+ bChk?.addEventListener('change', () => {
486
+ branch.selected = bChk.checked;
487
+ galaxy.selected = galaxy.branches.some(b => b.selected);
488
+ this.syncParentState();
489
+ this.renderTree();
490
+ this.updateCommandPreview();
491
+ });
492
+ });
493
+ });
494
+
495
+ // Branch from branch buttons in tree
496
+ this.el.treeContainerEl.querySelectorAll('.btn-tree-branch-action').forEach(btn => {
497
+ btn.addEventListener('click', async (e) => {
498
+ e.stopPropagation();
499
+ const branchName = btn.getAttribute('data-branch') || 'main';
500
+ const galaxyName = btn.getAttribute('data-galaxy') || 'Code Galaxy';
501
+ let candidateName = `feature-branch-from-${branchName}`;
502
+ let promptMsg = `Enter branch name diverging from [${branchName}]:`;
503
+ let cleanBranchName = '';
504
+
505
+ while (true) {
506
+ const newBranchName = await promptForText({
507
+ title: 'Create Branch from Branch',
508
+ msg: promptMsg,
509
+ defaultValue: candidateName,
510
+ cancelable: true,
511
+ });
512
+ if (!newBranchName || !newBranchName.trim()) return;
513
+ cleanBranchName = newBranchName.trim();
514
+ const validation = validateBranchName(cleanBranchName);
515
+ if (!validation.isValid) {
516
+ candidateName = cleanBranchName;
517
+ promptMsg = `Invalid branch name: ${validation.error}\n\nPlease enter a valid branch name:`;
518
+ continue;
519
+ }
520
+ break;
521
+ }
522
+
523
+ this.showStatus(`Branch created: ${cleanBranchName}`);
524
+ window.dispatchEvent(new CustomEvent('space-gib-branch-requested', {
525
+ detail: {
526
+ galaxyName,
527
+ sourceBranch: branchName,
528
+ newBranchName: cleanBranchName,
529
+ scope: 'branch',
530
+ },
531
+ bubbles: true,
532
+ composed: true,
533
+ }));
534
+ });
535
+ });
536
+ }
537
+
538
+ private syncParentState(): void {
539
+ this.treeState.selected = this.treeState.galaxies.some(g => g.selected);
540
+ }
541
+
542
+ private updateSelectionSummary(): void {
543
+ let totalBranches = 0;
544
+ let selectedBranches = 0;
545
+ this.treeState.galaxies.forEach(g => {
546
+ totalBranches += g.branches.length;
547
+ selectedBranches += g.branches.filter(b => b.selected).length;
548
+ });
549
+ this.el.selectionSummaryEl.textContent = `${selectedBranches} of ${totalBranches} branches selected`;
550
+ }
551
+
552
+ /**
553
+ * Generates the dynamic CLI command string.
554
+ */
555
+ protected updateCommandPreview(): void {
556
+ const host = window.location.host || 'space-gib.localhost';
557
+ const protocol = window.location.protocol === 'http:' ? 'http' : 'https';
558
+ const cosmosPath = this.treeState.cosmosId;
559
+ const baseUrl = `${protocol}://${host}/${cosmosPath}`;
560
+
561
+ let cmd = `$ vcs clone ${baseUrl}`;
562
+
563
+ if (this.activeScope === CLONE_SCOPE_ITEM && this.treeState.singleItem) {
564
+ cmd += ` \\\n --item "${this.treeState.singleItem.path}"`;
565
+ } else {
566
+ const selectedFlags: string[] = [];
567
+ this.treeState.galaxies.forEach(g => {
568
+ const selectedBranches = g.branches.filter(b => b.selected);
569
+ selectedBranches.forEach(b => {
570
+ selectedFlags.push(`--galaxy ${g.type}:${b.name}`);
571
+ });
572
+ });
573
+
574
+ if (selectedFlags.length > 0) {
575
+ cmd += ` \\\n ${selectedFlags.join(' ')}`;
576
+ }
577
+ }
578
+
579
+ if (this.el.cmdPreviewEl) {
580
+ this.el.cmdPreviewEl.querySelector('code')!.textContent = cmd;
581
+ }
582
+ }
583
+
584
+ private async copyCommandToClipboard(): Promise<void> {
585
+ const code = this.el.cmdPreviewEl.querySelector('code')?.textContent?.replace(/^\$\s*/, '') || '';
586
+ try {
587
+ await navigator.clipboard.writeText(code);
588
+ this.el.btnCopyEl.textContent = '✓ Copied!';
589
+ this.el.btnCopyEl.classList.add('copied');
590
+ this.showStatus('Command copied to clipboard!');
591
+ setTimeout(() => {
592
+ if (this.el.btnCopyEl) {
593
+ this.el.btnCopyEl.textContent = '📋 Copy';
594
+ this.el.btnCopyEl.classList.remove('copied');
595
+ }
596
+ }, 2000);
597
+ } catch (err) {
598
+ console.error('Failed to copy to clipboard:', err);
599
+ }
600
+ }
601
+
602
+ private executeDownload(type: 'zip' | 'dag'): void {
603
+ const desc = type === 'zip' ? 'Workspace ZIP archive' : 'ibGib DAG bundle';
604
+ this.showStatus(`Preparing ${desc} download...`);
605
+
606
+ this.dispatchEvent(new CustomEvent(EVENT_SPACE_GIB_CLONE_EXECUTED, {
607
+ bubbles: true,
608
+ composed: true,
609
+ detail: {
610
+ scope: this.activeScope,
611
+ type,
612
+ treeState: this.treeState,
613
+ }
614
+ }));
615
+
616
+ setTimeout(() => {
617
+ this.showStatus(`Download started: ${this.treeState.cosmosName}-${type}.zip`);
618
+ }, 800);
619
+ }
620
+
621
+ private showStatus(msg: string): void {
622
+ if (!this.el.statusMessageEl) return;
623
+ this.el.statusMessageEl.textContent = msg;
624
+ this.el.statusMessageEl.classList.add('visible');
625
+ setTimeout(() => {
626
+ if (this.el.statusMessageEl) {
627
+ this.el.statusMessageEl.classList.remove('visible');
628
+ }
629
+ }, 3000);
630
+ }
631
+
632
+ public close(): void {
633
+ this.dispatchEvent(new CustomEvent(EVENT_SPACE_GIB_CLONE_CLOSED, {
634
+ bubbles: true,
635
+ composed: true,
636
+ }));
637
+ this.remove();
638
+ }
639
+
640
+ override async disconnected(): Promise<void> {
641
+ const lc = `${this.lc}[${this.disconnected.name}]`;
642
+ try {
643
+ // cleanup if needed
644
+ } catch (error) {
645
+ console.error(`${lc} ${extractErrorMsg(error)}`);
646
+ }
647
+ }
648
+ }
@@ -0,0 +1,11 @@
1
+ import { getComponentSvc } from "@ibgib/web-gib/dist/ui/component/ibgib-component-service.mjs";
2
+ import { CloneComponentMeta } from "./clone.mjs";
3
+
4
+ export * from "./clone.mjs";
5
+ export * from "./clone-constants.mjs";
6
+ export * from "./clone-types.mjs";
7
+
8
+ export async function registerCloneComponent(): Promise<void> {
9
+ const componentSvc = await getComponentSvc();
10
+ componentSvc.registerComponentMeta(new CloneComponentMeta());
11
+ }