dirvi-lib 0.7.0 → 0.8.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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/tree-surfer/cursor.d.ts +52 -26
- package/dist/tree-surfer/cursor.d.ts.map +1 -1
- package/dist/tree-surfer/cursor.js +19 -44
- package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +2 -0
- package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +1 -0
- package/dist/tree-surfer/fold-node/fold-node.arb.js +1 -0
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +3 -4
- package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +1 -1
- package/dist/tree-surfer/fold-node/fold-node.impl.js +73 -67
- package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +3 -5
- package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +1 -1
- package/dist/tree-surfer/fold-node/fold-node.schema.js +2 -9
- package/dist/tree-surfer/fold-node/fold-node.types.d.ts +169 -46
- package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +1 -1
- package/dist/tree-surfer/fold-node/tree-node.test.d.ts +2 -0
- package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +1 -0
- package/dist/tree-surfer/fold-node/tree-node.test.js +90 -0
- package/dist/tree-surfer/index.d.ts +3 -1
- package/dist/tree-surfer/index.d.ts.map +1 -1
- package/dist/tree-surfer/index.js +3 -1
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +5 -5
- package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +1 -1
- package/dist/tree-surfer/nav-node/nav-node.impl.js +84 -99
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts +62 -34
- package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +1 -1
- package/dist/tree-surfer/nav-node/nav-node.types.js +1 -3
- package/dist/tree-surfer/state/state.impl.d.ts +7 -0
- package/dist/tree-surfer/state/state.impl.d.ts.map +1 -0
- package/dist/tree-surfer/state/state.impl.js +89 -0
- package/dist/tree-surfer/state/state.schemas.d.ts +9 -0
- package/dist/tree-surfer/state/state.schemas.d.ts.map +1 -0
- package/dist/tree-surfer/state/state.schemas.js +8 -0
- package/dist/tree-surfer/state/state.types.d.ts +21 -0
- package/dist/tree-surfer/state/state.types.d.ts.map +1 -0
- package/dist/tree-surfer/state/state.types.js +1 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.d.ts +82 -17
- package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +1 -1
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts +2 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +1 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.js +34 -0
- package/dist/tree-surfer/tree-node/tree-node.arb.js +164 -56
- package/dist/tree-surfer/tree-node/tree-node.impl.d.ts +1 -1
- package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +1 -1
- package/dist/tree-surfer/tree-node/tree-node.impl.js +41 -48
- package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts +2 -2
- package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +1 -1
- package/dist/tree-surfer/tree-node/tree-node.schemas.js +2 -2
- package/dist/tree-surfer/tree-node/tree-node.test.js +144 -76
- package/dist/tree-surfer/tree-node/tree-node.types.d.ts +89 -107
- package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +1 -1
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +6 -2
- package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +1 -1
- package/dist/tree-surfer-ui/application/intent-to-effect.js +1 -6
- package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts +10 -2
- package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +1 -1
- package/dist/tree-surfer-ui/application/user-input-to-intent.js +12 -5
- package/dist/tree-surfer-ui/domain/effect.d.ts +9 -8
- package/dist/tree-surfer-ui/domain/effect.d.ts.map +1 -1
- package/dist/tree-surfer-ui/domain/index.d.ts +1 -1
- package/dist/tree-surfer-ui/domain/index.d.ts.map +1 -1
- package/dist/tree-surfer-ui/domain/index.js +1 -1
- package/dist/tree-surfer-ui/domain/{input-state.d.ts → input-mode-state.d.ts} +2 -2
- package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +1 -0
- package/package.json +3 -2
- package/dist/tree-surfer/state.d.ts +0 -25
- package/dist/tree-surfer/state.d.ts.map +0 -1
- package/dist/tree-surfer/state.js +0 -118
- package/dist/tree-surfer-ui/domain/input-state.d.ts.map +0 -1
- /package/dist/tree-surfer-ui/domain/{input-state.js → input-mode-state.js} +0 -0
|
@@ -1,44 +1,76 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
: navNodeApi.from(entry.children, childFoldNode),
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
else if (treeNodeApi.isLeaf(entry)) {
|
|
25
|
-
navigationEntry = entry;
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
// This should be unreachable if BufferNode correctly extends TreeNode.
|
|
29
|
-
throw new Error('Unsupported tree node');
|
|
30
|
-
}
|
|
31
|
-
if (foldNode.folds.has(entry.id)) {
|
|
32
|
-
foldedEntriesSoFar.push(navigationEntry);
|
|
33
|
-
}
|
|
34
|
-
else {
|
|
35
|
-
visibleEntriesSoFar.push(navigationEntry);
|
|
36
|
-
}
|
|
1
|
+
export function createNavNodeApi(treeNodeApi, foldNodeService, cursorApi) {
|
|
2
|
+
function entryIsBranch(entry) {
|
|
3
|
+
return 'children' in entry;
|
|
4
|
+
}
|
|
5
|
+
function createNavNode(entries, foldRoot, parentPath) {
|
|
6
|
+
const visibleEntries = [];
|
|
7
|
+
const foldedEntries = [];
|
|
8
|
+
const foldedEntryIds = foldRoot === undefined
|
|
9
|
+
? undefined
|
|
10
|
+
: foldNodeService.foldedEntriesAtPath(foldRoot, parentPath);
|
|
11
|
+
for (const entry of entries) {
|
|
12
|
+
const entryPath = [...parentPath, entry.id];
|
|
13
|
+
const isFolded = foldRoot !== undefined &&
|
|
14
|
+
foldNodeService.getIfEntryFoldedAtPath(foldRoot, parentPath, entry.id);
|
|
15
|
+
const navigationEntry = createNavEntry(entry, foldRoot, entryPath);
|
|
16
|
+
if (isFolded) {
|
|
17
|
+
foldedEntries.push(navigationEntry);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
visibleEntries.push(navigationEntry);
|
|
37
21
|
}
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
entries: visibleEntries,
|
|
25
|
+
folded: foldedEntryIds === undefined || foldedEntryIds.length === 0
|
|
26
|
+
? null
|
|
27
|
+
: {
|
|
28
|
+
entries: foldedEntries,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function createNavEntry(entry, foldRoot, entryPath) {
|
|
33
|
+
if (treeNodeApi.isLeaf(entry)) {
|
|
38
34
|
return {
|
|
39
|
-
|
|
40
|
-
foldedEntries: foldedEntriesSoFar,
|
|
35
|
+
id: entry.id,
|
|
41
36
|
};
|
|
37
|
+
}
|
|
38
|
+
return createNavBranch(entry, foldRoot, entryPath);
|
|
39
|
+
}
|
|
40
|
+
function createNavBranch(entry, foldRoot, entryPath) {
|
|
41
|
+
if (!treeNodeApi.isBranch(entry)) {
|
|
42
|
+
throw new Error('Navigation root must be a branch');
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
id: entry.id,
|
|
46
|
+
children: entry.children === null
|
|
47
|
+
? null
|
|
48
|
+
: createNavNode(entry.children, foldRoot, entryPath),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function rootCursors(rootNode) {
|
|
52
|
+
if (rootNode.children === null) {
|
|
53
|
+
return [[]];
|
|
54
|
+
}
|
|
55
|
+
return [[], ...cursorsInNode(rootNode.children, [])];
|
|
56
|
+
}
|
|
57
|
+
function cursorsInNode(node, parentPath) {
|
|
58
|
+
const cursors = [];
|
|
59
|
+
for (const entry of node.entries) {
|
|
60
|
+
const entryPath = [...parentPath, entry.id];
|
|
61
|
+
cursors.push(entryPath);
|
|
62
|
+
if (!entryIsBranch(entry) || entry.children === null) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
cursors.push(...cursorsInNode(entry.children, entryPath));
|
|
66
|
+
}
|
|
67
|
+
return cursors;
|
|
68
|
+
}
|
|
69
|
+
const navNodeApi = {
|
|
70
|
+
entryIsBranch,
|
|
71
|
+
// Return a nav node from the TreeNode and FoldNode trees.
|
|
72
|
+
from(root, foldRoot) {
|
|
73
|
+
return createNavBranch(root, foldRoot, []);
|
|
42
74
|
},
|
|
43
75
|
getNodeAtPath(navigation, path) {
|
|
44
76
|
const [currentId, ...remainingPath] = path;
|
|
@@ -46,11 +78,11 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
|
|
|
46
78
|
if (currentId === undefined) {
|
|
47
79
|
return navigation;
|
|
48
80
|
}
|
|
49
|
-
const entry =
|
|
81
|
+
const entry = navigation.entries.find((candidate) => candidate.id === currentId);
|
|
50
82
|
if (entry === undefined) {
|
|
51
83
|
return undefined;
|
|
52
84
|
}
|
|
53
|
-
if (!
|
|
85
|
+
if (!entryIsBranch(entry)) {
|
|
54
86
|
return undefined;
|
|
55
87
|
}
|
|
56
88
|
// The directory has not been loaded/opened.
|
|
@@ -64,20 +96,20 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
|
|
|
64
96
|
if (currentId === undefined) {
|
|
65
97
|
return undefined;
|
|
66
98
|
}
|
|
67
|
-
const entry =
|
|
99
|
+
const entry = navigation.entries.find((candidate) => candidate.id === currentId);
|
|
68
100
|
if (entry === undefined) {
|
|
69
101
|
return undefined;
|
|
70
102
|
}
|
|
71
103
|
if (remainingPath.length === 0) {
|
|
72
104
|
return entry;
|
|
73
105
|
}
|
|
74
|
-
if (!
|
|
106
|
+
if (!entryIsBranch(entry) || entry.children === null) {
|
|
75
107
|
return undefined;
|
|
76
108
|
}
|
|
77
109
|
return navNodeApi.getEntryAtPath(entry.children, remainingPath);
|
|
78
110
|
},
|
|
79
111
|
nextCursor(rootNode, cursor) {
|
|
80
|
-
const cursors =
|
|
112
|
+
const cursors = rootCursors(rootNode);
|
|
81
113
|
const currentIndex = cursors.findIndex((candidate) => cursorApi.equal(candidate, cursor));
|
|
82
114
|
if (currentIndex === -1 || currentIndex === cursors.length - 1) {
|
|
83
115
|
return undefined;
|
|
@@ -85,7 +117,7 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
|
|
|
85
117
|
return cursors[currentIndex + 1];
|
|
86
118
|
},
|
|
87
119
|
previousCursor(rootNode, cursor) {
|
|
88
|
-
const cursors =
|
|
120
|
+
const cursors = rootCursors(rootNode);
|
|
89
121
|
const currentIndex = cursors.findIndex((candidate) => cursorApi.equal(candidate, cursor));
|
|
90
122
|
if (currentIndex <= 0) {
|
|
91
123
|
return undefined;
|
|
@@ -93,52 +125,32 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
|
|
|
93
125
|
return cursors[currentIndex - 1];
|
|
94
126
|
},
|
|
95
127
|
cursorAfterFold(rootNode, cursor) {
|
|
96
|
-
if (
|
|
128
|
+
if (cursor.length === 0) {
|
|
97
129
|
return undefined;
|
|
98
130
|
}
|
|
99
|
-
const cursors =
|
|
131
|
+
const cursors = rootCursors(rootNode);
|
|
100
132
|
const currentIndex = cursors.findIndex((candidate) => cursorApi.equal(candidate, cursor));
|
|
101
133
|
if (currentIndex === -1) {
|
|
102
134
|
return undefined;
|
|
103
135
|
}
|
|
104
|
-
const foldedPath = [...cursor.parentPath, cursor.entryId];
|
|
105
136
|
for (let index = currentIndex + 1; index < cursors.length; index += 1) {
|
|
106
137
|
const candidate = cursors[index];
|
|
107
138
|
if (candidate === undefined) {
|
|
108
139
|
continue;
|
|
109
140
|
}
|
|
110
|
-
if (cursorApi.cursorBelongsToSubtree(candidate,
|
|
141
|
+
if (cursorApi.cursorBelongsToSubtree(candidate, cursor)) {
|
|
111
142
|
continue;
|
|
112
143
|
}
|
|
113
144
|
return candidate;
|
|
114
145
|
}
|
|
115
146
|
// The fold row will be created at the end of this directory.
|
|
116
|
-
return
|
|
117
|
-
kind: CursorKind.Fold,
|
|
118
|
-
parentPath: cursor.parentPath,
|
|
119
|
-
};
|
|
147
|
+
return [...cursor];
|
|
120
148
|
},
|
|
121
|
-
parentCursor(
|
|
122
|
-
|
|
123
|
-
// The cursor is already in the root directory.
|
|
124
|
-
if (parentPath.length === 0) {
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
const entryId = parentPath.at(-1);
|
|
128
|
-
if (entryId === undefined) {
|
|
129
|
-
return undefined;
|
|
130
|
-
}
|
|
131
|
-
const containingPath = parentPath.slice(0, -1);
|
|
132
|
-
const entry = navNodeApi.getEntryAtPath(navigation, [...parentPath]);
|
|
133
|
-
// The parent path must identify a navigable branch.
|
|
134
|
-
if (entry === undefined || !isNavBranch(entry)) {
|
|
149
|
+
parentCursor(_navigation, cursor) {
|
|
150
|
+
if (cursor.length === 0) {
|
|
135
151
|
return undefined;
|
|
136
152
|
}
|
|
137
|
-
return
|
|
138
|
-
kind: CursorKind.Entry,
|
|
139
|
-
parentPath: containingPath,
|
|
140
|
-
entryId: entryId,
|
|
141
|
-
};
|
|
153
|
+
return cursor.slice(0, -1);
|
|
142
154
|
},
|
|
143
155
|
cursors(navigation, parentPath = []) {
|
|
144
156
|
return cursorsInNode(navigation, parentPath);
|
|
@@ -147,7 +159,7 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
|
|
|
147
159
|
const paths = [];
|
|
148
160
|
for (const entry of navigation.entries) {
|
|
149
161
|
const entryPath = [...parentPath, entry.id];
|
|
150
|
-
if (
|
|
162
|
+
if (entryIsBranch(entry)) {
|
|
151
163
|
// A null children value means that the directory has not been
|
|
152
164
|
// loaded/opened, so there are no visible descendant files.
|
|
153
165
|
if (entry.children !== null) {
|
|
@@ -162,30 +174,3 @@ export function createNavNodeApi(treeNodeApi, foldNodeApi, cursorApi) {
|
|
|
162
174
|
};
|
|
163
175
|
return navNodeApi;
|
|
164
176
|
}
|
|
165
|
-
function createEmptyFoldNode() {
|
|
166
|
-
return {
|
|
167
|
-
children: [],
|
|
168
|
-
folds: new Set(),
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
function cursorsInNode(node, parentPath) {
|
|
172
|
-
const cursors = [];
|
|
173
|
-
for (const entry of node.entries) {
|
|
174
|
-
cursors.push({
|
|
175
|
-
kind: CursorKind.Entry,
|
|
176
|
-
parentPath,
|
|
177
|
-
entryId: entry.id,
|
|
178
|
-
});
|
|
179
|
-
if (!isNavBranch(entry) || entry.children === null) {
|
|
180
|
-
continue;
|
|
181
|
-
}
|
|
182
|
-
cursors.push(...cursorsInNode(entry.children, [...parentPath, entry.id]));
|
|
183
|
-
}
|
|
184
|
-
if (node.foldedEntries.length > 0) {
|
|
185
|
-
cursors.push({
|
|
186
|
-
kind: CursorKind.Fold,
|
|
187
|
-
parentPath,
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
return cursors;
|
|
191
|
-
}
|
|
@@ -1,42 +1,70 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { FoldNodeRoot } from '../fold-node/fold-node.types.js';
|
|
1
|
+
import { BranchTreeNode, SerializableKey, TreeNode } from '../tree-node/tree-node.types.js';
|
|
3
2
|
import { Cursor } from '../cursor.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* replaces a branch's buffer children with a NavNode.
|
|
7
|
-
*/
|
|
8
|
-
export type NavEntry<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>> = NavLeaf<Id, BufferNode> | NavBranch<Id, BufferNode>;
|
|
9
|
-
export type NavLeaf<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>> = BufferNode & LeafTreeNode<Id>;
|
|
10
|
-
type ReplaceChildren<Node, Children> = Node extends {
|
|
11
|
-
children: unknown;
|
|
12
|
-
} ? Omit<Node, 'children'> & {
|
|
13
|
-
children: Children;
|
|
14
|
-
} : never;
|
|
15
|
-
export type NavBranch<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>> = ReplaceChildren<BufferNode, NavNode<Id, BufferNode> | null>;
|
|
16
|
-
export declare function isNavBranch<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>(entry: NavEntry<Id, BufferNode>): entry is NavBranch<Id, BufferNode>;
|
|
17
|
-
/**
|
|
18
|
-
* The derived tree as navigated: list of visible entries and folded entries.
|
|
19
|
-
*/
|
|
20
|
-
export type NavNode<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>> = {
|
|
3
|
+
import { FoldNode } from '../fold-node/fold-node.types.js';
|
|
4
|
+
export type NavNode<Id extends SerializableKey> = {
|
|
21
5
|
/**
|
|
22
|
-
*
|
|
6
|
+
* Synthetic UI entry representing folded children.
|
|
7
|
+
*
|
|
8
|
+
* It is absent when this directory has no folded children.
|
|
23
9
|
*/
|
|
24
|
-
|
|
10
|
+
folded: NavFoldEntry<Id> | null;
|
|
25
11
|
/**
|
|
26
|
-
*
|
|
12
|
+
* Loaded entries that are not folded at this directory, corresponding to
|
|
13
|
+
* navigable rows. Entries retain the order from the TreeNode.
|
|
27
14
|
*/
|
|
28
|
-
|
|
15
|
+
entries: NavEntry<Id>[];
|
|
29
16
|
};
|
|
30
|
-
export type
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
17
|
+
export type NavFoldEntry<Id extends SerializableKey> = {
|
|
18
|
+
/**
|
|
19
|
+
* Folded entries that are present in the loaded TreeNode.
|
|
20
|
+
*
|
|
21
|
+
* This is information for rendering the folded row.
|
|
22
|
+
*/
|
|
23
|
+
entries: NavEntry<Id>[];
|
|
24
|
+
};
|
|
25
|
+
export type NavEntry<Id extends SerializableKey> = NavLeaf<Id> | NavBranch<Id>;
|
|
26
|
+
export type NavLeaf<Id extends SerializableKey> = {
|
|
27
|
+
id: Id;
|
|
28
|
+
};
|
|
29
|
+
export type NavBranch<Id extends SerializableKey> = {
|
|
30
|
+
id: Id;
|
|
31
|
+
children: NavNode<Id> | null;
|
|
32
|
+
};
|
|
33
|
+
export type NavNodeApi<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = {
|
|
34
|
+
/**
|
|
35
|
+
* Tests whether a navigation entry represents a branch.
|
|
36
|
+
*
|
|
37
|
+
* A branch is an entry with a `children` property. Its `children` value is
|
|
38
|
+
* `null` when the corresponding tree branch has not been loaded or opened.
|
|
39
|
+
*/
|
|
40
|
+
entryIsBranch(entry: NavEntry<Id>): entry is NavBranch<Id>;
|
|
41
|
+
/**
|
|
42
|
+
* Creates a navigation projection of the tree node root.
|
|
43
|
+
*
|
|
44
|
+
* The root must be a branch node because navigation is rooted at a
|
|
45
|
+
* directory-like node. The returned navigation entry is always a `NavBranch`.
|
|
46
|
+
*
|
|
47
|
+
* The TreeNode is authoritative for:
|
|
48
|
+
*
|
|
49
|
+
* - entry identity and order;
|
|
50
|
+
* - whether the entry is a leaf or branch;
|
|
51
|
+
* - whether branch children are loaded; and
|
|
52
|
+
* - the loaded child entries.
|
|
53
|
+
*
|
|
54
|
+
* The optional FoldNode is authoritative for folding. When `foldRoot` is
|
|
55
|
+
* undefined, the projection contains no folded entries.
|
|
56
|
+
*
|
|
57
|
+
* A folded entry is omitted from its parent's navigable entries and is
|
|
58
|
+
* represented by that parent's synthetic `folded` entry instead.
|
|
59
|
+
*/
|
|
60
|
+
from(root: Node & BranchTreeNode<Id, Node>, foldRoot: FoldNode<Id> | undefined): NavBranch<Id>;
|
|
61
|
+
getNodeAtPath(navigation: NavNode<Id>, path: readonly Id[]): NavNode<Id> | undefined;
|
|
62
|
+
getEntryAtPath(navigation: NavNode<Id>, path: readonly Id[]): NavEntry<Id> | undefined;
|
|
63
|
+
nextCursor(rootNode: NavBranch<Id>, cursor: Cursor<Id>): Cursor<Id> | undefined;
|
|
64
|
+
previousCursor(rootNode: NavBranch<Id>, cursor: Cursor<Id>): Cursor<Id> | undefined;
|
|
65
|
+
cursorAfterFold(rootNode: NavBranch<Id>, cursor: Cursor<Id>): Cursor<Id> | undefined;
|
|
66
|
+
parentCursor(navigation: NavNode<Id>, cursor: Cursor<Id>): Cursor<Id> | undefined;
|
|
67
|
+
cursors(navigation: NavNode<Id>, parentPath?: Id[]): Cursor<Id>[];
|
|
68
|
+
visibleLeavesPaths(navigation: NavNode<Id>, parentPath?: readonly Id[]): Id[][];
|
|
40
69
|
};
|
|
41
|
-
export {};
|
|
42
70
|
//# sourceMappingURL=nav-node.types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nav-node.types.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/nav-node/nav-node.types.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,
|
|
1
|
+
{"version":3,"file":"nav-node.types.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/nav-node/nav-node.types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SerializableKey, TreeNode, TreeNodeApi } from '../tree-node/tree-node.types.js';
|
|
2
|
+
import { FoldNodeApi } from '../fold-node/fold-node.types.js';
|
|
3
|
+
import { CursorApi } from '../cursor.js';
|
|
4
|
+
import { NavNodeApi } from '../nav-node/nav-node.types.js';
|
|
5
|
+
import { StateApi } from './state.types.js';
|
|
6
|
+
export declare function createStateApi<Id extends SerializableKey, Node extends TreeNode<Id, Node>>(treeNodeApi: TreeNodeApi<Id, Node>, foldNodeApi: FoldNodeApi<Id>, cursorApi: CursorApi<Id>, navNodeApi: NavNodeApi<Id, Node>): StateApi<Id, Node>;
|
|
7
|
+
//# sourceMappingURL=state.impl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.impl.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/state/state.impl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,eAAe,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACzG,OAAO,EAAY,WAAW,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAU,SAAS,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAS,MAAM,kBAAkB,CAAC;AAEnD,wBAAgB,cAAc,CAC5B,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,EAE/B,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,EAClC,WAAW,EAAE,WAAW,CAAC,EAAE,CAAC,EAC5B,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,EACxB,UAAU,EAAE,UAAU,CAAC,EAAE,EAAE,IAAI,CAAC,GAC/B,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,CAoIpB"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export function createStateApi(treeNodeApi, foldNodeApi, cursorApi, navNodeApi) {
|
|
2
|
+
/**
|
|
3
|
+
* Create a new root and fold root.
|
|
4
|
+
*
|
|
5
|
+
* Works on fold root and descendant fold nodes using the generic.
|
|
6
|
+
*
|
|
7
|
+
* TODO: Later: Filter the fold node
|
|
8
|
+
*/
|
|
9
|
+
async function reload(oldRoot, oldFoldNode, parentPath, loadBranches) {
|
|
10
|
+
// Do not load a branch that was already closed.
|
|
11
|
+
if (!treeNodeApi.isOpenBranch(oldRoot)) {
|
|
12
|
+
return {
|
|
13
|
+
root: oldRoot,
|
|
14
|
+
foldRoot: oldFoldNode,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const newChildren = await loadBranches(parentPath);
|
|
18
|
+
let newRoot = {
|
|
19
|
+
...oldRoot,
|
|
20
|
+
children: newChildren,
|
|
21
|
+
};
|
|
22
|
+
let newFoldNode = oldFoldNode; // Start with assumption of no changes
|
|
23
|
+
for (const oldEntry of oldRoot.children) {
|
|
24
|
+
if (!treeNodeApi.isBranch(oldEntry) ||
|
|
25
|
+
!treeNodeApi.isOpenBranch(oldEntry)) {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const oldChildFoldNode = oldFoldNode === undefined
|
|
29
|
+
? undefined
|
|
30
|
+
: foldNodeApi.getChildById(oldFoldNode, oldEntry.id);
|
|
31
|
+
const entryPath = [...parentPath, oldEntry.id];
|
|
32
|
+
const reloadedChild = await reload(oldEntry, oldChildFoldNode, entryPath, loadBranches);
|
|
33
|
+
const updatedRoot = treeNodeApi.modifyAtPath(newRoot, [oldEntry.id], (entry) => {
|
|
34
|
+
if (!treeNodeApi.isBranch(entry)) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
...entry,
|
|
39
|
+
children: reloadedChild.root.children,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
if (updatedRoot !== undefined && treeNodeApi.isOpenBranch(updatedRoot)) {
|
|
43
|
+
newRoot = updatedRoot;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
root: newRoot,
|
|
48
|
+
foldRoot: newFoldNode,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Currently the cursor just goes to root.
|
|
53
|
+
*
|
|
54
|
+
* TODO: Cursor policy:
|
|
55
|
+
* 1. Keep the exact cursor if it survives,
|
|
56
|
+
* 2. Otherwise, choose the deepest surviving ancestor entry,
|
|
57
|
+
* 3. Otherwise, choose the nearest preceding old cursor that still survives,
|
|
58
|
+
* 4. Otherwise, choose the first new cursor.
|
|
59
|
+
* 5. If no cursor exists, use the root fold cursor.
|
|
60
|
+
*/
|
|
61
|
+
function resyncCursor(oldState, newRoot, newFoldNode) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
getNodeAtCursor(state) {
|
|
66
|
+
const path = cursorApi.getPath(state.cursor);
|
|
67
|
+
return treeNodeApi.getAtPath(state.root, path, (node) => node);
|
|
68
|
+
},
|
|
69
|
+
async resync(oldState, loadChildren) {
|
|
70
|
+
const reloaded = await reload(oldState.root, oldState.foldRoot, [], loadChildren);
|
|
71
|
+
/*
|
|
72
|
+
* `oldState.foldNode` is always present. At the root reload starts with
|
|
73
|
+
* that node, so `reloaded.foldNode` should also always be present.
|
|
74
|
+
*
|
|
75
|
+
* The fallback expresses the State invariant while satisfying the
|
|
76
|
+
* optional return type needed by recursive child reloads.
|
|
77
|
+
*/
|
|
78
|
+
const foldRoot = reloaded.foldRoot ?? oldState.foldRoot;
|
|
79
|
+
const cursor = resyncCursor(oldState, reloaded.root, foldRoot) ?? [];
|
|
80
|
+
const root = reloaded.root;
|
|
81
|
+
return {
|
|
82
|
+
...oldState,
|
|
83
|
+
root,
|
|
84
|
+
foldRoot,
|
|
85
|
+
cursor,
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { SerializableKey, TreeNode } from '../tree-node/tree-node.types.js';
|
|
3
|
+
import { State } from './state.types.js';
|
|
4
|
+
export declare function createStateSchema<Id extends SerializableKey, Node extends TreeNode<Id, Node>>(rootSchema: z.ZodType<State<Id, Node>['root']>, foldNodeRootSchema: z.ZodType<State<Id, Node>['foldRoot']>, cursorSchema: z.ZodType<State<Id, Node>['cursor']>): z.ZodObject<{
|
|
5
|
+
root: z.ZodType<Node & import("../tree-node/tree-node.types.js").BranchTreeNode<Id, Node>, unknown, z.core.$ZodTypeInternals<Node & import("../tree-node/tree-node.types.js").BranchTreeNode<Id, Node>, unknown>>;
|
|
6
|
+
foldRoot: z.ZodType<import("../index.js").FoldNode<Id>, unknown, z.core.$ZodTypeInternals<import("../index.js").FoldNode<Id>, unknown>>;
|
|
7
|
+
cursor: z.ZodType<import("../cursor.js").Cursor<Id>, unknown, z.core.$ZodTypeInternals<import("../cursor.js").Cursor<Id>, unknown>>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
//# sourceMappingURL=state.schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.schemas.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/state/state.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAC5E,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,wBAAgB,iBAAiB,CAC/B,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,EAE/B,UAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,EAC9C,kBAAkB,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,EAC1D,YAAY,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC;;;;kBAOnD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Cursor } from '../cursor.js';
|
|
2
|
+
import { BranchTreeNode, SerializableKey, TreeNode } from '../tree-node/tree-node.types.js';
|
|
3
|
+
import type { FoldNode } from '../fold-node/fold-node.types.js';
|
|
4
|
+
export type State<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = {
|
|
5
|
+
root: Node & BranchTreeNode<Id, Node>;
|
|
6
|
+
foldRoot: FoldNode<Id>;
|
|
7
|
+
cursor: Cursor<Id>;
|
|
8
|
+
};
|
|
9
|
+
export type StateApi<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = {
|
|
10
|
+
getNodeAtCursor(state: State<Id, Node>): Node | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Reloads the root's children and all descendant branches that were loaded in
|
|
13
|
+
* the previous state.
|
|
14
|
+
*
|
|
15
|
+
* `loadBranches([])` loads the root's direct children.
|
|
16
|
+
* `loadBranches(['src'])` loads the children of `src`, where 'src' is the
|
|
17
|
+
* id of a root's child.
|
|
18
|
+
*/
|
|
19
|
+
resync(oldState: State<Id, Node>, loadBranches: (path: Id[]) => Promise<Node[]>): Promise<State<Id, Node>>;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=state.types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.types.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/state/state.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EACL,cAAc,EACd,eAAe,EACf,QAAQ,EACT,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAEhE,MAAM,MAAM,KAAK,CACf,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B;IACF,IAAI,EAAE,IAAI,GAAG,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACtC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,QAAQ,CAAC,EAAE,SAAS,eAAe,EAAE,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI;IAClF,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC;IAE1D;;;;;;;OAOG;IACH,MAAM,CACJ,QAAQ,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,EACzB,YAAY,EAAE,CAAC,IAAI,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,EAAE,CAAC,GAC5C,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC,CAAC;CAC7B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|