@object-ui/app-shell 4.0.7 → 4.0.9
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/CHANGELOG.md +76 -0
- package/dist/console/AppContent.js +4 -2
- package/dist/hooks/useObjectActions.js +26 -2
- package/dist/layout/AppHeader.js +14 -8
- package/dist/layout/AppSidebar.js +2 -2
- package/dist/layout/AppSwitcher.js +1 -1
- package/dist/layout/PageHeader.d.ts +42 -0
- package/dist/layout/PageHeader.js +13 -0
- package/dist/layout/index.d.ts +2 -0
- package/dist/layout/index.js +1 -0
- package/dist/utils/getIcon.d.ts +4 -1
- package/dist/utils/getIcon.js +11 -2
- package/dist/views/CreateViewDialog.js +162 -23
- package/dist/views/DashboardView.js +69 -22
- package/dist/views/ObjectView.d.ts +1 -1
- package/dist/views/ObjectView.js +255 -30
- package/dist/views/RecordDetailView.js +90 -8
- package/dist/views/RecordFormPage.js +20 -1
- package/dist/views/ReportView.js +68 -16
- package/dist/views/ViewConfigPanel.js +24 -6
- package/package.json +29 -29
package/dist/views/ReportView.js
CHANGED
|
@@ -206,22 +206,66 @@ export function ReportView({ dataSource }) {
|
|
|
206
206
|
// Map @objectstack/spec report format to @object-ui/types ReportSchema:
|
|
207
207
|
// - 'label' → 'title'
|
|
208
208
|
// - 'columns' (with 'field') → 'fields' (with 'name') + auto-generate 'sections'
|
|
209
|
+
// - Hydrate type/options/referenceTo from the bound object's field metadata
|
|
210
|
+
// so the type-aware cell renderer can show select badges, lookup links,
|
|
211
|
+
// boolean ✓/✗, email/url/phone links, etc. instead of raw values.
|
|
209
212
|
const mapReportForViewer = (src) => {
|
|
210
213
|
const mapped = { ...src };
|
|
211
214
|
if (!mapped.title && mapped.label) {
|
|
212
215
|
mapped.title = mapped.label;
|
|
213
216
|
}
|
|
217
|
+
// Build a lookup of object-field metadata to hydrate column type info.
|
|
218
|
+
const objName = mapped.objectName || mapped.dataSource?.object || mapped.dataSource?.resource;
|
|
219
|
+
const objDef = objName ? objects?.find((o) => o.name === objName) : null;
|
|
220
|
+
const objFieldsArr = Array.isArray(objDef?.fields)
|
|
221
|
+
? objDef.fields
|
|
222
|
+
: objDef?.fields
|
|
223
|
+
? Object.entries(objDef.fields).map(([name, def]) => ({ name, ...def }))
|
|
224
|
+
: [];
|
|
225
|
+
const objFieldMap = {};
|
|
226
|
+
for (const f of objFieldsArr) {
|
|
227
|
+
if (f && f.name)
|
|
228
|
+
objFieldMap[f.name] = f;
|
|
229
|
+
}
|
|
230
|
+
const hydrate = (col) => {
|
|
231
|
+
const name = col.name || col.field;
|
|
232
|
+
const meta = name ? objFieldMap[name] : undefined;
|
|
233
|
+
if (!meta)
|
|
234
|
+
return col;
|
|
235
|
+
// Author-provided values win; only fill in what's missing.
|
|
236
|
+
const out = { ...col };
|
|
237
|
+
if (out.type === undefined && meta.type !== undefined)
|
|
238
|
+
out.type = meta.type;
|
|
239
|
+
if (out.options === undefined && Array.isArray(meta.options))
|
|
240
|
+
out.options = meta.options;
|
|
241
|
+
if (out.referenceTo === undefined) {
|
|
242
|
+
const ref = meta.referenceTo || meta.reference?.to || meta.target;
|
|
243
|
+
if (ref)
|
|
244
|
+
out.referenceTo = ref;
|
|
245
|
+
}
|
|
246
|
+
if (out.label === undefined && meta.label)
|
|
247
|
+
out.label = meta.label;
|
|
248
|
+
return out;
|
|
249
|
+
};
|
|
214
250
|
// Map spec 'columns' (field/label/aggregate) → ReportSchema 'fields' (name/label/aggregation)
|
|
215
251
|
if (!mapped.fields && Array.isArray(mapped.columns)) {
|
|
216
|
-
mapped.fields = mapped.columns.map((col) =>
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
252
|
+
mapped.fields = mapped.columns.map((col) => {
|
|
253
|
+
const hydrated = hydrate(col);
|
|
254
|
+
return {
|
|
255
|
+
name: hydrated.field || hydrated.name,
|
|
256
|
+
label: hydrated.label,
|
|
257
|
+
type: hydrated.type,
|
|
258
|
+
options: hydrated.options,
|
|
259
|
+
referenceTo: hydrated.referenceTo,
|
|
260
|
+
format: hydrated.format,
|
|
261
|
+
renderAs: hydrated.renderAs,
|
|
262
|
+
colorMap: hydrated.colorMap,
|
|
263
|
+
...(hydrated.aggregate ? { aggregation: hydrated.aggregate, showInSummary: true } : {}),
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
else if (Array.isArray(mapped.fields)) {
|
|
268
|
+
mapped.fields = mapped.fields.map(hydrate);
|
|
225
269
|
}
|
|
226
270
|
// Always regenerate sections from current fields so that live config
|
|
227
271
|
// changes (e.g. field picker updates) are immediately reflected in
|
|
@@ -229,7 +273,8 @@ export function ReportView({ dataSource }) {
|
|
|
229
273
|
// did not update the rendered report.
|
|
230
274
|
if (mapped.fields && Array.isArray(mapped.fields) && mapped.fields.length > 0) {
|
|
231
275
|
const hasSummaryFields = mapped.fields.some((f) => f.showInSummary || f.aggregation);
|
|
232
|
-
|
|
276
|
+
// Spec key is `type`; legacy renderer used `reportType`. Accept either.
|
|
277
|
+
const reportType = mapped.type || mapped.reportType || 'tabular';
|
|
233
278
|
const sections = [];
|
|
234
279
|
if (reportType === 'summary' || hasSummaryFields) {
|
|
235
280
|
sections.push({ type: 'summary', title: 'Key Metrics' });
|
|
@@ -241,27 +286,34 @@ export function ReportView({ dataSource }) {
|
|
|
241
286
|
name: f.name,
|
|
242
287
|
label: f.label,
|
|
243
288
|
type: f.type,
|
|
289
|
+
options: f.options,
|
|
290
|
+
referenceTo: f.referenceTo,
|
|
244
291
|
format: f.format,
|
|
245
292
|
renderAs: f.renderAs,
|
|
246
293
|
colorMap: f.colorMap,
|
|
247
294
|
})),
|
|
248
295
|
});
|
|
249
|
-
// Generate chart section from
|
|
250
|
-
|
|
296
|
+
// Generate chart section from chart config if configured.
|
|
297
|
+
// Spec keys: type / xAxis / yAxis. Legacy: chartType / xAxisField / yAxisFields[0].
|
|
298
|
+
const chartCfg = mapped.chart || mapped.chartConfig;
|
|
299
|
+
const chartTypeVal = chartCfg?.type || chartCfg?.chartType;
|
|
300
|
+
if (chartTypeVal) {
|
|
301
|
+
const xField = chartCfg.xAxis || chartCfg.xAxisField;
|
|
302
|
+
const yField = chartCfg.yAxis || chartCfg.yAxisFields?.[0];
|
|
251
303
|
sections.push({
|
|
252
304
|
type: 'chart',
|
|
253
305
|
title: 'Chart',
|
|
254
306
|
chart: {
|
|
255
307
|
type: 'chart',
|
|
256
|
-
chartType:
|
|
257
|
-
xAxisField:
|
|
258
|
-
yAxisFields:
|
|
308
|
+
chartType: chartTypeVal,
|
|
309
|
+
xAxisField: xField,
|
|
310
|
+
yAxisFields: yField ? [yField] : chartCfg.yAxisFields,
|
|
259
311
|
},
|
|
260
312
|
});
|
|
261
313
|
}
|
|
262
314
|
// Preserve any user-defined chart sections from the original schema
|
|
263
315
|
if (Array.isArray(src.sections)) {
|
|
264
|
-
const chartSections = src.sections.filter((s) => s.type === 'chart' && !
|
|
316
|
+
const chartSections = src.sections.filter((s) => s.type === 'chart' && !chartTypeVal);
|
|
265
317
|
sections.push(...chartSections);
|
|
266
318
|
}
|
|
267
319
|
mapped.sections = sections;
|
|
@@ -12,13 +12,23 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
12
12
|
* All changes are buffered in a local draft state. Clicking Save commits
|
|
13
13
|
* the draft via onSave; Discard resets to the original activeView.
|
|
14
14
|
*/
|
|
15
|
-
import { useMemo, useEffect, useRef, useCallback } from 'react';
|
|
16
|
-
import { ConfigPanelRenderer, useConfigDraft } from '@object-ui/components';
|
|
15
|
+
import { useMemo, useEffect, useRef, useCallback, useState } from 'react';
|
|
16
|
+
import { ConfigPanelRenderer, useConfigDraft, Button } from '@object-ui/components';
|
|
17
|
+
import { Settings2 } from 'lucide-react';
|
|
17
18
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
18
19
|
import { buildViewConfigSchema, deriveFieldOptions, toFilterGroup, toSortItems, VIEW_TYPE_LABELS, } from '@object-ui/plugin-view';
|
|
19
20
|
export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, objectDef, onViewUpdate, onSave, onCreate }) {
|
|
20
21
|
const { t } = useObjectTranslation();
|
|
21
22
|
const panelRef = useRef(null);
|
|
23
|
+
// "Show advanced settings" — when false (default), the panel only shows
|
|
24
|
+
// the Airtable-essential subset. When true, every section/field is
|
|
25
|
+
// surfaced for power users. Reset whenever the panel closes so reopening
|
|
26
|
+
// returns to the simplified view.
|
|
27
|
+
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!open)
|
|
30
|
+
setShowAdvanced(false);
|
|
31
|
+
}, [open]);
|
|
22
32
|
// Default empty view for create mode
|
|
23
33
|
const defaultNewView = useMemo(() => ({
|
|
24
34
|
id: `view_${Date.now()}`,
|
|
@@ -53,7 +63,8 @@ export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, obje
|
|
|
53
63
|
// Bridge: filter/sort → builder format
|
|
54
64
|
const filterGroupValue = useMemo(() => toFilterGroup(draft.filter), [draft.filter]);
|
|
55
65
|
const sortItemsValue = useMemo(() => toSortItems(draft.sort), [draft.sort]);
|
|
56
|
-
// Build schema
|
|
66
|
+
// Build schema. essentialOnly is the default; the user can opt-in to the
|
|
67
|
+
// full advanced surface via the "Show advanced settings" toggle below.
|
|
57
68
|
const schema = useMemo(() => buildViewConfigSchema({
|
|
58
69
|
t,
|
|
59
70
|
fieldOptions,
|
|
@@ -61,8 +72,8 @@ export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, obje
|
|
|
61
72
|
updateField,
|
|
62
73
|
filterGroupValue,
|
|
63
74
|
sortItemsValue,
|
|
64
|
-
essentialOnly:
|
|
65
|
-
}), [t, fieldOptions, objectDef, updateField, filterGroupValue, sortItemsValue]);
|
|
75
|
+
essentialOnly: !showAdvanced,
|
|
76
|
+
}), [t, fieldOptions, objectDef, updateField, filterGroupValue, sortItemsValue, showAdvanced]);
|
|
66
77
|
// Override breadcrumb with dynamic view type
|
|
67
78
|
const viewType = draft.type || 'grid';
|
|
68
79
|
const dynamicSchema = useMemo(() => ({
|
|
@@ -90,5 +101,12 @@ export function ViewConfigPanel({ open, onClose, mode = 'edit', activeView, obje
|
|
|
90
101
|
const panelTitle = mode === 'create'
|
|
91
102
|
? t('console.objectView.createView')
|
|
92
103
|
: t('console.objectView.configureView');
|
|
93
|
-
|
|
104
|
+
// Header-extra: "Show advanced" toggle button. Subtle gear icon button
|
|
105
|
+
// sitting next to undo/redo/close — clearly affordant but not visually
|
|
106
|
+
// dominant. Title swaps between "Show advanced settings" and "Show fewer
|
|
107
|
+
// settings" so the user always knows what the next click will do.
|
|
108
|
+
const advancedToggle = (_jsx(Button, { size: "sm", variant: showAdvanced ? 'secondary' : 'ghost', onClick: () => setShowAdvanced(v => !v), className: "h-7 w-7 p-0", "data-testid": "view-config-advanced-toggle", "aria-pressed": showAdvanced, title: showAdvanced
|
|
109
|
+
? t('console.objectView.showFewerSettings', { defaultValue: 'Show fewer settings' })
|
|
110
|
+
: t('console.objectView.showAdvancedSettings', { defaultValue: 'Show advanced settings' }), children: _jsx(Settings2, { className: "h-3.5 w-3.5" }) }));
|
|
111
|
+
return (_jsx(ConfigPanelRenderer, { open: open, onClose: onClose, schema: dynamicSchema, draft: draft, isDirty: isDirty, onFieldChange: updateField, onSave: handleSave, onDiscard: handleDiscard, onUndo: undo, onRedo: redo, canUndo: canUndo, canRedo: canRedo, undoLabel: t('designer.undo'), redoLabel: t('designer.redo'), objectDef: objectDef, saveLabel: t('console.objectView.save'), discardLabel: t('console.objectView.discard'), panelRef: panelRef, role: "complementary", ariaLabel: panelTitle, tabIndex: -1, testId: "view-config-panel", closeTitle: t('console.objectView.closePanel'), footerTestId: "view-config-footer", saveTestId: "view-config-save", discardTestId: "view-config-discard", headerExtra: advancedToggle, className: "transition-all overflow-hidden" }));
|
|
94
112
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/app-shell",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Minimal application shell for ObjectUI - framework-agnostic rendering engine",
|
|
@@ -27,45 +27,45 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"lucide-react": "^1.14.0",
|
|
29
29
|
"sonner": "^2.0.7",
|
|
30
|
-
"@object-ui/auth": "4.0.
|
|
31
|
-
"@object-ui/collaboration": "4.0.
|
|
32
|
-
"@object-ui/
|
|
33
|
-
"@object-ui/
|
|
34
|
-
"@object-ui/
|
|
35
|
-
"@object-ui/
|
|
36
|
-
"@object-ui/
|
|
37
|
-
"@object-ui/
|
|
38
|
-
"@object-ui/
|
|
39
|
-
"@object-ui/
|
|
40
|
-
"@object-ui/
|
|
30
|
+
"@object-ui/auth": "4.0.9",
|
|
31
|
+
"@object-ui/collaboration": "4.0.9",
|
|
32
|
+
"@object-ui/core": "4.0.9",
|
|
33
|
+
"@object-ui/data-objectstack": "4.0.9",
|
|
34
|
+
"@object-ui/fields": "4.0.9",
|
|
35
|
+
"@object-ui/i18n": "4.0.9",
|
|
36
|
+
"@object-ui/layout": "4.0.9",
|
|
37
|
+
"@object-ui/permissions": "4.0.9",
|
|
38
|
+
"@object-ui/react": "4.0.9",
|
|
39
|
+
"@object-ui/types": "4.0.9",
|
|
40
|
+
"@object-ui/components": "4.0.9"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"react": "^18.0.0 || ^19.0.0",
|
|
44
44
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
45
45
|
"react-router-dom": "^6.0.0 || ^7.0.0",
|
|
46
|
-
"@object-ui/plugin-calendar": "4.0.
|
|
47
|
-
"@object-ui/plugin-charts": "4.0.
|
|
48
|
-
"@object-ui/plugin-chatbot": "4.0.
|
|
49
|
-
"@object-ui/plugin-dashboard": "4.0.
|
|
50
|
-
"@object-ui/plugin-designer": "4.0.
|
|
51
|
-
"@object-ui/plugin-detail": "4.0.
|
|
52
|
-
"@object-ui/plugin-form": "4.0.
|
|
53
|
-
"@object-ui/plugin-grid": "4.0.
|
|
54
|
-
"@object-ui/plugin-kanban": "4.0.
|
|
55
|
-
"@object-ui/plugin-list": "4.0.
|
|
56
|
-
"@object-ui/plugin-report": "4.0.
|
|
57
|
-
"@object-ui/plugin-view": "4.0.
|
|
46
|
+
"@object-ui/plugin-calendar": "4.0.9",
|
|
47
|
+
"@object-ui/plugin-charts": "4.0.9",
|
|
48
|
+
"@object-ui/plugin-chatbot": "4.0.9",
|
|
49
|
+
"@object-ui/plugin-dashboard": "4.0.9",
|
|
50
|
+
"@object-ui/plugin-designer": "4.0.9",
|
|
51
|
+
"@object-ui/plugin-detail": "4.0.9",
|
|
52
|
+
"@object-ui/plugin-form": "4.0.9",
|
|
53
|
+
"@object-ui/plugin-grid": "4.0.9",
|
|
54
|
+
"@object-ui/plugin-kanban": "4.0.9",
|
|
55
|
+
"@object-ui/plugin-list": "4.0.9",
|
|
56
|
+
"@object-ui/plugin-report": "4.0.9",
|
|
57
|
+
"@object-ui/plugin-view": "4.0.9"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@types/node": "^25.
|
|
60
|
+
"@types/node": "^25.7.0",
|
|
61
61
|
"@types/react": "19.2.14",
|
|
62
62
|
"@types/react-dom": "19.2.3",
|
|
63
|
-
"react": "19.2.
|
|
64
|
-
"react-dom": "19.2.
|
|
65
|
-
"react-router-dom": "^7.
|
|
63
|
+
"react": "19.2.6",
|
|
64
|
+
"react-dom": "19.2.6",
|
|
65
|
+
"react-router-dom": "^7.15.0",
|
|
66
66
|
"sonner": "^2.0.7",
|
|
67
67
|
"typescript": "^6.0.3",
|
|
68
|
-
"vite": "^8.0.
|
|
68
|
+
"vite": "^8.0.12"
|
|
69
69
|
},
|
|
70
70
|
"keywords": [
|
|
71
71
|
"objectui",
|