@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.
- package/dist/client/bootstrap.mjs +24 -51
- package/dist/client/chunk-FIAGRVBY.mjs +10933 -0
- package/dist/client/chunk-XLQ3UCQY.mjs +53 -0
- package/dist/client/css/activity-bar.css +435 -0
- package/dist/client/css/base.css +167 -0
- package/dist/client/css/components.css +218 -0
- package/dist/client/css/cosmic.css +557 -0
- package/dist/client/css/dev-tools.css +163 -0
- package/dist/client/css/landing.css +184 -0
- package/dist/client/css/layout.css +1017 -0
- package/dist/client/css/onboarding.css +181 -0
- package/dist/client/css/repos.css +740 -0
- package/dist/client/css/variables.css +64 -0
- package/dist/client/index.html +208 -31
- package/dist/client/index.mjs +22 -45
- package/dist/client/privacy.html +22 -22
- package/dist/client/script.mjs +1 -1
- package/dist/client/style.css +11 -1056
- package/dist/respec-gib.node.mjs +5 -0
- package/dist/server/server.mjs +6880 -3110
- package/package.json +6 -6
- package/src/client/AUTO-GENERATED-version.mts +1 -1
- package/src/client/cosmos/seed-cosmos-workspace.mts +83 -0
- package/src/client/css/activity-bar.css +435 -0
- package/src/client/css/base.css +167 -0
- package/src/client/css/components.css +218 -0
- package/src/client/css/cosmic.css +557 -0
- package/src/client/css/dev-tools.css +163 -0
- package/src/client/css/landing.css +184 -0
- package/src/client/css/layout.css +1017 -0
- package/src/client/css/onboarding.css +181 -0
- package/src/client/css/repos.css +740 -0
- package/src/client/css/variables.css +64 -0
- package/src/client/dev-tools/vcs-workspace.mts +7 -7
- package/src/client/index.html +208 -31
- package/src/client/privacy.html +22 -22
- package/src/client/style.css +11 -1056
- package/src/client/ui/component/changes/changes.css +413 -0
- package/src/client/ui/component/changes/changes.html +37 -0
- package/src/client/ui/component/changes/changes.mts +766 -0
- package/src/client/ui/component/changes/index.mts +9 -0
- package/src/client/ui/component/chat-view/IMPLEMENTATION.md +48 -0
- package/src/client/ui/component/chat-view/chat-view.css +381 -0
- package/src/client/ui/component/chat-view/chat-view.html +52 -0
- package/src/client/ui/component/chat-view/chat-view.mts +264 -0
- package/src/client/ui/component/chat-view/index.mts +9 -0
- package/src/client/ui/component/clone/IMPLEMENTATION.md +68 -0
- package/src/client/ui/component/clone/clone-constants.mts +16 -0
- package/src/client/ui/component/clone/clone-types.mts +60 -0
- package/src/client/ui/component/clone/clone.css +383 -0
- package/src/client/ui/component/clone/clone.html +89 -0
- package/src/client/ui/component/clone/clone.mts +648 -0
- package/src/client/ui/component/clone/index.mts +11 -0
- package/src/client/ui/component/code-editor/IMPLEMENTATION.md +89 -0
- package/src/client/ui/component/code-editor/code-editor.css +357 -0
- package/src/client/ui/component/code-editor/code-editor.html +67 -0
- package/src/client/ui/component/code-editor/code-editor.mts +643 -0
- package/src/client/ui/component/code-editor/index.mts +9 -0
- package/src/client/ui/component/cosmos-navigator/IMPLEMENTATION.md +31 -0
- package/src/client/ui/component/cosmos-navigator/cosmos-navigator.css +864 -0
- package/src/client/ui/component/cosmos-navigator/cosmos-navigator.html +92 -0
- package/src/client/ui/component/cosmos-navigator/cosmos-navigator.mts +1317 -0
- package/src/client/ui/component/cosmos-navigator/index.mts +18 -0
- package/src/client/ui/component/file-explorer/IMPLEMENTATION.md +42 -0
- package/src/client/ui/component/file-explorer/file-explorer.css +720 -0
- package/src/client/ui/component/file-explorer/file-explorer.html +55 -0
- package/src/client/ui/component/file-explorer/file-explorer.mts +1412 -0
- package/src/client/ui/component/file-explorer/index.mts +9 -0
- package/src/client/ui/component/nested-chat/IMPLEMENTATION.md +18 -0
- package/src/client/ui/component/nested-chat/chat/IMPLEMENTATION.md +14 -0
- package/src/client/ui/component/nested-chat/chat/chat-constants.mts +14 -0
- package/src/client/ui/component/nested-chat/chat/chat-helpers.mts +66 -0
- package/src/client/ui/component/nested-chat/chat/chat.css +654 -0
- package/src/client/ui/component/nested-chat/chat/chat.html +90 -0
- package/src/client/ui/component/nested-chat/chat/chat.mts +561 -0
- package/src/client/ui/component/nested-chat/chat/index.mts +7 -0
- package/src/client/ui/component/nested-chat/index.mts +14 -0
- package/src/client/ui/component/nested-chat/nested-chat-constants.mts +12 -0
- package/src/client/ui/component/nested-chat/nested-chat-types.mts +53 -0
- package/src/client/ui/component/nested-chat/nested-chat.css +142 -0
- package/src/client/ui/component/nested-chat/nested-chat.html +11 -0
- package/src/client/ui/component/nested-chat/nested-chat.mts +395 -0
- package/src/client/ui/component/notes-explorer/index.mts +9 -0
- package/src/client/ui/component/notes-explorer/notes-explorer.css +313 -0
- package/src/client/ui/component/notes-explorer/notes-explorer.html +136 -0
- package/src/client/ui/component/notes-explorer/notes-explorer.mts +240 -0
- package/src/client/ui/component/notes-inspector/index.mts +9 -0
- package/src/client/ui/component/notes-inspector/notes-inspector.css +244 -0
- package/src/client/ui/component/notes-inspector/notes-inspector.html +97 -0
- package/src/client/ui/component/notes-inspector/notes-inspector.mts +152 -0
- package/src/client/ui/component/pitch/IMPLEMENTATION.md +48 -0
- package/src/client/ui/component/pitch/index.mts +9 -0
- package/src/client/ui/component/pitch/pitch.css +295 -0
- package/src/client/ui/component/pitch/pitch.html +97 -0
- package/src/client/ui/component/pitch/pitch.mts +135 -0
- package/src/client/ui/component/timeline/IMPLEMENTATION.md +33 -0
- package/src/client/ui/component/timeline/index.mts +9 -0
- package/src/client/ui/component/timeline/timeline.css +279 -0
- package/src/client/ui/component/timeline/timeline.html +23 -0
- package/src/client/ui/component/timeline/timeline.mts +261 -0
- package/src/client/ui/component/timeline-item-details/IMPLEMENTATION.md +28 -0
- package/src/client/ui/component/timeline-item-details/index.mts +9 -0
- package/src/client/ui/component/timeline-item-details/timeline-item-details.css +286 -0
- package/src/client/ui/component/timeline-item-details/timeline-item-details.html +64 -0
- package/src/client/ui/component/timeline-item-details/timeline-item-details.mts +194 -0
- package/src/client/ui/router/index.mts +3 -0
- package/src/client/ui/router/space-gib-router-helpers.mts +72 -0
- package/src/client/ui/router/space-gib-router-helpers.respec.mts +529 -0
- package/src/client/ui/router/space-gib-router-service.mts +63 -0
- package/src/client/ui/router/space-gib-router-types.mts +49 -0
- package/src/client/ui/shell/cosmic-big-bang.mts +853 -0
- package/src/client/ui/shell/space-gib-shell-constants.mts +7 -0
- package/src/client/ui/shell/space-gib-shell-service.mts +2297 -32
- package/src/server/server.mts +20 -1
- package/tsconfig.test.json +5 -1
- package/dist/client/chunk-CRJV762I.mjs +0 -3102
- package/dist/client/chunk-LXRCF5OS.mjs +0 -25
- package/dist/client/chunk-UBDQUZZV.mjs +0 -1
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
import thisCss from "./code-editor.css";
|
|
2
|
+
import thisHtml from "./code-editor.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 {
|
|
9
|
+
IbGibDynamicComponentMetaBase, IbGibDynamicComponentInstanceBase,
|
|
10
|
+
} from "@ibgib/web-gib/dist/ui/component/ibgib-dynamic-component-bases.mjs";
|
|
11
|
+
import { ElementsBase, IbGibDynamicComponentInstance } from "@ibgib/web-gib/dist/ui/component/component-types.mjs";
|
|
12
|
+
import { getComponentCtorArg } from "@ibgib/web-gib/dist/app-bootstrap/init-orchestration.mjs";
|
|
13
|
+
import { getDeterministicColorInfo } from "@ibgib/web-gib/dist/helpers.mjs";
|
|
14
|
+
import { GibColorInfo } from "@ibgib/web-gib/dist/types.mjs";
|
|
15
|
+
import { getIbGibFromSpace, getTjpAddr } from "@ibgib/core-gib/dist/common/other/ibgib-helper.mjs";
|
|
16
|
+
import { getBranchAndRootItemInfosAndIbGibsAndSpaceFromGalaxyIbGib } from "@ibgib/core-gib/dist/cosmology/branching/item/item-helpers.mjs";
|
|
17
|
+
import { B2tFSGalaxyIbGib_V1 } from "@ibgib/core-gib/dist/cosmology/branching/repo/b2tfs-repo-types.mjs";
|
|
18
|
+
import { getPlaygroundMetaspaceAndSpace } from "../../../cosmos/seed-cosmos-workspace.mjs";
|
|
19
|
+
import { getSpaceGibRouterSvc } from "../../router/index.mjs";
|
|
20
|
+
|
|
21
|
+
export const CODE_EDITOR_COMPONENT_NAME = 'space-gib-code-editor';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Metadata factory for space-gib-code-editor Web Component.
|
|
25
|
+
*/
|
|
26
|
+
export class CodeEditorComponentMeta extends IbGibDynamicComponentMetaBase {
|
|
27
|
+
protected lc: string = `[${CodeEditorComponentMeta.name}]`;
|
|
28
|
+
|
|
29
|
+
routeRegExp?: RegExp = new RegExp(`^${CODE_EDITOR_COMPONENT_NAME}$`);
|
|
30
|
+
componentName = CODE_EDITOR_COMPONENT_NAME;
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
super(getComponentCtorArg());
|
|
34
|
+
if (!customElements.get(this.componentName)) {
|
|
35
|
+
customElements.define(this.componentName, CodeEditorComponentInstance);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async createInstance({
|
|
40
|
+
path,
|
|
41
|
+
ibGibAddr
|
|
42
|
+
}: {
|
|
43
|
+
path: string;
|
|
44
|
+
ibGibAddr: IbGibAddr;
|
|
45
|
+
}): Promise<IbGibDynamicComponentInstance> {
|
|
46
|
+
const component = document.createElement(this.componentName) as CodeEditorComponentInstance;
|
|
47
|
+
await component.initialize({
|
|
48
|
+
ibGibAddr: ibGibAddr || ROOT_ADDR,
|
|
49
|
+
meta: this,
|
|
50
|
+
html: thisHtml,
|
|
51
|
+
css: [thisCss],
|
|
52
|
+
});
|
|
53
|
+
return component;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* References to DOM elements within the code editor Shadow Root.
|
|
59
|
+
*/
|
|
60
|
+
export interface CodeEditorElements extends ElementsBase {
|
|
61
|
+
rootEl: HTMLDivElement;
|
|
62
|
+
placeholderEl?: HTMLDivElement;
|
|
63
|
+
mainContainerEl?: HTMLDivElement;
|
|
64
|
+
headerEl: HTMLElement;
|
|
65
|
+
btnSaveSpinnerEl?: HTMLSpanElement;
|
|
66
|
+
crumbRepoEl: HTMLSpanElement;
|
|
67
|
+
crumbPathEl: HTMLSpanElement;
|
|
68
|
+
crumbBranchEl: HTMLSpanElement;
|
|
69
|
+
crumbTipEl: HTMLSpanElement;
|
|
70
|
+
crumbTipContainerEl: HTMLDivElement;
|
|
71
|
+
statLangEl: HTMLSpanElement;
|
|
72
|
+
statLinesEl: HTMLSpanElement;
|
|
73
|
+
statSizeEl: HTMLSpanElement;
|
|
74
|
+
btnToggleReadonlyEl: HTMLButtonElement;
|
|
75
|
+
modeIconEl: HTMLSpanElement;
|
|
76
|
+
modeTextEl: HTMLSpanElement;
|
|
77
|
+
btnCopyCodeEl: HTMLButtonElement;
|
|
78
|
+
editorGutterEl: HTMLDivElement;
|
|
79
|
+
editorTextareaEl: HTMLTextAreaElement;
|
|
80
|
+
contentEl: HTMLDivElement;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Concrete implementation of the space-gib-code-editor component.
|
|
85
|
+
*/
|
|
86
|
+
export class CodeEditorComponentInstance
|
|
87
|
+
extends IbGibDynamicComponentInstanceBase<IbGib_V1, CodeEditorElements>
|
|
88
|
+
implements IbGibDynamicComponentInstance<IbGib_V1, CodeEditorElements> {
|
|
89
|
+
|
|
90
|
+
protected lc: string = `[${CodeEditorComponentInstance.name}]`;
|
|
91
|
+
|
|
92
|
+
private _readOnly: boolean = false;
|
|
93
|
+
private filepath: string = '';
|
|
94
|
+
private repo: string = '';
|
|
95
|
+
private branch: string = 'main';
|
|
96
|
+
private tipAddr: string = '';
|
|
97
|
+
private language: string = '';
|
|
98
|
+
private originalContent?: string;
|
|
99
|
+
private isUntracked: boolean = false;
|
|
100
|
+
private debounceTimer?: any;
|
|
101
|
+
private saveStartTime: number = 0;
|
|
102
|
+
private isSaving: boolean = false;
|
|
103
|
+
private branchColorCache = new Map<string, GibColorInfo>();
|
|
104
|
+
|
|
105
|
+
get el(): CodeEditorElements {
|
|
106
|
+
if (!this.elements) {
|
|
107
|
+
throw new Error(`elements is not defined in ${this.lc}`);
|
|
108
|
+
}
|
|
109
|
+
return this.elements;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Read-only mode flag. When true, the editor acts as a protected code viewer.
|
|
114
|
+
*/
|
|
115
|
+
get readOnly(): boolean {
|
|
116
|
+
return this._readOnly;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
set readOnly(val: boolean) {
|
|
120
|
+
this._readOnly = Boolean(val);
|
|
121
|
+
this.updateReadOnlyState();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Alias for readOnly */
|
|
125
|
+
get readonly(): boolean {
|
|
126
|
+
return this.readOnly;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
set readonly(val: boolean) {
|
|
130
|
+
this.readOnly = val;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
static get observedAttributes(): string[] {
|
|
134
|
+
return ['readonly'];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
override async attributeChangedCallback(name: string, oldValue: any, newValue: any): Promise<void> {
|
|
138
|
+
await super.attributeChangedCallback(name, oldValue, newValue);
|
|
139
|
+
if (name === 'readonly') {
|
|
140
|
+
this.readOnly = newValue !== null && newValue !== 'false';
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
override async created(): Promise<void> {
|
|
145
|
+
const lc = `${this.lc}[${this.created.name}]`;
|
|
146
|
+
try {
|
|
147
|
+
const root = this.shadowRoot!;
|
|
148
|
+
this.elements = {
|
|
149
|
+
rootEl: root.getElementById('editor-root') as HTMLDivElement,
|
|
150
|
+
placeholderEl: root.getElementById('editor-empty-placeholder') as HTMLDivElement,
|
|
151
|
+
mainContainerEl: root.getElementById('editor-main-container') as HTMLDivElement,
|
|
152
|
+
headerEl: root.getElementById('editor-header') as HTMLElement,
|
|
153
|
+
crumbRepoEl: root.getElementById('crumb-repo') as HTMLSpanElement,
|
|
154
|
+
crumbPathEl: root.getElementById('crumb-path') as HTMLSpanElement,
|
|
155
|
+
crumbBranchEl: root.getElementById('crumb-branch') as HTMLSpanElement,
|
|
156
|
+
crumbTipEl: root.getElementById('crumb-tip') as HTMLSpanElement,
|
|
157
|
+
crumbTipContainerEl: root.getElementById('crumb-tip-container') as HTMLDivElement,
|
|
158
|
+
statLangEl: root.getElementById('stat-lang') as HTMLSpanElement,
|
|
159
|
+
statLinesEl: root.getElementById('stat-lines') as HTMLSpanElement,
|
|
160
|
+
statSizeEl: root.getElementById('stat-size') as HTMLSpanElement,
|
|
161
|
+
btnToggleReadonlyEl: root.getElementById('btn-toggle-readonly') as HTMLButtonElement,
|
|
162
|
+
btnSaveSpinnerEl: root.getElementById('btn-save-spinner') as HTMLSpanElement,
|
|
163
|
+
modeIconEl: root.getElementById('mode-icon') as HTMLSpanElement,
|
|
164
|
+
modeTextEl: root.getElementById('mode-text') as HTMLSpanElement,
|
|
165
|
+
btnCopyCodeEl: root.getElementById('btn-copy-code') as HTMLButtonElement,
|
|
166
|
+
editorGutterEl: root.getElementById('editor-gutter') as HTMLDivElement,
|
|
167
|
+
editorTextareaEl: root.getElementById('editor-textarea') as HTMLTextAreaElement,
|
|
168
|
+
contentEl: root.getElementById('editor-root') as HTMLDivElement,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Sync readonly attribute if set on host
|
|
172
|
+
if (this.hasAttribute('readonly')) {
|
|
173
|
+
this._readOnly = true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!this.filepath) {
|
|
177
|
+
this.clearFile({ repo: this.repo, branch: this.branch });
|
|
178
|
+
} else {
|
|
179
|
+
this.updateReadOnlyState();
|
|
180
|
+
this.updateGutter();
|
|
181
|
+
this.updateStats();
|
|
182
|
+
this.updateBranchColorStyling();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
this.bindEvents();
|
|
186
|
+
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
private bindEvents(): void {
|
|
193
|
+
const lc = `${this.lc}[${this.bindEvents.name}]`;
|
|
194
|
+
if (!this.el) return;
|
|
195
|
+
|
|
196
|
+
// Textarea input: update line numbers & stats, reset 500ms debounce
|
|
197
|
+
this.el.editorTextareaEl?.addEventListener('input', () => {
|
|
198
|
+
this.updateGutter();
|
|
199
|
+
this.updateStats();
|
|
200
|
+
|
|
201
|
+
if (this.debounceTimer) {
|
|
202
|
+
clearTimeout(this.debounceTimer);
|
|
203
|
+
}
|
|
204
|
+
this.debounceTimer = setTimeout(() => {
|
|
205
|
+
this.saveStartTime = Date.now();
|
|
206
|
+
this.showSavingIndicator();
|
|
207
|
+
this.handleDebouncedSave();
|
|
208
|
+
}, 500);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Synchronize scroll between textarea and line number gutter
|
|
212
|
+
this.el.editorTextareaEl?.addEventListener('scroll', () => {
|
|
213
|
+
if (this.el.editorGutterEl && this.el.editorTextareaEl) {
|
|
214
|
+
this.el.editorGutterEl.scrollTop = this.el.editorTextareaEl.scrollTop;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Handle Tab key in textarea for indentation
|
|
219
|
+
this.el.editorTextareaEl?.addEventListener('keydown', (e: KeyboardEvent) => {
|
|
220
|
+
if (this._readOnly) return;
|
|
221
|
+
|
|
222
|
+
if (e.key === 'Tab') {
|
|
223
|
+
e.preventDefault();
|
|
224
|
+
const textarea = this.el.editorTextareaEl;
|
|
225
|
+
const start = textarea.selectionStart;
|
|
226
|
+
const end = textarea.selectionEnd;
|
|
227
|
+
|
|
228
|
+
// Insert 4 spaces
|
|
229
|
+
textarea.value = textarea.value.substring(0, start) + ' ' + textarea.value.substring(end);
|
|
230
|
+
textarea.selectionStart = textarea.selectionEnd = start + 4;
|
|
231
|
+
|
|
232
|
+
this.updateGutter();
|
|
233
|
+
this.updateStats();
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Toggle Read-Only / Editable Mode
|
|
238
|
+
this.el.btnToggleReadonlyEl?.addEventListener('click', () => {
|
|
239
|
+
this.readOnly = !this.readOnly;
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Copy Code Button
|
|
243
|
+
this.el.btnCopyCodeEl?.addEventListener('click', async () => {
|
|
244
|
+
const code = this.getContent();
|
|
245
|
+
try {
|
|
246
|
+
if (navigator.clipboard) {
|
|
247
|
+
await navigator.clipboard.writeText(code);
|
|
248
|
+
} else {
|
|
249
|
+
// Fallback
|
|
250
|
+
const ta = document.createElement('textarea');
|
|
251
|
+
ta.value = code;
|
|
252
|
+
document.body.appendChild(ta);
|
|
253
|
+
ta.select();
|
|
254
|
+
document.execCommand('copy');
|
|
255
|
+
document.body.removeChild(ta);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (this.el.btnCopyCodeEl) {
|
|
259
|
+
const originalHtml = this.el.btnCopyCodeEl.innerHTML;
|
|
260
|
+
this.el.btnCopyCodeEl.innerHTML = '<span class="btn-icon">✓</span>';
|
|
261
|
+
setTimeout(() => {
|
|
262
|
+
if (this.el?.btnCopyCodeEl) {
|
|
263
|
+
this.el.btnCopyCodeEl.innerHTML = originalHtml;
|
|
264
|
+
}
|
|
265
|
+
}, 1500);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
this.dispatchEvent(new CustomEvent('space-gib-code-copied', {
|
|
269
|
+
bubbles: true,
|
|
270
|
+
composed: true,
|
|
271
|
+
detail: { filepath: this.filepath }
|
|
272
|
+
}));
|
|
273
|
+
} catch (err) {
|
|
274
|
+
console.warn(`${lc} Failed to copy code: ${extractErrorMsg(err)}`);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
private updateReadOnlyState(): void {
|
|
280
|
+
if (!this.elements) return;
|
|
281
|
+
|
|
282
|
+
if (this.el.editorTextareaEl) {
|
|
283
|
+
this.el.editorTextareaEl.readOnly = this._readOnly;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (this.el.rootEl) {
|
|
287
|
+
this.el.rootEl.classList.toggle('readonly-mode', this._readOnly);
|
|
288
|
+
this.el.rootEl.classList.toggle('editable-mode', !this._readOnly);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (this.el.btnToggleReadonlyEl) {
|
|
292
|
+
this.el.btnToggleReadonlyEl.classList.toggle('readonly', this._readOnly);
|
|
293
|
+
this.el.btnToggleReadonlyEl.classList.toggle('editable', !this._readOnly);
|
|
294
|
+
if (!this.isSaving) {
|
|
295
|
+
this.el.btnToggleReadonlyEl.classList.remove('saving');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!this.isSaving) {
|
|
300
|
+
if (this.el.btnSaveSpinnerEl) {
|
|
301
|
+
this.el.btnSaveSpinnerEl.style.display = 'none';
|
|
302
|
+
}
|
|
303
|
+
if (this.el.modeIconEl) {
|
|
304
|
+
this.el.modeIconEl.style.display = 'inline';
|
|
305
|
+
this.el.modeIconEl.textContent = this._readOnly ? '🔒' : '✏️';
|
|
306
|
+
}
|
|
307
|
+
if (this.el.modeTextEl) {
|
|
308
|
+
this.el.modeTextEl.textContent = this._readOnly ? 'Read-Only' : 'Editable';
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
private showSavingIndicator(): void {
|
|
314
|
+
this.isSaving = true;
|
|
315
|
+
if (this.elements) {
|
|
316
|
+
if (this.el.btnSaveSpinnerEl) {
|
|
317
|
+
this.el.btnSaveSpinnerEl.style.display = 'inline-block';
|
|
318
|
+
}
|
|
319
|
+
if (this.el.modeIconEl) {
|
|
320
|
+
this.el.modeIconEl.style.display = 'none';
|
|
321
|
+
}
|
|
322
|
+
if (this.el.modeTextEl) {
|
|
323
|
+
this.el.modeTextEl.textContent = 'Saving...';
|
|
324
|
+
}
|
|
325
|
+
if (this.el.btnToggleReadonlyEl) {
|
|
326
|
+
this.el.btnToggleReadonlyEl.classList.add('saving');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private hideSavingIndicator(): void {
|
|
332
|
+
this.isSaving = false;
|
|
333
|
+
if (this.elements) {
|
|
334
|
+
if (this.el.btnSaveSpinnerEl) {
|
|
335
|
+
this.el.btnSaveSpinnerEl.style.display = 'none';
|
|
336
|
+
}
|
|
337
|
+
if (this.el.modeIconEl) {
|
|
338
|
+
this.el.modeIconEl.style.display = 'inline';
|
|
339
|
+
}
|
|
340
|
+
if (this.el.btnToggleReadonlyEl) {
|
|
341
|
+
this.el.btnToggleReadonlyEl.classList.remove('saving');
|
|
342
|
+
}
|
|
343
|
+
this.updateReadOnlyState();
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private async handleDebouncedSave(): Promise<void> {
|
|
348
|
+
if (!this.filepath) return;
|
|
349
|
+
const currentContent = this.getContent();
|
|
350
|
+
|
|
351
|
+
// Diff against originalContent
|
|
352
|
+
const isReverted = !this.isUntracked && this.originalContent !== undefined && currentContent === this.originalContent;
|
|
353
|
+
|
|
354
|
+
this.dispatchEvent(new CustomEvent('space-gib-code-changed', {
|
|
355
|
+
bubbles: true,
|
|
356
|
+
composed: true,
|
|
357
|
+
detail: {
|
|
358
|
+
filepath: this.filepath,
|
|
359
|
+
content: currentContent,
|
|
360
|
+
lineCount: this.getLineCount(),
|
|
361
|
+
isReverted,
|
|
362
|
+
isUntracked: this.isUntracked,
|
|
363
|
+
repo: this.repo,
|
|
364
|
+
branch: this.branch,
|
|
365
|
+
}
|
|
366
|
+
}));
|
|
367
|
+
|
|
368
|
+
// Minimum 0.5s (500ms) animation duration for saving indicator
|
|
369
|
+
const elapsed = Date.now() - this.saveStartTime;
|
|
370
|
+
const remaining = Math.max(0, 500 - elapsed);
|
|
371
|
+
setTimeout(() => {
|
|
372
|
+
this.hideSavingIndicator();
|
|
373
|
+
this.saveStartTime = 0;
|
|
374
|
+
}, remaining);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
private getLineCount(): number {
|
|
378
|
+
const text = this.el?.editorTextareaEl?.value || '';
|
|
379
|
+
return text.split('\n').length;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
private updateGutter(): void {
|
|
383
|
+
if (!this.el?.editorGutterEl) return;
|
|
384
|
+
const lineCount = this.getLineCount();
|
|
385
|
+
const linesHtml = Array.from({ length: lineCount }, (_, i) => `<span class="line-num">${i + 1}</span>`).join('');
|
|
386
|
+
this.el.editorGutterEl.innerHTML = linesHtml;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private updateStats(): void {
|
|
390
|
+
if (!this.elements) return;
|
|
391
|
+
const text = this.el.editorTextareaEl?.value || '';
|
|
392
|
+
const lines = text.split('\n').length;
|
|
393
|
+
const bytes = new Blob([text]).size;
|
|
394
|
+
|
|
395
|
+
if (this.el.statLinesEl) {
|
|
396
|
+
this.el.statLinesEl.textContent = `${lines} line${lines === 1 ? '' : 's'}`;
|
|
397
|
+
}
|
|
398
|
+
if (this.el.statSizeEl) {
|
|
399
|
+
this.el.statSizeEl.textContent = bytes > 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${bytes} B`;
|
|
400
|
+
}
|
|
401
|
+
if (this.el.statLangEl) {
|
|
402
|
+
this.el.statLangEl.textContent = this.language;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Loads a file into the editor, updating breadcrumbs, content, branch tip, and stats.
|
|
408
|
+
*/
|
|
409
|
+
public loadFile(opts: {
|
|
410
|
+
filepath: string;
|
|
411
|
+
content?: string;
|
|
412
|
+
originalContent?: string;
|
|
413
|
+
isUntracked?: boolean;
|
|
414
|
+
branch?: string;
|
|
415
|
+
tipAddr?: string;
|
|
416
|
+
language?: string;
|
|
417
|
+
repo?: string;
|
|
418
|
+
readOnly?: boolean;
|
|
419
|
+
}): void {
|
|
420
|
+
if (this.debounceTimer) {
|
|
421
|
+
clearTimeout(this.debounceTimer);
|
|
422
|
+
this.debounceTimer = undefined;
|
|
423
|
+
}
|
|
424
|
+
this.saveStartTime = 0;
|
|
425
|
+
this.hideSavingIndicator();
|
|
426
|
+
|
|
427
|
+
this.filepath = opts.filepath || this.filepath;
|
|
428
|
+
if (opts.repo) this.repo = opts.repo;
|
|
429
|
+
if (opts.branch) this.branch = opts.branch;
|
|
430
|
+
if (opts.tipAddr) this.tipAddr = opts.tipAddr;
|
|
431
|
+
|
|
432
|
+
if (opts.originalContent !== undefined) {
|
|
433
|
+
this.originalContent = opts.originalContent;
|
|
434
|
+
} else if (opts.content !== undefined && !opts.isUntracked) {
|
|
435
|
+
this.originalContent = opts.content;
|
|
436
|
+
} else {
|
|
437
|
+
this.originalContent = undefined;
|
|
438
|
+
}
|
|
439
|
+
this.isUntracked = Boolean(opts.isUntracked);
|
|
440
|
+
|
|
441
|
+
// Auto-detect language from extension if not provided
|
|
442
|
+
if (opts.language) {
|
|
443
|
+
this.language = opts.language;
|
|
444
|
+
} else {
|
|
445
|
+
const ext = this.filepath.split('.').pop()?.toLowerCase();
|
|
446
|
+
switch (ext) {
|
|
447
|
+
case 'ts':
|
|
448
|
+
case 'mts':
|
|
449
|
+
this.language = 'TypeScript';
|
|
450
|
+
break;
|
|
451
|
+
case 'js':
|
|
452
|
+
case 'mjs':
|
|
453
|
+
this.language = 'JavaScript';
|
|
454
|
+
break;
|
|
455
|
+
case 'html':
|
|
456
|
+
this.language = 'HTML';
|
|
457
|
+
break;
|
|
458
|
+
case 'css':
|
|
459
|
+
this.language = 'CSS';
|
|
460
|
+
break;
|
|
461
|
+
case 'json':
|
|
462
|
+
this.language = 'JSON';
|
|
463
|
+
break;
|
|
464
|
+
case 'md':
|
|
465
|
+
this.language = 'Markdown';
|
|
466
|
+
break;
|
|
467
|
+
default:
|
|
468
|
+
this.language = 'Plain Text';
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (opts.readOnly !== undefined) {
|
|
473
|
+
this.readOnly = opts.readOnly;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (this.elements) {
|
|
477
|
+
if (this.el.placeholderEl) this.el.placeholderEl.style.display = 'none';
|
|
478
|
+
if (this.el.mainContainerEl) this.el.mainContainerEl.style.display = 'flex';
|
|
479
|
+
|
|
480
|
+
if (this.el.crumbRepoEl) this.el.crumbRepoEl.textContent = this.repo;
|
|
481
|
+
if (this.el.crumbPathEl) this.el.crumbPathEl.textContent = this.filepath;
|
|
482
|
+
if (this.el.crumbBranchEl) this.el.crumbBranchEl.textContent = this.branch;
|
|
483
|
+
if (this.el.crumbTipEl) this.el.crumbTipEl.textContent = this.tipAddr;
|
|
484
|
+
|
|
485
|
+
if (opts.content !== undefined && this.el.editorTextareaEl) {
|
|
486
|
+
this.el.editorTextareaEl.value = opts.content;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
this.updateReadOnlyState();
|
|
490
|
+
this.updateGutter();
|
|
491
|
+
this.updateStats();
|
|
492
|
+
}
|
|
493
|
+
this.updateBranchColorStyling();
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
public setTipAddr(tipAddr: string): void {
|
|
497
|
+
this.tipAddr = tipAddr;
|
|
498
|
+
if (this.elements && this.el.crumbTipEl) {
|
|
499
|
+
this.el.crumbTipEl.textContent = tipAddr;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
public markAsCommitted(tipAddr?: string): void {
|
|
504
|
+
this.originalContent = this.getContent();
|
|
505
|
+
this.isUntracked = false;
|
|
506
|
+
if (tipAddr) {
|
|
507
|
+
this.setTipAddr(tipAddr);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Clears the active file and displays the empty state placeholder.
|
|
513
|
+
*/
|
|
514
|
+
public clearFile(opts?: { repo?: string; branch?: string }): void {
|
|
515
|
+
if (this.debounceTimer) {
|
|
516
|
+
clearTimeout(this.debounceTimer);
|
|
517
|
+
this.debounceTimer = undefined;
|
|
518
|
+
}
|
|
519
|
+
this.saveStartTime = 0;
|
|
520
|
+
this.hideSavingIndicator();
|
|
521
|
+
this.originalContent = undefined;
|
|
522
|
+
this.isUntracked = false;
|
|
523
|
+
|
|
524
|
+
this.filepath = '';
|
|
525
|
+
if (opts?.repo) this.repo = opts.repo;
|
|
526
|
+
if (opts?.branch) this.branch = opts.branch;
|
|
527
|
+
this.tipAddr = '';
|
|
528
|
+
this.language = '';
|
|
529
|
+
|
|
530
|
+
if (this.elements) {
|
|
531
|
+
if (this.el.placeholderEl) this.el.placeholderEl.style.display = 'flex';
|
|
532
|
+
if (this.el.mainContainerEl) this.el.mainContainerEl.style.display = 'none';
|
|
533
|
+
|
|
534
|
+
if (this.el.crumbRepoEl) this.el.crumbRepoEl.textContent = this.repo;
|
|
535
|
+
if (this.el.crumbPathEl) this.el.crumbPathEl.textContent = '';
|
|
536
|
+
if (this.el.crumbBranchEl) this.el.crumbBranchEl.textContent = this.branch;
|
|
537
|
+
if (this.el.crumbTipEl) this.el.crumbTipEl.textContent = '';
|
|
538
|
+
|
|
539
|
+
if (this.el.editorTextareaEl) {
|
|
540
|
+
this.el.editorTextareaEl.value = '';
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
this.updateReadOnlyState();
|
|
544
|
+
this.updateGutter();
|
|
545
|
+
this.updateStats();
|
|
546
|
+
}
|
|
547
|
+
this.updateBranchColorStyling();
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
public getFilepath(): string {
|
|
551
|
+
return this.filepath;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
public getContent(): string {
|
|
555
|
+
return this.el?.editorTextareaEl?.value || '';
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
public setContent(content: string): void {
|
|
559
|
+
if (this.el?.editorTextareaEl) {
|
|
560
|
+
this.el.editorTextareaEl.value = content;
|
|
561
|
+
this.updateGutter();
|
|
562
|
+
this.updateStats();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
public setReadOnly(readOnly: boolean): void {
|
|
567
|
+
this.readOnly = readOnly;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
public setGalaxyAndBranch(repo: string, branch: string = 'main'): void {
|
|
571
|
+
this.repo = repo;
|
|
572
|
+
this.branch = branch;
|
|
573
|
+
if (this.elements) {
|
|
574
|
+
if (this.el.crumbRepoEl) this.el.crumbRepoEl.textContent = this.repo;
|
|
575
|
+
if (this.el.crumbBranchEl) this.el.crumbBranchEl.textContent = this.branch;
|
|
576
|
+
}
|
|
577
|
+
this.updateBranchColorStyling();
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
public clearBranchColorCache(): void {
|
|
581
|
+
this.branchColorCache.clear();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
private async resolveGalaxyIbGib(galaxyName: string, space: any): Promise<B2tFSGalaxyIbGib_V1 | undefined> {
|
|
585
|
+
const router = getSpaceGibRouterSvc();
|
|
586
|
+
for (const manifest of router.cosmosManifestCache?.values() || []) {
|
|
587
|
+
const entry = manifest.data?.galaxies?.[galaxyName];
|
|
588
|
+
if (entry?.galaxyAddr) {
|
|
589
|
+
const gib = await getIbGibFromSpace<B2tFSGalaxyIbGib_V1>({ addr: entry.galaxyAddr, space });
|
|
590
|
+
if (gib) return gib;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return undefined;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
public async updateBranchColorStyling(): Promise<void> {
|
|
597
|
+
try {
|
|
598
|
+
if (!this.repo) return;
|
|
599
|
+
const cacheKey = `${this.repo || 'unknown'}~${this.branch}`;
|
|
600
|
+
let colorInfo = this.branchColorCache.get(cacheKey);
|
|
601
|
+
if (!colorInfo) {
|
|
602
|
+
const { metaspace, space } = await getPlaygroundMetaspaceAndSpace();
|
|
603
|
+
const galaxyIbGib = await this.resolveGalaxyIbGib(this.repo, space);
|
|
604
|
+
let branchAddrOrTjp: string | undefined;
|
|
605
|
+
if (galaxyIbGib) {
|
|
606
|
+
const branchAndRoot = await getBranchAndRootItemInfosAndIbGibsAndSpaceFromGalaxyIbGib({
|
|
607
|
+
galaxyIbGib,
|
|
608
|
+
branchName: this.branch,
|
|
609
|
+
metaspace,
|
|
610
|
+
}).catch(() => undefined);
|
|
611
|
+
if (branchAndRoot?.branchIbGib) {
|
|
612
|
+
branchAddrOrTjp = getTjpAddr({ ibGib: branchAndRoot.branchIbGib }) || branchAndRoot.branchInfo?.branchAddr;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
colorInfo = getDeterministicColorInfo({
|
|
616
|
+
ibGibAddr: branchAddrOrTjp,
|
|
617
|
+
gib: branchAddrOrTjp ? undefined : this.branch,
|
|
618
|
+
translucentAlpha: 18,
|
|
619
|
+
});
|
|
620
|
+
if (this.repo && branchAddrOrTjp) {
|
|
621
|
+
this.branchColorCache.set(cacheKey, colorInfo);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (colorInfo) {
|
|
626
|
+
this.style.setProperty('--branch-accent-color', colorInfo.punctiliarColor);
|
|
627
|
+
this.style.setProperty('--branch-accent-translucent', colorInfo.punctiliarColorTranslucent);
|
|
628
|
+
this.style.setProperty('--branch-accent-contrast', colorInfo.punctiliarColorContrast);
|
|
629
|
+
}
|
|
630
|
+
} catch (err) {
|
|
631
|
+
console.warn(`${this.lc} Error updating branch color styling: ${extractErrorMsg(err)}`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
override async disconnected(): Promise<void> {
|
|
636
|
+
const lc = `${this.lc}[${this.disconnected.name}]`;
|
|
637
|
+
try {
|
|
638
|
+
// cleanup if needed
|
|
639
|
+
} catch (error) {
|
|
640
|
+
console.error(`${lc} ${extractErrorMsg(error)}`);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getComponentSvc } from "@ibgib/web-gib/dist/ui/component/ibgib-component-service.mjs";
|
|
2
|
+
import { CodeEditorComponentMeta } from "./code-editor.mjs";
|
|
3
|
+
|
|
4
|
+
export * from "./code-editor.mjs";
|
|
5
|
+
|
|
6
|
+
export async function registerCodeEditorComponent(): Promise<void> {
|
|
7
|
+
const componentSvc = await getComponentSvc();
|
|
8
|
+
componentSvc.registerComponentMeta(new CodeEditorComponentMeta());
|
|
9
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# space-gib-cosmos-navigator Implementation Specification
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
`space-gib-cosmos-navigator` is a custom Web Component built on top of the `@ibgib/web-gib` dynamic component microframework. It acts as the core cosmological navigation interface for `space-gib`, presenting both the overarching **Polycosmos** (multi-project) zoom state and the active **Cosmos** (project) zoom state within a single unified component.
|
|
5
|
+
|
|
6
|
+
## Architecture & Lifecycles
|
|
7
|
+
- **Tag Name**: `space-gib-cosmos-navigator`
|
|
8
|
+
- **Component Meta**: `CosmosNavigatorComponentMeta` (extends `IbGibDynamicComponentMetaBase`)
|
|
9
|
+
- **Component Instance**: `CosmosNavigatorComponentInstance` (extends `IbGibDynamicComponentInstanceBase`)
|
|
10
|
+
- **Backing ibGib**: Initialized with `ROOT_ADDR` (virtual root genesis).
|
|
11
|
+
- **Shadow DOM**: Uses open Shadow DOM. Encapsulates the starfield particle canvas, the central Big Bang singularity, orbiting cosmos nodes, and active cosmos cluster satellites.
|
|
12
|
+
- **Dynamic Injection**: Injected by `SpaceGibShellService` into host `<div id="cosmos-navigator-container"></div>` via `IbGibComponentService`.
|
|
13
|
+
|
|
14
|
+
## State & Zoom Continuum
|
|
15
|
+
1. **Empty Primordial State (0 Cosmoses)**:
|
|
16
|
+
- Central Big Bang singularity is visible.
|
|
17
|
+
- Clicking it triggers the Big Bang explosion and creates Cosmos 1.
|
|
18
|
+
2. **Polycosmos State (Zoomed Out)**:
|
|
19
|
+
- Central Big Bang singularity acts as the "+ New Cosmos" trigger.
|
|
20
|
+
- Existing cosmoses orbit around it as glowing galaxy nodes with space count badges.
|
|
21
|
+
- Clicking an orbiting cosmos calls `selectCosmos(id)` to zoom in.
|
|
22
|
+
- Clicking the central Big Bang creates a new Cosmos (orbiting nodes disperse, explosion fires, new Cosmos N is born).
|
|
23
|
+
3. **Cosmos State (Zoomed In)**:
|
|
24
|
+
- Focuses on the selected active cosmos.
|
|
25
|
+
- Central Superspaces galaxy node unfolds its satellites (Code Space `</>`, Text Space `📝`).
|
|
26
|
+
- Clicking a space triggers the app launch animation sequence and adds it to the Activity Bar rail.
|
|
27
|
+
|
|
28
|
+
## Custom Events Emitted (Composed & Bubbling)
|
|
29
|
+
- `space-gib-cosmos-selected`: Emitted when a cosmos is selected (detail: `{ cosmosId, name, spaces }`).
|
|
30
|
+
- `space-gib-space-added`: Emitted when a space (code/text) is added to the active cosmos (detail: `{ cosmosId, space }`).
|
|
31
|
+
- `space-gib-polycosmos-activated`: Emitted when navigating out to the Polycosmos level.
|