@growthub/cli 0.12.1 → 0.13.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/assets/worker-kits/growthub-agency-portal-starter-v1/apps/agency-portal/package-lock.json +46 -110
- package/assets/worker-kits/growthub-agency-portal-starter-v1/apps/agency-portal/package.json +4 -1
- package/assets/worker-kits/growthub-creative-video-pipeline-v1/apps/creative-video-pipeline/package-lock.json +898 -3
- package/assets/worker-kits/growthub-creative-video-pipeline-v1/apps/creative-video-pipeline/package.json +4 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/DataModelShell.jsx +34 -213
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/data-model/components/dm-shared.jsx +8 -2
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/globals.css +911 -8
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/views/[viewId]/page.jsx +206 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-builder.jsx +11 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/app/workspace-rail.jsx +1275 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-data-model.js +11 -4
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-helper-apply.js +96 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/lib/workspace-schema.js +102 -0
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package-lock.json +1592 -77
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/apps/workspace/package.json +4 -1
- package/assets/worker-kits/growthub-custom-workspace-starter-v1/kit.json +1 -0
- package/package.json +1 -1
|
@@ -25,12 +25,10 @@ import {
|
|
|
25
25
|
Layers,
|
|
26
26
|
Link2,
|
|
27
27
|
Lock,
|
|
28
|
-
List,
|
|
29
28
|
Mail,
|
|
30
29
|
Maximize2,
|
|
31
30
|
MoreHorizontal,
|
|
32
31
|
Plus,
|
|
33
|
-
Pin,
|
|
34
32
|
Pencil,
|
|
35
33
|
Search,
|
|
36
34
|
ShoppingCart,
|
|
@@ -62,7 +60,6 @@ import {
|
|
|
62
60
|
parseSandboxAllowList,
|
|
63
61
|
parseSandboxEnvRefs,
|
|
64
62
|
replaceTableContent,
|
|
65
|
-
snapshotTableViewState,
|
|
66
63
|
transformTableSchema,
|
|
67
64
|
updateTableFieldSettings,
|
|
68
65
|
updateTableCell,
|
|
@@ -199,88 +196,20 @@ function applyRowsView(rows, settings) {
|
|
|
199
196
|
});
|
|
200
197
|
}
|
|
201
198
|
|
|
202
|
-
function ObjectViewPicker({ tables, selectedTable,
|
|
199
|
+
function ObjectViewPicker({ tables, selectedTable, onSelectSource }) {
|
|
203
200
|
const pickerRef = useRef(null);
|
|
204
201
|
const [open, setOpen] = useState(false);
|
|
205
|
-
const [mode, setMode] = useState("all");
|
|
206
|
-
const [newViewName, setNewViewName] = useState("");
|
|
207
|
-
const [viewMenuId, setViewMenuId] = useState("");
|
|
208
|
-
const currentViews = selectedTable?.fieldSettings?.views || [];
|
|
209
|
-
const favoriteObjects = tables.filter((table) => table.fieldSettings?.favorite);
|
|
210
202
|
|
|
211
203
|
useEffect(() => {
|
|
212
204
|
function handlePointer(event) {
|
|
213
205
|
if (!pickerRef.current?.contains(event.target)) {
|
|
214
|
-
|
|
206
|
+
setOpen(false);
|
|
215
207
|
}
|
|
216
208
|
}
|
|
217
209
|
document.addEventListener("pointerdown", handlePointer);
|
|
218
210
|
return () => document.removeEventListener("pointerdown", handlePointer);
|
|
219
211
|
}, []);
|
|
220
212
|
|
|
221
|
-
function applyView(view) {
|
|
222
|
-
if (!selectedTable) return;
|
|
223
|
-
const nextState = view
|
|
224
|
-
? { ...snapshotTableViewState(view), activeViewId: view.id }
|
|
225
|
-
: { activeViewId: "", hidden: [], order: selectedTable.columns, sort: [], filter: null };
|
|
226
|
-
onSave((config) => updateTableFieldSettings(config, selectedTable, (settings) => ({
|
|
227
|
-
...settings,
|
|
228
|
-
...nextState
|
|
229
|
-
})));
|
|
230
|
-
setOpen(false);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
function createView() {
|
|
234
|
-
const name = newViewName.trim();
|
|
235
|
-
if (!selectedTable || !name) return;
|
|
236
|
-
const viewId = `view_${Date.now().toString(36)}`;
|
|
237
|
-
onSave((config) => updateTableFieldSettings(config, selectedTable, (settings) => ({
|
|
238
|
-
...settings,
|
|
239
|
-
activeViewId: viewId,
|
|
240
|
-
views: [...(settings.views || []), {
|
|
241
|
-
id: viewId,
|
|
242
|
-
name,
|
|
243
|
-
favorite: false,
|
|
244
|
-
locked: false,
|
|
245
|
-
...snapshotTableViewState(settings)
|
|
246
|
-
}]
|
|
247
|
-
})));
|
|
248
|
-
setNewViewName("");
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function toggleViewFavorite(viewId) {
|
|
252
|
-
if (!selectedTable) return;
|
|
253
|
-
onSave((config) => updateTableFieldSettings(config, selectedTable, (settings) => ({
|
|
254
|
-
...settings,
|
|
255
|
-
views: (settings.views || []).map((view) => view.id === viewId ? { ...view, favorite: !view.favorite } : view)
|
|
256
|
-
})));
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function deleteView(viewId) {
|
|
260
|
-
if (!selectedTable) return;
|
|
261
|
-
onSave((config) => updateTableFieldSettings(config, selectedTable, (settings) => ({
|
|
262
|
-
...settings,
|
|
263
|
-
activeViewId: settings.activeViewId === viewId ? "" : settings.activeViewId,
|
|
264
|
-
views: (settings.views || []).filter((view) => view.id !== viewId)
|
|
265
|
-
})));
|
|
266
|
-
setViewMenuId("");
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function renameView(view) {
|
|
270
|
-
if (!selectedTable) return;
|
|
271
|
-
const nextName = window.prompt("Rename view", view.name);
|
|
272
|
-
if (!nextName?.trim()) return;
|
|
273
|
-
onSave((config) => updateTableFieldSettings(config, selectedTable, (settings) => ({
|
|
274
|
-
...settings,
|
|
275
|
-
views: (settings.views || []).map((candidate) => candidate.id === view.id ? { ...candidate, name: nextName.trim() } : candidate)
|
|
276
|
-
})));
|
|
277
|
-
setViewMenuId("");
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const activeView = currentViews.find((view) => view.id === selectedTable?.fieldSettings?.activeViewId) || null;
|
|
281
|
-
const objects = mode === "views" ? [] : tables;
|
|
282
|
-
const views = mode === "objects" ? [] : currentViews;
|
|
283
|
-
|
|
284
213
|
return (
|
|
285
214
|
<div
|
|
286
215
|
ref={pickerRef}
|
|
@@ -288,125 +217,36 @@ function ObjectViewPicker({ tables, selectedTable, saving, onSelectSource, onSav
|
|
|
288
217
|
onBlur={(event) => {
|
|
289
218
|
if (!event.currentTarget.contains(event.relatedTarget)) {
|
|
290
219
|
setOpen(false);
|
|
291
|
-
setViewMenuId("");
|
|
292
220
|
}
|
|
293
221
|
}}
|
|
294
222
|
>
|
|
295
223
|
<button type="button" className="dm-picker-trigger" onClick={() => setOpen((current) => !current)}>
|
|
296
224
|
<LucideIcon name={selectedTable?.icon || OBJECT_TYPE_PRESETS[selectedTable?.objectType]?.icon || "Database"} size={14} />
|
|
297
225
|
<span className="dm-picker-trigger-copy">
|
|
298
|
-
<strong>{
|
|
226
|
+
<strong>{selectedTable?.label || "Object"}</strong>
|
|
299
227
|
<em>{pluralize(selectedTable?.columns?.length || 0, "field")} · {pluralize(selectedTable?.rows?.length || 0, "record")}</em>
|
|
300
228
|
</span>
|
|
301
229
|
<ChevronDown size={14} />
|
|
302
230
|
</button>
|
|
303
231
|
{open && (
|
|
304
232
|
<div className="dm-picker-popover">
|
|
305
|
-
|
|
306
|
-
<
|
|
307
|
-
|
|
308
|
-
{
|
|
309
|
-
<
|
|
310
|
-
<
|
|
311
|
-
|
|
312
|
-
|
|
233
|
+
<div className="dm-picker-section">
|
|
234
|
+
<p>Objects</p>
|
|
235
|
+
<div className="dm-picker-scroll">
|
|
236
|
+
{tables.map((table, objIdx) => (
|
|
237
|
+
<div key={`${table.id || table.source}:${objIdx}`} className={`dm-picker-item${selectedTable?.source === table.source ? " active" : ""}`}>
|
|
238
|
+
<button type="button" className="dm-picker-row" onClick={() => {
|
|
239
|
+
onSelectSource(table.source);
|
|
240
|
+
setOpen(false);
|
|
241
|
+
}}>
|
|
242
|
+
<LucideIcon name={table.icon || OBJECT_TYPE_PRESETS[table.objectType]?.icon || "Database"} size={14} />
|
|
243
|
+
<span>{table.label}</span>
|
|
244
|
+
{isLockedObject(table) && <Lock size={12} className="dm-picker-lock" />}
|
|
245
|
+
</button>
|
|
246
|
+
</div>
|
|
313
247
|
))}
|
|
314
248
|
</div>
|
|
315
|
-
)}
|
|
316
|
-
<div className="dm-picker-tabs">
|
|
317
|
-
{[
|
|
318
|
-
{ id: "all", label: "All" },
|
|
319
|
-
{ id: "objects", label: "Objects" },
|
|
320
|
-
{ id: "views", label: "Views" },
|
|
321
|
-
].map((item) => (
|
|
322
|
-
<button key={item.id} type="button" className={mode === item.id ? "active" : ""} onClick={() => setMode(item.id)}>
|
|
323
|
-
{item.label}
|
|
324
|
-
</button>
|
|
325
|
-
))}
|
|
326
249
|
</div>
|
|
327
|
-
{objects.length > 0 && (
|
|
328
|
-
<div className="dm-picker-section">
|
|
329
|
-
<p>Objects</p>
|
|
330
|
-
<div className="dm-picker-scroll">
|
|
331
|
-
{objects.map((table, objIdx) => (
|
|
332
|
-
<div key={`${table.id || table.source}:${objIdx}`} className={`dm-picker-item${selectedTable?.source === table.source ? " active" : ""}`}>
|
|
333
|
-
<button type="button" className="dm-picker-row" onClick={() => {
|
|
334
|
-
onSelectSource(table.source);
|
|
335
|
-
setOpen(false);
|
|
336
|
-
}}>
|
|
337
|
-
<LucideIcon name={table.icon || OBJECT_TYPE_PRESETS[table.objectType]?.icon || "Database"} size={14} />
|
|
338
|
-
<span>{table.label}</span>
|
|
339
|
-
{isLockedObject(table) && <Lock size={12} className="dm-picker-lock" />}
|
|
340
|
-
</button>
|
|
341
|
-
</div>
|
|
342
|
-
))}
|
|
343
|
-
</div>
|
|
344
|
-
</div>
|
|
345
|
-
)}
|
|
346
|
-
{selectedTable && (
|
|
347
|
-
<div className="dm-picker-section">
|
|
348
|
-
<p>Views</p>
|
|
349
|
-
<button type="button" className={`dm-picker-row${!activeView ? " active" : ""}`} onClick={() => applyView(null)}>
|
|
350
|
-
<List size={14} />
|
|
351
|
-
<span>{selectedTable.label}</span>
|
|
352
|
-
{isLockedObject(selectedTable) && <Lock size={12} className="dm-picker-lock" />}
|
|
353
|
-
</button>
|
|
354
|
-
<div className="dm-picker-scroll">
|
|
355
|
-
{views.map((view) => (
|
|
356
|
-
<div key={view.id} className={`dm-picker-item${activeView?.id === view.id ? " active" : ""}`}>
|
|
357
|
-
<button type="button" className="dm-picker-row" onClick={() => applyView(view)}>
|
|
358
|
-
<List size={14} />
|
|
359
|
-
<span>{view.name}</span>
|
|
360
|
-
</button>
|
|
361
|
-
<div className="dm-picker-actions">
|
|
362
|
-
<button
|
|
363
|
-
type="button"
|
|
364
|
-
className="dm-picker-icon-btn"
|
|
365
|
-
aria-label="View actions"
|
|
366
|
-
onClick={(event) => {
|
|
367
|
-
event.stopPropagation();
|
|
368
|
-
setViewMenuId((current) => current === view.id ? "" : view.id);
|
|
369
|
-
}}
|
|
370
|
-
>
|
|
371
|
-
<MoreHorizontal size={12} style={{ transform: "rotate(90deg)" }} />
|
|
372
|
-
</button>
|
|
373
|
-
{viewMenuId === view.id && (
|
|
374
|
-
<div className="dm-picker-menu">
|
|
375
|
-
<button type="button" onClick={() => toggleViewFavorite(view.id)}>
|
|
376
|
-
<Pin size={13} />
|
|
377
|
-
{view.favorite ? "Unpin" : "Pin"}
|
|
378
|
-
</button>
|
|
379
|
-
<button type="button" onClick={() => renameView(view)}>
|
|
380
|
-
<Type size={13} />
|
|
381
|
-
Rename
|
|
382
|
-
</button>
|
|
383
|
-
{!view.locked && (
|
|
384
|
-
<button type="button" className="danger" onClick={() => deleteView(view.id)}>
|
|
385
|
-
<X size={13} />
|
|
386
|
-
Delete
|
|
387
|
-
</button>
|
|
388
|
-
)}
|
|
389
|
-
</div>
|
|
390
|
-
)}
|
|
391
|
-
</div>
|
|
392
|
-
</div>
|
|
393
|
-
))}
|
|
394
|
-
</div>
|
|
395
|
-
<div className="dm-picker-create">
|
|
396
|
-
<input
|
|
397
|
-
value={newViewName}
|
|
398
|
-
placeholder="New view name"
|
|
399
|
-
onChange={(event) => setNewViewName(event.target.value)}
|
|
400
|
-
onKeyDown={(event) => {
|
|
401
|
-
if (event.key === "Enter") createView();
|
|
402
|
-
}}
|
|
403
|
-
/>
|
|
404
|
-
<button type="button" className="dm-btn-outline" disabled={saving || !newViewName.trim()} onClick={createView}>
|
|
405
|
-
<Plus size={13} />Add view
|
|
406
|
-
</button>
|
|
407
|
-
</div>
|
|
408
|
-
</div>
|
|
409
|
-
)}
|
|
410
250
|
</div>
|
|
411
251
|
)}
|
|
412
252
|
</div>
|
|
@@ -1403,7 +1243,7 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave,
|
|
|
1403
1243
|
setFilterDraft({ fieldId: table.columns[0] || "", operator: "eq", value: "" });
|
|
1404
1244
|
}, [table.id, table.columns]);
|
|
1405
1245
|
|
|
1406
|
-
const settings = table.fieldSettings || { hidden: [], order: table.columns, sort: [], filter: null
|
|
1246
|
+
const settings = table.fieldSettings || { hidden: [], order: table.columns, sort: [], filter: null };
|
|
1407
1247
|
const orderedColumns = useMemo(() => mergeColumnOrder(settings.order, table.columns), [settings.order, table.columns]);
|
|
1408
1248
|
const visibleColumns = useMemo(() => orderedColumns.filter((column) => !settings.hidden.includes(column)), [orderedColumns, settings.hidden]);
|
|
1409
1249
|
const rowEntries = useMemo(() => {
|
|
@@ -1420,10 +1260,6 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave,
|
|
|
1420
1260
|
return 0;
|
|
1421
1261
|
});
|
|
1422
1262
|
}, [table.rows, settings]);
|
|
1423
|
-
const activeView = useMemo(
|
|
1424
|
-
() => (settings.views || []).find((view) => view.id === settings.activeViewId) || null,
|
|
1425
|
-
[settings.views, settings.activeViewId]
|
|
1426
|
-
);
|
|
1427
1263
|
const selectedRowCount = selectedRows.size;
|
|
1428
1264
|
const pageCount = Math.max(1, Math.ceil(rowEntries.length / pageSize));
|
|
1429
1265
|
const safePageIndex = Math.min(pageIndex, pageCount - 1);
|
|
@@ -1529,27 +1365,7 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave,
|
|
|
1529
1365
|
hidden: [],
|
|
1530
1366
|
order: table.columns,
|
|
1531
1367
|
sort: [],
|
|
1532
|
-
filter: null
|
|
1533
|
-
activeViewId: ""
|
|
1534
|
-
}));
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
function saveCurrentAsNewView() {
|
|
1538
|
-
const name = window.prompt("View name");
|
|
1539
|
-
if (!name?.trim()) return;
|
|
1540
|
-
const viewId = `view_${Date.now().toString(36)}`;
|
|
1541
|
-
updateSettings((current) => ({
|
|
1542
|
-
...current,
|
|
1543
|
-
activeViewId: viewId,
|
|
1544
|
-
views: [...(current.views || []), { id: viewId, name: name.trim(), favorite: false, locked: false, ...snapshotTableViewState(current) }]
|
|
1545
|
-
}));
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
function updateCurrentView() {
|
|
1549
|
-
if (!activeView) return;
|
|
1550
|
-
updateSettings((current) => ({
|
|
1551
|
-
...current,
|
|
1552
|
-
views: (current.views || []).map((view) => view.id === activeView.id ? { ...view, ...snapshotTableViewState(current) } : view)
|
|
1368
|
+
filter: null
|
|
1553
1369
|
}));
|
|
1554
1370
|
}
|
|
1555
1371
|
|
|
@@ -1670,15 +1486,6 @@ function DataModelTableSurface({ table, tables, workspaceConfig, saving, onSave,
|
|
|
1670
1486
|
</div>
|
|
1671
1487
|
)}
|
|
1672
1488
|
</span>
|
|
1673
|
-
{activeView ? (
|
|
1674
|
-
<button type="button" className="dm-btn-ghost" onClick={updateCurrentView}>
|
|
1675
|
-
Update view
|
|
1676
|
-
</button>
|
|
1677
|
-
) : (
|
|
1678
|
-
<button type="button" className="dm-btn-ghost" onClick={saveCurrentAsNewView}>
|
|
1679
|
-
Save as new view
|
|
1680
|
-
</button>
|
|
1681
|
-
)}
|
|
1682
1489
|
{table.rows.length > 0 && (
|
|
1683
1490
|
<button type="button" className="dm-btn-ghost" onClick={() => {
|
|
1684
1491
|
const blob = new Blob([exportTableAsCsv(table)], { type: "text/csv" });
|
|
@@ -2280,6 +2087,20 @@ export default function DataModelShell() {
|
|
|
2280
2087
|
if (!selectedSource && tables[0]) setSelectedSource(tables[0].source);
|
|
2281
2088
|
}, [selectedSource, tables]);
|
|
2282
2089
|
|
|
2090
|
+
useEffect(() => {
|
|
2091
|
+
const objectParam = searchParams?.get("object");
|
|
2092
|
+
if (!objectParam || !tables.length) return;
|
|
2093
|
+
const target = tables.find((table) => (
|
|
2094
|
+
table.objectId === objectParam
|
|
2095
|
+
|| table.id === objectParam
|
|
2096
|
+
|| table.source === objectParam
|
|
2097
|
+
|| table.label === objectParam
|
|
2098
|
+
));
|
|
2099
|
+
if (target && target.source !== selectedSource) {
|
|
2100
|
+
setSelectedSource(target.source);
|
|
2101
|
+
}
|
|
2102
|
+
}, [searchParams, selectedSource, tables]);
|
|
2103
|
+
|
|
2283
2104
|
// Flush any accumulated patch keys to the server. Called by the debounce
|
|
2284
2105
|
// timer and on visibilitychange/beforeunload so no local edit is lost.
|
|
2285
2106
|
const flushPendingPatch = useCallback(async () => {
|
|
@@ -2439,7 +2260,7 @@ export default function DataModelShell() {
|
|
|
2439
2260
|
},
|
|
2440
2261
|
{
|
|
2441
2262
|
id: "helper.repair", group: "Ask helper", label: "Ask helper — repair workspace",
|
|
2442
|
-
run: () => openHelperWith("repair", "Inspect this workspace for missing references, broken bindings, or incomplete
|
|
2263
|
+
run: () => openHelperWith("repair", "Inspect this workspace for missing references, broken bindings, or incomplete object configuration. Propose the smallest fix for each issue.")
|
|
2443
2264
|
},
|
|
2444
2265
|
{
|
|
2445
2266
|
id: "helper.explain", group: "Ask helper", label: "Ask helper — explain this workspace",
|
|
@@ -10,15 +10,19 @@ import {
|
|
|
10
10
|
Code2,
|
|
11
11
|
Database,
|
|
12
12
|
FileText,
|
|
13
|
+
Folder,
|
|
14
|
+
FolderOpen,
|
|
13
15
|
Globe,
|
|
14
16
|
Hash,
|
|
15
17
|
Layers,
|
|
18
|
+
LayoutDashboard,
|
|
16
19
|
Link2,
|
|
17
20
|
List,
|
|
18
21
|
Mail,
|
|
19
22
|
Plus,
|
|
20
23
|
ShoppingCart,
|
|
21
24
|
Star,
|
|
25
|
+
Table,
|
|
22
26
|
Tag,
|
|
23
27
|
Terminal,
|
|
24
28
|
ToggleLeft,
|
|
@@ -30,14 +34,16 @@ import { OBJECT_TYPE_PRESETS } from "@/lib/workspace-data-model";
|
|
|
30
34
|
|
|
31
35
|
const LUCIDE_MAP = {
|
|
32
36
|
Activity, BarChart2, Box, Building2, Calendar, CheckSquare, Code2,
|
|
33
|
-
Database, FileText,
|
|
34
|
-
|
|
37
|
+
Database, FileText, Folder, FolderOpen, Globe, Hash, Layers,
|
|
38
|
+
LayoutDashboard, Link2, List, Mail, Plus, ShoppingCart, Star, Table,
|
|
39
|
+
Tag, Terminal, ToggleLeft, Type, Users, Zap,
|
|
35
40
|
};
|
|
36
41
|
|
|
37
42
|
const ICON_PICKER_SET = [
|
|
38
43
|
"Database", "Globe", "Code2", "Users", "CheckSquare", "Building2",
|
|
39
44
|
"Tag", "Star", "Zap", "FileText", "Mail", "BarChart2",
|
|
40
45
|
"Layers", "Box", "Activity", "ShoppingCart", "Terminal",
|
|
46
|
+
"Folder", "FolderOpen", "LayoutDashboard", "Table", "List",
|
|
41
47
|
];
|
|
42
48
|
|
|
43
49
|
const OBJECT_TYPE_BADGE = {
|