@jxsuite/studio 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/iframe-entry.js +622 -49
- package/dist/iframe-entry.js.map +15 -12
- package/dist/studio.css +1333 -0
- package/dist/studio.js +53069 -31746
- package/dist/studio.js.map +109 -74
- package/package.json +11 -9
- package/src/canvas/canvas-live-render.ts +21 -2
- package/src/canvas/canvas-render.ts +35 -16
- package/src/canvas/canvas-utils.ts +118 -72
- package/src/canvas/iframe-entry.ts +20 -0
- package/src/canvas/iframe-eval.ts +155 -0
- package/src/canvas/iframe-host.ts +131 -5
- package/src/canvas/iframe-inline-edit.ts +107 -1
- package/src/canvas/iframe-protocol.ts +43 -3
- package/src/canvas/iframe-render.ts +18 -0
- package/src/collab/collab-session.ts +23 -8
- package/src/collab/collab-state.ts +6 -3
- package/src/component-props.ts +104 -0
- package/src/editor/inline-edit-apply.ts +36 -1
- package/src/editor/inline-edit.ts +58 -1
- package/src/editor/shortcuts.ts +41 -12
- package/src/files/file-ops.ts +14 -0
- package/src/files/files.ts +53 -1
- package/src/grid/cell-editors.ts +288 -0
- package/src/grid/cell-popovers.ts +108 -0
- package/src/grid/csv-codec.ts +122 -0
- package/src/grid/edit-buffer.ts +490 -0
- package/src/grid/grid-controller.ts +417 -0
- package/src/grid/grid-layout.ts +83 -0
- package/src/grid/grid-open.ts +167 -0
- package/src/grid/grid-panel.ts +302 -0
- package/src/grid/grid-source.ts +210 -0
- package/src/grid/grid-view.ts +367 -0
- package/src/grid/schema-columns.ts +261 -0
- package/src/grid/sources/connector-source.ts +217 -0
- package/src/grid/sources/content-source.ts +542 -0
- package/src/grid/sources/csv-file-source.ts +247 -0
- package/src/grid/tabulator-tables.d.ts +120 -0
- package/src/panels/block-action-bar.ts +8 -3
- package/src/panels/chat-panel.ts +98 -0
- package/src/panels/data-grid.ts +9 -387
- package/src/panels/editors.ts +43 -17
- package/src/panels/events-panel.ts +137 -44
- package/src/panels/formula-workspace.ts +379 -0
- package/src/panels/frontmatter-fields.ts +231 -0
- package/src/panels/frontmatter-panel.ts +132 -0
- package/src/panels/git-panel.ts +1 -1
- package/src/panels/head-panel.ts +11 -175
- package/src/panels/properties-panel.ts +154 -144
- package/src/panels/right-panel.ts +9 -47
- package/src/panels/signals-panel.ts +115 -23
- package/src/panels/statement-editor.ts +710 -0
- package/src/panels/style-panel.ts +25 -1
- package/src/panels/stylebook-panel.ts +0 -2
- package/src/panels/tab-bar.ts +175 -6
- package/src/panels/tab-strip.ts +62 -6
- package/src/panels/toolbar.ts +37 -3
- package/src/services/ai-project-tools.ts +541 -0
- package/src/services/ai-session-store.ts +28 -0
- package/src/services/ai-system-prompt.ts +194 -24
- package/src/services/ai-tools.ts +88 -21
- package/src/services/automation.ts +16 -0
- package/src/services/document-assistant.ts +81 -11
- package/src/services/gated-registry.ts +72 -0
- package/src/services/live-preview.ts +0 -0
- package/src/services/preview-eval.ts +68 -0
- package/src/services/project-adoption.ts +31 -0
- package/src/site-context.ts +2 -0
- package/src/store.ts +4 -0
- package/src/studio.ts +83 -52
- package/src/tabs/patch-ops.ts +25 -0
- package/src/tabs/tab.ts +39 -1
- package/src/tabs/transact.ts +60 -13
- package/src/types.ts +12 -0
- package/src/ui/dynamic-slot.ts +272 -0
- package/src/ui/expression-editor.ts +423 -125
- package/src/ui/field-row.ts +5 -0
- package/src/ui/formula-catalog.ts +557 -0
- package/src/ui/formula-chips.ts +216 -0
- package/src/ui/formula-palette.ts +211 -0
- package/src/ui/layers.ts +40 -0
- package/src/ui/media-picker.ts +1 -1
- package/src/ui/panel-resize.ts +15 -1
- package/src/utils/preview-format.ts +26 -0
- package/src/view.ts +4 -4
- package/src/workspace/workspace.ts +14 -0
package/src/panels/data-grid.ts
CHANGED
|
@@ -1,84 +1,28 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* Data
|
|
3
|
+
* Data section actions — the owner console entry points over the platform's data surface.
|
|
4
4
|
*
|
|
5
5
|
* Integration: the settings modal's contributed sections stay fully generic — extension-sections
|
|
6
6
|
* passes this module's {@link dataSectionActions} into the ContributedSectionOptions.actions slot
|
|
7
7
|
* for the data-domain sections ("connections"/"data") whenever the platform implements the
|
|
8
8
|
* protocol's data routes. Those actions surface Test Connection, Push Schema (dry-run plan
|
|
9
|
-
* confirmation before apply), and Open Data Grid
|
|
10
|
-
* (
|
|
11
|
-
*
|
|
12
|
-
* The grid is v1-minimal by design: connection + table picker (declared tables plus system tables
|
|
13
|
-
* discovered from backend introspection), 50-row pagination, inline cell edit committed via
|
|
14
|
-
* dataUpdateRow keyed on the introspected primary key, an add-row footer, and two-step per-row
|
|
15
|
-
* delete. No filter/sort UI.
|
|
9
|
+
* confirmation before apply), and Open Data Grid — which opens the grid-tab source picker; table
|
|
10
|
+
* editing itself lives in the spreadsheet grid tabs (src/grid/), which replaced the old modal grid
|
|
11
|
+
* at feature parity (paging, cell edit, add/delete row) and added batch save with undo.
|
|
16
12
|
*/
|
|
17
13
|
|
|
18
14
|
import { html, nothing } from "lit-html";
|
|
19
|
-
import { live } from "lit-html/directives/live.js";
|
|
20
15
|
import { openModal } from "../ui/layers";
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
deleteRow,
|
|
24
|
-
fetchConnections,
|
|
25
|
-
fetchRows,
|
|
26
|
-
insertRow,
|
|
27
|
-
pushSchema,
|
|
28
|
-
testConnection,
|
|
29
|
-
updateRow,
|
|
30
|
-
} from "../services/data-service";
|
|
16
|
+
import { dataSurfaceAvailable, pushSchema, testConnection } from "../services/data-service";
|
|
17
|
+
import { openGridSourcePicker } from "../grid/grid-open";
|
|
31
18
|
import type { TemplateResult } from "lit-html";
|
|
32
|
-
import type {
|
|
33
|
-
DataColumnMeta,
|
|
34
|
-
DataConnectionInfo,
|
|
35
|
-
DataConnectionTestResult,
|
|
36
|
-
DataPushResult,
|
|
37
|
-
} from "../types";
|
|
19
|
+
import type { DataConnectionTestResult, DataPushResult } from "../types";
|
|
38
20
|
import type { SectionActionsContext } from "../settings/contributed-section";
|
|
39
21
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
// ─── Grid state ───────────────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
interface GridState {
|
|
45
|
-
connections: DataConnectionInfo[];
|
|
46
|
-
connection: string | null;
|
|
47
|
-
table: string | null;
|
|
48
|
-
columns: DataColumnMeta[];
|
|
49
|
-
rows: Record<string, unknown>[];
|
|
50
|
-
total: number;
|
|
51
|
-
offset: number;
|
|
52
|
-
loading: boolean;
|
|
53
|
-
error: string | null;
|
|
54
|
-
/** Pk value armed for the two-step delete confirm. */
|
|
55
|
-
pendingDelete: string | null;
|
|
56
|
-
/** Add-row footer draft, keyed by column name. */
|
|
57
|
-
draft: Record<string, string>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const blankGrid = (): GridState => ({
|
|
61
|
-
columns: [],
|
|
62
|
-
connection: null,
|
|
63
|
-
connections: [],
|
|
64
|
-
draft: {},
|
|
65
|
-
error: null,
|
|
66
|
-
loading: false,
|
|
67
|
-
offset: 0,
|
|
68
|
-
pendingDelete: null,
|
|
69
|
-
rows: [],
|
|
70
|
-
table: null,
|
|
71
|
-
total: 0,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
let grid: GridState = blankGrid();
|
|
75
|
-
let gridHandle: ReturnType<typeof openModal> | null = null;
|
|
22
|
+
// ─── Grid opening (delegates to the grid-tab source picker) ──────────────────
|
|
76
23
|
|
|
77
24
|
/** Reset all module UI state and close any open surfaces (test hook / project switch). */
|
|
78
25
|
export function resetDataGridState(): void {
|
|
79
|
-
grid = blankGrid();
|
|
80
|
-
gridHandle?.close();
|
|
81
|
-
gridHandle = null;
|
|
82
26
|
actionsState = { pushing: false, testResult: null, testing: null };
|
|
83
27
|
closePushDialog();
|
|
84
28
|
}
|
|
@@ -88,325 +32,6 @@ export function isDataGridAvailable(): boolean {
|
|
|
88
32
|
return dataSurfaceAvailable();
|
|
89
33
|
}
|
|
90
34
|
|
|
91
|
-
/** Close the grid modal. */
|
|
92
|
-
export function closeDataGrid(): void {
|
|
93
|
-
gridHandle?.close();
|
|
94
|
-
gridHandle = null;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Open the data grid modal, loading connections and the first page.
|
|
99
|
-
*
|
|
100
|
-
* @param {{ connection?: string; table?: string }} [preselect]
|
|
101
|
-
*/
|
|
102
|
-
export async function openDataGrid(preselect: { connection?: string; table?: string } = {}) {
|
|
103
|
-
if (!isDataGridAvailable() || gridHandle) {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
grid = blankGrid();
|
|
107
|
-
grid.loading = true;
|
|
108
|
-
renderGrid();
|
|
109
|
-
const res = await fetchConnections().catch(() => null);
|
|
110
|
-
grid.connections = res?.connections ?? [];
|
|
111
|
-
grid.connection =
|
|
112
|
-
(preselect.connection &&
|
|
113
|
-
grid.connections.some((c) => c.name === preselect.connection) &&
|
|
114
|
-
preselect.connection) ||
|
|
115
|
-
grid.connections.find((c) => c.isDefault)?.name ||
|
|
116
|
-
grid.connections[0]?.name ||
|
|
117
|
-
null;
|
|
118
|
-
const tables = currentTables();
|
|
119
|
-
grid.table =
|
|
120
|
-
preselect.table && tables.includes(preselect.table) ? preselect.table : (tables[0] ?? null);
|
|
121
|
-
await loadRows();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** Table names of the selected connection (declared + backend-introspected system tables). */
|
|
125
|
-
function currentTables(): string[] {
|
|
126
|
-
return grid.connections.find((c) => c.name === grid.connection)?.tables ?? [];
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/** The primary-key column name (backend convention: "id" when nothing is flagged). */
|
|
130
|
-
function pkColumn(): string {
|
|
131
|
-
return grid.columns.find((c) => c.pk)?.name ?? "id";
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Load the current page. */
|
|
135
|
-
async function loadRows(): Promise<void> {
|
|
136
|
-
if (!grid.table) {
|
|
137
|
-
grid.rows = [];
|
|
138
|
-
grid.columns = [];
|
|
139
|
-
grid.total = 0;
|
|
140
|
-
grid.loading = false;
|
|
141
|
-
renderGrid();
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
grid.loading = true;
|
|
145
|
-
renderGrid();
|
|
146
|
-
try {
|
|
147
|
-
const result = await fetchRows({
|
|
148
|
-
limit: DATA_GRID_PAGE_SIZE,
|
|
149
|
-
offset: grid.offset,
|
|
150
|
-
table: grid.table,
|
|
151
|
-
...(grid.connection === null ? {} : { connection: grid.connection }),
|
|
152
|
-
});
|
|
153
|
-
grid.rows = result.rows;
|
|
154
|
-
grid.columns = result.columns;
|
|
155
|
-
grid.total = result.total;
|
|
156
|
-
grid.error = null;
|
|
157
|
-
} catch (error) {
|
|
158
|
-
grid.rows = [];
|
|
159
|
-
grid.columns = [];
|
|
160
|
-
grid.total = 0;
|
|
161
|
-
grid.error = error instanceof Error ? error.message : String(error);
|
|
162
|
-
}
|
|
163
|
-
grid.loading = false;
|
|
164
|
-
grid.pendingDelete = null;
|
|
165
|
-
renderGrid();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** Commit one edited cell via dataUpdateRow, keyed on the row's pk. */
|
|
169
|
-
async function commitCell(
|
|
170
|
-
row: Record<string, unknown>,
|
|
171
|
-
column: DataColumnMeta,
|
|
172
|
-
raw: string,
|
|
173
|
-
): Promise<void> {
|
|
174
|
-
const current = row[column.name];
|
|
175
|
-
const currentText = current == null ? "" : String(current);
|
|
176
|
-
if (raw === currentText || !grid.table) {
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
try {
|
|
180
|
-
const updated = await updateRow({
|
|
181
|
-
pk: row[pkColumn()] as string | number,
|
|
182
|
-
set: { [column.name]: raw === "" ? null : raw },
|
|
183
|
-
table: grid.table,
|
|
184
|
-
...(grid.connection === null ? {} : { connection: grid.connection }),
|
|
185
|
-
});
|
|
186
|
-
const index = grid.rows.indexOf(row);
|
|
187
|
-
if (index !== -1) {
|
|
188
|
-
grid.rows[index] = updated.row;
|
|
189
|
-
}
|
|
190
|
-
grid.error = null;
|
|
191
|
-
} catch (error) {
|
|
192
|
-
grid.error = error instanceof Error ? error.message : String(error);
|
|
193
|
-
}
|
|
194
|
-
renderGrid();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/** Two-step delete: first click arms the row, second click deletes. */
|
|
198
|
-
async function requestDelete(pk: string): Promise<void> {
|
|
199
|
-
if (grid.pendingDelete !== pk) {
|
|
200
|
-
grid.pendingDelete = pk;
|
|
201
|
-
renderGrid();
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (!grid.table) {
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
try {
|
|
208
|
-
await deleteRow({
|
|
209
|
-
pk,
|
|
210
|
-
table: grid.table,
|
|
211
|
-
...(grid.connection === null ? {} : { connection: grid.connection }),
|
|
212
|
-
});
|
|
213
|
-
await loadRows();
|
|
214
|
-
} catch (error) {
|
|
215
|
-
grid.error = error instanceof Error ? error.message : String(error);
|
|
216
|
-
grid.pendingDelete = null;
|
|
217
|
-
renderGrid();
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/** Insert the add-row footer draft. */
|
|
222
|
-
async function commitDraft(): Promise<void> {
|
|
223
|
-
if (!grid.table) {
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
const values: Record<string, unknown> = {};
|
|
227
|
-
for (const [name, raw] of Object.entries(grid.draft)) {
|
|
228
|
-
if (raw !== "") {
|
|
229
|
-
values[name] = raw;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
try {
|
|
233
|
-
await insertRow({
|
|
234
|
-
table: grid.table,
|
|
235
|
-
values,
|
|
236
|
-
...(grid.connection === null ? {} : { connection: grid.connection }),
|
|
237
|
-
});
|
|
238
|
-
grid.draft = {};
|
|
239
|
-
await loadRows();
|
|
240
|
-
} catch (error) {
|
|
241
|
-
grid.error = error instanceof Error ? error.message : String(error);
|
|
242
|
-
renderGrid();
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/** Columns editable in the grid (system-managed columns are display-only). */
|
|
247
|
-
function editableColumn(column: DataColumnMeta): boolean {
|
|
248
|
-
return !column.pk && column.name !== "created_at" && column.name !== "updated_at";
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function gridBody(): TemplateResult {
|
|
252
|
-
if (grid.loading) {
|
|
253
|
-
return html`<div class="data-grid-empty">Loading…</div>`;
|
|
254
|
-
}
|
|
255
|
-
if (grid.error) {
|
|
256
|
-
return html`<div class="data-grid-error">${grid.error}</div>`;
|
|
257
|
-
}
|
|
258
|
-
if (!grid.table) {
|
|
259
|
-
return html`<div class="data-grid-empty">
|
|
260
|
-
No tables on this connection — push a schema first.
|
|
261
|
-
</div>`;
|
|
262
|
-
}
|
|
263
|
-
const pk = pkColumn();
|
|
264
|
-
return html`
|
|
265
|
-
<table class="data-grid-table">
|
|
266
|
-
<thead>
|
|
267
|
-
<tr>
|
|
268
|
-
${grid.columns.map(
|
|
269
|
-
(c) =>
|
|
270
|
-
html`<th title=${c.type}>
|
|
271
|
-
${c.name}${c.pk ? html` <span class="data-grid-pk">pk</span>` : nothing}
|
|
272
|
-
</th>`,
|
|
273
|
-
)}
|
|
274
|
-
<th></th>
|
|
275
|
-
</tr>
|
|
276
|
-
</thead>
|
|
277
|
-
<tbody>
|
|
278
|
-
${grid.rows.map((row) => {
|
|
279
|
-
const pkValue = String(row[pk] ?? "");
|
|
280
|
-
return html`<tr>
|
|
281
|
-
${grid.columns.map((c) =>
|
|
282
|
-
editableColumn(c)
|
|
283
|
-
? html`<td>
|
|
284
|
-
<input
|
|
285
|
-
class="data-grid-cell"
|
|
286
|
-
.value=${live(row[c.name] == null ? "" : String(row[c.name]))}
|
|
287
|
-
@change=${(e: Event) =>
|
|
288
|
-
void commitCell(row, c, (e.target as HTMLInputElement).value)}
|
|
289
|
-
/>
|
|
290
|
-
</td>`
|
|
291
|
-
: html`<td class="data-grid-readonly">
|
|
292
|
-
${row[c.name] == null ? "" : String(row[c.name])}
|
|
293
|
-
</td>`,
|
|
294
|
-
)}
|
|
295
|
-
<td>
|
|
296
|
-
<sp-action-button
|
|
297
|
-
quiet
|
|
298
|
-
size="s"
|
|
299
|
-
class="data-grid-delete"
|
|
300
|
-
@click=${() => void requestDelete(pkValue)}
|
|
301
|
-
>
|
|
302
|
-
${grid.pendingDelete === pkValue ? "Confirm?" : "Delete"}
|
|
303
|
-
</sp-action-button>
|
|
304
|
-
</td>
|
|
305
|
-
</tr>`;
|
|
306
|
-
})}
|
|
307
|
-
</tbody>
|
|
308
|
-
<tfoot>
|
|
309
|
-
<tr class="data-grid-add-row">
|
|
310
|
-
${grid.columns.map((c) =>
|
|
311
|
-
editableColumn(c)
|
|
312
|
-
? html`<td>
|
|
313
|
-
<input
|
|
314
|
-
class="data-grid-draft"
|
|
315
|
-
placeholder=${c.name}
|
|
316
|
-
.value=${live(grid.draft[c.name] ?? "")}
|
|
317
|
-
@input=${(e: Event) => {
|
|
318
|
-
grid.draft[c.name] = (e.target as HTMLInputElement).value;
|
|
319
|
-
}}
|
|
320
|
-
/>
|
|
321
|
-
</td>`
|
|
322
|
-
: html`<td></td>`,
|
|
323
|
-
)}
|
|
324
|
-
<td>
|
|
325
|
-
<sp-action-button size="s" class="data-grid-add" @click=${() => void commitDraft()}>
|
|
326
|
-
Add
|
|
327
|
-
</sp-action-button>
|
|
328
|
-
</td>
|
|
329
|
-
</tr>
|
|
330
|
-
</tfoot>
|
|
331
|
-
</table>
|
|
332
|
-
`;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
function renderGrid(): void {
|
|
336
|
-
const first = grid.total === 0 ? 0 : grid.offset + 1;
|
|
337
|
-
const last = grid.offset + grid.rows.length;
|
|
338
|
-
const tpl = html`
|
|
339
|
-
<sp-underlay open @close=${closeDataGrid}></sp-underlay>
|
|
340
|
-
<div class="settings-modal data-grid-modal">
|
|
341
|
-
<div class="settings-modal-header">
|
|
342
|
-
<h2 class="settings-modal-title">Data</h2>
|
|
343
|
-
<sp-picker
|
|
344
|
-
size="s"
|
|
345
|
-
class="data-grid-connection"
|
|
346
|
-
value=${grid.connection ?? ""}
|
|
347
|
-
@change=${(e: Event) => {
|
|
348
|
-
grid.connection = (e.target as HTMLInputElement).value || null;
|
|
349
|
-
grid.offset = 0;
|
|
350
|
-
grid.table = currentTables()[0] ?? null;
|
|
351
|
-
void loadRows();
|
|
352
|
-
}}
|
|
353
|
-
>
|
|
354
|
-
${grid.connections.map(
|
|
355
|
-
(c) => html`<sp-menu-item value=${c.name}>${c.name}</sp-menu-item>`,
|
|
356
|
-
)}
|
|
357
|
-
</sp-picker>
|
|
358
|
-
<sp-picker
|
|
359
|
-
size="s"
|
|
360
|
-
class="data-grid-tables"
|
|
361
|
-
value=${grid.table ?? ""}
|
|
362
|
-
@change=${(e: Event) => {
|
|
363
|
-
grid.table = (e.target as HTMLInputElement).value || null;
|
|
364
|
-
grid.offset = 0;
|
|
365
|
-
grid.draft = {};
|
|
366
|
-
void loadRows();
|
|
367
|
-
}}
|
|
368
|
-
>
|
|
369
|
-
${currentTables().map(
|
|
370
|
-
(table) => html`<sp-menu-item value=${table}>${table}</sp-menu-item>`,
|
|
371
|
-
)}
|
|
372
|
-
</sp-picker>
|
|
373
|
-
<sp-action-button quiet size="s" title="Close" @click=${closeDataGrid}>
|
|
374
|
-
<sp-icon-close slot="icon"></sp-icon-close>
|
|
375
|
-
</sp-action-button>
|
|
376
|
-
</div>
|
|
377
|
-
<div class="settings-modal-body data-grid-body">${gridBody()}</div>
|
|
378
|
-
<div class="data-grid-footer">
|
|
379
|
-
<sp-action-button
|
|
380
|
-
size="s"
|
|
381
|
-
class="data-grid-prev"
|
|
382
|
-
?disabled=${grid.offset === 0}
|
|
383
|
-
@click=${() => {
|
|
384
|
-
grid.offset = Math.max(0, grid.offset - DATA_GRID_PAGE_SIZE);
|
|
385
|
-
void loadRows();
|
|
386
|
-
}}
|
|
387
|
-
>Prev</sp-action-button
|
|
388
|
-
>
|
|
389
|
-
<span class="data-grid-range">${first}–${last} of ${grid.total}</span>
|
|
390
|
-
<sp-action-button
|
|
391
|
-
size="s"
|
|
392
|
-
class="data-grid-next"
|
|
393
|
-
?disabled=${grid.offset + DATA_GRID_PAGE_SIZE >= grid.total}
|
|
394
|
-
@click=${() => {
|
|
395
|
-
grid.offset += DATA_GRID_PAGE_SIZE;
|
|
396
|
-
void loadRows();
|
|
397
|
-
}}
|
|
398
|
-
>Next</sp-action-button
|
|
399
|
-
>
|
|
400
|
-
</div>
|
|
401
|
-
</div>
|
|
402
|
-
`;
|
|
403
|
-
if (gridHandle) {
|
|
404
|
-
gridHandle.update(tpl);
|
|
405
|
-
} else {
|
|
406
|
-
gridHandle = openModal(tpl);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
35
|
// ─── Push dialog (dry-run plan confirmation before apply) ─────────────────────
|
|
411
36
|
|
|
412
37
|
interface PushDialogState {
|
|
@@ -598,10 +223,7 @@ function renderSectionActions(sectionKey: string, ctx: SectionActionsContext): T
|
|
|
598
223
|
<sp-action-button
|
|
599
224
|
size="s"
|
|
600
225
|
class="data-action-grid"
|
|
601
|
-
@click=${() =>
|
|
602
|
-
void openDataGrid(
|
|
603
|
-
sectionKey === "connections" && selected ? { connection: selected } : {},
|
|
604
|
-
)}
|
|
226
|
+
@click=${() => void openGridSourcePicker()}
|
|
605
227
|
>
|
|
606
228
|
Open Data Grid
|
|
607
229
|
</sp-action-button>
|
package/src/panels/editors.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { activeTab } from "../workspace/workspace";
|
|
|
14
14
|
import { mutateUpdateDef, mutateUpdateProperty, transactDoc } from "../tabs/transact";
|
|
15
15
|
import { view } from "../view";
|
|
16
16
|
import { codeService, getFunctionArgs, setLintMarkers } from "../services/code-services";
|
|
17
|
+
import { globalEntries, namedFormulaEntries } from "../ui/formula-catalog";
|
|
17
18
|
|
|
18
19
|
import type { OxLintDiagnostic } from "../services/code-services";
|
|
19
20
|
import type { JxMutableNode, JxPrototypeDef } from "@jxsuite/schema/types";
|
|
@@ -185,7 +186,10 @@ export function registerFunctionCompletions() {
|
|
|
185
186
|
view._completionRegistered = true;
|
|
186
187
|
monaco.languages.registerCompletionItemProvider("javascript", {
|
|
187
188
|
provideCompletionItems(model, position) {
|
|
188
|
-
|
|
189
|
+
if (!activeTab.value) {
|
|
190
|
+
return { suggestions: [] };
|
|
191
|
+
}
|
|
192
|
+
const defs = activeTab.value.doc.document?.state || {};
|
|
189
193
|
const word = model.getWordUntilPosition(position);
|
|
190
194
|
const range = {
|
|
191
195
|
endColumn: word.endColumn,
|
|
@@ -194,23 +198,45 @@ export function registerFunctionCompletions() {
|
|
|
194
198
|
startLineNumber: position.lineNumber,
|
|
195
199
|
};
|
|
196
200
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
201
|
+
// Named-formula catalog metadata enriches their completions with documentation.
|
|
202
|
+
const formulaDocs = new Map(namedFormulaEntries(defs).map((e) => [e.name, e.description]));
|
|
203
|
+
|
|
204
|
+
const suggestions: monaco.languages.CompletionItem[] = Object.entries(defs).map(
|
|
205
|
+
([key, def]) => {
|
|
206
|
+
let kind = monaco.languages.CompletionItemKind.Variable;
|
|
207
|
+
if (
|
|
208
|
+
(def as JxPrototypeDef)?.$prototype === "Function" ||
|
|
209
|
+
(def as Record<string, unknown>)?.$handler ||
|
|
210
|
+
formulaDocs.has(key)
|
|
211
|
+
) {
|
|
212
|
+
kind = monaco.languages.CompletionItemKind.Function;
|
|
213
|
+
} else if ((def as JxPrototypeDef)?.$prototype) {
|
|
214
|
+
kind = monaco.languages.CompletionItemKind.Property;
|
|
215
|
+
}
|
|
216
|
+
const item: monaco.languages.CompletionItem = {
|
|
217
|
+
insertText: `state.${key}`,
|
|
218
|
+
kind,
|
|
219
|
+
label: `state.${key}`,
|
|
220
|
+
range,
|
|
221
|
+
};
|
|
222
|
+
const documentation = formulaDocs.get(key);
|
|
223
|
+
if (documentation) {
|
|
224
|
+
item.documentation = documentation;
|
|
225
|
+
}
|
|
226
|
+
return item;
|
|
227
|
+
},
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
// Blessed pure globals from the formula catalog (Math.*, JSON.*, Object.*, …).
|
|
231
|
+
for (const entry of globalEntries()) {
|
|
232
|
+
suggestions.push({
|
|
233
|
+
documentation: entry.description,
|
|
234
|
+
insertText: `window.${entry.label}`,
|
|
235
|
+
kind: monaco.languages.CompletionItemKind.Function,
|
|
236
|
+
label: entry.label,
|
|
211
237
|
range,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
214
240
|
return { suggestions };
|
|
215
241
|
},
|
|
216
242
|
triggerCharacters: ["."],
|