@xnetjs/react 0.12.0 → 2.0.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/{chunk-SEYIZDHG.js → chunk-ANCTAEML.js} +8 -4
- package/dist/{chunk-RIN2MXZK.js → chunk-H44ZSIH4.js} +43 -7
- package/dist/{chunk-WL7OCTSM.js → chunk-HTFAHJQK.js} +1 -1
- package/dist/{chunk-BDBDZXYS.js → chunk-SZQNGRTO.js} +1 -1
- package/dist/{chunk-Q3NDLI6Z.js → chunk-VZMZQTR7.js} +2 -2
- package/dist/{chunk-EJEGIEIC.js → chunk-ZGP56IIJ.js} +1 -1
- package/dist/core.js +3 -3
- package/dist/database.js +2 -2
- package/dist/{experimental-B2FrBnkV.d.ts → experimental-DyPoZcJT.d.ts} +39 -1
- package/dist/experimental.d.ts +1 -1
- package/dist/experimental.js +7 -3
- package/dist/index.d.ts +230 -5
- package/dist/index.js +727 -59
- package/dist/internal.js +2 -2
- package/package.json +10 -10
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
useInfiniteQuery,
|
|
6
6
|
useIsOffline,
|
|
7
7
|
useNode
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-VZMZQTR7.js";
|
|
9
9
|
import {
|
|
10
10
|
useCell,
|
|
11
11
|
useDatabase,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
useDatabaseSchema,
|
|
15
15
|
useRelatedRows,
|
|
16
16
|
useReverseRelations
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-HTFAHJQK.js";
|
|
18
18
|
import {
|
|
19
19
|
AuthErrorScreen,
|
|
20
20
|
AuthenticatingScreen,
|
|
@@ -94,7 +94,7 @@ import {
|
|
|
94
94
|
useUndo,
|
|
95
95
|
useVerification,
|
|
96
96
|
useVisibleComments
|
|
97
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-ANCTAEML.js";
|
|
98
98
|
import {
|
|
99
99
|
EMPTY_PAGE_INFO,
|
|
100
100
|
computeFallbackPageInfo,
|
|
@@ -106,10 +106,10 @@ import {
|
|
|
106
106
|
useMutate,
|
|
107
107
|
useQuery,
|
|
108
108
|
useSyncManager
|
|
109
|
-
} from "./chunk-
|
|
109
|
+
} from "./chunk-ZGP56IIJ.js";
|
|
110
110
|
import {
|
|
111
111
|
useUndoScope
|
|
112
|
-
} from "./chunk-
|
|
112
|
+
} from "./chunk-SZQNGRTO.js";
|
|
113
113
|
import {
|
|
114
114
|
InstrumentationContext,
|
|
115
115
|
useInstrumentation
|
|
@@ -122,6 +122,7 @@ import {
|
|
|
122
122
|
TracingContext,
|
|
123
123
|
XNetContext,
|
|
124
124
|
XNetProvider,
|
|
125
|
+
mergeEditorContributions,
|
|
125
126
|
useCommand,
|
|
126
127
|
useCommands,
|
|
127
128
|
useContributions,
|
|
@@ -129,6 +130,7 @@ import {
|
|
|
129
130
|
useEditorExtensions,
|
|
130
131
|
useEditorExtensionsSafe,
|
|
131
132
|
useImporters,
|
|
133
|
+
useMergedEditorContributions,
|
|
132
134
|
useNodeStore,
|
|
133
135
|
usePluginRegistry,
|
|
134
136
|
usePluginRegistryOptional,
|
|
@@ -142,7 +144,7 @@ import {
|
|
|
142
144
|
useView,
|
|
143
145
|
useViews,
|
|
144
146
|
useXNet
|
|
145
|
-
} from "./chunk-
|
|
147
|
+
} from "./chunk-H44ZSIH4.js";
|
|
146
148
|
|
|
147
149
|
// src/hooks/useEffectiveSchema.ts
|
|
148
150
|
import {
|
|
@@ -4205,6 +4207,92 @@ function formatSavedViewInspectorValue(key, value) {
|
|
|
4205
4207
|
return formatSavedViewCellValue(key, value);
|
|
4206
4208
|
}
|
|
4207
4209
|
|
|
4210
|
+
// src/hooks/usePresence.ts
|
|
4211
|
+
import { useCallback as useCallback2, useEffect as useEffect5, useRef as useRef3, useState as useState5 } from "react";
|
|
4212
|
+
function readPeers(awareness, ownKeys) {
|
|
4213
|
+
const peers = [];
|
|
4214
|
+
awareness.getStates().forEach((state, clientId) => {
|
|
4215
|
+
if (clientId === awareness.clientID || !state) return;
|
|
4216
|
+
for (const key of ownKeys) {
|
|
4217
|
+
if (key in state) {
|
|
4218
|
+
peers.push({ clientId, state });
|
|
4219
|
+
return;
|
|
4220
|
+
}
|
|
4221
|
+
}
|
|
4222
|
+
});
|
|
4223
|
+
return peers;
|
|
4224
|
+
}
|
|
4225
|
+
function usePresence(awareness, initialState, options = {}) {
|
|
4226
|
+
const { throttleMs = 33 } = options;
|
|
4227
|
+
const [peers, setPeers] = useState5([]);
|
|
4228
|
+
const [clientId, setClientId] = useState5(null);
|
|
4229
|
+
const ownKeysRef = useRef3(new Set(Object.keys(initialState)));
|
|
4230
|
+
const initialStateRef = useRef3(initialState);
|
|
4231
|
+
initialStateRef.current = initialState;
|
|
4232
|
+
const awarenessRef = useRef3(null);
|
|
4233
|
+
const pendingRef = useRef3({});
|
|
4234
|
+
const lastBroadcastRef = useRef3(0);
|
|
4235
|
+
const timerRef = useRef3(null);
|
|
4236
|
+
const flush = useCallback2(() => {
|
|
4237
|
+
const aw = awarenessRef.current;
|
|
4238
|
+
if (!aw) return;
|
|
4239
|
+
lastBroadcastRef.current = Date.now();
|
|
4240
|
+
const patch = pendingRef.current;
|
|
4241
|
+
pendingRef.current = {};
|
|
4242
|
+
aw.setLocalState({ ...aw.getLocalState() ?? {}, ...patch });
|
|
4243
|
+
}, []);
|
|
4244
|
+
const setState = useCallback2(
|
|
4245
|
+
(patch) => {
|
|
4246
|
+
for (const key of Object.keys(patch)) ownKeysRef.current.add(key);
|
|
4247
|
+
pendingRef.current = { ...pendingRef.current, ...patch };
|
|
4248
|
+
const elapsed = Date.now() - lastBroadcastRef.current;
|
|
4249
|
+
if (elapsed >= throttleMs) {
|
|
4250
|
+
flush();
|
|
4251
|
+
} else if (timerRef.current === null) {
|
|
4252
|
+
timerRef.current = setTimeout(() => {
|
|
4253
|
+
timerRef.current = null;
|
|
4254
|
+
flush();
|
|
4255
|
+
}, throttleMs - elapsed);
|
|
4256
|
+
}
|
|
4257
|
+
},
|
|
4258
|
+
[flush, throttleMs]
|
|
4259
|
+
);
|
|
4260
|
+
useEffect5(() => {
|
|
4261
|
+
if (!awareness) {
|
|
4262
|
+
awarenessRef.current = null;
|
|
4263
|
+
setClientId(null);
|
|
4264
|
+
setPeers([]);
|
|
4265
|
+
return;
|
|
4266
|
+
}
|
|
4267
|
+
awarenessRef.current = awareness;
|
|
4268
|
+
setClientId(awareness.clientID);
|
|
4269
|
+
const ownKeys = ownKeysRef.current;
|
|
4270
|
+
awareness.setLocalState({
|
|
4271
|
+
...awareness.getLocalState() ?? {},
|
|
4272
|
+
...initialStateRef.current
|
|
4273
|
+
});
|
|
4274
|
+
lastBroadcastRef.current = Date.now();
|
|
4275
|
+
const handleChange = () => {
|
|
4276
|
+
setPeers(readPeers(awareness, ownKeysRef.current));
|
|
4277
|
+
};
|
|
4278
|
+
handleChange();
|
|
4279
|
+
awareness.on("change", handleChange);
|
|
4280
|
+
return () => {
|
|
4281
|
+
awareness.off("change", handleChange);
|
|
4282
|
+
if (timerRef.current !== null) {
|
|
4283
|
+
clearTimeout(timerRef.current);
|
|
4284
|
+
timerRef.current = null;
|
|
4285
|
+
}
|
|
4286
|
+
pendingRef.current = {};
|
|
4287
|
+
const remaining = { ...awareness.getLocalState() ?? {} };
|
|
4288
|
+
for (const key of ownKeys) delete remaining[key];
|
|
4289
|
+
awareness.setLocalState(remaining);
|
|
4290
|
+
if (awarenessRef.current === awareness) awarenessRef.current = null;
|
|
4291
|
+
};
|
|
4292
|
+
}, [awareness]);
|
|
4293
|
+
return { peers, setState, clientId };
|
|
4294
|
+
}
|
|
4295
|
+
|
|
4208
4296
|
// src/hooks/useCanvasTaskSync.ts
|
|
4209
4297
|
function useCanvasTaskSync({
|
|
4210
4298
|
canvasId,
|
|
@@ -4237,7 +4325,7 @@ import {
|
|
|
4237
4325
|
convertCellValue,
|
|
4238
4326
|
FormulaService
|
|
4239
4327
|
} from "@xnetjs/data";
|
|
4240
|
-
import { useCallback as
|
|
4328
|
+
import { useCallback as useCallback3, useEffect as useEffect6, useMemo as useMemo5, useRef as useRef4, useState as useState6 } from "react";
|
|
4241
4329
|
function toFieldModel(node) {
|
|
4242
4330
|
return {
|
|
4243
4331
|
id: node.id,
|
|
@@ -4358,8 +4446,8 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4358
4446
|
return effective.map((e) => e.field);
|
|
4359
4447
|
}, [fields, activeView]);
|
|
4360
4448
|
const { store } = useNodeStore();
|
|
4361
|
-
const [rollupValues, setRollupValues] =
|
|
4362
|
-
|
|
4449
|
+
const [rollupValues, setRollupValues] = useState6(/* @__PURE__ */ new Map());
|
|
4450
|
+
useEffect6(() => {
|
|
4363
4451
|
const rollupFields = fields.filter((f) => f.type === "rollup");
|
|
4364
4452
|
if (rollupFields.length === 0 || !store) {
|
|
4365
4453
|
setRollupValues((prev) => prev.size > 0 ? /* @__PURE__ */ new Map() : prev);
|
|
@@ -4459,11 +4547,11 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4459
4547
|
return sortRows(filtered, columns, activeView.sorts);
|
|
4460
4548
|
}, [rowNodes, activeView, fields, databaseId, rollupValues]);
|
|
4461
4549
|
const loading = dbStatus === "loading" || fieldStatus === "loading" || viewStatus === "loading" || rowStatus === "loading";
|
|
4462
|
-
const rowTailRef =
|
|
4463
|
-
const fieldTailRef =
|
|
4464
|
-
const viewTailRef =
|
|
4465
|
-
const optionTailRef =
|
|
4466
|
-
|
|
4550
|
+
const rowTailRef = useRef4(void 0);
|
|
4551
|
+
const fieldTailRef = useRef4(void 0);
|
|
4552
|
+
const viewTailRef = useRef4(void 0);
|
|
4553
|
+
const optionTailRef = useRef4(/* @__PURE__ */ new Map());
|
|
4554
|
+
useEffect6(() => {
|
|
4467
4555
|
const dataTail = rows.reduce(
|
|
4468
4556
|
(max, r) => !max || compareSortKeys(r.sortKey, max) > 0 ? r.sortKey : max,
|
|
4469
4557
|
void 0
|
|
@@ -4472,19 +4560,19 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4472
4560
|
rowTailRef.current = dataTail;
|
|
4473
4561
|
}
|
|
4474
4562
|
}, [rows]);
|
|
4475
|
-
|
|
4563
|
+
useEffect6(() => {
|
|
4476
4564
|
const dataTail = fields[fields.length - 1]?.sortKey;
|
|
4477
4565
|
if (dataTail && (!fieldTailRef.current || compareSortKeys(dataTail, fieldTailRef.current) > 0)) {
|
|
4478
4566
|
fieldTailRef.current = dataTail;
|
|
4479
4567
|
}
|
|
4480
4568
|
}, [fields]);
|
|
4481
|
-
|
|
4569
|
+
useEffect6(() => {
|
|
4482
4570
|
const dataTail = views[views.length - 1]?.sortKey;
|
|
4483
4571
|
if (dataTail && (!viewTailRef.current || compareSortKeys(dataTail, viewTailRef.current) > 0)) {
|
|
4484
4572
|
viewTailRef.current = dataTail;
|
|
4485
4573
|
}
|
|
4486
4574
|
}, [views]);
|
|
4487
|
-
|
|
4575
|
+
useEffect6(() => {
|
|
4488
4576
|
for (const [fieldId, list] of optionsByField) {
|
|
4489
4577
|
const dataTail = list[list.length - 1]?.sortKey;
|
|
4490
4578
|
const current = optionTailRef.current.get(fieldId);
|
|
@@ -4493,7 +4581,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4493
4581
|
}
|
|
4494
4582
|
}
|
|
4495
4583
|
}, [optionsByField]);
|
|
4496
|
-
const nextAppendKey =
|
|
4584
|
+
const nextAppendKey = useCallback3((tailRef) => {
|
|
4497
4585
|
const key = generateSortKey(tailRef.current, void 0);
|
|
4498
4586
|
tailRef.current = key;
|
|
4499
4587
|
return key;
|
|
@@ -4511,19 +4599,19 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4511
4599
|
localDID: did ?? null,
|
|
4512
4600
|
options: { localOnly: true }
|
|
4513
4601
|
});
|
|
4514
|
-
const updateRowProps =
|
|
4602
|
+
const updateRowProps = useCallback3(
|
|
4515
4603
|
async (rowId, properties) => {
|
|
4516
4604
|
await mutate.update(DatabaseRowSchema, rowId, properties);
|
|
4517
4605
|
},
|
|
4518
4606
|
[mutate]
|
|
4519
4607
|
);
|
|
4520
|
-
const updateCell =
|
|
4608
|
+
const updateCell = useCallback3(
|
|
4521
4609
|
async (rowId, fieldId, value) => {
|
|
4522
4610
|
await updateRowProps(rowId, { [cellKey(fieldId)]: value });
|
|
4523
4611
|
},
|
|
4524
4612
|
[updateRowProps]
|
|
4525
4613
|
);
|
|
4526
|
-
const clearCells =
|
|
4614
|
+
const clearCells = useCallback3(
|
|
4527
4615
|
async (cells) => {
|
|
4528
4616
|
const byRow = /* @__PURE__ */ new Map();
|
|
4529
4617
|
for (const { rowId, fieldId } of cells) {
|
|
@@ -4535,7 +4623,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4535
4623
|
},
|
|
4536
4624
|
[updateRowProps]
|
|
4537
4625
|
);
|
|
4538
|
-
const addRow =
|
|
4626
|
+
const addRow = useCallback3(
|
|
4539
4627
|
async (afterRowId, cells, opts) => {
|
|
4540
4628
|
let sortKey;
|
|
4541
4629
|
if (afterRowId) {
|
|
@@ -4570,13 +4658,13 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4570
4658
|
},
|
|
4571
4659
|
[mutate, databaseId, rows, nextAppendKey]
|
|
4572
4660
|
);
|
|
4573
|
-
const deleteRows =
|
|
4661
|
+
const deleteRows = useCallback3(
|
|
4574
4662
|
async (rowIds) => {
|
|
4575
4663
|
await Promise.all(rowIds.map((id) => mutate.remove(id)));
|
|
4576
4664
|
},
|
|
4577
4665
|
[mutate]
|
|
4578
4666
|
);
|
|
4579
|
-
const moveRowToIndex =
|
|
4667
|
+
const moveRowToIndex = useCallback3(
|
|
4580
4668
|
async (rowId, targetIndex) => {
|
|
4581
4669
|
if (activeView && activeView.sorts.length > 0) return;
|
|
4582
4670
|
const ordered = rows.map((r) => ({ id: r.id, key: r.sortKey }));
|
|
@@ -4585,7 +4673,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4585
4673
|
},
|
|
4586
4674
|
[rows, activeView, updateRowProps]
|
|
4587
4675
|
);
|
|
4588
|
-
const createOption =
|
|
4676
|
+
const createOption = useCallback3(
|
|
4589
4677
|
async (fieldId, name) => {
|
|
4590
4678
|
const existing = optionsByField.get(fieldId) ?? [];
|
|
4591
4679
|
const match = existing.find((o) => o.name.toLowerCase() === name.toLowerCase());
|
|
@@ -4604,7 +4692,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4604
4692
|
},
|
|
4605
4693
|
[mutate, databaseId, optionsByField]
|
|
4606
4694
|
);
|
|
4607
|
-
const addField =
|
|
4695
|
+
const addField = useCallback3(
|
|
4608
4696
|
async (name, type, config, opts) => {
|
|
4609
4697
|
const node = await mutate.create(DatabaseFieldSchema, {
|
|
4610
4698
|
database: databaseId,
|
|
@@ -4619,13 +4707,13 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4619
4707
|
},
|
|
4620
4708
|
[mutate, databaseId, nextAppendKey]
|
|
4621
4709
|
);
|
|
4622
|
-
const renameField =
|
|
4710
|
+
const renameField = useCallback3(
|
|
4623
4711
|
async (fieldId, name) => {
|
|
4624
4712
|
await mutate.update(DatabaseFieldSchema, fieldId, { name });
|
|
4625
4713
|
},
|
|
4626
4714
|
[mutate]
|
|
4627
4715
|
);
|
|
4628
|
-
const updateFieldConfig =
|
|
4716
|
+
const updateFieldConfig = useCallback3(
|
|
4629
4717
|
async (fieldId, config) => {
|
|
4630
4718
|
await mutate.update(DatabaseFieldSchema, fieldId, {
|
|
4631
4719
|
config
|
|
@@ -4633,7 +4721,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4633
4721
|
},
|
|
4634
4722
|
[mutate]
|
|
4635
4723
|
);
|
|
4636
|
-
const changeFieldType =
|
|
4724
|
+
const changeFieldType = useCallback3(
|
|
4637
4725
|
async (fieldId, type) => {
|
|
4638
4726
|
const field = fields.find((f) => f.id === fieldId);
|
|
4639
4727
|
const sourceType = field?.type ?? "text";
|
|
@@ -4670,7 +4758,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4670
4758
|
},
|
|
4671
4759
|
[mutate, fields, rows, createOption, updateRowProps]
|
|
4672
4760
|
);
|
|
4673
|
-
const removeField =
|
|
4761
|
+
const removeField = useCallback3(
|
|
4674
4762
|
async (fieldId) => {
|
|
4675
4763
|
const orphanedOptions = optionsByField.get(fieldId) ?? [];
|
|
4676
4764
|
await Promise.all(orphanedOptions.map((o) => mutate.remove(o.id)));
|
|
@@ -4678,7 +4766,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4678
4766
|
},
|
|
4679
4767
|
[mutate, optionsByField]
|
|
4680
4768
|
);
|
|
4681
|
-
const moveFieldToIndex =
|
|
4769
|
+
const moveFieldToIndex = useCallback3(
|
|
4682
4770
|
async (fieldId, targetIndex) => {
|
|
4683
4771
|
if (!activeView) return;
|
|
4684
4772
|
const ordered = visibleFields.map((f) => ({
|
|
@@ -4692,7 +4780,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4692
4780
|
},
|
|
4693
4781
|
[mutate, activeView, visibleFields, fields]
|
|
4694
4782
|
);
|
|
4695
|
-
const resizeField =
|
|
4783
|
+
const resizeField = useCallback3(
|
|
4696
4784
|
async (fieldId, width) => {
|
|
4697
4785
|
if (!activeView) return;
|
|
4698
4786
|
await mutate.update(DatabaseViewSchema, activeView.id, {
|
|
@@ -4701,7 +4789,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4701
4789
|
},
|
|
4702
4790
|
[mutate, activeView]
|
|
4703
4791
|
);
|
|
4704
|
-
const setFieldHiddenInView =
|
|
4792
|
+
const setFieldHiddenInView = useCallback3(
|
|
4705
4793
|
async (fieldId, hidden) => {
|
|
4706
4794
|
if (!activeView) return;
|
|
4707
4795
|
const set = new Set(activeView.hiddenFields);
|
|
@@ -4711,7 +4799,7 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4711
4799
|
},
|
|
4712
4800
|
[mutate, activeView]
|
|
4713
4801
|
);
|
|
4714
|
-
const toggleSort =
|
|
4802
|
+
const toggleSort = useCallback3(
|
|
4715
4803
|
async (fieldId) => {
|
|
4716
4804
|
if (!activeView) return;
|
|
4717
4805
|
const current = activeView.sorts.find((s) => s.columnId === fieldId);
|
|
@@ -4727,28 +4815,28 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4727
4815
|
},
|
|
4728
4816
|
[mutate, activeView]
|
|
4729
4817
|
);
|
|
4730
|
-
const setFilters =
|
|
4818
|
+
const setFilters = useCallback3(
|
|
4731
4819
|
async (filters) => {
|
|
4732
4820
|
if (!activeView) return;
|
|
4733
4821
|
await mutate.update(DatabaseViewSchema, activeView.id, { filters });
|
|
4734
4822
|
},
|
|
4735
4823
|
[mutate, activeView]
|
|
4736
4824
|
);
|
|
4737
|
-
const setGroupBy =
|
|
4825
|
+
const setGroupBy = useCallback3(
|
|
4738
4826
|
async (fieldId) => {
|
|
4739
4827
|
if (!activeView) return;
|
|
4740
4828
|
await mutate.update(DatabaseViewSchema, activeView.id, { groupBy: fieldId ?? void 0 });
|
|
4741
4829
|
},
|
|
4742
4830
|
[mutate, activeView]
|
|
4743
4831
|
);
|
|
4744
|
-
const setRowHeight =
|
|
4832
|
+
const setRowHeight = useCallback3(
|
|
4745
4833
|
async (rowHeight) => {
|
|
4746
4834
|
if (!activeView) return;
|
|
4747
4835
|
await mutate.update(DatabaseViewSchema, activeView.id, { rowHeight });
|
|
4748
4836
|
},
|
|
4749
4837
|
[mutate, activeView]
|
|
4750
4838
|
);
|
|
4751
|
-
const setColumnSummary =
|
|
4839
|
+
const setColumnSummary = useCallback3(
|
|
4752
4840
|
async (fieldId, fn) => {
|
|
4753
4841
|
if (!activeView) return;
|
|
4754
4842
|
const next = { ...activeView.columnSummaries };
|
|
@@ -4758,28 +4846,28 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4758
4846
|
},
|
|
4759
4847
|
[mutate, activeView]
|
|
4760
4848
|
);
|
|
4761
|
-
const setFormConfig =
|
|
4849
|
+
const setFormConfig = useCallback3(
|
|
4762
4850
|
async (config) => {
|
|
4763
4851
|
if (!activeView) return;
|
|
4764
4852
|
await mutate.update(DatabaseViewSchema, activeView.id, { formConfig: config });
|
|
4765
4853
|
},
|
|
4766
4854
|
[mutate, activeView]
|
|
4767
4855
|
);
|
|
4768
|
-
const setFormRules =
|
|
4856
|
+
const setFormRules = useCallback3(
|
|
4769
4857
|
async (rules) => {
|
|
4770
4858
|
if (!activeView) return;
|
|
4771
4859
|
await mutate.update(DatabaseViewSchema, activeView.id, { formRules: rules });
|
|
4772
4860
|
},
|
|
4773
4861
|
[mutate, activeView]
|
|
4774
4862
|
);
|
|
4775
|
-
const setFormAccepting =
|
|
4863
|
+
const setFormAccepting = useCallback3(
|
|
4776
4864
|
async (accepting) => {
|
|
4777
4865
|
if (!activeView) return;
|
|
4778
4866
|
await mutate.update(DatabaseViewSchema, activeView.id, { formAccepting: accepting });
|
|
4779
4867
|
},
|
|
4780
4868
|
[mutate, activeView]
|
|
4781
4869
|
);
|
|
4782
|
-
const addView =
|
|
4870
|
+
const addView = useCallback3(
|
|
4783
4871
|
async (name, type) => {
|
|
4784
4872
|
const node = await mutate.create(DatabaseViewSchema, {
|
|
4785
4873
|
database: databaseId,
|
|
@@ -4791,13 +4879,13 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4791
4879
|
},
|
|
4792
4880
|
[mutate, databaseId, nextAppendKey]
|
|
4793
4881
|
);
|
|
4794
|
-
const renameView =
|
|
4882
|
+
const renameView = useCallback3(
|
|
4795
4883
|
async (id, name) => {
|
|
4796
4884
|
await mutate.update(DatabaseViewSchema, id, { name });
|
|
4797
4885
|
},
|
|
4798
4886
|
[mutate]
|
|
4799
4887
|
);
|
|
4800
|
-
const removeView =
|
|
4888
|
+
const removeView = useCallback3(
|
|
4801
4889
|
async (id) => {
|
|
4802
4890
|
await mutate.remove(id);
|
|
4803
4891
|
},
|
|
@@ -4843,8 +4931,531 @@ function useGridDatabase(databaseId, options = {}) {
|
|
|
4843
4931
|
};
|
|
4844
4932
|
}
|
|
4845
4933
|
|
|
4934
|
+
// src/hooks/useTimeMachine.ts
|
|
4935
|
+
import { CHECKPOINT_SCHEMA_IRI } from "@xnetjs/data";
|
|
4936
|
+
import {
|
|
4937
|
+
HistoryEngine,
|
|
4938
|
+
HistoryHorizonError,
|
|
4939
|
+
MemorySnapshotStorage,
|
|
4940
|
+
ScopeTimeline,
|
|
4941
|
+
SnapshotCache,
|
|
4942
|
+
createCheckpoint,
|
|
4943
|
+
listCheckpoints,
|
|
4944
|
+
restoreToFrontier
|
|
4945
|
+
} from "@xnetjs/history";
|
|
4946
|
+
import { useCallback as useCallback4, useEffect as useEffect7, useMemo as useMemo6, useRef as useRef5, useState as useState7 } from "react";
|
|
4947
|
+
function useTimeMachine(nodeId, options) {
|
|
4948
|
+
const { store, isReady } = useNodeStore();
|
|
4949
|
+
const memberKey = options?.memberIds?.join(",") ?? "";
|
|
4950
|
+
const members = useMemo6(() => {
|
|
4951
|
+
if (!nodeId) return [];
|
|
4952
|
+
const extra = options?.memberIds ?? [];
|
|
4953
|
+
return [nodeId, ...extra.filter((id) => id !== nodeId)];
|
|
4954
|
+
}, [nodeId, memberKey]);
|
|
4955
|
+
const [timeline, setTimeline] = useState7([]);
|
|
4956
|
+
const [checkpoints, setCheckpoints] = useState7([]);
|
|
4957
|
+
const [horizon, setHorizon] = useState7(null);
|
|
4958
|
+
const [docSnapshotCount, setDocSnapshotCount] = useState7(null);
|
|
4959
|
+
const [loading, setLoading] = useState7(false);
|
|
4960
|
+
const [error, setError] = useState7(null);
|
|
4961
|
+
const [rawPosition, setRawPosition] = useState7(null);
|
|
4962
|
+
const position = rawPosition === null ? Math.max(0, timeline.length - 1) : Math.max(0, Math.min(rawPosition, timeline.length - 1));
|
|
4963
|
+
const atLatest = timeline.length === 0 || position === timeline.length - 1;
|
|
4964
|
+
const [preview, setPreview] = useState7(null);
|
|
4965
|
+
const [diffs, setDiffs] = useState7([]);
|
|
4966
|
+
const engineRef = useRef5(null);
|
|
4967
|
+
const getEngines = useCallback4(() => {
|
|
4968
|
+
if (!store) return null;
|
|
4969
|
+
const storage = store.getStorageAdapter();
|
|
4970
|
+
if (!storage) return null;
|
|
4971
|
+
if (!engineRef.current || engineRef.current.storage !== storage) {
|
|
4972
|
+
const snapshots = new SnapshotCache(new MemorySnapshotStorage(), { interval: 50 });
|
|
4973
|
+
engineRef.current = {
|
|
4974
|
+
engine: new HistoryEngine(storage, snapshots),
|
|
4975
|
+
scope: new ScopeTimeline(storage),
|
|
4976
|
+
storage
|
|
4977
|
+
};
|
|
4978
|
+
}
|
|
4979
|
+
return engineRef.current;
|
|
4980
|
+
}, [store]);
|
|
4981
|
+
const reload = useCallback4(async () => {
|
|
4982
|
+
if (!nodeId || !isReady || !store) return;
|
|
4983
|
+
const engines = getEngines();
|
|
4984
|
+
if (!engines) return;
|
|
4985
|
+
setLoading(true);
|
|
4986
|
+
setError(null);
|
|
4987
|
+
try {
|
|
4988
|
+
const [line, nodeHorizon, named] = await Promise.all([
|
|
4989
|
+
engines.scope.getMergedTimeline(members),
|
|
4990
|
+
engines.engine.getHorizon(nodeId),
|
|
4991
|
+
listCheckpoints(store, nodeId)
|
|
4992
|
+
]);
|
|
4993
|
+
setTimeline(line);
|
|
4994
|
+
setHorizon(nodeHorizon);
|
|
4995
|
+
setCheckpoints(named);
|
|
4996
|
+
const snapshotStorage = engines.storage;
|
|
4997
|
+
if (typeof snapshotStorage.getYjsSnapshots === "function") {
|
|
4998
|
+
const snapshots = await snapshotStorage.getYjsSnapshots(nodeId);
|
|
4999
|
+
setDocSnapshotCount(snapshots.length);
|
|
5000
|
+
} else {
|
|
5001
|
+
setDocSnapshotCount(null);
|
|
5002
|
+
}
|
|
5003
|
+
} catch (err) {
|
|
5004
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5005
|
+
} finally {
|
|
5006
|
+
setLoading(false);
|
|
5007
|
+
}
|
|
5008
|
+
}, [nodeId, isReady, store, members, getEngines]);
|
|
5009
|
+
useEffect7(() => {
|
|
5010
|
+
setRawPosition(null);
|
|
5011
|
+
void reload();
|
|
5012
|
+
}, [reload]);
|
|
5013
|
+
useEffect7(() => {
|
|
5014
|
+
if (!store || !nodeId) return;
|
|
5015
|
+
const memberSet = new Set(members);
|
|
5016
|
+
const unsub = store.subscribe((event) => {
|
|
5017
|
+
if (memberSet.has(event.change.payload.nodeId) || event.change.payload.schemaId === CHECKPOINT_SCHEMA_IRI) {
|
|
5018
|
+
void reload();
|
|
5019
|
+
}
|
|
5020
|
+
});
|
|
5021
|
+
return unsub;
|
|
5022
|
+
}, [store, nodeId, members, reload]);
|
|
5023
|
+
useEffect7(() => {
|
|
5024
|
+
let cancelled = false;
|
|
5025
|
+
const engines = getEngines();
|
|
5026
|
+
if (!nodeId || !engines || timeline.length === 0) {
|
|
5027
|
+
setPreview(null);
|
|
5028
|
+
setDiffs([]);
|
|
5029
|
+
return;
|
|
5030
|
+
}
|
|
5031
|
+
let hash = null;
|
|
5032
|
+
for (let i = position; i >= 0; i--) {
|
|
5033
|
+
if (timeline[i].nodeId === nodeId) {
|
|
5034
|
+
hash = timeline[i].change.hash;
|
|
5035
|
+
break;
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
if (!hash) {
|
|
5039
|
+
setPreview(null);
|
|
5040
|
+
setDiffs([]);
|
|
5041
|
+
return;
|
|
5042
|
+
}
|
|
5043
|
+
const target = { type: "hash", hash };
|
|
5044
|
+
Promise.all([
|
|
5045
|
+
engines.engine.materializeAt(nodeId, target),
|
|
5046
|
+
engines.engine.diff(nodeId, target, { type: "latest" })
|
|
5047
|
+
]).then(([state, propertyDiffs]) => {
|
|
5048
|
+
if (cancelled) return;
|
|
5049
|
+
setPreview(state.node);
|
|
5050
|
+
setDiffs(propertyDiffs);
|
|
5051
|
+
}).catch((err) => {
|
|
5052
|
+
if (cancelled) return;
|
|
5053
|
+
setPreview(null);
|
|
5054
|
+
setDiffs([]);
|
|
5055
|
+
if (err instanceof HistoryHorizonError) {
|
|
5056
|
+
setHorizon(err.horizon);
|
|
5057
|
+
} else {
|
|
5058
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5059
|
+
}
|
|
5060
|
+
});
|
|
5061
|
+
return () => {
|
|
5062
|
+
cancelled = true;
|
|
5063
|
+
};
|
|
5064
|
+
}, [nodeId, timeline, position, getEngines]);
|
|
5065
|
+
const setPosition = useCallback4(
|
|
5066
|
+
(index) => {
|
|
5067
|
+
const max = timeline.length - 1;
|
|
5068
|
+
const clamped = Math.max(0, Math.min(index, max));
|
|
5069
|
+
setRawPosition(clamped >= max ? null : clamped);
|
|
5070
|
+
},
|
|
5071
|
+
[timeline.length]
|
|
5072
|
+
);
|
|
5073
|
+
const stepBack = useCallback4(() => setPosition(position - 1), [setPosition, position]);
|
|
5074
|
+
const stepForward = useCallback4(() => setPosition(position + 1), [setPosition, position]);
|
|
5075
|
+
const goToLatest = useCallback4(() => setRawPosition(null), []);
|
|
5076
|
+
const createNamedVersion = useCallback4(
|
|
5077
|
+
async (name, note) => {
|
|
5078
|
+
if (!nodeId || !store) return null;
|
|
5079
|
+
const engines = getEngines();
|
|
5080
|
+
if (!engines) return null;
|
|
5081
|
+
try {
|
|
5082
|
+
const checkpoint = await createCheckpoint(store, engines.storage, {
|
|
5083
|
+
name,
|
|
5084
|
+
...note !== void 0 ? { note } : {},
|
|
5085
|
+
nodeIds: members,
|
|
5086
|
+
scopeId: nodeId
|
|
5087
|
+
});
|
|
5088
|
+
await reload();
|
|
5089
|
+
return checkpoint;
|
|
5090
|
+
} catch (err) {
|
|
5091
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5092
|
+
return null;
|
|
5093
|
+
}
|
|
5094
|
+
},
|
|
5095
|
+
[nodeId, store, members, getEngines, reload]
|
|
5096
|
+
);
|
|
5097
|
+
const positionOfCheckpoint = useCallback4(
|
|
5098
|
+
(checkpoint) => {
|
|
5099
|
+
const frontier = checkpoint.properties.frontier;
|
|
5100
|
+
if (!frontier) return null;
|
|
5101
|
+
let best = -1;
|
|
5102
|
+
for (let i = 0; i < timeline.length; i++) {
|
|
5103
|
+
const pinned = frontier[timeline[i].nodeId];
|
|
5104
|
+
if (pinned && pinned.hash === timeline[i].change.hash) {
|
|
5105
|
+
best = Math.max(best, i);
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
return best >= 0 ? best : null;
|
|
5109
|
+
},
|
|
5110
|
+
[timeline]
|
|
5111
|
+
);
|
|
5112
|
+
const frontierAt = useCallback4(
|
|
5113
|
+
(index) => {
|
|
5114
|
+
const scope = getEngines()?.scope;
|
|
5115
|
+
if (!scope) return {};
|
|
5116
|
+
return scope.frontierAtPosition(timeline, index);
|
|
5117
|
+
},
|
|
5118
|
+
[getEngines, timeline]
|
|
5119
|
+
);
|
|
5120
|
+
const restore = useCallback4(async () => {
|
|
5121
|
+
if (!nodeId || !store || timeline.length === 0) return null;
|
|
5122
|
+
const engines = getEngines();
|
|
5123
|
+
if (!engines) return null;
|
|
5124
|
+
try {
|
|
5125
|
+
const frontier = engines.scope.frontierAtPosition(timeline, position);
|
|
5126
|
+
const result = await restoreToFrontier(store, engines.engine, frontier, members);
|
|
5127
|
+
setRawPosition(null);
|
|
5128
|
+
await reload();
|
|
5129
|
+
return result;
|
|
5130
|
+
} catch (err) {
|
|
5131
|
+
if (err instanceof HistoryHorizonError) setHorizon(err.horizon);
|
|
5132
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5133
|
+
return null;
|
|
5134
|
+
}
|
|
5135
|
+
}, [nodeId, store, timeline, position, members, getEngines, reload]);
|
|
5136
|
+
return {
|
|
5137
|
+
timeline,
|
|
5138
|
+
changeCount: timeline.length,
|
|
5139
|
+
position,
|
|
5140
|
+
atLatest,
|
|
5141
|
+
setPosition,
|
|
5142
|
+
stepBack,
|
|
5143
|
+
stepForward,
|
|
5144
|
+
goToLatest,
|
|
5145
|
+
preview,
|
|
5146
|
+
diffs,
|
|
5147
|
+
horizon,
|
|
5148
|
+
docSnapshotCount,
|
|
5149
|
+
checkpoints,
|
|
5150
|
+
createNamedVersion,
|
|
5151
|
+
positionOfCheckpoint,
|
|
5152
|
+
frontierAt,
|
|
5153
|
+
restore,
|
|
5154
|
+
loading,
|
|
5155
|
+
error,
|
|
5156
|
+
reload
|
|
5157
|
+
};
|
|
5158
|
+
}
|
|
5159
|
+
|
|
5160
|
+
// src/hooks/useDraft.ts
|
|
5161
|
+
import { DRAFT_SCHEMA_IRI, DatabaseRowSchema as DatabaseRowSchema2, DatabaseSchema as DatabaseSchema2 } from "@xnetjs/data";
|
|
5162
|
+
import {
|
|
5163
|
+
HistoryEngine as HistoryEngine2,
|
|
5164
|
+
MemorySnapshotStorage as MemorySnapshotStorage2,
|
|
5165
|
+
SnapshotCache as SnapshotCache2,
|
|
5166
|
+
createDraft as createDraftNode,
|
|
5167
|
+
discardDraft,
|
|
5168
|
+
draftEntries,
|
|
5169
|
+
forkNodeIntoDraft,
|
|
5170
|
+
listDrafts,
|
|
5171
|
+
mergeDraft,
|
|
5172
|
+
refreshDraftFromMain,
|
|
5173
|
+
threeWayPropertyMerge
|
|
5174
|
+
} from "@xnetjs/history";
|
|
5175
|
+
import { useCallback as useCallback5, useEffect as useEffect8, useRef as useRef6, useState as useState8 } from "react";
|
|
5176
|
+
var baseIRI = (schemaId) => schemaId.split("@")[0];
|
|
5177
|
+
async function resolveMembers(store, hostId) {
|
|
5178
|
+
const members = [hostId];
|
|
5179
|
+
const host = await store.getRaw(hostId);
|
|
5180
|
+
if (host && baseIRI(host.schemaId) === baseIRI(DatabaseSchema2.schema["@id"])) {
|
|
5181
|
+
const rows = await store.list({ schemaId: DatabaseRowSchema2.schema["@id"] });
|
|
5182
|
+
for (const row of rows) {
|
|
5183
|
+
if (row.properties.database === hostId) members.push(row.id);
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
return members;
|
|
5187
|
+
}
|
|
5188
|
+
function clonesFromEntries(draft) {
|
|
5189
|
+
const clones = {};
|
|
5190
|
+
for (const [originalId, entry] of Object.entries(draftEntries(draft))) {
|
|
5191
|
+
clones[originalId] = entry.cloneId;
|
|
5192
|
+
}
|
|
5193
|
+
return clones;
|
|
5194
|
+
}
|
|
5195
|
+
function useDraft(hostId) {
|
|
5196
|
+
const { store, isReady } = useNodeStore();
|
|
5197
|
+
const [drafts, setDrafts] = useState8([]);
|
|
5198
|
+
const [checkedOut, setCheckedOut] = useState8(null);
|
|
5199
|
+
const [loading, setLoading] = useState8(false);
|
|
5200
|
+
const [error, setError] = useState8(null);
|
|
5201
|
+
const engineRef = useRef6(null);
|
|
5202
|
+
const getEngines = useCallback5(() => {
|
|
5203
|
+
if (!store) return null;
|
|
5204
|
+
const storage = store.getStorageAdapter();
|
|
5205
|
+
if (!storage) return null;
|
|
5206
|
+
if (!engineRef.current || engineRef.current.storage !== storage) {
|
|
5207
|
+
engineRef.current = {
|
|
5208
|
+
engine: new HistoryEngine2(
|
|
5209
|
+
storage,
|
|
5210
|
+
new SnapshotCache2(new MemorySnapshotStorage2(), { interval: 50 })
|
|
5211
|
+
),
|
|
5212
|
+
storage
|
|
5213
|
+
};
|
|
5214
|
+
}
|
|
5215
|
+
return engineRef.current;
|
|
5216
|
+
}, [store]);
|
|
5217
|
+
const draftIdsRef = useRef6(/* @__PURE__ */ new Set());
|
|
5218
|
+
const reload = useCallback5(async () => {
|
|
5219
|
+
if (!hostId || !isReady || !store) return;
|
|
5220
|
+
setLoading(true);
|
|
5221
|
+
setError(null);
|
|
5222
|
+
try {
|
|
5223
|
+
const open = await listDrafts(store, hostId);
|
|
5224
|
+
setDrafts(open);
|
|
5225
|
+
for (const draft of open) draftIdsRef.current.add(draft.id);
|
|
5226
|
+
const overlay = store.getCheckedOutDraft();
|
|
5227
|
+
if (overlay) {
|
|
5228
|
+
const draftNode = await store.getRaw(overlay.draftId);
|
|
5229
|
+
setCheckedOut(
|
|
5230
|
+
draftNode && draftNode.properties.status === "open" && draftNode.properties.target === hostId ? draftNode : null
|
|
5231
|
+
);
|
|
5232
|
+
} else {
|
|
5233
|
+
setCheckedOut(null);
|
|
5234
|
+
}
|
|
5235
|
+
} catch (err) {
|
|
5236
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5237
|
+
} finally {
|
|
5238
|
+
setLoading(false);
|
|
5239
|
+
}
|
|
5240
|
+
}, [hostId, isReady, store]);
|
|
5241
|
+
useEffect8(() => {
|
|
5242
|
+
void reload();
|
|
5243
|
+
}, [reload]);
|
|
5244
|
+
useEffect8(() => {
|
|
5245
|
+
if (!store || !hostId) return;
|
|
5246
|
+
const unsubStore = store.subscribe((event) => {
|
|
5247
|
+
if (event.change.payload.schemaId === DRAFT_SCHEMA_IRI || draftIdsRef.current.has(event.change.payload.nodeId)) {
|
|
5248
|
+
void reload();
|
|
5249
|
+
}
|
|
5250
|
+
});
|
|
5251
|
+
const unsubOverlay = store.subscribeToDraftOverlay(() => void reload());
|
|
5252
|
+
return () => {
|
|
5253
|
+
unsubStore();
|
|
5254
|
+
unsubOverlay();
|
|
5255
|
+
};
|
|
5256
|
+
}, [store, hostId, reload]);
|
|
5257
|
+
const checkout = useCallback5(
|
|
5258
|
+
async (draftId) => {
|
|
5259
|
+
if (!hostId || !store) return false;
|
|
5260
|
+
const engines = getEngines();
|
|
5261
|
+
if (!engines) return false;
|
|
5262
|
+
try {
|
|
5263
|
+
const draft = await store.getRaw(draftId);
|
|
5264
|
+
if (!draft || draft.properties.status !== "open") return false;
|
|
5265
|
+
const members = await resolveMembers(store, hostId);
|
|
5266
|
+
store.setCheckedOutDraft({
|
|
5267
|
+
draftId,
|
|
5268
|
+
members,
|
|
5269
|
+
clones: clonesFromEntries(draft),
|
|
5270
|
+
// Lazy copy-on-write: the first write to an unforked member clones
|
|
5271
|
+
// it. Returning null declines — the write then targets the
|
|
5272
|
+
// original (never-fork schemas stay live by policy).
|
|
5273
|
+
onMissingMember: async (originalId) => {
|
|
5274
|
+
try {
|
|
5275
|
+
const entry = await forkNodeIntoDraft(store, engines.storage, draftId, originalId);
|
|
5276
|
+
return entry.cloneId;
|
|
5277
|
+
} catch {
|
|
5278
|
+
return null;
|
|
5279
|
+
}
|
|
5280
|
+
}
|
|
5281
|
+
});
|
|
5282
|
+
await reload();
|
|
5283
|
+
return true;
|
|
5284
|
+
} catch (err) {
|
|
5285
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5286
|
+
return false;
|
|
5287
|
+
}
|
|
5288
|
+
},
|
|
5289
|
+
[hostId, store, getEngines, reload]
|
|
5290
|
+
);
|
|
5291
|
+
const returnToMain = useCallback5(() => {
|
|
5292
|
+
if (!store) return;
|
|
5293
|
+
store.setCheckedOutDraft(null);
|
|
5294
|
+
}, [store]);
|
|
5295
|
+
const createDraft = useCallback5(
|
|
5296
|
+
async (name) => {
|
|
5297
|
+
if (!hostId || !store) return null;
|
|
5298
|
+
try {
|
|
5299
|
+
const draft = await createDraftNode(store, { name, targetId: hostId });
|
|
5300
|
+
await checkout(draft.id);
|
|
5301
|
+
return draft;
|
|
5302
|
+
} catch (err) {
|
|
5303
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5304
|
+
return null;
|
|
5305
|
+
}
|
|
5306
|
+
},
|
|
5307
|
+
[hostId, store, checkout]
|
|
5308
|
+
);
|
|
5309
|
+
const discard = useCallback5(
|
|
5310
|
+
async (draftId) => {
|
|
5311
|
+
if (!store) return false;
|
|
5312
|
+
const engines = getEngines();
|
|
5313
|
+
if (!engines) return false;
|
|
5314
|
+
try {
|
|
5315
|
+
if (store.getCheckedOutDraft()?.draftId === draftId) {
|
|
5316
|
+
store.setCheckedOutDraft(null);
|
|
5317
|
+
}
|
|
5318
|
+
await discardDraft(store, engines.storage, draftId);
|
|
5319
|
+
await reload();
|
|
5320
|
+
return true;
|
|
5321
|
+
} catch (err) {
|
|
5322
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5323
|
+
return false;
|
|
5324
|
+
}
|
|
5325
|
+
},
|
|
5326
|
+
[store, getEngines, reload]
|
|
5327
|
+
);
|
|
5328
|
+
const merge = useCallback5(
|
|
5329
|
+
async (draftId) => {
|
|
5330
|
+
if (!store) return null;
|
|
5331
|
+
const engines = getEngines();
|
|
5332
|
+
if (!engines) return null;
|
|
5333
|
+
try {
|
|
5334
|
+
const result = await mergeDraft(store, engines.storage, engines.engine, draftId);
|
|
5335
|
+
if (result.status === "merged" && store.getCheckedOutDraft()?.draftId === draftId) {
|
|
5336
|
+
store.setCheckedOutDraft(null);
|
|
5337
|
+
}
|
|
5338
|
+
await reload();
|
|
5339
|
+
return result;
|
|
5340
|
+
} catch (err) {
|
|
5341
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5342
|
+
return null;
|
|
5343
|
+
}
|
|
5344
|
+
},
|
|
5345
|
+
[store, getEngines, reload]
|
|
5346
|
+
);
|
|
5347
|
+
const refresh = useCallback5(
|
|
5348
|
+
async (draftId) => {
|
|
5349
|
+
if (!store) return null;
|
|
5350
|
+
const engines = getEngines();
|
|
5351
|
+
if (!engines) return null;
|
|
5352
|
+
try {
|
|
5353
|
+
const result = await refreshDraftFromMain(store, engines.storage, engines.engine, draftId);
|
|
5354
|
+
await reload();
|
|
5355
|
+
return result;
|
|
5356
|
+
} catch (err) {
|
|
5357
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5358
|
+
return null;
|
|
5359
|
+
}
|
|
5360
|
+
},
|
|
5361
|
+
[store, getEngines, reload]
|
|
5362
|
+
);
|
|
5363
|
+
const setReviewRequested = useCallback5(
|
|
5364
|
+
async (draftId, requested) => {
|
|
5365
|
+
if (!store) return;
|
|
5366
|
+
try {
|
|
5367
|
+
await store.update(draftId, { properties: { reviewRequested: requested } });
|
|
5368
|
+
} catch (err) {
|
|
5369
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5370
|
+
}
|
|
5371
|
+
},
|
|
5372
|
+
[store]
|
|
5373
|
+
);
|
|
5374
|
+
const computeReview = useCallback5(
|
|
5375
|
+
async (draftId) => {
|
|
5376
|
+
if (!store) return null;
|
|
5377
|
+
const engines = getEngines();
|
|
5378
|
+
if (!engines) return null;
|
|
5379
|
+
try {
|
|
5380
|
+
const draft = await store.getRaw(draftId);
|
|
5381
|
+
if (!draft) return null;
|
|
5382
|
+
const cards = [];
|
|
5383
|
+
const members = [];
|
|
5384
|
+
for (const [id, entry] of Object.entries(draftEntries(draft))) {
|
|
5385
|
+
const originalId = id;
|
|
5386
|
+
const cloneId = entry.cloneId;
|
|
5387
|
+
const [base, ours, theirs] = await Promise.all([
|
|
5388
|
+
engines.engine.materializeAt(originalId, {
|
|
5389
|
+
type: "hash",
|
|
5390
|
+
hash: entry.forkedAtHash
|
|
5391
|
+
}),
|
|
5392
|
+
store.getRaw(originalId),
|
|
5393
|
+
store.getRaw(cloneId)
|
|
5394
|
+
]);
|
|
5395
|
+
if (!ours || !theirs) continue;
|
|
5396
|
+
const result = threeWayPropertyMerge(
|
|
5397
|
+
base.node.properties,
|
|
5398
|
+
ours.properties,
|
|
5399
|
+
theirs.properties
|
|
5400
|
+
);
|
|
5401
|
+
for (const [property, draftValue] of Object.entries(result.patch)) {
|
|
5402
|
+
cards.push({
|
|
5403
|
+
originalId,
|
|
5404
|
+
property,
|
|
5405
|
+
base: base.node.properties[property],
|
|
5406
|
+
main: ours.properties[property],
|
|
5407
|
+
draft: draftValue,
|
|
5408
|
+
conflict: false
|
|
5409
|
+
});
|
|
5410
|
+
}
|
|
5411
|
+
for (const conflict of result.conflicts) {
|
|
5412
|
+
cards.push({
|
|
5413
|
+
originalId,
|
|
5414
|
+
property: conflict.property,
|
|
5415
|
+
base: conflict.base,
|
|
5416
|
+
main: conflict.ours,
|
|
5417
|
+
draft: conflict.theirs,
|
|
5418
|
+
conflict: true
|
|
5419
|
+
});
|
|
5420
|
+
}
|
|
5421
|
+
const hasDocument = Boolean(entry.forkedAtYjsStateVector);
|
|
5422
|
+
let documentDiffers = false;
|
|
5423
|
+
if (hasDocument) {
|
|
5424
|
+
const [cloneBlob, mainBlob] = await Promise.all([
|
|
5425
|
+
engines.storage.getDocumentContent(cloneId),
|
|
5426
|
+
engines.storage.getDocumentContent(originalId)
|
|
5427
|
+
]);
|
|
5428
|
+
documentDiffers = (cloneBlob?.length ?? 0) !== (mainBlob?.length ?? 0);
|
|
5429
|
+
}
|
|
5430
|
+
members.push({ originalId, cloneId, hasDocument, documentDiffers });
|
|
5431
|
+
}
|
|
5432
|
+
return { cards, members };
|
|
5433
|
+
} catch (err) {
|
|
5434
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
5435
|
+
return null;
|
|
5436
|
+
}
|
|
5437
|
+
},
|
|
5438
|
+
[store, getEngines]
|
|
5439
|
+
);
|
|
5440
|
+
return {
|
|
5441
|
+
drafts,
|
|
5442
|
+
checkedOut,
|
|
5443
|
+
createDraft,
|
|
5444
|
+
checkout,
|
|
5445
|
+
returnToMain,
|
|
5446
|
+
discard,
|
|
5447
|
+
merge,
|
|
5448
|
+
refresh,
|
|
5449
|
+
setReviewRequested,
|
|
5450
|
+
computeReview,
|
|
5451
|
+
loading,
|
|
5452
|
+
error,
|
|
5453
|
+
reload
|
|
5454
|
+
};
|
|
5455
|
+
}
|
|
5456
|
+
|
|
4846
5457
|
// src/hooks/useGlobalUndo.ts
|
|
4847
|
-
import { useCallback as
|
|
5458
|
+
import { useCallback as useCallback6, useContext, useEffect as useEffect9, useReducer } from "react";
|
|
4848
5459
|
function readUndoContext(ctx) {
|
|
4849
5460
|
if (!ctx) return { undoManager: null, nodeStore: null };
|
|
4850
5461
|
return { undoManager: ctx.undoManager, nodeStore: ctx.nodeStore };
|
|
@@ -4853,17 +5464,17 @@ function useGlobalUndo() {
|
|
|
4853
5464
|
const ctx = useContext(XNetContext);
|
|
4854
5465
|
const { undoManager, nodeStore } = readUndoContext(ctx);
|
|
4855
5466
|
const [, bump] = useReducer((n) => n + 1, 0);
|
|
4856
|
-
|
|
5467
|
+
useEffect9(() => {
|
|
4857
5468
|
if (!nodeStore) return;
|
|
4858
5469
|
return nodeStore.subscribe(() => bump());
|
|
4859
5470
|
}, [nodeStore]);
|
|
4860
|
-
const undo =
|
|
5471
|
+
const undo = useCallback6(async () => {
|
|
4861
5472
|
if (!undoManager) return false;
|
|
4862
5473
|
const ok = await undoManager.undoLatest();
|
|
4863
5474
|
bump();
|
|
4864
5475
|
return ok;
|
|
4865
5476
|
}, [undoManager]);
|
|
4866
|
-
const redo =
|
|
5477
|
+
const redo = useCallback6(async () => {
|
|
4867
5478
|
if (!undoManager) return false;
|
|
4868
5479
|
const ok = await undoManager.redoLatest();
|
|
4869
5480
|
bump();
|
|
@@ -4877,6 +5488,57 @@ function useGlobalUndo() {
|
|
|
4877
5488
|
};
|
|
4878
5489
|
}
|
|
4879
5490
|
|
|
5491
|
+
// src/hooks/useCanCreate.ts
|
|
5492
|
+
import { useEffect as useEffect10, useMemo as useMemo7, useRef as useRef7, useState as useState9 } from "react";
|
|
5493
|
+
var INITIAL_STATE = {
|
|
5494
|
+
canCreate: false,
|
|
5495
|
+
loading: true,
|
|
5496
|
+
error: null
|
|
5497
|
+
};
|
|
5498
|
+
function useCanCreate(schemaId, properties) {
|
|
5499
|
+
const { store, isReady } = useNodeStore();
|
|
5500
|
+
const [state, setState] = useState9(INITIAL_STATE);
|
|
5501
|
+
const requestRef = useRef7(0);
|
|
5502
|
+
const propertiesKey = useMemo7(() => JSON.stringify(properties ?? {}), [properties]);
|
|
5503
|
+
useEffect10(() => {
|
|
5504
|
+
if (!store || !isReady) {
|
|
5505
|
+
setState((prev) => ({ ...prev, loading: true }));
|
|
5506
|
+
return;
|
|
5507
|
+
}
|
|
5508
|
+
if (!store.auth) {
|
|
5509
|
+
setState({
|
|
5510
|
+
...INITIAL_STATE,
|
|
5511
|
+
loading: false,
|
|
5512
|
+
error: new Error("Authorization API is not configured on this NodeStore")
|
|
5513
|
+
});
|
|
5514
|
+
return;
|
|
5515
|
+
}
|
|
5516
|
+
const run = async () => {
|
|
5517
|
+
const requestId = ++requestRef.current;
|
|
5518
|
+
setState((prev) => ({ ...prev, loading: true, error: null }));
|
|
5519
|
+
try {
|
|
5520
|
+
const decision = await store.auth.can({
|
|
5521
|
+
action: "create",
|
|
5522
|
+
// A synthetic id: the draft node must not collide with a stored one.
|
|
5523
|
+
nodeId: `draft:${schemaId}`,
|
|
5524
|
+
node: {
|
|
5525
|
+
schemaId,
|
|
5526
|
+
properties: JSON.parse(propertiesKey)
|
|
5527
|
+
}
|
|
5528
|
+
});
|
|
5529
|
+
if (requestRef.current !== requestId) return;
|
|
5530
|
+
setState({ canCreate: decision.allowed, loading: false, error: null });
|
|
5531
|
+
} catch (error) {
|
|
5532
|
+
if (requestRef.current !== requestId) return;
|
|
5533
|
+
const normalized = error instanceof Error ? error : new Error(String(error));
|
|
5534
|
+
setState((prev) => ({ ...prev, loading: false, error: normalized }));
|
|
5535
|
+
}
|
|
5536
|
+
};
|
|
5537
|
+
void run();
|
|
5538
|
+
}, [isReady, propertiesKey, schemaId, store]);
|
|
5539
|
+
return state;
|
|
5540
|
+
}
|
|
5541
|
+
|
|
4880
5542
|
// src/index.ts
|
|
4881
5543
|
import { WebSocketSyncProvider } from "@xnetjs/runtime";
|
|
4882
5544
|
import {
|
|
@@ -4907,7 +5569,7 @@ import {
|
|
|
4907
5569
|
} from "@xnetjs/runtime";
|
|
4908
5570
|
|
|
4909
5571
|
// src/hooks/useBilling.ts
|
|
4910
|
-
import { useCallback as
|
|
5572
|
+
import { useCallback as useCallback7, useContext as useContext2, useEffect as useEffect11, useMemo as useMemo8, useState as useState10 } from "react";
|
|
4911
5573
|
var toHttpUrl = (hubUrl) => {
|
|
4912
5574
|
try {
|
|
4913
5575
|
const url = new URL(hubUrl);
|
|
@@ -4944,20 +5606,20 @@ function deriveBilling(state) {
|
|
|
4944
5606
|
}
|
|
4945
5607
|
function useBilling() {
|
|
4946
5608
|
const context = useContext2(XNetContext);
|
|
4947
|
-
const [state, setState] =
|
|
4948
|
-
const [loading, setLoading] =
|
|
5609
|
+
const [state, setState] = useState10(null);
|
|
5610
|
+
const [loading, setLoading] = useState10(
|
|
4949
5611
|
Boolean(context?.hubUrl || context?.billing?.apiBase)
|
|
4950
5612
|
);
|
|
4951
|
-
const [error, setError] =
|
|
4952
|
-
const apiBase =
|
|
5613
|
+
const [error, setError] = useState10(null);
|
|
5614
|
+
const apiBase = useMemo8(() => {
|
|
4953
5615
|
if (context?.billing?.apiBase) return context.billing.apiBase.replace(/\/$/, "");
|
|
4954
5616
|
return context?.hubUrl ? toHttpUrl(context.hubUrl) : null;
|
|
4955
5617
|
}, [context?.billing?.apiBase, context?.hubUrl]);
|
|
4956
|
-
const authHeaders =
|
|
5618
|
+
const authHeaders = useCallback7(async () => {
|
|
4957
5619
|
const token = context?.getHubAuthToken ? await context.getHubAuthToken() : "";
|
|
4958
5620
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
4959
5621
|
}, [context]);
|
|
4960
|
-
const reload =
|
|
5622
|
+
const reload = useCallback7(async () => {
|
|
4961
5623
|
if (!apiBase) {
|
|
4962
5624
|
setState(null);
|
|
4963
5625
|
setLoading(false);
|
|
@@ -4975,10 +5637,10 @@ function useBilling() {
|
|
|
4975
5637
|
setLoading(false);
|
|
4976
5638
|
}
|
|
4977
5639
|
}, [apiBase, authHeaders]);
|
|
4978
|
-
|
|
5640
|
+
useEffect11(() => {
|
|
4979
5641
|
void reload();
|
|
4980
5642
|
}, [reload]);
|
|
4981
|
-
const redirectVia =
|
|
5643
|
+
const redirectVia = useCallback7(
|
|
4982
5644
|
async (path, body) => {
|
|
4983
5645
|
if (!apiBase) throw new Error("Hub URL not configured");
|
|
4984
5646
|
const res = await fetch(`${apiBase}${path}`, {
|
|
@@ -4992,11 +5654,11 @@ function useBilling() {
|
|
|
4992
5654
|
},
|
|
4993
5655
|
[apiBase, authHeaders]
|
|
4994
5656
|
);
|
|
4995
|
-
const openCheckout =
|
|
5657
|
+
const openCheckout = useCallback7(
|
|
4996
5658
|
(priceRef, options = {}) => redirectVia("/billing/checkout", buildCheckoutBody(priceRef, options)),
|
|
4997
5659
|
[redirectVia]
|
|
4998
5660
|
);
|
|
4999
|
-
const openPortal =
|
|
5661
|
+
const openPortal = useCallback7(
|
|
5000
5662
|
(returnUrl) => redirectVia("/billing/portal", returnUrl ? { returnUrl } : {}),
|
|
5001
5663
|
[redirectVia]
|
|
5002
5664
|
);
|
|
@@ -5102,6 +5764,7 @@ export {
|
|
|
5102
5764
|
isReactionVisible,
|
|
5103
5765
|
isSavedViewVisualPreviewEmbeddable,
|
|
5104
5766
|
isTaskOverdue,
|
|
5767
|
+
mergeEditorContributions,
|
|
5105
5768
|
mergeSavedViewFeedEnrichment,
|
|
5106
5769
|
moderateThread,
|
|
5107
5770
|
onboardingReducer,
|
|
@@ -5120,6 +5783,7 @@ export {
|
|
|
5120
5783
|
useBilling,
|
|
5121
5784
|
useBlame,
|
|
5122
5785
|
useCan,
|
|
5786
|
+
useCanCreate,
|
|
5123
5787
|
useCanEdit,
|
|
5124
5788
|
useCanvasTaskSync,
|
|
5125
5789
|
useCell,
|
|
@@ -5136,6 +5800,7 @@ export {
|
|
|
5136
5800
|
useDatabaseSchema,
|
|
5137
5801
|
useDemoMode,
|
|
5138
5802
|
useDiff,
|
|
5803
|
+
useDraft,
|
|
5139
5804
|
useEditorExtensions,
|
|
5140
5805
|
useEditorExtensionsSafe,
|
|
5141
5806
|
useEffectiveSchema,
|
|
@@ -5152,6 +5817,7 @@ export {
|
|
|
5152
5817
|
useInfiniteQuery,
|
|
5153
5818
|
useInstrumentation,
|
|
5154
5819
|
useIsOffline,
|
|
5820
|
+
useMergedEditorContributions,
|
|
5155
5821
|
useMessageRequests,
|
|
5156
5822
|
useModeratedThread,
|
|
5157
5823
|
useMutate,
|
|
@@ -5164,6 +5830,7 @@ export {
|
|
|
5164
5830
|
usePluginRegistryOptional,
|
|
5165
5831
|
usePlugins,
|
|
5166
5832
|
usePolicyFilteredReactionCounters,
|
|
5833
|
+
usePresence,
|
|
5167
5834
|
useQuery,
|
|
5168
5835
|
useRelatedRows,
|
|
5169
5836
|
useRemoteSchema,
|
|
@@ -5178,6 +5845,7 @@ export {
|
|
|
5178
5845
|
useTaskProjectionSync,
|
|
5179
5846
|
useTasks,
|
|
5180
5847
|
useTelemetryReporter,
|
|
5848
|
+
useTimeMachine,
|
|
5181
5849
|
useTracingReporter,
|
|
5182
5850
|
useUndo,
|
|
5183
5851
|
useVerification,
|