ag-grid-url-sync 0.5.1 → 0.5.2
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/README.md +244 -26
- package/dist/core/ag-grid-url-sync.d.ts +6 -0
- package/dist/core/ag-grid-url-sync.js.map +1 -1
- package/dist/core/codecs.d.ts +28 -0
- package/dist/core/codecs.js +39 -0
- package/dist/core/codecs.js.map +1 -0
- package/dist/core/data-issue-codes.d.ts +3 -0
- package/dist/core/data-issue-codes.js +10 -0
- package/dist/core/data-issue-codes.js.map +1 -0
- package/dist/core/grid-integration.d.ts +11 -1
- package/dist/core/grid-integration.js +20 -11
- package/dist/core/grid-integration.js.map +1 -1
- package/dist/core/grid-url-sync.d.ts +62 -0
- package/dist/core/grid-url-sync.js +738 -0
- package/dist/core/grid-url-sync.js.map +1 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/issues.d.ts +2 -2
- package/dist/core/issues.js +2 -9
- package/dist/core/issues.js.map +1 -1
- package/dist/core/own-write.d.ts +138 -0
- package/dist/core/own-write.js +133 -0
- package/dist/core/own-write.js.map +1 -0
- package/dist/core/types.d.ts +151 -4
- package/dist/core/types.js +4 -0
- package/dist/core/types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/sync-holder.d.ts +22 -0
- package/dist/react/sync-holder.js +68 -0
- package/dist/react/sync-holder.js.map +1 -0
- package/dist/react/sync-options.d.ts +17 -0
- package/dist/react/sync-options.js +22 -0
- package/dist/react/sync-options.js.map +1 -0
- package/dist/react/types.d.ts +71 -3
- package/dist/react/use-ag-grid-url-sync.d.ts +3 -0
- package/dist/react/use-ag-grid-url-sync.js +310 -526
- package/dist/react/use-ag-grid-url-sync.js.map +1 -1
- package/dist/react/use-grid-url-sync.d.ts +14 -0
- package/dist/react/use-grid-url-sync.js +206 -0
- package/dist/react/use-grid-url-sync.js.map +1 -0
- package/package.json +16 -3
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import { URLSyncError } from './types.js';
|
|
2
|
+
import { DEFAULT_CONFIG } from './options.js';
|
|
3
|
+
import { codecOptions, individual } from './codecs.js';
|
|
4
|
+
import { reportIssue, rethrowIfThrown, toIssue } from './issues.js';
|
|
5
|
+
import { toError } from './to-error.js';
|
|
6
|
+
import { parseUrlFilters } from './url-parser.js';
|
|
7
|
+
import { generateUrl } from './url-generator.js';
|
|
8
|
+
import { resolveParamPrefix } from './individual-params.js';
|
|
9
|
+
import { fromGridModel, getFilterModel, toGridModel } from './grid-integration.js';
|
|
10
|
+
import { classifyEvent, modelKey, settleWrite, startWrite, takeOver } from './own-write.js';
|
|
11
|
+
const NO_ISSUES = Object.freeze([]);
|
|
12
|
+
const NO_VIEWS = Object.freeze([]);
|
|
13
|
+
export const IDLE_SNAPSHOT = Object.freeze({
|
|
14
|
+
status: 'idle',
|
|
15
|
+
url: '',
|
|
16
|
+
hasFilters: false,
|
|
17
|
+
issues: NO_ISSUES,
|
|
18
|
+
views: NO_VIEWS,
|
|
19
|
+
activeViewId: null
|
|
20
|
+
});
|
|
21
|
+
function hasWindow() {
|
|
22
|
+
return typeof window !== 'undefined';
|
|
23
|
+
}
|
|
24
|
+
function sameIssues(a, b) {
|
|
25
|
+
return (a.length === b.length &&
|
|
26
|
+
a.every((issue, index) => issue.code === b[index]?.code &&
|
|
27
|
+
issue.column === b[index]?.column &&
|
|
28
|
+
issue.message === b[index]?.message));
|
|
29
|
+
}
|
|
30
|
+
export function createGridUrlSync(options) {
|
|
31
|
+
return createSync(options);
|
|
32
|
+
}
|
|
33
|
+
const handovers = new WeakMap();
|
|
34
|
+
export function handOver(sync) {
|
|
35
|
+
return handovers.get(sync)?.() ?? null;
|
|
36
|
+
}
|
|
37
|
+
export function createSync(options, start = {}) {
|
|
38
|
+
const { api, onIssue, views } = options;
|
|
39
|
+
const codec = codecOptions(options.codec ?? individual());
|
|
40
|
+
const maxValueLength = options.maxValueLength ?? DEFAULT_CONFIG.maxValueLength;
|
|
41
|
+
const keys = JSON.stringify({ ...codec, maxValueLength });
|
|
42
|
+
const listeners = new Set();
|
|
43
|
+
let status = 'active';
|
|
44
|
+
let state = {};
|
|
45
|
+
let url = '';
|
|
46
|
+
let applyIssues = NO_ISSUES;
|
|
47
|
+
let readIssues = NO_ISSUES;
|
|
48
|
+
let writeIssues = NO_ISSUES;
|
|
49
|
+
const historyMode = options.history ?? 'none';
|
|
50
|
+
let own = null;
|
|
51
|
+
let ownSource = 'grid';
|
|
52
|
+
const listensToPopstate = options.popstate ?? historyMode === 'push';
|
|
53
|
+
let active = null;
|
|
54
|
+
let viewList = NO_VIEWS;
|
|
55
|
+
let snapshot = IDLE_SNAPSHOT;
|
|
56
|
+
function emit(issue, source) {
|
|
57
|
+
try {
|
|
58
|
+
onIssue?.({ issue, source });
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
reportLater(toError(error));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function startCall(source, mode) {
|
|
65
|
+
const issues = [];
|
|
66
|
+
return {
|
|
67
|
+
source,
|
|
68
|
+
issues,
|
|
69
|
+
options: {
|
|
70
|
+
...codec,
|
|
71
|
+
maxValueLength,
|
|
72
|
+
onParseError: error => {
|
|
73
|
+
if (!(error instanceof URLSyncError))
|
|
74
|
+
return;
|
|
75
|
+
issues.push(error);
|
|
76
|
+
if (mode !== 'collect')
|
|
77
|
+
emit(error, source);
|
|
78
|
+
},
|
|
79
|
+
throwOnError: mode === 'throw' ? (options.throwOnError ?? false) : false
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function fail(call, cause, code) {
|
|
84
|
+
rethrowIfThrown(cause);
|
|
85
|
+
reportIssue(call.options, toIssue(cause, code));
|
|
86
|
+
}
|
|
87
|
+
function refreshViews() {
|
|
88
|
+
if (!views)
|
|
89
|
+
return;
|
|
90
|
+
const next = views.listViews();
|
|
91
|
+
if (!sameViews(viewList, next))
|
|
92
|
+
viewList = Object.freeze(next);
|
|
93
|
+
}
|
|
94
|
+
function notReady() {
|
|
95
|
+
const call = startCall('api', 'throw');
|
|
96
|
+
fail(call, new URLSyncError('The sync is destroyed', undefined, {
|
|
97
|
+
code: 'NOT_READY'
|
|
98
|
+
}), 'NOT_READY');
|
|
99
|
+
}
|
|
100
|
+
function publish() {
|
|
101
|
+
const list = [...applyIssues, ...readIssues, ...writeIssues];
|
|
102
|
+
const hasFilters = Object.keys(state).length > 0;
|
|
103
|
+
const sameList = sameIssues(snapshot.issues, list);
|
|
104
|
+
const activeViewId = active?.id ?? null;
|
|
105
|
+
if (snapshot.status === status &&
|
|
106
|
+
snapshot.url === url &&
|
|
107
|
+
snapshot.hasFilters === hasFilters &&
|
|
108
|
+
snapshot.views === viewList &&
|
|
109
|
+
snapshot.activeViewId === activeViewId &&
|
|
110
|
+
sameList) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const issues = sameList
|
|
114
|
+
? snapshot.issues
|
|
115
|
+
: list.length
|
|
116
|
+
? Object.freeze(list)
|
|
117
|
+
: NO_ISSUES;
|
|
118
|
+
snapshot = Object.freeze({
|
|
119
|
+
status,
|
|
120
|
+
url,
|
|
121
|
+
hasFilters,
|
|
122
|
+
issues,
|
|
123
|
+
views: viewList,
|
|
124
|
+
activeViewId
|
|
125
|
+
});
|
|
126
|
+
for (const listener of Array.from(listeners)) {
|
|
127
|
+
try {
|
|
128
|
+
listener();
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
reportLater(toError(error));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
function readGrid() {
|
|
136
|
+
const call = startCall('grid', 'collect');
|
|
137
|
+
try {
|
|
138
|
+
state = getFilterModel(api, call.options);
|
|
139
|
+
}
|
|
140
|
+
catch (cause) {
|
|
141
|
+
fail(call, cause, 'GRID_ERROR');
|
|
142
|
+
}
|
|
143
|
+
url = hasWindow()
|
|
144
|
+
? generateUrl(window.location.href, state, call.options)
|
|
145
|
+
: '';
|
|
146
|
+
const known = new Set(readIssues.map(issueKey));
|
|
147
|
+
for (const issue of call.issues) {
|
|
148
|
+
if (!known.has(issueKey(issue)))
|
|
149
|
+
emit(issue, 'grid');
|
|
150
|
+
}
|
|
151
|
+
readIssues = call.issues.length ? call.issues : NO_ISSUES;
|
|
152
|
+
}
|
|
153
|
+
function apply(target, source, mode, onCreate) {
|
|
154
|
+
const call = startCall(source, mode);
|
|
155
|
+
let filters;
|
|
156
|
+
try {
|
|
157
|
+
filters = parseUrlFilters(target, call.options);
|
|
158
|
+
}
|
|
159
|
+
catch (cause) {
|
|
160
|
+
fail(call, cause, 'INVALID_URL');
|
|
161
|
+
}
|
|
162
|
+
const applies = filters && !(onCreate && Object.keys(filters).length === 0);
|
|
163
|
+
const before = applies ? liveState() : null;
|
|
164
|
+
let failed = false;
|
|
165
|
+
if (filters && applies) {
|
|
166
|
+
let model;
|
|
167
|
+
try {
|
|
168
|
+
model = toGridModel(filters, api, call.options);
|
|
169
|
+
}
|
|
170
|
+
catch (cause) {
|
|
171
|
+
own = null;
|
|
172
|
+
failed = true;
|
|
173
|
+
fail(call, cause, 'GRID_ERROR');
|
|
174
|
+
}
|
|
175
|
+
if (model)
|
|
176
|
+
failed = !setModel(model, 'url', call);
|
|
177
|
+
}
|
|
178
|
+
applyIssues = call.issues.length ? call.issues : NO_ISSUES;
|
|
179
|
+
readGrid();
|
|
180
|
+
if (filters && applies && settleUrlWrite(filters, before, failed)) {
|
|
181
|
+
followActiveView('url');
|
|
182
|
+
}
|
|
183
|
+
publish();
|
|
184
|
+
return { issues: call.issues };
|
|
185
|
+
}
|
|
186
|
+
function setModel(model, kind, call) {
|
|
187
|
+
own = startWrite(own, kind);
|
|
188
|
+
ownSource = call.source;
|
|
189
|
+
try {
|
|
190
|
+
api.setFilterModel(model);
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch (cause) {
|
|
194
|
+
own = null;
|
|
195
|
+
fail(call, cause, 'GRID_ERROR');
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
function writeModel(model, kind, call) {
|
|
200
|
+
const before = liveModel();
|
|
201
|
+
if (!setModel(model, kind, call))
|
|
202
|
+
return false;
|
|
203
|
+
const accepted = acceptedPart(model);
|
|
204
|
+
const after = liveModel();
|
|
205
|
+
const settled = settleWrite(own, {
|
|
206
|
+
kind,
|
|
207
|
+
failed: false,
|
|
208
|
+
afterKey: after ? modelKey(after) : null,
|
|
209
|
+
before,
|
|
210
|
+
beforeKey: before ? modelKey(before) : null,
|
|
211
|
+
accepted,
|
|
212
|
+
acceptedKey: modelKey(accepted),
|
|
213
|
+
acceptedUrlKey: urlKey(accepted),
|
|
214
|
+
acceptedColumnUrlKeys: columnUrlKeys(accepted)
|
|
215
|
+
});
|
|
216
|
+
own = settled.own;
|
|
217
|
+
readGrid();
|
|
218
|
+
if (settled.landed) {
|
|
219
|
+
followActiveView(kind);
|
|
220
|
+
writeLanding(kind, call.source);
|
|
221
|
+
}
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
function liveState() {
|
|
225
|
+
try {
|
|
226
|
+
return getFilterModel(api, startCall('grid', 'collect').options);
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function liveModel() {
|
|
233
|
+
try {
|
|
234
|
+
return api.getFilterModel() ?? {};
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function liveKey(kind) {
|
|
241
|
+
if (kind === 'url') {
|
|
242
|
+
const live = liveState();
|
|
243
|
+
return live ? filtersKey(live) : null;
|
|
244
|
+
}
|
|
245
|
+
const live = liveModel();
|
|
246
|
+
return live ? modelKey(live) : null;
|
|
247
|
+
}
|
|
248
|
+
function liveKeys(kind) {
|
|
249
|
+
const key = liveKey(kind);
|
|
250
|
+
if (key === null)
|
|
251
|
+
return null;
|
|
252
|
+
if (kind === 'url')
|
|
253
|
+
return { key };
|
|
254
|
+
return { key, urlKey: urlKey(liveModel() ?? {}) };
|
|
255
|
+
}
|
|
256
|
+
function urlKey(model) {
|
|
257
|
+
return filtersKey(urlForm(model));
|
|
258
|
+
}
|
|
259
|
+
function urlForm(model) {
|
|
260
|
+
return fromGridModel(model, startCall('grid', 'collect').options);
|
|
261
|
+
}
|
|
262
|
+
function columnUrlKeys(model) {
|
|
263
|
+
const keys = new Map();
|
|
264
|
+
for (const [colId, column] of Object.entries(model)) {
|
|
265
|
+
const filters = urlForm(Object.fromEntries([[colId, column]]));
|
|
266
|
+
if (Object.keys(filters).length > 0)
|
|
267
|
+
keys.set(colId, filtersKey(filters));
|
|
268
|
+
}
|
|
269
|
+
return keys;
|
|
270
|
+
}
|
|
271
|
+
function pendingView(id, model) {
|
|
272
|
+
const accepted = acceptedPart(model);
|
|
273
|
+
const filters = urlForm(accepted);
|
|
274
|
+
return {
|
|
275
|
+
id,
|
|
276
|
+
model: null,
|
|
277
|
+
key: modelKey(accepted),
|
|
278
|
+
urlKey: Object.keys(filters).length > 0 ? filtersKey(filters) : null
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function landsActive(view, live, api) {
|
|
282
|
+
return (modelKey(live) === view.key ||
|
|
283
|
+
(api && view.urlKey !== null && urlKey(live) === view.urlKey));
|
|
284
|
+
}
|
|
285
|
+
function shows(model) {
|
|
286
|
+
if (model === null)
|
|
287
|
+
return false;
|
|
288
|
+
const live = liveModel();
|
|
289
|
+
return live !== null && modelKey(live) === modelKey(model);
|
|
290
|
+
}
|
|
291
|
+
function followActiveView(landing, seen = 'call', api = true) {
|
|
292
|
+
if (!active)
|
|
293
|
+
return;
|
|
294
|
+
if (landing === 'view' || landing === 'restore') {
|
|
295
|
+
active = { ...active, model: liveModel() };
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (active.model === null) {
|
|
299
|
+
const live = liveModel();
|
|
300
|
+
if (live !== null && landsActive(active, live, api)) {
|
|
301
|
+
active = { ...active, model: live };
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (seen === 'echo')
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (seen === 'queued' || shows(active.model))
|
|
308
|
+
return;
|
|
309
|
+
const { id } = active;
|
|
310
|
+
active = null;
|
|
311
|
+
clearPointer(id, startCall('grid', 'report'));
|
|
312
|
+
}
|
|
313
|
+
function clearPointer(id, call) {
|
|
314
|
+
try {
|
|
315
|
+
if (views?.getActiveViewId() === id)
|
|
316
|
+
views.persistActiveViewId(null);
|
|
317
|
+
}
|
|
318
|
+
catch (cause) {
|
|
319
|
+
fail(call, cause, 'STORAGE_WRITE_FAILED');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
function persistPointer(id, call) {
|
|
323
|
+
try {
|
|
324
|
+
views?.persistActiveViewId(id);
|
|
325
|
+
}
|
|
326
|
+
catch (cause) {
|
|
327
|
+
fail(call, cause, 'STORAGE_WRITE_FAILED');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
function settleUrlWrite(filters, before, failed) {
|
|
331
|
+
const accepted = acceptedPart(filters);
|
|
332
|
+
const settled = settleWrite(own, {
|
|
333
|
+
kind: 'url',
|
|
334
|
+
failed,
|
|
335
|
+
afterKey: filtersKey(state),
|
|
336
|
+
before,
|
|
337
|
+
beforeKey: before ? filtersKey(before) : null,
|
|
338
|
+
accepted,
|
|
339
|
+
acceptedKey: filtersKey(accepted)
|
|
340
|
+
});
|
|
341
|
+
own = settled.own;
|
|
342
|
+
return settled.landed;
|
|
343
|
+
}
|
|
344
|
+
function acceptedPart(filters) {
|
|
345
|
+
if (!('getColumn' in api))
|
|
346
|
+
return filters;
|
|
347
|
+
return Object.fromEntries(Object.entries(filters).filter(([colId]) => {
|
|
348
|
+
try {
|
|
349
|
+
return api.getColumn(colId) !== null;
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
function classify(source) {
|
|
357
|
+
const modelForm = own !== null && own.kind !== 'url';
|
|
358
|
+
const live = modelForm ? (liveModel() ?? {}) : state;
|
|
359
|
+
const pendingModel = modelForm && own?.phase === 'pending';
|
|
360
|
+
const result = classifyEvent(own, {
|
|
361
|
+
key: modelForm ? modelKey(live) : filtersKey(state),
|
|
362
|
+
live,
|
|
363
|
+
source,
|
|
364
|
+
urlKey: pendingModel ? urlKey(live) : undefined,
|
|
365
|
+
columnUrlKeys: pendingModel ? columnUrlKeys(live) : undefined
|
|
366
|
+
});
|
|
367
|
+
own = result.own;
|
|
368
|
+
return result;
|
|
369
|
+
}
|
|
370
|
+
function givesFilters(target) {
|
|
371
|
+
try {
|
|
372
|
+
const filters = parseUrlFilters(target, startCall('url', 'collect').options);
|
|
373
|
+
return Object.keys(filters).length > 0;
|
|
374
|
+
}
|
|
375
|
+
catch {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function hasFilterParams(target) {
|
|
380
|
+
const prefix = resolveParamPrefix(codec.paramPrefix);
|
|
381
|
+
try {
|
|
382
|
+
return Array.from(new URL(target).searchParams.keys()).some(key => key.startsWith(prefix) || key === codec.groupedParam);
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function filtersKey(filters) {
|
|
389
|
+
const sorted = Object.fromEntries(Object.entries(filters).sort(byColumnId));
|
|
390
|
+
return generateUrl('http://key.invalid/', sorted, startCall('grid', 'collect').options);
|
|
391
|
+
}
|
|
392
|
+
function write(source, replace) {
|
|
393
|
+
try {
|
|
394
|
+
if (historyMode instanceof Function) {
|
|
395
|
+
historyMode(url);
|
|
396
|
+
}
|
|
397
|
+
else if (replace || historyMode === 'replace') {
|
|
398
|
+
window.history.replaceState(window.history.state, '', url);
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
window.history.pushState(window.history.state, '', url);
|
|
402
|
+
}
|
|
403
|
+
writeIssues = NO_ISSUES;
|
|
404
|
+
}
|
|
405
|
+
catch (cause) {
|
|
406
|
+
const issue = toIssue(cause, 'HISTORY_WRITE_FAILED');
|
|
407
|
+
writeIssues = [issue];
|
|
408
|
+
emit(issue, source);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function writeChange(source, replace) {
|
|
412
|
+
if (historyMode === 'none' || !hasWindow())
|
|
413
|
+
return;
|
|
414
|
+
const key = filtersKey(state);
|
|
415
|
+
const current = parseUrlFilters(window.location.href, startCall('grid', 'collect').options);
|
|
416
|
+
if (key === filtersKey(current))
|
|
417
|
+
return;
|
|
418
|
+
write(source, replace);
|
|
419
|
+
}
|
|
420
|
+
function writeLanding(kind, source) {
|
|
421
|
+
if (kind !== 'url')
|
|
422
|
+
writeChange(source, kind === 'restore');
|
|
423
|
+
}
|
|
424
|
+
function holdsView(target, view) {
|
|
425
|
+
try {
|
|
426
|
+
const filters = parseUrlFilters(target, startCall('url', 'collect').options);
|
|
427
|
+
return filtersKey(filters) === urlKey(view.filterModel);
|
|
428
|
+
}
|
|
429
|
+
catch {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function restore(view) {
|
|
434
|
+
active = pendingView(view.id, view.filterModel);
|
|
435
|
+
if (!writeModel(view.filterModel, 'restore', startCall('url', 'report'))) {
|
|
436
|
+
active = null;
|
|
437
|
+
}
|
|
438
|
+
publish();
|
|
439
|
+
}
|
|
440
|
+
function onFilterChanged(event) {
|
|
441
|
+
if (status !== 'active')
|
|
442
|
+
return;
|
|
443
|
+
if (own?.phase === 'writing') {
|
|
444
|
+
const key = liveKey(own.kind);
|
|
445
|
+
own = key === null ? null : { phase: 'landed', kind: own.kind, key };
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
readGrid();
|
|
449
|
+
const result = classify(event?.source);
|
|
450
|
+
followActiveView(result.kind === 'landing' ? result.write : null, result.kind !== 'echo' ? 'change' : result.queued ? 'queued' : 'echo', event?.source === 'api');
|
|
451
|
+
if (result.kind === 'change')
|
|
452
|
+
writeChange('grid', false);
|
|
453
|
+
if (result.kind === 'landing' && result.write !== null) {
|
|
454
|
+
writeLanding(result.write, ownSource);
|
|
455
|
+
}
|
|
456
|
+
if (result.queued && shownWhilePending(own)) {
|
|
457
|
+
own = { ...own, before: null, beforeKey: null };
|
|
458
|
+
writeLanding(own.kind, ownSource);
|
|
459
|
+
}
|
|
460
|
+
publish();
|
|
461
|
+
}
|
|
462
|
+
function shownWhilePending(record) {
|
|
463
|
+
return (record?.phase === 'pending' &&
|
|
464
|
+
record.kind !== 'url' &&
|
|
465
|
+
(record.waits?.length ?? 0) === 0 &&
|
|
466
|
+
record.acceptedUrlKey !== undefined &&
|
|
467
|
+
urlKey(liveModel() ?? {}) === record.acceptedUrlKey);
|
|
468
|
+
}
|
|
469
|
+
function onPopstate() {
|
|
470
|
+
if (status !== 'active')
|
|
471
|
+
return;
|
|
472
|
+
apply(window.location.href, 'url', 'report', false);
|
|
473
|
+
}
|
|
474
|
+
function destroy() {
|
|
475
|
+
if (status === 'destroyed')
|
|
476
|
+
return;
|
|
477
|
+
status = 'destroyed';
|
|
478
|
+
if (listensToPopstate && hasWindow()) {
|
|
479
|
+
window.removeEventListener('popstate', onPopstate);
|
|
480
|
+
}
|
|
481
|
+
api.removeEventListener('filterChanged', onFilterChanged);
|
|
482
|
+
api.removeEventListener('gridPreDestroyed', destroy);
|
|
483
|
+
publish();
|
|
484
|
+
}
|
|
485
|
+
const sync = {
|
|
486
|
+
getSnapshot: () => snapshot,
|
|
487
|
+
subscribe(listener) {
|
|
488
|
+
listeners.add(listener);
|
|
489
|
+
return () => {
|
|
490
|
+
listeners.delete(listener);
|
|
491
|
+
};
|
|
492
|
+
},
|
|
493
|
+
applyUrl(target) {
|
|
494
|
+
if (status !== 'active') {
|
|
495
|
+
notReady();
|
|
496
|
+
return { issues: NO_ISSUES };
|
|
497
|
+
}
|
|
498
|
+
const href = target ?? (hasWindow() ? window.location.href : undefined);
|
|
499
|
+
if (href === undefined)
|
|
500
|
+
return { issues: NO_ISSUES };
|
|
501
|
+
return apply(href, 'api', 'throw', false);
|
|
502
|
+
},
|
|
503
|
+
applyFilters(filters) {
|
|
504
|
+
if (status !== 'active') {
|
|
505
|
+
notReady();
|
|
506
|
+
return { issues: NO_ISSUES };
|
|
507
|
+
}
|
|
508
|
+
const call = startCall('api', 'throw');
|
|
509
|
+
let model;
|
|
510
|
+
try {
|
|
511
|
+
model = toGridModel(filters, api, call.options);
|
|
512
|
+
}
|
|
513
|
+
catch (cause) {
|
|
514
|
+
fail(call, cause, 'GRID_ERROR');
|
|
515
|
+
}
|
|
516
|
+
if (model)
|
|
517
|
+
writeModel(model, 'app', call);
|
|
518
|
+
applyIssues = call.issues.length ? call.issues : NO_ISSUES;
|
|
519
|
+
publish();
|
|
520
|
+
return { issues: call.issues };
|
|
521
|
+
},
|
|
522
|
+
getUrl(baseUrl) {
|
|
523
|
+
const base = baseUrl ?? (hasWindow() ? window.location.href : undefined);
|
|
524
|
+
if (base === undefined)
|
|
525
|
+
return '';
|
|
526
|
+
const call = startCall('api', 'throw');
|
|
527
|
+
let filters = state;
|
|
528
|
+
if (status === 'active') {
|
|
529
|
+
try {
|
|
530
|
+
filters = getFilterModel(api, call.options);
|
|
531
|
+
}
|
|
532
|
+
catch (cause) {
|
|
533
|
+
fail(call, cause, 'GRID_ERROR');
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
return generateUrl(base, filters, call.options);
|
|
538
|
+
}
|
|
539
|
+
catch (cause) {
|
|
540
|
+
fail(call, cause, 'INVALID_URL');
|
|
541
|
+
return '';
|
|
542
|
+
}
|
|
543
|
+
},
|
|
544
|
+
saveView(name) {
|
|
545
|
+
if (!views)
|
|
546
|
+
return null;
|
|
547
|
+
if (status !== 'active') {
|
|
548
|
+
notReady();
|
|
549
|
+
return null;
|
|
550
|
+
}
|
|
551
|
+
const call = startCall('api', 'throw');
|
|
552
|
+
let model;
|
|
553
|
+
try {
|
|
554
|
+
model = api.getFilterModel() ?? {};
|
|
555
|
+
}
|
|
556
|
+
catch (cause) {
|
|
557
|
+
fail(call, cause, 'GRID_ERROR');
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
560
|
+
let view;
|
|
561
|
+
try {
|
|
562
|
+
view = views.saveView(name, model);
|
|
563
|
+
}
|
|
564
|
+
catch (cause) {
|
|
565
|
+
fail(call, cause, 'STORAGE_WRITE_FAILED');
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
if (own?.phase === 'pending' &&
|
|
569
|
+
(own.kind === 'view' || own.kind === 'restore')) {
|
|
570
|
+
own = null;
|
|
571
|
+
}
|
|
572
|
+
active = { ...pendingView(view.id, model), model };
|
|
573
|
+
refreshViews();
|
|
574
|
+
publish();
|
|
575
|
+
return view;
|
|
576
|
+
},
|
|
577
|
+
loadView(id) {
|
|
578
|
+
if (!views)
|
|
579
|
+
return;
|
|
580
|
+
if (status !== 'active') {
|
|
581
|
+
notReady();
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
const call = startCall('api', 'throw');
|
|
585
|
+
const wanted = id ?? null;
|
|
586
|
+
const view = wanted === null
|
|
587
|
+
? null
|
|
588
|
+
: views.listViews().find(candidate => candidate.id === wanted);
|
|
589
|
+
if (view === undefined) {
|
|
590
|
+
refreshViews();
|
|
591
|
+
publish();
|
|
592
|
+
fail(call, new URLSyncError(`No saved view with id "${wanted}"`, undefined, {
|
|
593
|
+
code: 'VIEW_NOT_FOUND'
|
|
594
|
+
}), 'VIEW_NOT_FOUND');
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
const previous = active;
|
|
598
|
+
active = view ? pendingView(view.id, view.filterModel) : null;
|
|
599
|
+
let wrote = false;
|
|
600
|
+
try {
|
|
601
|
+
wrote = writeModel(view?.filterModel ?? {}, 'view', call);
|
|
602
|
+
}
|
|
603
|
+
finally {
|
|
604
|
+
if (!wrote)
|
|
605
|
+
active = previous;
|
|
606
|
+
}
|
|
607
|
+
if (!wrote)
|
|
608
|
+
return;
|
|
609
|
+
applyIssues = NO_ISSUES;
|
|
610
|
+
publish();
|
|
611
|
+
if ((active?.id ?? null) === wanted)
|
|
612
|
+
persistPointer(wanted, call);
|
|
613
|
+
},
|
|
614
|
+
deleteView(id) {
|
|
615
|
+
if (!views)
|
|
616
|
+
return;
|
|
617
|
+
const call = startCall('api', 'throw');
|
|
618
|
+
try {
|
|
619
|
+
views.deleteView(id);
|
|
620
|
+
}
|
|
621
|
+
catch (cause) {
|
|
622
|
+
fail(call, cause, 'STORAGE_WRITE_FAILED');
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const clears = status === 'active' && active?.id === id && shows(active.model);
|
|
626
|
+
if (active?.id === id)
|
|
627
|
+
active = null;
|
|
628
|
+
refreshViews();
|
|
629
|
+
try {
|
|
630
|
+
if (clears)
|
|
631
|
+
writeModel({}, 'view', call);
|
|
632
|
+
}
|
|
633
|
+
finally {
|
|
634
|
+
publish();
|
|
635
|
+
}
|
|
636
|
+
},
|
|
637
|
+
destroy
|
|
638
|
+
};
|
|
639
|
+
if (isDestroyed(api)) {
|
|
640
|
+
status = 'destroyed';
|
|
641
|
+
emit(new URLSyncError('The grid is already destroyed', undefined, {
|
|
642
|
+
code: 'NOT_READY'
|
|
643
|
+
}), 'api');
|
|
644
|
+
publish();
|
|
645
|
+
return sync;
|
|
646
|
+
}
|
|
647
|
+
refreshViews();
|
|
648
|
+
const handed = start.handover;
|
|
649
|
+
if (handed?.own && handed.keys === keys) {
|
|
650
|
+
own = takeOver(handed.own, liveKeys(handed.own.kind));
|
|
651
|
+
ownSource = handed.source;
|
|
652
|
+
}
|
|
653
|
+
if (handed && start.keepApplyIssues)
|
|
654
|
+
applyIssues = handed.applyIssues;
|
|
655
|
+
handovers.set(sync, () => ({
|
|
656
|
+
own: own?.phase === 'writing' ? null : own,
|
|
657
|
+
source: ownSource,
|
|
658
|
+
keys,
|
|
659
|
+
applyIssues
|
|
660
|
+
}));
|
|
661
|
+
const pointer = views?.getActiveViewId();
|
|
662
|
+
const stored = pointer
|
|
663
|
+
? viewList.find(view => view.id === pointer)
|
|
664
|
+
: undefined;
|
|
665
|
+
api.addEventListener('filterChanged', onFilterChanged);
|
|
666
|
+
api.addEventListener('gridPreDestroyed', destroy);
|
|
667
|
+
if (listensToPopstate && hasWindow()) {
|
|
668
|
+
window.addEventListener('popstate', onPopstate);
|
|
669
|
+
}
|
|
670
|
+
if (options.applyUrlOnCreate !== false && hasWindow()) {
|
|
671
|
+
const href = window.location.href;
|
|
672
|
+
const hasParams = hasFilterParams(href);
|
|
673
|
+
const claims = hasParams && givesFilters(href);
|
|
674
|
+
if (stored && (!claims || holdsView(href, stored))) {
|
|
675
|
+
restore(stored);
|
|
676
|
+
}
|
|
677
|
+
else if (views && !claims) {
|
|
678
|
+
readGrid();
|
|
679
|
+
publish();
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
apply(href, 'url', 'report', true);
|
|
683
|
+
if (claims && pointer)
|
|
684
|
+
persistPointer(null, startCall('url', 'report'));
|
|
685
|
+
}
|
|
686
|
+
if (!stored &&
|
|
687
|
+
!hasParams &&
|
|
688
|
+
historyMode !== 'none' &&
|
|
689
|
+
Object.keys(state).length > 0) {
|
|
690
|
+
write('url', true);
|
|
691
|
+
publish();
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
const keptViewId = start.keptViewId ?? null;
|
|
696
|
+
const kept = keptViewId === null
|
|
697
|
+
? undefined
|
|
698
|
+
: viewList.find(view => view.id === keptViewId);
|
|
699
|
+
if (kept) {
|
|
700
|
+
restore(kept);
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
readGrid();
|
|
704
|
+
publish();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return sync;
|
|
708
|
+
}
|
|
709
|
+
function sameViews(a, b) {
|
|
710
|
+
return (a.length === b.length &&
|
|
711
|
+
a.every((view, index) => {
|
|
712
|
+
const other = b[index];
|
|
713
|
+
return (other !== undefined &&
|
|
714
|
+
view.id === other.id &&
|
|
715
|
+
view.name === other.name &&
|
|
716
|
+
view.updatedAt === other.updatedAt &&
|
|
717
|
+
JSON.stringify(view.filterModel) === JSON.stringify(other.filterModel));
|
|
718
|
+
}));
|
|
719
|
+
}
|
|
720
|
+
function byColumnId([a], [b]) {
|
|
721
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
722
|
+
}
|
|
723
|
+
function issueKey(issue) {
|
|
724
|
+
return `${issue.code}\u0000${issue.column ?? ''}\u0000${issue.message}`;
|
|
725
|
+
}
|
|
726
|
+
function reportLater(error) {
|
|
727
|
+
if ('reportError' in globalThis) {
|
|
728
|
+
globalThis.reportError(error);
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
setTimeout(() => {
|
|
732
|
+
throw error;
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
function isDestroyed(api) {
|
|
736
|
+
return 'isDestroyed' in api && api.isDestroyed();
|
|
737
|
+
}
|
|
738
|
+
//# sourceMappingURL=grid-url-sync.js.map
|