@crowdedkingdoms/crowdyjs 9.0.0 → 11.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/MIGRATION.md +60 -38
- package/README.md +86 -50
- package/dist/crowdy-client.d.ts +3 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +2 -0
- package/dist/crowdy-studio/controller.d.ts +188 -0
- package/dist/crowdy-studio/controller.d.ts.map +1 -0
- package/dist/crowdy-studio/controller.js +915 -0
- package/dist/crowdy-studio/diagnostics.d.ts +22 -0
- package/dist/crowdy-studio/diagnostics.d.ts.map +1 -0
- package/dist/crowdy-studio/diagnostics.js +141 -0
- package/dist/crowdy-studio/dom-shell.d.ts +55 -0
- package/dist/crowdy-studio/dom-shell.d.ts.map +1 -0
- package/dist/crowdy-studio/dom-shell.js +527 -0
- package/dist/crowdy-studio/editor.d.ts +17 -0
- package/dist/crowdy-studio/editor.d.ts.map +1 -0
- package/dist/crowdy-studio/editor.js +1 -0
- package/dist/crowdy-studio/index.d.ts +10 -0
- package/dist/crowdy-studio/index.d.ts.map +1 -0
- package/dist/crowdy-studio/index.js +9 -0
- package/dist/crowdy-studio/models.d.ts +149 -0
- package/dist/crowdy-studio/models.d.ts.map +1 -0
- package/dist/crowdy-studio/models.js +62 -0
- package/dist/crowdy-studio/monaco-editor.d.ts +16 -0
- package/dist/crowdy-studio/monaco-editor.d.ts.map +1 -0
- package/dist/crowdy-studio/monaco-editor.js +548 -0
- package/dist/crowdy-studio/mount.d.ts +17 -0
- package/dist/crowdy-studio/mount.d.ts.map +1 -0
- package/dist/crowdy-studio/mount.js +86 -0
- package/dist/crowdy-studio/starter-projects.d.ts +11 -0
- package/dist/crowdy-studio/starter-projects.d.ts.map +1 -0
- package/dist/crowdy-studio/starter-projects.js +102 -0
- package/dist/crowdy-studio/styles.d.ts +2 -0
- package/dist/crowdy-studio/styles.d.ts.map +1 -0
- package/dist/crowdy-studio/styles.js +14 -0
- package/dist/crowdy-studio/textarea-editor.d.ts +8 -0
- package/dist/crowdy-studio/textarea-editor.d.ts.map +1 -0
- package/dist/crowdy-studio/textarea-editor.js +71 -0
- package/dist/domains/crowdyStudio.d.ts +27 -0
- package/dist/domains/crowdyStudio.d.ts.map +1 -0
- package/dist/domains/crowdyStudio.js +301 -0
- package/dist/domains/playerCompute.d.ts +1 -1
- package/dist/domains/playerCompute.js +1 -1
- package/dist/generated/graphql.d.ts +792 -7
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +57 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -5
- package/dist/kit/npcs.d.ts.map +1 -1
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/live-coding/rust-analysis.d.ts.map +1 -1
- package/dist/live-coding/rust-analysis.js +64 -1
- package/dist/live-coding/worker-transport.d.ts +3 -0
- package/dist/live-coding/worker-transport.d.ts.map +1 -1
- package/dist/live-coding/worker-transport.js +13 -0
- package/package.json +4 -4
- package/dist/live-coding/ide.d.ts +0 -23
- package/dist/live-coding/ide.d.ts.map +0 -1
- package/dist/live-coding/ide.js +0 -514
- package/dist/live-coding/index.d.ts +0 -8
- package/dist/live-coding/index.d.ts.map +0 -1
- package/dist/live-coding/index.js +0 -7
- package/dist/live-coding/live-coding-controller.d.ts +0 -75
- package/dist/live-coding/live-coding-controller.d.ts.map +0 -1
- package/dist/live-coding/live-coding-controller.js +0 -141
- package/dist/live-coding/mount.d.ts +0 -26
- package/dist/live-coding/mount.d.ts.map +0 -1
- package/dist/live-coding/mount.js +0 -108
- package/dist/live-coding/templates.d.ts +0 -17
- package/dist/live-coding/templates.d.ts.map +0 -1
- package/dist/live-coding/templates.js +0 -59
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import { projectTargets, } from './models.js';
|
|
2
|
+
import { CROWDY_STUDIO_STYLES } from './styles.js';
|
|
3
|
+
/** Modular DOM shell; editor implementations mount into `editorHost`. */
|
|
4
|
+
export class CrowdyStudioDomShell {
|
|
5
|
+
constructor(host, controller) {
|
|
6
|
+
this.controller = controller;
|
|
7
|
+
this.panelButtons = new Map();
|
|
8
|
+
this.panels = new Map();
|
|
9
|
+
this.activePanel = 'problems';
|
|
10
|
+
this.disposed = false;
|
|
11
|
+
const style = document.createElement('style');
|
|
12
|
+
style.textContent = CROWDY_STUDIO_STYLES;
|
|
13
|
+
this.root = element('div', 'ck-crowdy-studio');
|
|
14
|
+
this.root.append(style);
|
|
15
|
+
const toolbar = element('div', 'ck-crowdy-studio-toolbar');
|
|
16
|
+
const projectTools = element('div', 'ck-crowdy-studio-project-tools');
|
|
17
|
+
this.projectSelect = document.createElement('select');
|
|
18
|
+
this.projectSelect.setAttribute('aria-label', 'Current project');
|
|
19
|
+
const newButton = button('New project');
|
|
20
|
+
this.saveBadge = element('span', 'ck-crowdy-studio-save');
|
|
21
|
+
this.saveAction = button('Retry save');
|
|
22
|
+
this.remoteAction = button('Use cloud version');
|
|
23
|
+
this.saveAction.hidden = true;
|
|
24
|
+
this.remoteAction.hidden = true;
|
|
25
|
+
projectTools.append(this.projectSelect, newButton, this.saveBadge, this.saveAction, this.remoteAction);
|
|
26
|
+
const runtimeTools = element('div', 'ck-crowdy-studio-runtime-tools');
|
|
27
|
+
this.testAction = button('Test draft');
|
|
28
|
+
this.deployAction = button('Deploy live');
|
|
29
|
+
const stop = button('Stop project');
|
|
30
|
+
this.runtimeStatus = element('span', 'ck-crowdy-studio-status');
|
|
31
|
+
this.budgetStatus = element('span', 'ck-crowdy-studio-status');
|
|
32
|
+
runtimeTools.append(this.testAction, this.deployAction, stop, this.runtimeStatus, this.budgetStatus);
|
|
33
|
+
toolbar.append(projectTools, runtimeTools);
|
|
34
|
+
this.newForm = document.createElement('form');
|
|
35
|
+
this.newForm.className = 'ck-crowdy-studio-new';
|
|
36
|
+
this.newForm.dataset.open = 'false';
|
|
37
|
+
this.newName = document.createElement('input');
|
|
38
|
+
this.newName.required = true;
|
|
39
|
+
this.newName.placeholder = 'Project name';
|
|
40
|
+
this.newName.setAttribute('aria-label', 'New project name');
|
|
41
|
+
this.newKind = select([
|
|
42
|
+
['SERVER', 'Server'],
|
|
43
|
+
['CLIENT', 'Client'],
|
|
44
|
+
['FULL_STACK', 'Full stack'],
|
|
45
|
+
]);
|
|
46
|
+
for (const option of Array.from(this.newKind.options ?? [])) {
|
|
47
|
+
option.disabled =
|
|
48
|
+
option.value === 'SERVER'
|
|
49
|
+
? !this.controller.canTarget('SERVER', 'write')
|
|
50
|
+
: option.value === 'CLIENT'
|
|
51
|
+
? !this.controller.canTarget('CLIENT', 'write')
|
|
52
|
+
: !this.controller.canTarget('SERVER', 'write') ||
|
|
53
|
+
!this.controller.canTarget('CLIENT', 'write');
|
|
54
|
+
}
|
|
55
|
+
this.newKind.setAttribute('aria-label', 'New project type');
|
|
56
|
+
const create = button('Create project', 'submit');
|
|
57
|
+
const cancel = button('Cancel');
|
|
58
|
+
this.newForm.append(this.newName, this.newKind, create, cancel);
|
|
59
|
+
const main = element('div', 'ck-crowdy-studio-main');
|
|
60
|
+
this.explorer = element('aside', 'ck-crowdy-studio-explorer');
|
|
61
|
+
const editorColumn = element('section', 'ck-crowdy-studio-editor-column');
|
|
62
|
+
this.tabs = element('div', 'ck-crowdy-studio-tabs');
|
|
63
|
+
this.editorHost = element('div', 'ck-crowdy-studio-editor');
|
|
64
|
+
editorColumn.append(this.tabs, this.editorHost);
|
|
65
|
+
const settings = element('aside', 'ck-crowdy-studio-settings');
|
|
66
|
+
const settingsTitle = element('h3');
|
|
67
|
+
settingsTitle.textContent = 'Project settings';
|
|
68
|
+
this.settingsName = input('Project name');
|
|
69
|
+
this.settingsDescription = input('Description');
|
|
70
|
+
this.serverModuleName = input('Server module name');
|
|
71
|
+
this.clientModuleName = input('Client module name');
|
|
72
|
+
this.pairing = select([
|
|
73
|
+
['NONE', 'No pairing'],
|
|
74
|
+
['OPTIONAL', 'Optional companion'],
|
|
75
|
+
['REQUIRED', 'Require client companion'],
|
|
76
|
+
]);
|
|
77
|
+
settings.append(settingsTitle, labeled('Name', this.settingsName), labeled('Description', this.settingsDescription), labeled('Server module', this.serverModuleName), labeled('Client module', this.clientModuleName), labeled('Pairing', this.pairing));
|
|
78
|
+
main.append(this.explorer, editorColumn, settings);
|
|
79
|
+
const bottom = element('section', 'ck-crowdy-studio-bottom');
|
|
80
|
+
const panelTabs = element('div', 'ck-crowdy-studio-panel-tabs');
|
|
81
|
+
this.problemsPanel = this.createPanel('problems', 'Problems', panelTabs);
|
|
82
|
+
this.buildPanel = this.createPanel('build', 'Build', panelTabs);
|
|
83
|
+
this.logsPanel = this.createPanel('logs', 'Logs', panelTabs);
|
|
84
|
+
this.runsPanel = this.createPanel('runs', 'Runs', panelTabs);
|
|
85
|
+
this.invokePanel = this.createPanel('invoke', 'Invoke', panelTabs);
|
|
86
|
+
const panelBody = element('div');
|
|
87
|
+
for (const panel of this.panels.values())
|
|
88
|
+
panelBody.append(panel);
|
|
89
|
+
bottom.append(panelTabs, panelBody);
|
|
90
|
+
this.invokeExport = input('Export name');
|
|
91
|
+
this.invokeExport.value = 'invoke';
|
|
92
|
+
this.invokeParams = document.createElement('textarea');
|
|
93
|
+
this.invokeParams.placeholder = '{"example": true}';
|
|
94
|
+
this.invokeParams.setAttribute('aria-label', 'Invoke JSON parameters');
|
|
95
|
+
const invokeButton = button('Invoke server export');
|
|
96
|
+
this.invokeResult = document.createElement('pre');
|
|
97
|
+
const invokeControls = element('div', 'ck-crowdy-studio-invoke');
|
|
98
|
+
invokeControls.append(this.invokeExport, this.invokeParams, invokeButton);
|
|
99
|
+
this.invokePanel.append(invokeControls, this.invokeResult);
|
|
100
|
+
this.root.append(toolbar, this.newForm, main, bottom);
|
|
101
|
+
host.appendChild(this.root);
|
|
102
|
+
newButton.addEventListener('click', () => {
|
|
103
|
+
this.newForm.dataset.open =
|
|
104
|
+
this.newForm.dataset.open === 'true' ? 'false' : 'true';
|
|
105
|
+
if (this.newForm.dataset.open === 'true')
|
|
106
|
+
this.newName.focus();
|
|
107
|
+
});
|
|
108
|
+
cancel.addEventListener('click', () => {
|
|
109
|
+
this.newForm.dataset.open = 'false';
|
|
110
|
+
});
|
|
111
|
+
this.newForm.addEventListener('submit', (event) => {
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
void this.run(async () => {
|
|
114
|
+
await this.controller.createProject({
|
|
115
|
+
name: this.newName.value,
|
|
116
|
+
kind: this.newKind.value,
|
|
117
|
+
});
|
|
118
|
+
this.newName.value = '';
|
|
119
|
+
this.newForm.dataset.open = 'false';
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
this.projectSelect.addEventListener('change', () => {
|
|
123
|
+
void this.run(() => this.controller.switchProject(this.projectSelect.value));
|
|
124
|
+
});
|
|
125
|
+
this.testAction.addEventListener('click', () => void this.run(() => this.controller.testDraft()));
|
|
126
|
+
this.deployAction.addEventListener('click', () => void this.run(() => this.controller.deployLive()));
|
|
127
|
+
stop.addEventListener('click', () => void this.run(() => this.controller.stopProject()));
|
|
128
|
+
this.saveAction.addEventListener('click', () => {
|
|
129
|
+
void this.run(() => this.controller.getState().saveState === 'CONFLICT'
|
|
130
|
+
? this.controller.overwriteConflict()
|
|
131
|
+
: this.controller.retrySave());
|
|
132
|
+
});
|
|
133
|
+
this.remoteAction.addEventListener('click', () => void this.run(() => this.controller.acceptRemoteConflict()));
|
|
134
|
+
this.settingsName.addEventListener('change', () => this.controller.updateSettings({ name: this.settingsName.value }));
|
|
135
|
+
this.settingsDescription.addEventListener('change', () => this.controller.updateSettings({
|
|
136
|
+
description: this.settingsDescription.value,
|
|
137
|
+
}));
|
|
138
|
+
this.serverModuleName.addEventListener('change', () => this.controller.updateSettings({
|
|
139
|
+
serverModuleName: this.serverModuleName.value,
|
|
140
|
+
}));
|
|
141
|
+
this.clientModuleName.addEventListener('change', () => this.controller.updateSettings({
|
|
142
|
+
clientModuleName: this.clientModuleName.value,
|
|
143
|
+
}));
|
|
144
|
+
this.pairing.addEventListener('change', () => this.controller.setPairingPreference(this.pairing.value));
|
|
145
|
+
invokeButton.addEventListener('click', () => {
|
|
146
|
+
void this.run(() => this.controller.invoke(this.invokeExport.value, this.invokeParams.value));
|
|
147
|
+
});
|
|
148
|
+
this.controller.setSurfaceVisible('usage', true);
|
|
149
|
+
this.activatePanel('problems');
|
|
150
|
+
}
|
|
151
|
+
render(state) {
|
|
152
|
+
if (this.disposed)
|
|
153
|
+
return;
|
|
154
|
+
this.renderProjects(state);
|
|
155
|
+
this.renderSaveState(state);
|
|
156
|
+
this.renderExplorer(state);
|
|
157
|
+
this.renderTabs(state);
|
|
158
|
+
this.renderSettings(state);
|
|
159
|
+
const projectTargetsAvailable = state.project
|
|
160
|
+
? projectTargets(state.project.kind).every((target) => this.controller.canTarget(target, 'write'))
|
|
161
|
+
: false;
|
|
162
|
+
this.testAction.disabled = !projectTargetsAvailable;
|
|
163
|
+
this.deployAction.disabled = !projectTargetsAvailable;
|
|
164
|
+
this.renderProblems(state);
|
|
165
|
+
this.renderBuild(state);
|
|
166
|
+
this.renderRuntimeRows(this.logsPanel, state.logs);
|
|
167
|
+
this.renderRuntimeRows(this.runsPanel, state.runs);
|
|
168
|
+
this.invokeResult.textContent = state.invokeResult
|
|
169
|
+
? formatInvokeResult(state.invokeResult)
|
|
170
|
+
: '';
|
|
171
|
+
this.runtimeStatus.textContent = [
|
|
172
|
+
state.runtime.phase,
|
|
173
|
+
state.runtime.target,
|
|
174
|
+
state.runtime.message,
|
|
175
|
+
]
|
|
176
|
+
.filter(Boolean)
|
|
177
|
+
.join(' · ');
|
|
178
|
+
this.budgetStatus.textContent = budgetText(state);
|
|
179
|
+
}
|
|
180
|
+
dispose() {
|
|
181
|
+
if (this.disposed)
|
|
182
|
+
return;
|
|
183
|
+
this.disposed = true;
|
|
184
|
+
this.controller.setSurfaceVisible('usage', false);
|
|
185
|
+
this.controller.setSurfaceVisible('logs', false);
|
|
186
|
+
this.controller.setSurfaceVisible('runs', false);
|
|
187
|
+
this.root.remove();
|
|
188
|
+
}
|
|
189
|
+
createPanel(name, label, tabs) {
|
|
190
|
+
const tab = button(label);
|
|
191
|
+
tab.className = 'ck-crowdy-studio-panel-tab';
|
|
192
|
+
tab.addEventListener('click', () => this.activatePanel(name));
|
|
193
|
+
this.panelButtons.set(name, tab);
|
|
194
|
+
tabs.append(tab);
|
|
195
|
+
const panel = element('div', 'ck-crowdy-studio-panel');
|
|
196
|
+
panel.dataset.panel = name;
|
|
197
|
+
this.panels.set(name, panel);
|
|
198
|
+
return panel;
|
|
199
|
+
}
|
|
200
|
+
activatePanel(name) {
|
|
201
|
+
const previous = this.activePanel;
|
|
202
|
+
this.activePanel = name;
|
|
203
|
+
for (const [key, buttonElement] of this.panelButtons) {
|
|
204
|
+
buttonElement.dataset.active = String(key === name);
|
|
205
|
+
}
|
|
206
|
+
for (const [key, panel] of this.panels) {
|
|
207
|
+
panel.dataset.active = String(key === name);
|
|
208
|
+
}
|
|
209
|
+
const previousPoll = polledSurface(previous);
|
|
210
|
+
const nextPoll = polledSurface(name);
|
|
211
|
+
if (previousPoll && previousPoll !== nextPoll) {
|
|
212
|
+
this.controller.setSurfaceVisible(previousPoll, false);
|
|
213
|
+
}
|
|
214
|
+
if (nextPoll)
|
|
215
|
+
this.controller.setSurfaceVisible(nextPoll, true);
|
|
216
|
+
}
|
|
217
|
+
renderProjects(state) {
|
|
218
|
+
const selected = state.project?.projectId ?? '';
|
|
219
|
+
const options = state.projects.map((project) => {
|
|
220
|
+
const option = document.createElement('option');
|
|
221
|
+
option.value = project.projectId;
|
|
222
|
+
option.textContent = `${project.name} · ${project.kind
|
|
223
|
+
.toLowerCase()
|
|
224
|
+
.replace('_', ' ')}`;
|
|
225
|
+
return option;
|
|
226
|
+
});
|
|
227
|
+
if (options.length === 0) {
|
|
228
|
+
const empty = document.createElement('option');
|
|
229
|
+
empty.value = '';
|
|
230
|
+
empty.textContent = 'No projects yet';
|
|
231
|
+
options.push(empty);
|
|
232
|
+
}
|
|
233
|
+
this.projectSelect.replaceChildren(...options);
|
|
234
|
+
this.projectSelect.value = selected;
|
|
235
|
+
}
|
|
236
|
+
renderSaveState(state) {
|
|
237
|
+
const label = {
|
|
238
|
+
SAVING: 'Saving…',
|
|
239
|
+
SAVED: 'Saved',
|
|
240
|
+
CONFLICT: 'Conflict',
|
|
241
|
+
OFFLINE: 'Offline',
|
|
242
|
+
}[state.saveState];
|
|
243
|
+
this.saveBadge.dataset.state = state.saveState;
|
|
244
|
+
this.saveBadge.textContent = state.saveMessage
|
|
245
|
+
? `${label}: ${state.saveMessage}`
|
|
246
|
+
: label;
|
|
247
|
+
this.saveAction.hidden = !['CONFLICT', 'OFFLINE'].includes(state.saveState);
|
|
248
|
+
this.saveAction.textContent =
|
|
249
|
+
state.saveState === 'CONFLICT' ? 'Keep my version' : 'Retry save';
|
|
250
|
+
this.remoteAction.hidden = state.saveState !== 'CONFLICT';
|
|
251
|
+
}
|
|
252
|
+
renderExplorer(state) {
|
|
253
|
+
this.explorer.replaceChildren();
|
|
254
|
+
if (!state.project) {
|
|
255
|
+
this.explorer.append(empty('Create a project to start authoring.'));
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
for (const target of projectTargets(state.project.kind)) {
|
|
259
|
+
const files = state.project.files.filter((file) => file.target === target);
|
|
260
|
+
this.explorer.append(this.projectSection(target, files.map((file) => ({
|
|
261
|
+
source: 'PROJECT',
|
|
262
|
+
target: file.target,
|
|
263
|
+
path: file.path,
|
|
264
|
+
}))));
|
|
265
|
+
}
|
|
266
|
+
this.explorer.append(this.referenceSection('Personal library', state.personalLibraryFiles), this.referenceSection('Common files', state.commonFiles));
|
|
267
|
+
}
|
|
268
|
+
projectSection(target, refs) {
|
|
269
|
+
const section = element('section', 'ck-crowdy-studio-section');
|
|
270
|
+
const header = element('div', 'ck-crowdy-studio-section-header');
|
|
271
|
+
const title = document.createElement('span');
|
|
272
|
+
title.textContent = `${target} files`;
|
|
273
|
+
const add = button('+ File');
|
|
274
|
+
add.setAttribute('aria-label', `Add ${target} file`);
|
|
275
|
+
add.disabled = !this.controller.canTarget(target, 'write');
|
|
276
|
+
add.addEventListener('click', () => {
|
|
277
|
+
const path = window.prompt(`New ${target} file path`, 'src/new.rs');
|
|
278
|
+
if (!path)
|
|
279
|
+
return;
|
|
280
|
+
void this.run(() => Promise.resolve(this.controller.addFile(target, path)));
|
|
281
|
+
});
|
|
282
|
+
header.append(title, add);
|
|
283
|
+
section.append(header);
|
|
284
|
+
for (const ref of refs) {
|
|
285
|
+
section.append(this.projectFileRow(ref));
|
|
286
|
+
}
|
|
287
|
+
if (refs.length === 0)
|
|
288
|
+
section.append(empty('No files'));
|
|
289
|
+
return section;
|
|
290
|
+
}
|
|
291
|
+
projectFileRow(ref) {
|
|
292
|
+
const row = element('div', 'ck-crowdy-studio-file');
|
|
293
|
+
const open = button(ref.path);
|
|
294
|
+
open.title = `${ref.target}:${ref.path}`;
|
|
295
|
+
open.addEventListener('click', () => this.controller.openFile(ref));
|
|
296
|
+
const rename = button('✎');
|
|
297
|
+
rename.setAttribute('aria-label', `Rename ${ref.path}`);
|
|
298
|
+
rename.addEventListener('click', () => {
|
|
299
|
+
const next = window.prompt('Rename file', ref.path);
|
|
300
|
+
if (!next || !ref.target)
|
|
301
|
+
return;
|
|
302
|
+
void this.run(() => Promise.resolve(this.controller.renameFile(ref.target, ref.path, next)));
|
|
303
|
+
});
|
|
304
|
+
const remove = button('×');
|
|
305
|
+
remove.setAttribute('aria-label', `Delete ${ref.path}`);
|
|
306
|
+
remove.addEventListener('click', () => {
|
|
307
|
+
if (!ref.target ||
|
|
308
|
+
!window.confirm(`Delete ${ref.target}:${ref.path}?`)) {
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
void this.run(() => Promise.resolve(this.controller.deleteFile(ref.target, ref.path)));
|
|
312
|
+
});
|
|
313
|
+
const library = button('Library');
|
|
314
|
+
library.setAttribute('aria-label', `Save ${ref.path} to My Library`);
|
|
315
|
+
library.addEventListener('click', () => {
|
|
316
|
+
if (!ref.target)
|
|
317
|
+
return;
|
|
318
|
+
const title = window.prompt('Library file title', ref.path.split('/').at(-1) ?? ref.path);
|
|
319
|
+
if (title == null)
|
|
320
|
+
return;
|
|
321
|
+
void this.run(() => this.controller.saveProjectFileToLibrary(ref.target, ref.path, title));
|
|
322
|
+
});
|
|
323
|
+
row.append(open, library, rename, remove);
|
|
324
|
+
return row;
|
|
325
|
+
}
|
|
326
|
+
referenceSection(titleText, files) {
|
|
327
|
+
const section = element('section', 'ck-crowdy-studio-section');
|
|
328
|
+
const header = element('div', 'ck-crowdy-studio-section-header');
|
|
329
|
+
header.textContent = titleText;
|
|
330
|
+
section.append(header);
|
|
331
|
+
for (const file of files) {
|
|
332
|
+
const ref = {
|
|
333
|
+
source: file.source,
|
|
334
|
+
target: file.target,
|
|
335
|
+
path: file.path,
|
|
336
|
+
referenceId: file.id,
|
|
337
|
+
};
|
|
338
|
+
const row = element('div', 'ck-crowdy-studio-file');
|
|
339
|
+
const open = button(`${file.target ? `${file.target.toLowerCase()}/` : ''}${file.path}`);
|
|
340
|
+
open.addEventListener('click', () => this.controller.openFile(ref));
|
|
341
|
+
const add = button('Add');
|
|
342
|
+
add.setAttribute('aria-label', `Add ${file.title} to project`);
|
|
343
|
+
add.addEventListener('click', () => {
|
|
344
|
+
const destination = window.prompt('Destination project path', file.path);
|
|
345
|
+
if (!destination)
|
|
346
|
+
return;
|
|
347
|
+
void this.run(() => this.controller.importReferenceFile(file, destination));
|
|
348
|
+
});
|
|
349
|
+
row.append(open, add);
|
|
350
|
+
section.append(row);
|
|
351
|
+
}
|
|
352
|
+
if (files.length === 0)
|
|
353
|
+
section.append(empty('No files'));
|
|
354
|
+
return section;
|
|
355
|
+
}
|
|
356
|
+
renderTabs(state) {
|
|
357
|
+
this.tabs.replaceChildren();
|
|
358
|
+
for (const ref of state.openFiles) {
|
|
359
|
+
const tab = button(`${ref.target ? `${ref.target.toLowerCase()}/` : ''}${ref.path}`);
|
|
360
|
+
tab.className = 'ck-crowdy-studio-tab';
|
|
361
|
+
tab.dataset.active = String(Boolean(state.activeFile && sameRef(state.activeFile, ref)));
|
|
362
|
+
tab.addEventListener('click', () => this.controller.openFile(ref));
|
|
363
|
+
const close = document.createElement('span');
|
|
364
|
+
close.textContent = '×';
|
|
365
|
+
close.setAttribute('role', 'button');
|
|
366
|
+
close.setAttribute('aria-label', `Close ${ref.path}`);
|
|
367
|
+
close.addEventListener('click', (event) => {
|
|
368
|
+
event.stopPropagation();
|
|
369
|
+
this.controller.closeFile(ref);
|
|
370
|
+
});
|
|
371
|
+
tab.append(close);
|
|
372
|
+
this.tabs.append(tab);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
renderSettings(state) {
|
|
376
|
+
const project = state.project;
|
|
377
|
+
const disabled = !project;
|
|
378
|
+
for (const control of [
|
|
379
|
+
this.settingsName,
|
|
380
|
+
this.settingsDescription,
|
|
381
|
+
this.serverModuleName,
|
|
382
|
+
this.clientModuleName,
|
|
383
|
+
this.pairing,
|
|
384
|
+
]) {
|
|
385
|
+
control.disabled = disabled;
|
|
386
|
+
}
|
|
387
|
+
if (!project)
|
|
388
|
+
return;
|
|
389
|
+
setInputUnlessFocused(this.settingsName, project.metadata.name);
|
|
390
|
+
setInputUnlessFocused(this.settingsDescription, project.metadata.description ?? '');
|
|
391
|
+
setInputUnlessFocused(this.serverModuleName, project.metadata.serverModuleName ?? '');
|
|
392
|
+
setInputUnlessFocused(this.clientModuleName, project.metadata.clientModuleName ?? '');
|
|
393
|
+
this.serverModuleName.disabled = !projectTargets(project.kind).includes('SERVER');
|
|
394
|
+
this.clientModuleName.disabled = !projectTargets(project.kind).includes('CLIENT');
|
|
395
|
+
this.pairing.disabled = project.kind !== 'FULL_STACK';
|
|
396
|
+
this.pairing.value = project.metadata.pairingPreference;
|
|
397
|
+
}
|
|
398
|
+
renderProblems(state) {
|
|
399
|
+
this.problemsPanel.replaceChildren();
|
|
400
|
+
const diagnostics = [
|
|
401
|
+
...state.authoritativeDiagnostics,
|
|
402
|
+
...state.localDiagnostics,
|
|
403
|
+
];
|
|
404
|
+
if (diagnostics.length === 0) {
|
|
405
|
+
this.problemsPanel.append(empty('No problems.'));
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
for (const diagnostic of diagnostics) {
|
|
409
|
+
const row = element('button', 'ck-crowdy-studio-problem');
|
|
410
|
+
row.dataset.source = diagnostic.source;
|
|
411
|
+
const source = document.createElement('span');
|
|
412
|
+
source.textContent =
|
|
413
|
+
diagnostic.source === 'rustc' ? 'rustc' : 'advisory';
|
|
414
|
+
const location = document.createElement('span');
|
|
415
|
+
location.textContent = `${diagnostic.target.toLowerCase()}/${diagnostic.path}:${diagnostic.line}:${diagnostic.column}`;
|
|
416
|
+
const message = document.createElement('span');
|
|
417
|
+
message.textContent = diagnostic.message;
|
|
418
|
+
row.append(source, location, message);
|
|
419
|
+
row.addEventListener('click', () => this.controller.openFile({
|
|
420
|
+
source: 'PROJECT',
|
|
421
|
+
target: diagnostic.target,
|
|
422
|
+
path: diagnostic.path,
|
|
423
|
+
}));
|
|
424
|
+
this.problemsPanel.append(row);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
renderBuild(state) {
|
|
428
|
+
this.buildPanel.replaceChildren();
|
|
429
|
+
const output = document.createElement('pre');
|
|
430
|
+
output.textContent = state.buildOutput || 'No build has run.';
|
|
431
|
+
this.buildPanel.append(output);
|
|
432
|
+
}
|
|
433
|
+
renderRuntimeRows(panel, rows) {
|
|
434
|
+
panel.replaceChildren();
|
|
435
|
+
if (rows.length === 0) {
|
|
436
|
+
panel.append(empty('No runtime records.'));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
for (const row of rows) {
|
|
440
|
+
const line = document.createElement('pre');
|
|
441
|
+
line.textContent = `${row.success ? '✓' : '✗'} ${row.startedAt} ${row.moduleName} ${row.triggerSource} · ${row.durationUs}µs · ${row.fuelUsed} fuel${row.errorMessage ? `\n${row.errorMessage}` : ''}`;
|
|
442
|
+
panel.append(line);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
async run(operation) {
|
|
446
|
+
try {
|
|
447
|
+
await operation();
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
this.runtimeStatus.textContent =
|
|
451
|
+
error instanceof Error ? error.message : String(error);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function polledSurface(panel) {
|
|
456
|
+
if (panel === 'logs' || panel === 'runs')
|
|
457
|
+
return panel;
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
function budgetText(state) {
|
|
461
|
+
const usage = state.usage
|
|
462
|
+
? `units ${state.usage.hourUnitsUsed}/${state.usage.unitsPerHour ?? '∞'} · compiles ${state.usage.compilesThisHour}/${state.usage.maxCompilesPerHour}`
|
|
463
|
+
: '';
|
|
464
|
+
const wallet = state.wallet
|
|
465
|
+
? `wallet ${state.wallet.balanceCents} ${state.wallet.currency}`
|
|
466
|
+
: '';
|
|
467
|
+
return [usage, wallet].filter(Boolean).join(' · ');
|
|
468
|
+
}
|
|
469
|
+
function formatInvokeResult(result) {
|
|
470
|
+
return [
|
|
471
|
+
result.resultJson ?? result.resultBase64 ?? '(empty result)',
|
|
472
|
+
result.fuelUsed ? `${result.fuelUsed} fuel` : '',
|
|
473
|
+
result.durationUs !== undefined ? `${result.durationUs}µs` : '',
|
|
474
|
+
]
|
|
475
|
+
.filter(Boolean)
|
|
476
|
+
.join('\n');
|
|
477
|
+
}
|
|
478
|
+
function sameRef(a, b) {
|
|
479
|
+
return (a.source === b.source &&
|
|
480
|
+
a.target === b.target &&
|
|
481
|
+
a.path === b.path &&
|
|
482
|
+
a.referenceId === b.referenceId);
|
|
483
|
+
}
|
|
484
|
+
function element(tag, className) {
|
|
485
|
+
const value = document.createElement(tag);
|
|
486
|
+
if (className)
|
|
487
|
+
value.className = className;
|
|
488
|
+
return value;
|
|
489
|
+
}
|
|
490
|
+
function button(label, type = 'button') {
|
|
491
|
+
const value = document.createElement('button');
|
|
492
|
+
value.type = type;
|
|
493
|
+
value.textContent = label;
|
|
494
|
+
return value;
|
|
495
|
+
}
|
|
496
|
+
function input(placeholder) {
|
|
497
|
+
const value = document.createElement('input');
|
|
498
|
+
value.placeholder = placeholder;
|
|
499
|
+
return value;
|
|
500
|
+
}
|
|
501
|
+
function labeled(label, control) {
|
|
502
|
+
const value = document.createElement('label');
|
|
503
|
+
const text = document.createElement('span');
|
|
504
|
+
text.textContent = label;
|
|
505
|
+
value.append(text, control);
|
|
506
|
+
return value;
|
|
507
|
+
}
|
|
508
|
+
function select(options) {
|
|
509
|
+
const value = document.createElement('select');
|
|
510
|
+
for (const [optionValue, label] of options) {
|
|
511
|
+
const option = document.createElement('option');
|
|
512
|
+
option.value = optionValue;
|
|
513
|
+
option.textContent = label;
|
|
514
|
+
value.append(option);
|
|
515
|
+
}
|
|
516
|
+
return value;
|
|
517
|
+
}
|
|
518
|
+
function empty(message) {
|
|
519
|
+
const value = element('div', 'ck-crowdy-studio-empty');
|
|
520
|
+
value.textContent = message;
|
|
521
|
+
return value;
|
|
522
|
+
}
|
|
523
|
+
function setInputUnlessFocused(inputElement, value) {
|
|
524
|
+
if (document.activeElement !== inputElement && inputElement.value !== value) {
|
|
525
|
+
inputElement.value = value;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CrowdyStudioState } from './controller.js';
|
|
2
|
+
import type { CrowdyStudioFileRef } from './models.js';
|
|
3
|
+
export type CrowdyStudioEditorMode = 'monaco' | 'textarea';
|
|
4
|
+
/** Editor implementation used by the DOM mount. */
|
|
5
|
+
export interface CrowdyStudioEditorAdapter {
|
|
6
|
+
readonly mode: CrowdyStudioEditorMode;
|
|
7
|
+
sync(state: CrowdyStudioState): void;
|
|
8
|
+
layout(): void;
|
|
9
|
+
dispose(): void;
|
|
10
|
+
}
|
|
11
|
+
export interface CrowdyStudioEditorCallbacks {
|
|
12
|
+
onProjectFileChange(target: 'SERVER' | 'CLIENT', path: string, content: string): void;
|
|
13
|
+
onLocalDiagnostics(diagnostics: CrowdyStudioState['localDiagnostics']): void;
|
|
14
|
+
onOpenFile(ref: CrowdyStudioFileRef): void;
|
|
15
|
+
onFailure?(error: Error): void;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/crowdy-studio/editor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAEvD,MAAM,MAAM,sBAAsB,GAAG,QAAQ,GAAG,UAAU,CAAC;AAE3D,mDAAmD;AACnD,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,IAAI,CAAC,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACrC,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,2BAA2B;IAC1C,mBAAmB,CACjB,MAAM,EAAE,QAAQ,GAAG,QAAQ,EAC3B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,GACd,IAAI,CAAC;IACR,kBAAkB,CAChB,WAAW,EAAE,iBAAiB,CAAC,kBAAkB,CAAC,GACjD,IAAI,CAAC;IACR,UAAU,CAAC,GAAG,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC3C,SAAS,CAAC,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { CrowdyStudioController, type CrowdyStudioControllerOptions, type CrowdyStudioBroker, type CrowdyStudioInvokeResult, type CrowdyStudioPhase, type CrowdyStudioPolledSurface, type CrowdyStudioRun, type CrowdyStudioRuntimeStatus, type CrowdyStudioPlayerCompute, type CrowdyStudioPlayerWallet, type CrowdyStudioState, type CrowdyStudioStopResult, type CrowdyStudioUsageSnapshot, type CrowdyStudioWalletSnapshot, } from './controller.js';
|
|
2
|
+
export { mountCrowdyStudio, type CrowdyStudioHandle, type MountCrowdyStudioOptions, } from './mount.js';
|
|
3
|
+
export { CrowdyStudioOfflineError, CrowdyStudioRevisionConflictError, cloneCrowdyStudioProject, crowdyStudioFileKey, crowdyStudioFileUri, normalizeCrowdyStudioPath, projectTargets, type CreateCrowdyStudioProjectInput, type ImportCrowdyStudioReferenceFileInput, type CrowdyStudioFileRef, type CrowdyStudioPairingPreference, type CrowdyStudioProject, type CrowdyStudioProjectFile, type CrowdyStudioProjectKind, type CrowdyStudioProjectMetadata, type CrowdyStudioProjectProvider, type CrowdyStudioProjectRevision, type CrowdyStudioProjectScope, type CrowdyStudioProjectSummary, type CrowdyStudioReferenceFile, type CrowdyStudioSaveState, type CrowdyStudioTarget, type SaveCrowdyStudioLibraryFileInput, type SaveCrowdyStudioProjectInput, } from './models.js';
|
|
4
|
+
export { createCrowdyStudioStarterProject, type CrowdyStudioNewProjectOptions, } from './starter-projects.js';
|
|
5
|
+
export { parseRustcDiagnostics, type CrowdyStudioDiagnostic, type CrowdyStudioDiagnosticSeverity, type CrowdyStudioDiagnosticSource, } from './diagnostics.js';
|
|
6
|
+
export { isCurrentDiagnosticVersion, type MonacoCrowdyStudioEditorOptions, } from './monaco-editor.js';
|
|
7
|
+
export { EMBEDDED_PLATFORM_INDEX, MAX_PLATFORM_CRATES, MAX_PLATFORM_CRATE_FIELD_LENGTH, MAX_PLATFORM_INDEX_BYTES, computePlatformIndexContentHash, loadPlatformIndex, type PlatformCrate, type PlatformIndex, type PlatformSymbol, type PlatformSymbolKind, } from '../live-coding/platform-index.js';
|
|
8
|
+
export { DEFAULT_VFS_LIMITS, VfsLimitError, VirtualFileSystem, offsetAt, type VirtualDocument, type VirtualFileSystemLimits, } from '../live-coding/vfs.js';
|
|
9
|
+
export { WorkerLanguageClient, WorkerMessageReader, WorkerMessageWriter, createDefaultRustLanguageWorker, type LanguageWorkerLike, type WorkerLanguageClientOptions, } from '../live-coding/worker-transport.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/crowdy-studio/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,KAAK,6BAA6B,EAClC,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EACtB,KAAK,yBAAyB,EAC9B,KAAK,eAAe,EACpB,KAAK,yBAAyB,EAC9B,KAAK,yBAAyB,EAC9B,KAAK,wBAAwB,EAC7B,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,yBAAyB,EAC9B,KAAK,0BAA0B,GAChC,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,iBAAiB,EACjB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,GAC9B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,wBAAwB,EACxB,iCAAiC,EACjC,wBAAwB,EACxB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,EACzB,cAAc,EACd,KAAK,8BAA8B,EACnC,KAAK,oCAAoC,EACzC,KAAK,mBAAmB,EACxB,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,KAAK,uBAAuB,EAC5B,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,2BAA2B,EAChC,KAAK,wBAAwB,EAC7B,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC9B,KAAK,qBAAqB,EAC1B,KAAK,kBAAkB,EACvB,KAAK,gCAAgC,EACrC,KAAK,4BAA4B,GAClC,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gCAAgC,EAChC,KAAK,6BAA6B,GACnC,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,qBAAqB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,8BAA8B,EACnC,KAAK,4BAA4B,GAClC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,0BAA0B,EAC1B,KAAK,+BAA+B,GACrC,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,+BAA+B,EAC/B,wBAAwB,EACxB,+BAA+B,EAC/B,iBAAiB,EACjB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,kCAAkC,CAAC;AAC1C,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACR,KAAK,eAAe,EACpB,KAAK,uBAAuB,GAC7B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,mBAAmB,EACnB,+BAA+B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,2BAA2B,GACjC,MAAM,oCAAoC,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { CrowdyStudioController, } from './controller.js';
|
|
2
|
+
export { mountCrowdyStudio, } from './mount.js';
|
|
3
|
+
export { CrowdyStudioOfflineError, CrowdyStudioRevisionConflictError, cloneCrowdyStudioProject, crowdyStudioFileKey, crowdyStudioFileUri, normalizeCrowdyStudioPath, projectTargets, } from './models.js';
|
|
4
|
+
export { createCrowdyStudioStarterProject, } from './starter-projects.js';
|
|
5
|
+
export { parseRustcDiagnostics, } from './diagnostics.js';
|
|
6
|
+
export { isCurrentDiagnosticVersion, } from './monaco-editor.js';
|
|
7
|
+
export { EMBEDDED_PLATFORM_INDEX, MAX_PLATFORM_CRATES, MAX_PLATFORM_CRATE_FIELD_LENGTH, MAX_PLATFORM_INDEX_BYTES, computePlatformIndexContentHash, loadPlatformIndex, } from '../live-coding/platform-index.js';
|
|
8
|
+
export { DEFAULT_VFS_LIMITS, VfsLimitError, VirtualFileSystem, offsetAt, } from '../live-coding/vfs.js';
|
|
9
|
+
export { WorkerLanguageClient, WorkerMessageReader, WorkerMessageWriter, createDefaultRustLanguageWorker, } from '../live-coding/worker-transport.js';
|