@zeyos/zx 4.1.0 → 4.2.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/CHANGELOG.md +61 -0
- package/DESIGN-SYSTEM.md +33 -3
- package/README.md +17 -5
- package/dist/types/components/app-icon/app-icon.d.ts +8 -1
- package/dist/types/components/aurora/aurora.d.ts +72 -0
- package/dist/types/components/calendar/calendar-model.d.ts +202 -0
- package/dist/types/components/calendar/calendar.d.ts +500 -0
- package/dist/types/components/card-view/card-view.d.ts +303 -0
- package/dist/types/components/card-view/record-card.d.ts +242 -0
- package/dist/types/components/kanban-view/kanban-view.d.ts +572 -0
- package/dist/types/components/table/table.d.ts +120 -31
- package/dist/types/components/table-view/table-view.d.ts +192 -0
- package/dist/types/components/view/record-view.d.ts +464 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/zeyos/calendar.d.ts +91 -0
- package/dist/types/zeyos/icons.d.ts +19 -3
- package/dist/types/zeyos/index.d.ts +4 -0
- package/dist/types/zeyos/legacy-saved-views.d.ts +66 -0
- package/dist/types/zeyos/saved-views.d.ts +331 -0
- package/dist/types/zeyos/view.d.ts +305 -0
- package/dist/zx-zeyos.esm.js +4627 -1460
- package/dist/zx-zeyos.esm.min.js +1 -1
- package/dist/zx.css +2492 -410
- package/dist/zx.esm.js +4891 -115
- package/dist/zx.esm.min.js +3 -3
- package/dist/zx.global.js +4891 -115
- package/dist/zx.global.min.js +3 -3
- package/dist/zx.min.css +1 -1
- package/docs/api.json +1702 -191
- package/docs/llms.md +207 -20
- package/docs/llms.txt +16 -6
- package/package.json +1 -1
- package/src/components/app-icon/app-icon.css +140 -30
- package/src/components/app-icon/app-icon.js +32 -5
- package/src/components/app-rail/app-rail.css +11 -2
- package/src/components/app-sidebar/app-sidebar.css +11 -2
- package/src/components/aurora/aurora.css +143 -0
- package/src/components/aurora/aurora.js +146 -0
- package/src/components/avatar/avatar.css +1 -1
- package/src/components/button/button.css +34 -2
- package/src/components/calendar/calendar-model.js +402 -0
- package/src/components/calendar/calendar.css +567 -0
- package/src/components/calendar/calendar.js +1524 -0
- package/src/components/card/card.css +25 -3
- package/src/components/card-view/card-view.css +389 -0
- package/src/components/card-view/card-view.js +527 -0
- package/src/components/card-view/record-card.js +392 -0
- package/src/components/context-menu/context-menu.css +2 -2
- package/src/components/date-picker/date-picker.css +15 -3
- package/src/components/datebox/date-range-box.css +15 -2
- package/src/components/datebox/datebox.css +16 -3
- package/src/components/dropdown/dropdown.css +15 -3
- package/src/components/kanban-view/kanban-view.css +216 -0
- package/src/components/kanban-view/kanban-view.js +1228 -0
- package/src/components/launcher/launcher.css +18 -5
- package/src/components/master-panel/master-panel.css +14 -3
- package/src/components/menu-button/menu-button.css +2 -2
- package/src/components/message/message.css +15 -10
- package/src/components/modal/modal.css +15 -3
- package/src/components/navigation-bar/navigation-bar.css +17 -5
- package/src/components/panel/panel.css +10 -2
- package/src/components/select/select.css +20 -4
- package/src/components/sheet/sheet.css +4 -1
- package/src/components/table/table.css +80 -4
- package/src/components/table/table.js +322 -37
- package/src/components/table-view/table-view.css +18 -0
- package/src/components/table-view/table-view.js +347 -0
- package/src/components/tag-picker/tag-picker.css +15 -3
- package/src/components/tooltip/tooltip.css +15 -3
- package/src/components/view/record-view.css +114 -0
- package/src/components/view/record-view.js +770 -0
- package/src/index.js +15 -0
- package/src/zeyos/calendar.js +115 -0
- package/src/zeyos/icons.js +15 -8
- package/src/zeyos/index.js +13 -0
- package/src/zeyos/launcher.js +2 -2
- package/src/zeyos/legacy-saved-views.js +155 -0
- package/src/zeyos/saved-views.js +815 -0
- package/src/zeyos/table.js +3 -1
- package/src/zeyos/view.js +543 -0
- package/styles/icons.css +3 -10
- package/styles/tokens/semantic.css +74 -2
- package/styles/zx.css +6 -0
package/src/index.js
CHANGED
|
@@ -89,6 +89,7 @@ export { Toolbar } from './components/toolbar/toolbar.js';
|
|
|
89
89
|
export { Toggle } from './components/toggle/toggle.js';
|
|
90
90
|
export { Groupbox } from './components/groupbox/groupbox.js';
|
|
91
91
|
export { Card } from './components/card/card.js';
|
|
92
|
+
export { Aurora } from './components/aurora/aurora.js';
|
|
92
93
|
export { Panel } from './components/panel/panel.js';
|
|
93
94
|
export { MasterPanel } from './components/master-panel/master-panel.js';
|
|
94
95
|
export {
|
|
@@ -133,7 +134,21 @@ export {
|
|
|
133
134
|
DateRangeBox, formatRangeText, parseRangeText
|
|
134
135
|
} from './components/datebox/date-range-box.js';
|
|
135
136
|
export { Timebox, splitTime, joinTime } from './components/timebox/timebox.js';
|
|
137
|
+
export { Calendar } from './components/calendar/calendar.js';
|
|
138
|
+
export {
|
|
139
|
+
CALENDAR_VIEWS, addCalendarDays, calendarDayDifference, calendarDayKey,
|
|
140
|
+
calendarEventIntersects, calendarEventSpansDays, calendarRange, cloneCalendarEvent,
|
|
141
|
+
layoutCalendarSpans, layoutTimedCalendarEvents, normalizeCalendarEvents,
|
|
142
|
+
shiftCalendarEvent, startOfCalendarDay, startOfCalendarWeek
|
|
143
|
+
} from './components/calendar/calendar-model.js';
|
|
144
|
+
export {
|
|
145
|
+
RecordView, normalizeViewFields, reconcileFieldOrder, moveViewField,
|
|
146
|
+
readViewField, renderViewField, sortViewRecords, normalizeViewState, viewRecordId
|
|
147
|
+
} from './components/view/record-view.js';
|
|
136
148
|
export { Table, reorderTableRows } from './components/table/table.js';
|
|
149
|
+
export { TableView } from './components/table-view/table-view.js';
|
|
150
|
+
export { CardView } from './components/card-view/card-view.js';
|
|
151
|
+
export { KanbanView } from './components/kanban-view/kanban-view.js';
|
|
137
152
|
export { Grid, billingColumns, isBillingLine } from './components/grid/grid.js';
|
|
138
153
|
export {
|
|
139
154
|
Chart, ChartJsAdapter, chartSummaryTable, cloneChartValue, isEmptyChartData
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Calendar, calendarDayDifference } from '../index.js';
|
|
2
|
+
import { dateToUnixSeconds, unixSecondsToDate } from './query.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {Object} ZeyosCalendarOptions
|
|
6
|
+
* @property {Record<string, any>[]} [appointments=[]] Current ZeyOS appointment/event occurrences.
|
|
7
|
+
* @property {Record<string, any>[]} [events] Already-normalized generic Calendar records.
|
|
8
|
+
* @property {string|number|Date|null} [date] Calendar anchor.
|
|
9
|
+
* @property {'agenda'|'day'|'week'|'month'|'year'} [view='month'] Initial view.
|
|
10
|
+
* @property {boolean} [optimistic=true] Apply move/resize proposals locally before persistence.
|
|
11
|
+
* @property {boolean} [editable=false] Enable moving and resizing.
|
|
12
|
+
* @property {number} [weekStart=1] User-preferred first weekday.
|
|
13
|
+
* @property {boolean} [workweek=false] Show five days in week view.
|
|
14
|
+
* @property {(event: CustomEvent<Record<string, any>>) => void} [oneventchange] Persistence listener.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Maps one expanded ZeyOS appointment/event row to the product-neutral Calendar shape. Occurrence
|
|
19
|
+
* identity includes the start timestamp because recurring rows intentionally share their base ID;
|
|
20
|
+
* persistence callers keep using `event.data.ID`.
|
|
21
|
+
* @param {Record<string, any>} record ZeyOS row.
|
|
22
|
+
* @returns {Record<string, any>} Generic Calendar event.
|
|
23
|
+
*/
|
|
24
|
+
export function zeyosAppointmentToEvent(record) {
|
|
25
|
+
if (!record || typeof record !== 'object' || Array.isArray(record)) {
|
|
26
|
+
throw new TypeError('ZeyOS appointment must be an object');
|
|
27
|
+
}
|
|
28
|
+
const baseId = record.ID ?? record.id;
|
|
29
|
+
if (baseId == null || baseId === '') throw new TypeError('ZeyOS appointment requires ID');
|
|
30
|
+
const start = unixSecondsToDate(record.datefrom);
|
|
31
|
+
const end = unixSecondsToDate(record.dateto ?? record.datefrom);
|
|
32
|
+
if (!(start instanceof Date) || !(end instanceof Date)) throw new TypeError('ZeyOS appointment requires datefrom and dateto');
|
|
33
|
+
const association = String(record.assoc_name ?? '').trim();
|
|
34
|
+
const name = String(record.name ?? '').trim() || 'Untitled appointment';
|
|
35
|
+
const title = record.entity && association ? `${association}: ${name}` : name;
|
|
36
|
+
const allDay = record.allDay != null ? Boolean(record.allDay)
|
|
37
|
+
: isMidnight(start) && isMidnight(end) && calendarDayDifference(start, end) >= 1;
|
|
38
|
+
return {
|
|
39
|
+
id: String(record.occurrenceid ?? `${baseId}@${record.datefrom}`),
|
|
40
|
+
title,
|
|
41
|
+
start,
|
|
42
|
+
end,
|
|
43
|
+
allDay,
|
|
44
|
+
color: zeyosCalendarColor(record.color),
|
|
45
|
+
location: String(record.location ?? '').trim(),
|
|
46
|
+
editable: record.editable == null ? null : Boolean(record.editable),
|
|
47
|
+
durationEditable: record.durationEditable == null ? null : Boolean(record.durationEditable),
|
|
48
|
+
data: record
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @param {Record<string, any>[]} records @returns {Record<string, any>[]} */
|
|
53
|
+
export function zeyosAppointmentsToEvents(records) {
|
|
54
|
+
if (!Array.isArray(records)) throw new TypeError('ZeyOS appointments must be an array');
|
|
55
|
+
return records.map(zeyosAppointmentToEvent);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Converts a proposed Calendar occurrence back to the current ZeyOS appointment update fields.
|
|
60
|
+
* The base appointment identifier remains the source record's `ID`; date values become Unix
|
|
61
|
+
* seconds and the adapter never adds recurrence or DAV fields.
|
|
62
|
+
* @param {{start: Date, end: Date, data?: Record<string, any>}} event Calendar event proposal.
|
|
63
|
+
* @returns {{ID?: unknown, datefrom: number|null, dateto: number|null}}
|
|
64
|
+
*/
|
|
65
|
+
export function calendarEventToZeyosPatch(event) {
|
|
66
|
+
if (!event || !(event.start instanceof Date) || !(event.end instanceof Date)) {
|
|
67
|
+
throw new TypeError('Calendar event patch requires start and end Dates');
|
|
68
|
+
}
|
|
69
|
+
const patch = {
|
|
70
|
+
datefrom: dateToUnixSeconds(event.start),
|
|
71
|
+
dateto: dateToUnixSeconds(event.end)
|
|
72
|
+
};
|
|
73
|
+
if (event.data?.ID != null) return { ID: event.data.ID, ...patch };
|
|
74
|
+
return patch;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Builds Calendar options while preserving every explicit generic option. `appointments` is
|
|
79
|
+
* mapped only when present; callers may instead pass already-normalized `events`.
|
|
80
|
+
* @param {ZeyosCalendarOptions & Record<string, any>} [options={}] ZeyOS and Calendar options.
|
|
81
|
+
* @returns {Record<string, any>} Plain Calendar options.
|
|
82
|
+
*/
|
|
83
|
+
export function buildZeyosCalendarOptions(options = {}) {
|
|
84
|
+
const { appointments, ...calendarOptions } = options;
|
|
85
|
+
return {
|
|
86
|
+
weekStart: 1,
|
|
87
|
+
optimistic: true,
|
|
88
|
+
...calendarOptions,
|
|
89
|
+
events: appointments == null
|
|
90
|
+
? (Array.isArray(calendarOptions.events) ? calendarOptions.events : [])
|
|
91
|
+
: zeyosAppointmentsToEvents(appointments)
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Creates a Calendar that accepts current ZeyOS appointment/event occurrences directly.
|
|
97
|
+
* @param {Element|string|null} target Existing target or null.
|
|
98
|
+
* @param {ZeyosCalendarOptions & Record<string, any>} [options={}] Adapter and Calendar options.
|
|
99
|
+
* @returns {Calendar}
|
|
100
|
+
*/
|
|
101
|
+
export function zeyosCalendar(target, options = {}) {
|
|
102
|
+
return new Calendar(target, buildZeyosCalendarOptions(options));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** @param {unknown} value @returns {string|null} */
|
|
106
|
+
function zeyosCalendarColor(value) {
|
|
107
|
+
const color = String(value ?? '').trim();
|
|
108
|
+
if (!color) return null;
|
|
109
|
+
return /^[\da-f]{3,8}$/i.test(color) ? `#${color}` : color;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** @param {Date} date @returns {boolean} */
|
|
113
|
+
function isMidnight(date) {
|
|
114
|
+
return date.getHours() === 0 && date.getMinutes() === 0 && date.getSeconds() === 0 && date.getMilliseconds() === 0;
|
|
115
|
+
}
|
package/src/zeyos/icons.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { icon, loadFontAwesome } from '../index.js';
|
|
8
8
|
import { appIcon } from '../components/app-icon/app-icon.js';
|
|
9
9
|
import { isCssColor } from '../core/util.js';
|
|
10
|
-
import {
|
|
10
|
+
import { moduleInfo, normalizeModuleName } from './modules.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* The ZeyOS Font Awesome kit. It carries the licensed Font Awesome styles plus the custom
|
|
@@ -59,14 +59,17 @@ export function moduleIcon(name, options = {}) {
|
|
|
59
59
|
* @property {boolean} [standard=false] Use the stock Font Awesome fallback glyph.
|
|
60
60
|
* @property {string|Node|(()=>string|Node)|null} [icon] Explicit glyph override, useful for an offline fallback.
|
|
61
61
|
* @property {string} [color] Override the module colour for a server-defined fork or weblet.
|
|
62
|
+
* @property {string|number|null} [badge=null] Optional corner badge.
|
|
63
|
+
* @property {boolean} [selected=false] Whether the application identity is selected.
|
|
64
|
+
* @property {'tile'|'circle'} [shape='tile'] AppIcon vessel geometry.
|
|
62
65
|
* @property {false|'subtle'|'strong'} [glass='subtle'] AppIcon material treatment.
|
|
63
66
|
* @property {string|string[]} [class] Extra class names.
|
|
64
67
|
*/
|
|
65
68
|
|
|
66
69
|
/**
|
|
67
70
|
* Creates a module's icon chip: the glyph on the module's identity colour, the way ZeyOS draws
|
|
68
|
-
* module icons in navigation and record headers.
|
|
69
|
-
*
|
|
71
|
+
* module icons in navigation and record headers. AppIcon identity glyphs stay white across every
|
|
72
|
+
* module colour; material shading preserves their silhouette on light colours.
|
|
70
73
|
* @param {string} name Module, entity, or resource name.
|
|
71
74
|
* @param {ModuleChipOptions} [options={}] Display and accessibility options.
|
|
72
75
|
* @returns {HTMLElement} A `<span class="zx-module-icon">` wrapping the glyph.
|
|
@@ -74,7 +77,7 @@ export function moduleIcon(name, options = {}) {
|
|
|
74
77
|
export function moduleChip(name, options = {}) {
|
|
75
78
|
const {
|
|
76
79
|
size = 24, iconSize = '52%', label = null, title = false, standard = false,
|
|
77
|
-
color = null
|
|
80
|
+
color = null, badge = null, selected = false
|
|
78
81
|
} = options;
|
|
79
82
|
const key = normalizeModuleName(name);
|
|
80
83
|
const info = moduleInfo(name);
|
|
@@ -88,23 +91,27 @@ export function moduleChip(name, options = {}) {
|
|
|
88
91
|
iconSize,
|
|
89
92
|
color: background,
|
|
90
93
|
label,
|
|
94
|
+
badge,
|
|
95
|
+
selected,
|
|
96
|
+
shape: options.shape ?? 'tile',
|
|
91
97
|
glass: options.glass ?? 'subtle',
|
|
92
98
|
class: ['zx-module-icon', options.class].flat().filter(Boolean)
|
|
93
99
|
});
|
|
94
100
|
chip.dataset.module = key;
|
|
95
101
|
chip.style.setProperty('--zx-module-color', background);
|
|
96
|
-
chip.style.setProperty('--zx-module-glyph',
|
|
97
|
-
chip.style.setProperty('--zx-app-icon-glyph',
|
|
102
|
+
chip.style.setProperty('--zx-module-glyph', 'var(--zx-color-app-icon-glyph)');
|
|
103
|
+
chip.style.setProperty('--zx-app-icon-glyph', 'var(--zx-color-app-icon-glyph)');
|
|
98
104
|
if (title) chip.title = info.label;
|
|
99
105
|
return chip;
|
|
100
106
|
}
|
|
101
107
|
|
|
102
108
|
/**
|
|
103
|
-
* Creates the public ZeyOS
|
|
109
|
+
* Creates the public ZeyOS application-identity preset for any module, fork, or weblet. It uses
|
|
110
|
+
* the circular application vessel by default; use `moduleChip()` for compact entity/record tiles.
|
|
104
111
|
* @param {string} name Module/entity identifier.
|
|
105
112
|
* @param {ModuleChipOptions} [options={}] AppIcon options.
|
|
106
113
|
* @returns {HTMLElement}
|
|
107
114
|
*/
|
|
108
115
|
export function zeyosAppIcon(name, options = {}) {
|
|
109
|
-
return moduleChip(name, options);
|
|
116
|
+
return moduleChip(name, { shape: 'circle', ...options });
|
|
110
117
|
}
|
package/src/zeyos/index.js
CHANGED
|
@@ -25,3 +25,16 @@ export {
|
|
|
25
25
|
} from './select.js';
|
|
26
26
|
export { zeyosForm } from './form.js';
|
|
27
27
|
export { zeyosTable } from './table.js';
|
|
28
|
+
export {
|
|
29
|
+
buildZeyosCalendarOptions, calendarEventToZeyosPatch, zeyosAppointmentToEvent,
|
|
30
|
+
zeyosAppointmentsToEvents, zeyosCalendar
|
|
31
|
+
} from './calendar.js';
|
|
32
|
+
export { buildZeyosViewConfig, zeyosView } from './view.js';
|
|
33
|
+
export {
|
|
34
|
+
SAVED_VIEWS_VERSION, SavedViewDuplicateNameError, SavedViewRegistry,
|
|
35
|
+
SavedViewScopeMismatchError, createSavedViewRegistry, migrateSavedViewDocument,
|
|
36
|
+
normalizeSavedViewDocument, normalizeSavedViewScope
|
|
37
|
+
} from './saved-views.js';
|
|
38
|
+
export {
|
|
39
|
+
createLegacySavedViewTransport, legacySavedViewRequest
|
|
40
|
+
} from './legacy-saved-views.js';
|
package/src/zeyos/launcher.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Launcher } from '../components/launcher/launcher.js';
|
|
2
|
-
import { moduleChip } from './icons.js';
|
|
2
|
+
import { moduleChip, zeyosAppIcon } from './icons.js';
|
|
3
3
|
import { moduleInfo } from './modules.js';
|
|
4
4
|
|
|
5
5
|
/** Static application identifiers present in the current ZeyOS shell prototype. */
|
|
@@ -139,7 +139,7 @@ export function normalizeZeyosLauncherApplications(state = {}, options = {}) {
|
|
|
139
139
|
? options.renderApplicationIcon(app.raw, app)
|
|
140
140
|
: app.type === 'weblet' && typeof options.renderWebletIcon === 'function'
|
|
141
141
|
? options.renderWebletIcon(app.raw, app) : null;
|
|
142
|
-
return custom ??
|
|
142
|
+
return custom ?? zeyosAppIcon(app.module, {
|
|
143
143
|
size: 36,
|
|
144
144
|
standard: Boolean(options.standardIcons),
|
|
145
145
|
color: app.color
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} SavedViewScope
|
|
3
|
+
* @property {string|number} userId Client identity used by the controller; never sent to the legacy endpoint.
|
|
4
|
+
* @property {string|number|null} workspaceId ZeyOS fork/workspace id.
|
|
5
|
+
* @property {string} resource ZeyOS resource whose record views are being saved.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {Object} LegacySavedViewTransportOptions
|
|
10
|
+
* @property {string} [recordName='__zx_record_views_v1__'] Reserved `userfields.name` value.
|
|
11
|
+
* @property {string} [viewPrefix='zx.record-views:'] Namespace prepended to `userfields.view`.
|
|
12
|
+
* @property {string} [module=''] Legacy UMI/module value.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Converts the callback form of `PG.load` into the promise request consumed by
|
|
17
|
+
* `createLegacySavedViewTransport`. Pass a bound method (`PG.load.bind(PG)`) so legacy receivers
|
|
18
|
+
* that depend on `this` remain intact.
|
|
19
|
+
* @param {(params:Record<string,unknown>,onLoad:(data:unknown)=>void,onCatch:(message:unknown,type?:unknown,info?:unknown)=>boolean)=>unknown} load Legacy loader.
|
|
20
|
+
* @returns {(params:Record<string,unknown>)=>Promise<unknown>}
|
|
21
|
+
*/
|
|
22
|
+
export function legacySavedViewRequest(load) {
|
|
23
|
+
if (typeof load !== 'function') throw new TypeError('Legacy saved-view loader must be a function');
|
|
24
|
+
return (params) => new Promise((resolve, reject) => {
|
|
25
|
+
load(params, resolve, (message, type, info) => {
|
|
26
|
+
const error = new Error(String(message ?? 'ZeyOS saved-view request failed'));
|
|
27
|
+
Object.assign(error, { type, info });
|
|
28
|
+
reject(error);
|
|
29
|
+
return true;
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Adapts ZeyOS's existing `fields`, `fields_save`, and `fields_remove` endpoints to the atomic
|
|
36
|
+
* document transport used by saved record views. The endpoint derives the user from the
|
|
37
|
+
* authenticated session; accepting `userId` in the scope prevents accidental controller reuse but
|
|
38
|
+
* deliberately does not turn a client-supplied identity into an authorization input.
|
|
39
|
+
*
|
|
40
|
+
* `request` is injected because Zx supports both the legacy `PG.load` bridge and modern hosts. It
|
|
41
|
+
* receives the familiar plain endpoint parameters and returns a promise for the decoded response.
|
|
42
|
+
*
|
|
43
|
+
* @param {(params:Record<string,unknown>)=>Promise<unknown>} request Promise-based legacy request bridge.
|
|
44
|
+
* @param {LegacySavedViewTransportOptions} [options={}] Endpoint namespace options.
|
|
45
|
+
* @returns {{load:(scope:SavedViewScope)=>Promise<Record<string,unknown>|null>,save:(scope:SavedViewScope,document:Record<string,unknown>)=>Promise<Record<string,unknown>>,remove:(scope:SavedViewScope)=>Promise<void>}}
|
|
46
|
+
*/
|
|
47
|
+
export function createLegacySavedViewTransport(request, options = {}) {
|
|
48
|
+
if (typeof request !== 'function') throw new TypeError('Saved-view request must be a function');
|
|
49
|
+
const recordName = normalizeNonEmpty(options.recordName, '__zx_record_views_v1__', 'recordName');
|
|
50
|
+
const viewPrefix = normalizeNonEmpty(options.viewPrefix, 'zx.record-views:', 'viewPrefix');
|
|
51
|
+
const module = options.module == null ? '' : String(options.module);
|
|
52
|
+
|
|
53
|
+
const paramsFor = (scope, page) => {
|
|
54
|
+
const normalized = normalizeScope(scope);
|
|
55
|
+
return {
|
|
56
|
+
umi: module,
|
|
57
|
+
page,
|
|
58
|
+
fork: normalized.workspaceId,
|
|
59
|
+
view: `${viewPrefix}${normalized.resource}`
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
async load(scope) {
|
|
65
|
+
const response = await request(paramsFor(scope, 'fields'));
|
|
66
|
+
const value = findReservedDocument(response, recordName);
|
|
67
|
+
if (value == null || value === '') return null;
|
|
68
|
+
if (typeof value === 'string') {
|
|
69
|
+
try {
|
|
70
|
+
const parsed = JSON.parse(value);
|
|
71
|
+
return isRecord(parsed) ? parsed : null;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return isRecord(value) ? cloneJson(value) : null;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
async save(scope, document) {
|
|
80
|
+
if (!isRecord(document)) throw new TypeError('Saved-view document must be an object');
|
|
81
|
+
const copy = cloneJson(document);
|
|
82
|
+
await request({
|
|
83
|
+
...paramsFor(scope, 'fields_save'),
|
|
84
|
+
name: recordName,
|
|
85
|
+
data: JSON.stringify(copy)
|
|
86
|
+
});
|
|
87
|
+
return copy;
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
async remove(scope) {
|
|
91
|
+
await request({
|
|
92
|
+
...paramsFor(scope, 'fields_remove'),
|
|
93
|
+
name: recordName
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** @param {unknown} scope @returns {{userId:string,workspaceId:string|number|null,resource:string}} */
|
|
100
|
+
function normalizeScope(scope) {
|
|
101
|
+
if (!isRecord(scope)) throw new TypeError('Saved-view scope must be an object');
|
|
102
|
+
if (scope.userId == null || String(scope.userId).trim() === '') {
|
|
103
|
+
throw new TypeError('Saved-view scope requires userId');
|
|
104
|
+
}
|
|
105
|
+
const resource = String(scope.resource ?? '').trim();
|
|
106
|
+
if (!resource) throw new TypeError('Saved-view scope requires resource');
|
|
107
|
+
const workspaceId = scope.workspaceId == null || scope.workspaceId === '' ? null : scope.workspaceId;
|
|
108
|
+
return { userId: String(scope.userId), workspaceId, resource };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** @param {unknown} response @param {string} recordName @returns {unknown} */
|
|
112
|
+
function findReservedDocument(response, recordName) {
|
|
113
|
+
if (!Array.isArray(response) || !Array.isArray(response[0]) || !Array.isArray(response[1])) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
const index = response[0].findIndex((name) => String(name) === recordName);
|
|
117
|
+
return index < 0 ? null : response[1][index];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** @param {unknown} value @param {string} fallback @param {string} label @returns {string} */
|
|
121
|
+
function normalizeNonEmpty(value, fallback, label) {
|
|
122
|
+
const normalized = value == null ? fallback : String(value).trim();
|
|
123
|
+
if (!normalized) throw new TypeError(`${label} must not be empty`);
|
|
124
|
+
return normalized;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** @param {unknown} value @returns {value is Record<string,unknown>} */
|
|
128
|
+
function isRecord(value) {
|
|
129
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** @param {Record<string,unknown>} value @returns {Record<string,unknown>} */
|
|
133
|
+
function cloneJson(value) {
|
|
134
|
+
assertJsonValue(value, new Set());
|
|
135
|
+
try {
|
|
136
|
+
return JSON.parse(JSON.stringify(value));
|
|
137
|
+
} catch {
|
|
138
|
+
throw new TypeError('Saved-view document must be JSON-serializable');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** @param {unknown} value @param {Set<object>} ancestors @returns {void} */
|
|
143
|
+
function assertJsonValue(value, ancestors) {
|
|
144
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return;
|
|
145
|
+
if (typeof value === 'number' && Number.isFinite(value)) return;
|
|
146
|
+
if (typeof value !== 'object') throw new TypeError('Saved-view document must be JSON-serializable');
|
|
147
|
+
if (ancestors.has(value)) throw new TypeError('Saved-view document must be JSON-serializable');
|
|
148
|
+
if (!Array.isArray(value) && Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) {
|
|
149
|
+
throw new TypeError('Saved-view document must be JSON-serializable');
|
|
150
|
+
}
|
|
151
|
+
ancestors.add(value);
|
|
152
|
+
const entries = Array.isArray(value) ? value : Object.values(value);
|
|
153
|
+
for (const entry of entries) assertJsonValue(entry, ancestors);
|
|
154
|
+
ancestors.delete(value);
|
|
155
|
+
}
|