@gtkx/components 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/adw/toast.d.ts.map +1 -1
- package/dist/adw/toast.js +6 -2
- package/dist/adw/toast.js.map +1 -1
- package/dist/internal/cells.js +1 -1
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/collection-index.d.ts +4 -3
- package/dist/internal/collection-index.d.ts.map +1 -1
- package/dist/internal/collection-index.js +50 -33
- package/dist/internal/collection-index.js.map +1 -1
- package/dist/internal/collection-model.d.ts +3 -4
- package/dist/internal/collection-model.d.ts.map +1 -1
- package/dist/internal/collection-model.js +110 -74
- package/dist/internal/collection-model.js.map +1 -1
- package/dist/internal/collection.d.ts +2 -1
- package/dist/internal/collection.d.ts.map +1 -1
- package/dist/internal/collection.js +7 -6
- package/dist/internal/collection.js.map +1 -1
- package/dist/internal/expansion.d.ts.map +1 -1
- package/dist/internal/expansion.js +11 -19
- package/dist/internal/expansion.js.map +1 -1
- package/dist/internal/tree-expansion.d.ts +1 -2
- package/dist/internal/tree-expansion.d.ts.map +1 -1
- package/dist/internal/tree-expansion.js +12 -8
- package/dist/internal/tree-expansion.js.map +1 -1
- package/dist/internal/tree-order.d.ts +3 -4
- package/dist/internal/tree-order.d.ts.map +1 -1
- package/dist/internal/tree-order.js +61 -23
- package/dist/internal/tree-order.js.map +1 -1
- package/dist/types.d.ts +3 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +17 -9
- package/src/adw/toast.tsx +6 -2
- package/src/internal/cells.tsx +1 -1
- package/src/internal/collection-index.ts +63 -39
- package/src/internal/collection-model.ts +148 -85
- package/src/internal/collection.ts +10 -8
- package/src/internal/expansion.ts +12 -24
- package/src/internal/tree-expansion.ts +14 -9
- package/src/internal/tree-order.ts +98 -28
- package/src/types.ts +3 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ListItem, ListSection } from "../types.js";
|
|
2
|
-
import { encodePart } from "./keys.js";
|
|
2
|
+
import { decodePartAt, encodePart } from "./keys.js";
|
|
3
3
|
|
|
4
4
|
type Level = {
|
|
5
5
|
path: string;
|
|
@@ -10,83 +10,109 @@ type Level = {
|
|
|
10
10
|
type CollectionIndex = {
|
|
11
11
|
isTree: boolean;
|
|
12
12
|
groups: Level[];
|
|
13
|
-
|
|
13
|
+
childLevel: (level: Level, slot: number) => Level | undefined;
|
|
14
|
+
levelFor: (levelPath: string) => Level | undefined;
|
|
14
15
|
itemAt: (levelPath: string, slot: number) => ListItem | undefined;
|
|
15
16
|
sectionFor: (levelPath: string) => unknown;
|
|
16
|
-
expandablePathsFor: (id: string) => string[];
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
type IndexState = {
|
|
20
20
|
isTree: boolean;
|
|
21
21
|
groups: Level[];
|
|
22
|
-
children: Map<string, Level>;
|
|
23
22
|
levels: Map<string, Level>;
|
|
24
|
-
expandablePaths: Map<string, string[]>;
|
|
25
23
|
sectionValues: Map<string, unknown>;
|
|
26
24
|
};
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
type ParentItem = ListItem & { children: ListItem[] };
|
|
29
27
|
|
|
30
|
-
const
|
|
28
|
+
const emptyLevel = (): Level => ({ path: "", items: [], expandableFlags: [] });
|
|
29
|
+
const hasChildren = (item: ListItem): item is ParentItem => item.children !== undefined && item.children.length > 0;
|
|
31
30
|
|
|
32
|
-
function
|
|
33
|
-
const
|
|
31
|
+
function newLevel(state: IndexState, path: string, items: ListItem[]): Level {
|
|
32
|
+
const expandableFlags = state.isTree ? items.map((item) => hasChildren(item)) : [];
|
|
33
|
+
const level: Level = { path, items, expandableFlags };
|
|
34
|
+
state.levels.set(path, level);
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
36
|
+
return level;
|
|
37
|
+
}
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
function slotItems(state: IndexState, parent: Level, slot: number): ListItem[] | undefined {
|
|
40
|
+
const owner = state.levels.get(parent.path) === parent ? parent : levelFor(state, parent.path);
|
|
41
|
+
const item = owner?.items[slot];
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
return item !== undefined && hasChildren(item) ? item.children : undefined;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
function childLevel(state: IndexState, parent: Level, slot: number): Level | undefined {
|
|
47
|
+
if (!state.isTree) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
47
50
|
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
const path = parent.path + encodePart(String(slot));
|
|
52
|
+
const cached = state.levels.get(path);
|
|
53
|
+
|
|
54
|
+
if (cached !== undefined) {
|
|
55
|
+
return cached;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
const items = slotItems(state, parent, slot);
|
|
59
|
+
|
|
60
|
+
return items === undefined ? undefined : newLevel(state, path, items);
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
63
|
+
function descendPath(state: IndexState, levelPath: string, group: string): Level | undefined {
|
|
64
|
+
let level = state.levels.get(encodePart(group));
|
|
65
|
+
let offset = encodePart(group).length;
|
|
59
66
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
67
|
+
while (level !== undefined && offset < levelPath.length) {
|
|
68
|
+
const part = decodePartAt(levelPath, offset);
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
if (part === null) {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
level = childLevel(state, level, Number(part));
|
|
75
|
+
offset += encodePart(part).length;
|
|
66
76
|
}
|
|
67
77
|
|
|
68
78
|
return level;
|
|
69
79
|
}
|
|
70
80
|
|
|
81
|
+
function levelFor(state: IndexState, levelPath: string): Level | undefined {
|
|
82
|
+
const cached = state.levels.get(levelPath);
|
|
83
|
+
|
|
84
|
+
if (cached !== undefined) {
|
|
85
|
+
return cached;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const group = decodePartAt(levelPath, 0);
|
|
89
|
+
|
|
90
|
+
return group === null ? undefined : descendPath(state, levelPath, group);
|
|
91
|
+
}
|
|
92
|
+
|
|
71
93
|
function buildGroups(state: IndexState, source: ListItem[], sections: ListSection[] | undefined): Level[] {
|
|
72
94
|
if (sections === undefined) {
|
|
73
|
-
return [
|
|
95
|
+
return [newLevel(state, encodePart("0"), source)];
|
|
74
96
|
}
|
|
75
97
|
|
|
76
98
|
return sections.map((section, group) => {
|
|
77
99
|
const path = encodePart(String(group));
|
|
78
100
|
state.sectionValues.set(path, section.value);
|
|
79
101
|
|
|
80
|
-
return
|
|
102
|
+
return newLevel(state, path, section.data);
|
|
81
103
|
});
|
|
82
104
|
}
|
|
83
105
|
|
|
84
106
|
function isTreeSource(source: ListItem[], sections: ListSection[] | undefined, isFlat: boolean): boolean {
|
|
85
|
-
if (
|
|
107
|
+
if (isFlat) {
|
|
86
108
|
return false;
|
|
87
109
|
}
|
|
88
110
|
|
|
89
|
-
|
|
111
|
+
if (sections === undefined) {
|
|
112
|
+
return source.some((item) => hasChildren(item));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return sections.some((section) => section.data.some((item) => hasChildren(item)));
|
|
90
116
|
}
|
|
91
117
|
|
|
92
118
|
function createCollectionIndex(
|
|
@@ -99,9 +125,7 @@ function createCollectionIndex(
|
|
|
99
125
|
const state: IndexState = {
|
|
100
126
|
isTree: isTreeSource(source, sections, isFlat),
|
|
101
127
|
groups: [],
|
|
102
|
-
children: new Map(),
|
|
103
128
|
levels: new Map(),
|
|
104
|
-
expandablePaths: new Map(),
|
|
105
129
|
sectionValues: new Map(),
|
|
106
130
|
};
|
|
107
131
|
|
|
@@ -110,11 +134,11 @@ function createCollectionIndex(
|
|
|
110
134
|
return {
|
|
111
135
|
isTree: state.isTree,
|
|
112
136
|
groups: state.groups,
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
childLevel: (level, slot) => childLevel(state, level, slot),
|
|
138
|
+
levelFor: (levelPath) => levelFor(state, levelPath),
|
|
139
|
+
itemAt: (levelPath, slot) => levelFor(state, levelPath)?.items[slot],
|
|
115
140
|
sectionFor: (levelPath) => state.sectionValues.get(levelPath),
|
|
116
|
-
expandablePathsFor: (id) => state.expandablePaths.get(id) ?? NO_PATHS,
|
|
117
141
|
};
|
|
118
142
|
}
|
|
119
143
|
|
|
120
|
-
export { createCollectionIndex, type CollectionIndex, type Level };
|
|
144
|
+
export { createCollectionIndex, emptyLevel, type CollectionIndex, type Level };
|
|
@@ -4,9 +4,9 @@ import * as Gtk from "@gtkx/gi/gtk";
|
|
|
4
4
|
import { registerClass } from "@gtkx/runtime";
|
|
5
5
|
import type { CollectionIndex, Level } from "./collection-index.js";
|
|
6
6
|
import type { TreeExpansion } from "./tree-expansion.js";
|
|
7
|
-
import { createCollectionIndex } from "./collection-index.js";
|
|
7
|
+
import { createCollectionIndex, emptyLevel } from "./collection-index.js";
|
|
8
8
|
import { encodePart } from "./keys.js";
|
|
9
|
-
import { adoptIndex, createTreeExpansion, pruneSlots
|
|
9
|
+
import { adoptIndex, createTreeExpansion, pruneSlots } from "./tree-expansion.js";
|
|
10
10
|
|
|
11
11
|
type LevelStore = Gio.ListModel & LazyLevelStore;
|
|
12
12
|
|
|
@@ -25,20 +25,26 @@ type SlotRun = {
|
|
|
25
25
|
length: number;
|
|
26
26
|
};
|
|
27
27
|
|
|
28
|
+
type LevelSync = {
|
|
29
|
+
store: LevelStore;
|
|
30
|
+
level: Level;
|
|
31
|
+
};
|
|
32
|
+
|
|
28
33
|
type ModelState = {
|
|
29
34
|
root: Gio.ListStore;
|
|
30
35
|
rootModels: GObject.Object[];
|
|
31
36
|
model: Gtk.FlattenListModel;
|
|
32
37
|
groupStores: LevelStore[];
|
|
33
|
-
|
|
34
|
-
|
|
38
|
+
trees: Map<LevelStore, Gtk.TreeListModel>;
|
|
39
|
+
treeModels: Gtk.TreeListModel[];
|
|
35
40
|
expansion: TreeExpansion;
|
|
41
|
+
index: CollectionIndex;
|
|
36
42
|
};
|
|
37
43
|
|
|
38
44
|
type CollectionModel = {
|
|
39
45
|
model: Gtk.FlattenListModel;
|
|
40
46
|
expansion: TreeExpansion;
|
|
41
|
-
|
|
47
|
+
rowAt: (position: number) => Gtk.TreeListRow | null;
|
|
42
48
|
sync: (index: CollectionIndex) => void;
|
|
43
49
|
};
|
|
44
50
|
|
|
@@ -88,55 +94,30 @@ function slotRefFor(value: GObject.Object | null): SlotRef | null {
|
|
|
88
94
|
}
|
|
89
95
|
|
|
90
96
|
function slotPathAt(store: LevelStore, slot: number): string {
|
|
91
|
-
return store.path + encodePart(String(slot));
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function newLevelStore(path: string): LevelStore {
|
|
95
|
-
const store = new (registeredStoreClass())() as LevelStore;
|
|
96
|
-
store.path = path;
|
|
97
|
-
|
|
98
|
-
return store;
|
|
97
|
+
return store.level.path + encodePart(String(slot));
|
|
99
98
|
}
|
|
100
99
|
|
|
101
|
-
function
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
function growStore(store: LevelStore): void {
|
|
101
|
+
for (let slot = store.refs.length; slot < store.level.items.length; slot++) {
|
|
102
|
+
store.refs.push({ store, slot });
|
|
103
|
+
store.objects.push(null);
|
|
104
|
+
store.childStores.push(null);
|
|
106
105
|
}
|
|
107
|
-
|
|
108
|
-
const store = newLevelStore(path);
|
|
109
|
-
growStore(context, store, level);
|
|
110
|
-
|
|
111
|
-
return store;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function pushSlot(context: SyncContext, store: LevelStore, level: Level, slot: number): void {
|
|
115
|
-
const canExpand = level.expandableFlags[slot] ?? false;
|
|
116
|
-
store.refs.push({ store, slot });
|
|
117
|
-
store.objects.push(null);
|
|
118
|
-
store.expandableFlags.push(canExpand);
|
|
119
|
-
store.childStores.push(canExpand ? buildChildStore(context, slotPathAt(store, slot)) : null);
|
|
120
106
|
}
|
|
121
107
|
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
108
|
+
function newLevelStore(level: Level): LevelStore {
|
|
109
|
+
const store = new (registeredStoreClass())() as LevelStore;
|
|
110
|
+
store.level = level;
|
|
111
|
+
growStore(store);
|
|
127
112
|
|
|
128
|
-
|
|
129
|
-
const canExpand = !(store.expandableFlags[slot] ?? false);
|
|
130
|
-
store.expandableFlags[slot] = canExpand;
|
|
131
|
-
store.childStores[slot] = canExpand ? buildChildStore(context, slotPathAt(store, slot)) : null;
|
|
113
|
+
return store;
|
|
132
114
|
}
|
|
133
115
|
|
|
134
|
-
function collectFlips(
|
|
135
|
-
const overlap = Math.min(store.refs.length, level.items.length);
|
|
116
|
+
function collectFlips(previous: Level, next: Level, overlap: number): Set<number> {
|
|
136
117
|
const flipped: Set<number> = new Set();
|
|
137
118
|
|
|
138
119
|
for (let slot = 0; slot < overlap; slot++) {
|
|
139
|
-
if ((
|
|
120
|
+
if ((previous.expandableFlags[slot] ?? false) !== (next.expandableFlags[slot] ?? false)) {
|
|
140
121
|
flipped.add(slot);
|
|
141
122
|
}
|
|
142
123
|
}
|
|
@@ -144,20 +125,16 @@ function collectFlips(store: LevelStore, level: Level): Set<number> {
|
|
|
144
125
|
return flipped;
|
|
145
126
|
}
|
|
146
127
|
|
|
147
|
-
function
|
|
148
|
-
const flipped = collectFlips(store, level);
|
|
149
|
-
|
|
128
|
+
function pruneFlips(context: SyncContext, store: LevelStore, flipped: Set<number>): void {
|
|
150
129
|
if (flipped.size === 0) {
|
|
151
|
-
return
|
|
130
|
+
return;
|
|
152
131
|
}
|
|
153
132
|
|
|
154
|
-
pruneSlots(context.expansion, store.path, (slot) => flipped.has(slot));
|
|
133
|
+
pruneSlots(context.expansion, store.level.path, (slot) => flipped.has(slot));
|
|
155
134
|
|
|
156
135
|
for (const slot of flipped) {
|
|
157
|
-
|
|
136
|
+
store.childStores[slot] = null;
|
|
158
137
|
}
|
|
159
|
-
|
|
160
|
-
return flipped;
|
|
161
138
|
}
|
|
162
139
|
|
|
163
140
|
function detachRefs(store: LevelStore, nextLength: number): void {
|
|
@@ -176,11 +153,10 @@ function shrinkStore(context: SyncContext, store: LevelStore, nextLength: number
|
|
|
176
153
|
}
|
|
177
154
|
|
|
178
155
|
detachRefs(store, nextLength);
|
|
179
|
-
pruneSlots(context.expansion, store.path, (slot) => slot >= nextLength);
|
|
156
|
+
pruneSlots(context.expansion, store.level.path, (slot) => slot >= nextLength);
|
|
180
157
|
store.refs.length = nextLength;
|
|
181
158
|
store.objects.length = nextLength;
|
|
182
159
|
store.childStores.length = nextLength;
|
|
183
|
-
store.expandableFlags.length = nextLength;
|
|
184
160
|
}
|
|
185
161
|
|
|
186
162
|
function emitTailSplice(store: LevelStore, previousLength: number, nextLength: number): void {
|
|
@@ -223,61 +199,150 @@ function emitFlips(store: LevelStore, flipped: Set<number>): void {
|
|
|
223
199
|
}
|
|
224
200
|
}
|
|
225
201
|
|
|
226
|
-
function
|
|
227
|
-
|
|
202
|
+
function childSync(
|
|
203
|
+
context: SyncContext,
|
|
204
|
+
store: LevelStore,
|
|
205
|
+
slot: number,
|
|
206
|
+
flipped: Set<number>,
|
|
207
|
+
): LevelSync | undefined {
|
|
208
|
+
const child = store.childStores[slot] ?? null;
|
|
228
209
|
|
|
229
|
-
if (child
|
|
230
|
-
return;
|
|
210
|
+
if (child === null || flipped.has(slot)) {
|
|
211
|
+
return undefined;
|
|
231
212
|
}
|
|
232
213
|
|
|
233
|
-
const level = context.index.
|
|
214
|
+
const level = context.index.childLevel(store.level, slot);
|
|
215
|
+
|
|
216
|
+
return level === undefined ? undefined : { store: child, level };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function childSyncs(context: SyncContext, store: LevelStore, overlap: number, flipped: Set<number>): LevelSync[] {
|
|
220
|
+
const syncs: LevelSync[] = [];
|
|
234
221
|
|
|
235
|
-
|
|
236
|
-
|
|
222
|
+
for (let slot = 0; slot < overlap; slot++) {
|
|
223
|
+
const sync = childSync(context, store, slot, flipped);
|
|
224
|
+
|
|
225
|
+
if (sync !== undefined) {
|
|
226
|
+
syncs.push(sync);
|
|
227
|
+
}
|
|
237
228
|
}
|
|
229
|
+
|
|
230
|
+
return syncs;
|
|
238
231
|
}
|
|
239
232
|
|
|
240
|
-
function
|
|
233
|
+
function syncEntry(context: SyncContext, entry: LevelSync): LevelSync[] {
|
|
234
|
+
const { store, level } = entry;
|
|
241
235
|
const previousLength = store.refs.length;
|
|
242
236
|
const overlap = Math.min(previousLength, level.items.length);
|
|
243
|
-
const flipped =
|
|
237
|
+
const flipped = collectFlips(store.level, level, overlap);
|
|
238
|
+
store.level = level;
|
|
239
|
+
pruneFlips(context, store, flipped);
|
|
244
240
|
shrinkStore(context, store, level.items.length);
|
|
245
|
-
growStore(
|
|
241
|
+
growStore(store);
|
|
246
242
|
emitTailSplice(store, previousLength, level.items.length);
|
|
247
243
|
emitFlips(store, flipped);
|
|
248
244
|
|
|
249
|
-
|
|
250
|
-
|
|
245
|
+
return childSyncs(context, store, overlap, flipped);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function pushSyncs(pending: LevelSync[], syncs: LevelSync[]): void {
|
|
249
|
+
for (const sync of syncs) {
|
|
250
|
+
pending.push(sync);
|
|
251
251
|
}
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
-
function
|
|
254
|
+
function syncLevel(context: SyncContext, store: LevelStore, level: Level): void {
|
|
255
|
+
const pending: LevelSync[] = [{ store, level }];
|
|
256
|
+
|
|
257
|
+
while (pending.length > 0) {
|
|
258
|
+
const entry = pending.pop();
|
|
259
|
+
|
|
260
|
+
if (entry !== undefined) {
|
|
261
|
+
pushSyncs(pending, syncEntry(context, entry).toReversed());
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function ensureChildStore(index: CollectionIndex, store: LevelStore, slot: number): LevelStore | null {
|
|
267
|
+
const existing = store.childStores[slot] ?? null;
|
|
268
|
+
|
|
269
|
+
if (existing !== null || !(store.level.expandableFlags[slot] ?? false)) {
|
|
270
|
+
return existing;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const level = index.childLevel(store.level, slot);
|
|
274
|
+
|
|
275
|
+
if (level === undefined) {
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const created = newLevelStore(level);
|
|
280
|
+
store.childStores[slot] = created;
|
|
281
|
+
|
|
282
|
+
return created;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function childStoreFor(state: ModelState, object: GObject.Object): Gio.ListModel | null {
|
|
255
286
|
const ref = slotRefFor(object);
|
|
256
287
|
|
|
257
288
|
if (ref === null) {
|
|
258
289
|
return null;
|
|
259
290
|
}
|
|
260
291
|
|
|
261
|
-
return ref.store
|
|
292
|
+
return ensureChildStore(state.index, ref.store, ref.slot);
|
|
262
293
|
}
|
|
263
294
|
|
|
264
|
-
function
|
|
265
|
-
|
|
266
|
-
state.
|
|
267
|
-
|
|
295
|
+
function treeFor(state: ModelState, store: LevelStore): Gtk.TreeListModel {
|
|
296
|
+
return (
|
|
297
|
+
state.trees.get(store) ?? Gtk.TreeListModel.new(store, false, false, (object) => childStoreFor(state, object))
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function nextTrees(state: ModelState, index: CollectionIndex): Map<LevelStore, Gtk.TreeListModel> {
|
|
302
|
+
if (!index.isTree) {
|
|
303
|
+
return new Map();
|
|
268
304
|
}
|
|
269
305
|
|
|
270
|
-
return state.
|
|
306
|
+
return new Map(state.groupStores.map((store) => [store, treeFor(state, store)]));
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function pruneRebuiltLevels(state: ModelState, next: Map<LevelStore, Gtk.TreeListModel>): void {
|
|
310
|
+
const stores: Set<LevelStore> = new Set([...state.trees.keys(), ...next.keys()]);
|
|
311
|
+
|
|
312
|
+
for (const store of stores) {
|
|
313
|
+
if (state.trees.get(store) !== next.get(store)) {
|
|
314
|
+
pruneSlots(state.expansion, store.level.path, () => true);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function adoptTrees(state: ModelState, index: CollectionIndex): void {
|
|
320
|
+
const next = nextTrees(state, index);
|
|
321
|
+
pruneRebuiltLevels(state, next);
|
|
322
|
+
state.trees = next;
|
|
323
|
+
state.treeModels = next.values().toArray();
|
|
271
324
|
}
|
|
272
325
|
|
|
273
326
|
function desiredRootModels(state: ModelState, index: CollectionIndex): GObject.Object[] {
|
|
274
|
-
|
|
327
|
+
adoptTrees(state, index);
|
|
328
|
+
|
|
329
|
+
return index.isTree ? [...state.treeModels] : [...state.groupStores];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function rowAt(state: ModelState, position: number): Gtk.TreeListRow | null {
|
|
333
|
+
let offset = position;
|
|
334
|
+
|
|
335
|
+
for (const tree of state.treeModels) {
|
|
336
|
+
const count = tree.getNItems();
|
|
337
|
+
|
|
338
|
+
if (offset < count) {
|
|
339
|
+
return tree.getRow(offset);
|
|
340
|
+
}
|
|
275
341
|
|
|
276
|
-
|
|
277
|
-
return [ensureTree(state, first)];
|
|
342
|
+
offset -= count;
|
|
278
343
|
}
|
|
279
344
|
|
|
280
|
-
return
|
|
345
|
+
return null;
|
|
281
346
|
}
|
|
282
347
|
|
|
283
348
|
function hasSameModels(previous: GObject.Object[], next: GObject.Object[]): boolean {
|
|
@@ -293,7 +358,6 @@ function syncRoot(state: ModelState, index: CollectionIndex): void {
|
|
|
293
358
|
|
|
294
359
|
state.root.splice(0, state.rootModels.length, next);
|
|
295
360
|
state.rootModels = next;
|
|
296
|
-
resetExpansion(state.expansion);
|
|
297
361
|
}
|
|
298
362
|
|
|
299
363
|
function adjustGroupStores(state: ModelState, context: SyncContext): void {
|
|
@@ -301,9 +365,7 @@ function adjustGroupStores(state: ModelState, context: SyncContext): void {
|
|
|
301
365
|
state.groupStores.length = Math.min(state.groupStores.length, groups.length);
|
|
302
366
|
|
|
303
367
|
for (const level of groups.slice(state.groupStores.length)) {
|
|
304
|
-
|
|
305
|
-
growStore(context, store, level);
|
|
306
|
-
state.groupStores.push(store);
|
|
368
|
+
state.groupStores.push(newLevelStore(level));
|
|
307
369
|
}
|
|
308
370
|
}
|
|
309
371
|
|
|
@@ -320,6 +382,7 @@ function stepGroupStores(state: ModelState, context: SyncContext, surviving: num
|
|
|
320
382
|
function syncModel(state: ModelState, index: CollectionIndex): void {
|
|
321
383
|
const context: SyncContext = { index, expansion: state.expansion };
|
|
322
384
|
const surviving = Math.min(state.groupStores.length, index.groups.length);
|
|
385
|
+
state.index = index;
|
|
323
386
|
state.expansion.isSyncing = true;
|
|
324
387
|
|
|
325
388
|
try {
|
|
@@ -342,15 +405,16 @@ function createCollectionModel(): CollectionModel {
|
|
|
342
405
|
rootModels: [],
|
|
343
406
|
model: Gtk.FlattenListModel.new(root),
|
|
344
407
|
groupStores: [],
|
|
345
|
-
|
|
346
|
-
|
|
408
|
+
trees: new Map(),
|
|
409
|
+
treeModels: [],
|
|
347
410
|
expansion: createTreeExpansion(EMPTY_INDEX),
|
|
411
|
+
index: EMPTY_INDEX,
|
|
348
412
|
};
|
|
349
413
|
|
|
350
414
|
return {
|
|
351
415
|
model: state.model,
|
|
352
416
|
expansion: state.expansion,
|
|
353
|
-
|
|
417
|
+
rowAt: (position) => rowAt(state, position),
|
|
354
418
|
sync: (index) => {
|
|
355
419
|
syncModel(state, index);
|
|
356
420
|
},
|
|
@@ -359,11 +423,10 @@ function createCollectionModel(): CollectionModel {
|
|
|
359
423
|
|
|
360
424
|
class LazyLevelStore extends GObject.Object implements Gio.ListModelImpl {
|
|
361
425
|
declare itemsChanged: Gio.ListModel["itemsChanged"];
|
|
362
|
-
|
|
426
|
+
level: Level = emptyLevel();
|
|
363
427
|
refs: SlotRef[] = [];
|
|
364
428
|
objects: (Gtk.StringObject | null)[] = [];
|
|
365
429
|
childStores: (LevelStore | null)[] = [];
|
|
366
|
-
expandableFlags: boolean[] = [];
|
|
367
430
|
|
|
368
431
|
private createItem(position: number, ref: SlotRef): Gtk.StringObject {
|
|
369
432
|
const created = Gtk.StringObject.new("");
|
|
@@ -4,7 +4,8 @@ import type { CollectionModel, SlotRef } from "./collection-model.js";
|
|
|
4
4
|
import { slotPathAt, slotRefFor } from "./collection-model.js";
|
|
5
5
|
import { findPositions } from "./tree-order.js";
|
|
6
6
|
|
|
7
|
-
type Collection = Pick<CollectionModel, "model" | "expansion" | "
|
|
7
|
+
type Collection = Pick<CollectionModel, "model" | "expansion" | "rowAt"> & {
|
|
8
|
+
isTree: boolean;
|
|
8
9
|
itemAt: (ref: SlotRef) => ListItem | undefined;
|
|
9
10
|
sectionFor: (levelPath: string) => unknown;
|
|
10
11
|
idAt: (position: number) => string | null;
|
|
@@ -39,14 +40,14 @@ function refAt(collectionModel: CollectionModel, position: number): SlotRef | nu
|
|
|
39
40
|
return slotRefFor(collectionModel.model.getItem(position));
|
|
40
41
|
}
|
|
41
42
|
|
|
43
|
+
function itemAt(index: CollectionIndex, ref: SlotRef): ListItem | undefined {
|
|
44
|
+
return index.itemAt(ref.store.level.path, ref.slot);
|
|
45
|
+
}
|
|
46
|
+
|
|
42
47
|
function idAt(collectionModel: CollectionModel, index: CollectionIndex, position: number): string | null {
|
|
43
48
|
const ref = refAt(collectionModel, position);
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return index.itemAt(ref.store.path, ref.slot)?.id ?? null;
|
|
50
|
+
return ref === null ? null : (itemAt(index, ref)?.id ?? null);
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
function pathAt(collectionModel: CollectionModel, position: number): string | null {
|
|
@@ -69,8 +70,9 @@ function createCollection(collectionModel: CollectionModel, index: CollectionInd
|
|
|
69
70
|
return {
|
|
70
71
|
model: collectionModel.model,
|
|
71
72
|
expansion: collectionModel.expansion,
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
isTree: index.isTree,
|
|
74
|
+
rowAt: collectionModel.rowAt,
|
|
75
|
+
itemAt: (ref) => itemAt(index, ref),
|
|
74
76
|
sectionFor: index.sectionFor,
|
|
75
77
|
idAt: (position) => idAt(collectionModel, index, position),
|
|
76
78
|
pathAt: (position) => pathAt(collectionModel, position),
|