@gtkx/components 1.0.0-rc.4 → 1.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/README.md +5 -5
- package/dist/column-view.d.ts +2 -2
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +14 -12
- package/dist/column-view.js.map +1 -1
- package/dist/grid-view.js +1 -1
- package/dist/grid-view.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +31 -23
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +168 -77
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/collection-index.d.ts +6 -10
- package/dist/internal/collection-index.d.ts.map +1 -1
- package/dist/internal/collection-index.js +32 -69
- package/dist/internal/collection-index.js.map +1 -1
- package/dist/internal/collection-model.d.ts +23 -2
- package/dist/internal/collection-model.d.ts.map +1 -1
- package/dist/internal/collection-model.js +210 -95
- package/dist/internal/collection-model.js.map +1 -1
- package/dist/internal/collection.d.ts +8 -11
- package/dist/internal/collection.d.ts.map +1 -1
- package/dist/internal/collection.js +38 -49
- package/dist/internal/collection.js.map +1 -1
- package/dist/internal/controlled-sync.d.ts +11 -0
- package/dist/internal/controlled-sync.d.ts.map +1 -0
- package/dist/internal/controlled-sync.js +19 -0
- package/dist/internal/controlled-sync.js.map +1 -0
- package/dist/internal/drop-down.d.ts.map +1 -1
- package/dist/internal/drop-down.js +27 -15
- package/dist/internal/drop-down.js.map +1 -1
- package/dist/internal/expansion.d.ts +3 -2
- package/dist/internal/expansion.d.ts.map +1 -1
- package/dist/internal/expansion.js +80 -47
- package/dist/internal/expansion.js.map +1 -1
- package/dist/internal/keys.d.ts +5 -0
- package/dist/internal/keys.d.ts.map +1 -0
- package/dist/internal/keys.js +13 -0
- package/dist/internal/keys.js.map +1 -0
- package/dist/internal/selection.d.ts +3 -2
- package/dist/internal/selection.d.ts.map +1 -1
- package/dist/internal/selection.js +68 -35
- package/dist/internal/selection.js.map +1 -1
- package/dist/internal/slots.d.ts +10 -0
- package/dist/internal/slots.d.ts.map +1 -0
- package/dist/internal/slots.js +43 -0
- package/dist/internal/slots.js.map +1 -0
- package/dist/internal/tree-expansion.d.ts +20 -0
- package/dist/internal/tree-expansion.d.ts.map +1 -0
- package/dist/internal/tree-expansion.js +68 -0
- package/dist/internal/tree-expansion.js.map +1 -0
- package/dist/internal/tree-order.d.ts +27 -0
- package/dist/internal/tree-order.d.ts.map +1 -0
- package/dist/internal/tree-order.js +71 -0
- package/dist/internal/tree-order.js.map +1 -0
- package/dist/internal/use-collection.d.ts +1 -1
- package/dist/internal/use-collection.d.ts.map +1 -1
- package/dist/internal/use-collection.js +4 -4
- package/dist/internal/use-collection.js.map +1 -1
- package/dist/list-view.d.ts.map +1 -1
- package/dist/list-view.js +6 -6
- package/dist/list-view.js.map +1 -1
- package/dist/types.d.ts +70 -21
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +7 -7
- package/src/column-view.tsx +28 -25
- package/src/grid-view.tsx +1 -1
- package/src/index.ts +3 -0
- package/src/internal/cells.tsx +339 -117
- package/src/internal/collection-index.ts +44 -92
- package/src/internal/collection-model.ts +259 -107
- package/src/internal/collection.ts +45 -67
- package/src/internal/controlled-sync.ts +34 -0
- package/src/internal/drop-down.tsx +41 -20
- package/src/internal/expansion.ts +104 -67
- package/src/internal/keys.ts +18 -0
- package/src/internal/selection.tsx +100 -51
- package/src/internal/slots.ts +67 -0
- package/src/internal/tree-expansion.ts +110 -0
- package/src/internal/tree-order.ts +138 -0
- package/src/internal/use-collection.tsx +5 -5
- package/src/list-view.tsx +14 -10
- package/src/types.ts +76 -19
|
@@ -1,178 +1,283 @@
|
|
|
1
1
|
import * as Gio from "@gtkx/gi/gio";
|
|
2
2
|
import * as GObject from "@gtkx/gi/gobject";
|
|
3
3
|
import * as Gtk from "@gtkx/gi/gtk";
|
|
4
|
+
import { registerClass } from "@gtkx/runtime";
|
|
4
5
|
import type { CollectionIndex, Level } from "./collection-index.js";
|
|
5
|
-
import {
|
|
6
|
+
import type { TreeExpansion } from "./tree-expansion.js";
|
|
7
|
+
import { createCollectionIndex } from "./collection-index.js";
|
|
8
|
+
import { encodePart } from "./keys.js";
|
|
9
|
+
import { adoptIndex, createTreeExpansion, pruneSlots, resetExpansion } from "./tree-expansion.js";
|
|
6
10
|
|
|
7
|
-
type
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
type LevelStore = Gio.ListModel & LazyLevelStore;
|
|
12
|
+
|
|
13
|
+
type SlotRef = {
|
|
14
|
+
store: LevelStore;
|
|
15
|
+
slot: number;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type SyncContext = {
|
|
19
|
+
index: CollectionIndex;
|
|
20
|
+
expansion: TreeExpansion;
|
|
11
21
|
};
|
|
12
22
|
|
|
13
|
-
type
|
|
23
|
+
type SlotRun = {
|
|
14
24
|
start: number;
|
|
15
|
-
|
|
16
|
-
added: string[];
|
|
25
|
+
length: number;
|
|
17
26
|
};
|
|
18
27
|
|
|
19
28
|
type ModelState = {
|
|
20
29
|
root: Gio.ListStore;
|
|
21
30
|
rootModels: GObject.Object[];
|
|
22
31
|
model: Gtk.FlattenListModel;
|
|
23
|
-
|
|
24
|
-
levels: Map<string, LevelState>;
|
|
32
|
+
groupStores: LevelStore[];
|
|
25
33
|
tree: Gtk.TreeListModel | null;
|
|
34
|
+
treeRoot: LevelStore | null;
|
|
35
|
+
expansion: TreeExpansion;
|
|
26
36
|
};
|
|
27
37
|
|
|
28
38
|
type CollectionModel = {
|
|
29
39
|
model: Gtk.FlattenListModel;
|
|
40
|
+
expansion: TreeExpansion;
|
|
30
41
|
treeModel: () => Gtk.TreeListModel | null;
|
|
31
42
|
sync: (index: CollectionIndex) => void;
|
|
32
43
|
};
|
|
33
44
|
|
|
34
|
-
const
|
|
45
|
+
const STORE_CLASS_KEY = Symbol.for("gtkx.components.lazy-level-store");
|
|
46
|
+
const SLOTS_KEY = Symbol.for("gtkx.components.lazy-level-store.slots");
|
|
47
|
+
const EMPTY_INDEX = createCollectionIndex(undefined, undefined, true);
|
|
48
|
+
const SLOTS = sharedSlots();
|
|
35
49
|
|
|
36
|
-
const
|
|
50
|
+
const newRootStore = (): Gio.ListStore => new Gio.ListStore({ itemType: GObject.TYPE_OBJECT });
|
|
37
51
|
|
|
38
|
-
function
|
|
39
|
-
const
|
|
52
|
+
function sharedSlots(): WeakMap<GObject.Object, SlotRef> {
|
|
53
|
+
const cached: unknown = Reflect.get(globalThis, SLOTS_KEY);
|
|
54
|
+
|
|
55
|
+
if (cached instanceof WeakMap) {
|
|
56
|
+
return cached as WeakMap<GObject.Object, SlotRef>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const created: WeakMap<GObject.Object, SlotRef> = new WeakMap();
|
|
60
|
+
Reflect.set(globalThis, SLOTS_KEY, created);
|
|
40
61
|
|
|
41
|
-
return
|
|
62
|
+
return created;
|
|
42
63
|
}
|
|
43
64
|
|
|
44
|
-
function
|
|
45
|
-
const
|
|
65
|
+
function registeredStoreClass(): typeof LazyLevelStore {
|
|
66
|
+
const cached: unknown = Reflect.get(globalThis, STORE_CLASS_KEY);
|
|
46
67
|
|
|
47
|
-
if (
|
|
48
|
-
return
|
|
68
|
+
if (typeof cached === "function") {
|
|
69
|
+
return cached as typeof LazyLevelStore;
|
|
49
70
|
}
|
|
50
71
|
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
registerClass(LazyLevelStore, { typeName: "GtkxLazyLevelStore", implements: [Gio.ListModel] });
|
|
73
|
+
Reflect.set(globalThis, STORE_CLASS_KEY, LazyLevelStore);
|
|
53
74
|
|
|
54
|
-
return
|
|
75
|
+
return LazyLevelStore;
|
|
55
76
|
}
|
|
56
77
|
|
|
57
|
-
function
|
|
58
|
-
|
|
78
|
+
function slotRefFor(value: GObject.Object | null): SlotRef | null {
|
|
79
|
+
const item = value instanceof Gtk.TreeListRow ? value.getItem() : value;
|
|
59
80
|
|
|
60
|
-
|
|
61
|
-
|
|
81
|
+
if (item === null) {
|
|
82
|
+
return null;
|
|
62
83
|
}
|
|
63
84
|
|
|
64
|
-
|
|
85
|
+
const ref = SLOTS.get(item);
|
|
86
|
+
|
|
87
|
+
return ref === undefined || ref.slot === -1 ? null : ref;
|
|
65
88
|
}
|
|
66
89
|
|
|
67
|
-
function
|
|
68
|
-
|
|
90
|
+
function slotPathAt(store: LevelStore, slot: number): string {
|
|
91
|
+
return store.path + encodePart(String(slot));
|
|
92
|
+
}
|
|
69
93
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
94
|
+
function newLevelStore(path: string): LevelStore {
|
|
95
|
+
const store = new (registeredStoreClass())() as LevelStore;
|
|
96
|
+
store.path = path;
|
|
73
97
|
|
|
74
|
-
return
|
|
98
|
+
return store;
|
|
75
99
|
}
|
|
76
100
|
|
|
77
|
-
function
|
|
78
|
-
const
|
|
79
|
-
const start = commonPrefix(previous, next, shared);
|
|
101
|
+
function buildChildStore(context: SyncContext, path: string): LevelStore | null {
|
|
102
|
+
const level = context.index.children.get(path);
|
|
80
103
|
|
|
81
|
-
if (
|
|
104
|
+
if (level === undefined) {
|
|
82
105
|
return null;
|
|
83
106
|
}
|
|
84
107
|
|
|
85
|
-
const
|
|
108
|
+
const store = newLevelStore(path);
|
|
109
|
+
growStore(context, store, level);
|
|
86
110
|
|
|
87
|
-
return
|
|
111
|
+
return store;
|
|
88
112
|
}
|
|
89
113
|
|
|
90
|
-
function
|
|
91
|
-
const
|
|
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
|
+
}
|
|
92
121
|
|
|
93
|
-
|
|
94
|
-
|
|
122
|
+
function growStore(context: SyncContext, store: LevelStore, level: Level): void {
|
|
123
|
+
for (let slot = store.refs.length; slot < level.items.length; slot++) {
|
|
124
|
+
pushSlot(context, store, level, slot);
|
|
95
125
|
}
|
|
126
|
+
}
|
|
96
127
|
|
|
97
|
-
|
|
98
|
-
|
|
128
|
+
function flipSlot(context: SyncContext, store: LevelStore, slot: number): void {
|
|
129
|
+
const canExpand = !(store.expandableFlags[slot] ?? false);
|
|
130
|
+
store.expandableFlags[slot] = canExpand;
|
|
131
|
+
store.childStores[slot] = canExpand ? buildChildStore(context, slotPathAt(store, slot)) : null;
|
|
132
|
+
}
|
|
99
133
|
|
|
100
|
-
|
|
134
|
+
function collectFlips(store: LevelStore, level: Level): Set<number> {
|
|
135
|
+
const overlap = Math.min(store.refs.length, level.items.length);
|
|
136
|
+
const flipped: Set<number> = new Set();
|
|
137
|
+
|
|
138
|
+
for (let slot = 0; slot < overlap; slot++) {
|
|
139
|
+
if ((store.expandableFlags[slot] ?? false) !== (level.expandableFlags[slot] ?? false)) {
|
|
140
|
+
flipped.add(slot);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return flipped;
|
|
101
145
|
}
|
|
102
146
|
|
|
103
|
-
function
|
|
104
|
-
const
|
|
147
|
+
function syncOverlap(context: SyncContext, store: LevelStore, level: Level): Set<number> {
|
|
148
|
+
const flipped = collectFlips(store, level);
|
|
149
|
+
|
|
150
|
+
if (flipped.size === 0) {
|
|
151
|
+
return flipped;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
pruneSlots(context.expansion, store.path, (slot) => flipped.has(slot));
|
|
105
155
|
|
|
106
|
-
for (const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
for (const slot of flipped) {
|
|
157
|
+
flipSlot(context, store, slot);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return flipped;
|
|
161
|
+
}
|
|
110
162
|
|
|
111
|
-
|
|
112
|
-
|
|
163
|
+
function detachRefs(store: LevelStore, nextLength: number): void {
|
|
164
|
+
for (let slot = nextLength; slot < store.refs.length; slot++) {
|
|
165
|
+
const ref = store.refs[slot];
|
|
166
|
+
|
|
167
|
+
if (ref !== undefined) {
|
|
168
|
+
ref.slot = -1;
|
|
113
169
|
}
|
|
114
170
|
}
|
|
171
|
+
}
|
|
115
172
|
|
|
116
|
-
|
|
173
|
+
function shrinkStore(context: SyncContext, store: LevelStore, nextLength: number): void {
|
|
174
|
+
if (store.refs.length <= nextLength) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
detachRefs(store, nextLength);
|
|
179
|
+
pruneSlots(context.expansion, store.path, (slot) => slot >= nextLength);
|
|
180
|
+
store.refs.length = nextLength;
|
|
181
|
+
store.objects.length = nextLength;
|
|
182
|
+
store.childStores.length = nextLength;
|
|
183
|
+
store.expandableFlags.length = nextLength;
|
|
117
184
|
}
|
|
118
185
|
|
|
119
|
-
function
|
|
120
|
-
|
|
121
|
-
|
|
186
|
+
function emitTailSplice(store: LevelStore, previousLength: number, nextLength: number): void {
|
|
187
|
+
if (nextLength < previousLength) {
|
|
188
|
+
store.itemsChanged(nextLength, previousLength - nextLength, 0);
|
|
122
189
|
|
|
123
|
-
|
|
124
|
-
return false;
|
|
190
|
+
return;
|
|
125
191
|
}
|
|
126
192
|
|
|
127
|
-
|
|
128
|
-
|
|
193
|
+
if (nextLength > previousLength) {
|
|
194
|
+
store.itemsChanged(previousLength, 0, nextLength - previousLength);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function extendRuns(runs: SlotRun[], slot: number): void {
|
|
199
|
+
const last = runs.at(-1);
|
|
129
200
|
|
|
130
|
-
|
|
201
|
+
if (last !== undefined && last.start + last.length === slot) {
|
|
202
|
+
last.length += 1;
|
|
203
|
+
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
runs.push({ start: slot, length: 1 });
|
|
131
208
|
}
|
|
132
209
|
|
|
133
|
-
function
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
210
|
+
function collectFlipRuns(flipped: Set<number>): SlotRun[] {
|
|
211
|
+
const runs: SlotRun[] = [];
|
|
212
|
+
|
|
213
|
+
for (const slot of flipped) {
|
|
214
|
+
extendRuns(runs, slot);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return runs;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function emitFlips(store: LevelStore, flipped: Set<number>): void {
|
|
221
|
+
for (const run of collectFlipRuns(flipped)) {
|
|
222
|
+
store.itemsChanged(run.start, run.length, run.length);
|
|
138
223
|
}
|
|
139
224
|
}
|
|
140
225
|
|
|
141
|
-
function
|
|
142
|
-
|
|
226
|
+
function stepChildStore(context: SyncContext, store: LevelStore, slot: number, flipped: Set<number>): void {
|
|
227
|
+
const child = store.childStores[slot];
|
|
228
|
+
|
|
229
|
+
if (child == null || flipped.has(slot)) {
|
|
143
230
|
return;
|
|
144
231
|
}
|
|
145
232
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
233
|
+
const level = context.index.children.get(slotPathAt(store, slot));
|
|
234
|
+
|
|
235
|
+
if (level !== undefined) {
|
|
236
|
+
syncLevel(context, child, level);
|
|
150
237
|
}
|
|
151
238
|
}
|
|
152
239
|
|
|
153
|
-
function
|
|
154
|
-
const
|
|
240
|
+
function syncLevel(context: SyncContext, store: LevelStore, level: Level): void {
|
|
241
|
+
const previousLength = store.refs.length;
|
|
242
|
+
const overlap = Math.min(previousLength, level.items.length);
|
|
243
|
+
const flipped = syncOverlap(context, store, level);
|
|
244
|
+
shrinkStore(context, store, level.items.length);
|
|
245
|
+
growStore(context, store, level);
|
|
246
|
+
emitTailSplice(store, previousLength, level.items.length);
|
|
247
|
+
emitFlips(store, flipped);
|
|
248
|
+
|
|
249
|
+
for (let slot = 0; slot < overlap; slot++) {
|
|
250
|
+
stepChildStore(context, store, slot, flipped);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
155
253
|
|
|
156
|
-
|
|
254
|
+
function childStoreFor(object: GObject.Object): Gio.ListModel | null {
|
|
255
|
+
const ref = slotRefFor(object);
|
|
256
|
+
|
|
257
|
+
if (ref === null) {
|
|
157
258
|
return null;
|
|
158
259
|
}
|
|
159
260
|
|
|
160
|
-
return
|
|
261
|
+
return ref.store.childStores[ref.slot] ?? null;
|
|
161
262
|
}
|
|
162
263
|
|
|
163
|
-
function ensureTree(state: ModelState): Gtk.TreeListModel {
|
|
164
|
-
state.tree
|
|
165
|
-
|
|
264
|
+
function ensureTree(state: ModelState, first: LevelStore): Gtk.TreeListModel {
|
|
265
|
+
if (state.tree === null || state.treeRoot !== first) {
|
|
266
|
+
state.tree = Gtk.TreeListModel.new(first, false, false, (object) => childStoreFor(object));
|
|
267
|
+
state.treeRoot = first;
|
|
268
|
+
}
|
|
166
269
|
|
|
167
270
|
return state.tree;
|
|
168
271
|
}
|
|
169
272
|
|
|
170
273
|
function desiredRootModels(state: ModelState, index: CollectionIndex): GObject.Object[] {
|
|
171
|
-
|
|
172
|
-
|
|
274
|
+
const [first] = state.groupStores;
|
|
275
|
+
|
|
276
|
+
if (first !== undefined && index.isTree) {
|
|
277
|
+
return [ensureTree(state, first)];
|
|
173
278
|
}
|
|
174
279
|
|
|
175
|
-
return
|
|
280
|
+
return [...state.groupStores];
|
|
176
281
|
}
|
|
177
282
|
|
|
178
283
|
function hasSameModels(previous: GObject.Object[], next: GObject.Object[]): boolean {
|
|
@@ -188,51 +293,63 @@ function syncRoot(state: ModelState, index: CollectionIndex): void {
|
|
|
188
293
|
|
|
189
294
|
state.root.splice(0, state.rootModels.length, next);
|
|
190
295
|
state.rootModels = next;
|
|
296
|
+
resetExpansion(state.expansion);
|
|
191
297
|
}
|
|
192
298
|
|
|
193
|
-
function
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
let didChange = false;
|
|
299
|
+
function adjustGroupStores(state: ModelState, context: SyncContext): void {
|
|
300
|
+
const { groups } = context.index;
|
|
301
|
+
state.groupStores.length = Math.min(state.groupStores.length, groups.length);
|
|
197
302
|
|
|
198
|
-
for (const level of
|
|
199
|
-
|
|
200
|
-
|
|
303
|
+
for (const level of groups.slice(state.groupStores.length)) {
|
|
304
|
+
const store = newLevelStore(level.path);
|
|
305
|
+
growStore(context, store, level);
|
|
306
|
+
state.groupStores.push(store);
|
|
201
307
|
}
|
|
308
|
+
}
|
|
202
309
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
310
|
+
function stepGroupStores(state: ModelState, context: SyncContext, surviving: number): void {
|
|
311
|
+
for (const [group, level] of context.index.groups.entries()) {
|
|
312
|
+
const store = state.groupStores[group];
|
|
207
313
|
|
|
208
|
-
|
|
209
|
-
|
|
314
|
+
if (store !== undefined && group < surviving) {
|
|
315
|
+
syncLevel(context, store, level);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
210
318
|
}
|
|
211
319
|
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
|
|
320
|
+
function syncModel(state: ModelState, index: CollectionIndex): void {
|
|
321
|
+
const context: SyncContext = { index, expansion: state.expansion };
|
|
322
|
+
const surviving = Math.min(state.groupStores.length, index.groups.length);
|
|
323
|
+
state.expansion.isSyncing = true;
|
|
324
|
+
|
|
325
|
+
try {
|
|
326
|
+
adjustGroupStores(state, context);
|
|
327
|
+
syncRoot(state, index);
|
|
328
|
+
stepGroupStores(state, context, surviving);
|
|
329
|
+
} finally {
|
|
330
|
+
state.expansion.isSyncing = false;
|
|
215
331
|
}
|
|
216
332
|
|
|
217
|
-
|
|
218
|
-
refreshExpandable(levelFor(state, level.key), level);
|
|
219
|
-
}
|
|
333
|
+
adoptIndex(state.expansion, index);
|
|
220
334
|
}
|
|
221
335
|
|
|
222
336
|
function createCollectionModel(): CollectionModel {
|
|
223
|
-
|
|
337
|
+
registeredStoreClass();
|
|
338
|
+
const root = newRootStore();
|
|
224
339
|
|
|
225
340
|
const state: ModelState = {
|
|
226
341
|
root,
|
|
227
342
|
rootModels: [],
|
|
228
343
|
model: Gtk.FlattenListModel.new(root),
|
|
229
|
-
|
|
230
|
-
levels: new Map(),
|
|
344
|
+
groupStores: [],
|
|
231
345
|
tree: null,
|
|
346
|
+
treeRoot: null,
|
|
347
|
+
expansion: createTreeExpansion(EMPTY_INDEX),
|
|
232
348
|
};
|
|
233
349
|
|
|
234
350
|
return {
|
|
235
351
|
model: state.model,
|
|
352
|
+
expansion: state.expansion,
|
|
236
353
|
treeModel: () => state.tree,
|
|
237
354
|
sync: (index) => {
|
|
238
355
|
syncModel(state, index);
|
|
@@ -240,4 +357,39 @@ function createCollectionModel(): CollectionModel {
|
|
|
240
357
|
};
|
|
241
358
|
}
|
|
242
359
|
|
|
243
|
-
|
|
360
|
+
class LazyLevelStore extends GObject.Object implements Gio.ListModelImpl {
|
|
361
|
+
declare itemsChanged: Gio.ListModel["itemsChanged"];
|
|
362
|
+
path = "";
|
|
363
|
+
refs: SlotRef[] = [];
|
|
364
|
+
objects: (Gtk.StringObject | null)[] = [];
|
|
365
|
+
childStores: (LevelStore | null)[] = [];
|
|
366
|
+
expandableFlags: boolean[] = [];
|
|
367
|
+
|
|
368
|
+
private createItem(position: number, ref: SlotRef): Gtk.StringObject {
|
|
369
|
+
const created = Gtk.StringObject.new("");
|
|
370
|
+
this.objects[position] = created;
|
|
371
|
+
SLOTS.set(created, ref);
|
|
372
|
+
|
|
373
|
+
return created;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
vfuncGetItemType(): bigint {
|
|
377
|
+
return GObject.TYPE_OBJECT;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
vfuncGetNItems(): number {
|
|
381
|
+
return this.refs.length;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
vfuncGetItem(position: number): GObject.Object | null {
|
|
385
|
+
const ref = this.refs[position];
|
|
386
|
+
|
|
387
|
+
if (ref === undefined) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return this.objects[position] ?? this.createItem(position, ref);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export { createCollectionModel, slotPathAt, slotRefFor, type CollectionModel, type SlotRef };
|
|
@@ -1,104 +1,82 @@
|
|
|
1
|
-
import type * as GObject from "@gtkx/gi/gobject";
|
|
2
|
-
import type * as Gtk from "@gtkx/gi/gtk";
|
|
3
1
|
import type { ListItem } from "../types.js";
|
|
4
2
|
import type { CollectionIndex } from "./collection-index.js";
|
|
5
|
-
import type { CollectionModel } from "./collection-model.js";
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
itemFor: (id: string) => ListItem | undefined;
|
|
13
|
-
sectionFor: (id: string) => unknown;
|
|
3
|
+
import type { CollectionModel, SlotRef } from "./collection-model.js";
|
|
4
|
+
import { slotPathAt, slotRefFor } from "./collection-model.js";
|
|
5
|
+
import { findPositions } from "./tree-order.js";
|
|
6
|
+
|
|
7
|
+
type Collection = Pick<CollectionModel, "model" | "expansion" | "treeModel"> & {
|
|
8
|
+
itemAt: (ref: SlotRef) => ListItem | undefined;
|
|
9
|
+
sectionFor: (levelPath: string) => unknown;
|
|
14
10
|
idAt: (position: number) => string | null;
|
|
11
|
+
pathAt: (position: number) => string | null;
|
|
15
12
|
positionFor: (id: string) => number;
|
|
16
13
|
positionsFor: (ids: string[]) => number[];
|
|
17
14
|
};
|
|
18
15
|
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
function visitRow(
|
|
22
|
-
tree: Gtk.TreeListModel,
|
|
23
|
-
position: number,
|
|
24
|
-
visit: (row: Gtk.TreeListRow, id: string | null) => void,
|
|
25
|
-
): void {
|
|
26
|
-
const row = tree.getRow(position);
|
|
16
|
+
const NO_POSITIONS: number[] = [];
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
function positionsFor(collectionModel: CollectionModel, ids: string[]): number[] {
|
|
19
|
+
if (ids.length === 0) {
|
|
20
|
+
return NO_POSITIONS;
|
|
30
21
|
}
|
|
31
22
|
|
|
32
|
-
|
|
33
|
-
}
|
|
23
|
+
const { expansion } = collectionModel;
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
for (let position = 0; position < tree.getNItems(); position++) {
|
|
37
|
-
visitRow(tree, position, visit);
|
|
38
|
-
}
|
|
25
|
+
return findPositions(expansion.index, expansion.slots, new Set(ids));
|
|
39
26
|
}
|
|
40
27
|
|
|
41
|
-
function
|
|
42
|
-
const
|
|
28
|
+
function positionFor(collectionModel: CollectionModel, id: string): number {
|
|
29
|
+
const [first = -1] = positionsFor(collectionModel, [id]);
|
|
43
30
|
|
|
44
|
-
|
|
45
|
-
out.push(position);
|
|
46
|
-
}
|
|
31
|
+
return first;
|
|
47
32
|
}
|
|
48
33
|
|
|
49
|
-
function
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const count = tree.getNItems();
|
|
53
|
-
|
|
54
|
-
for (let position = 0; position < count; position++) {
|
|
55
|
-
collectWanted(tree.getItem(position), position, wanted, positions);
|
|
34
|
+
function refAt(collectionModel: CollectionModel, position: number): SlotRef | null {
|
|
35
|
+
if (position < 0) {
|
|
36
|
+
return null;
|
|
56
37
|
}
|
|
57
38
|
|
|
58
|
-
return
|
|
39
|
+
return slotRefFor(collectionModel.model.getItem(position));
|
|
59
40
|
}
|
|
60
41
|
|
|
61
|
-
function
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
for (const id of ids) {
|
|
65
|
-
const position = index.positionFor(id);
|
|
42
|
+
function idAt(collectionModel: CollectionModel, index: CollectionIndex, position: number): string | null {
|
|
43
|
+
const ref = refAt(collectionModel, position);
|
|
66
44
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
45
|
+
if (ref === null) {
|
|
46
|
+
return null;
|
|
70
47
|
}
|
|
71
48
|
|
|
72
|
-
return
|
|
49
|
+
return index.itemAt(ref.store.path, ref.slot)?.id ?? null;
|
|
73
50
|
}
|
|
74
51
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
return indexPositions(index, ids);
|
|
78
|
-
}
|
|
52
|
+
function pathAt(collectionModel: CollectionModel, position: number): string | null {
|
|
53
|
+
const ref = refAt(collectionModel, position);
|
|
79
54
|
|
|
80
|
-
|
|
55
|
+
if (ref === null) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
81
58
|
|
|
82
|
-
return
|
|
59
|
+
return slotPathAt(ref.store, ref.slot);
|
|
83
60
|
}
|
|
84
61
|
|
|
85
|
-
function
|
|
86
|
-
const
|
|
62
|
+
function isCollectionIdle(collection: Collection): boolean {
|
|
63
|
+
const { expansion } = collection;
|
|
87
64
|
|
|
88
|
-
return
|
|
65
|
+
return !expansion.isApplying && !expansion.isSyncing;
|
|
89
66
|
}
|
|
90
67
|
|
|
91
|
-
function createCollection(
|
|
68
|
+
function createCollection(collectionModel: CollectionModel, index: CollectionIndex): Collection {
|
|
92
69
|
return {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
treeModel: () => (index.isTree ?
|
|
96
|
-
|
|
70
|
+
model: collectionModel.model,
|
|
71
|
+
expansion: collectionModel.expansion,
|
|
72
|
+
treeModel: () => (index.isTree ? collectionModel.treeModel() : null),
|
|
73
|
+
itemAt: (ref) => index.itemAt(ref.store.path, ref.slot),
|
|
97
74
|
sectionFor: index.sectionFor,
|
|
98
|
-
idAt: (position) =>
|
|
99
|
-
|
|
100
|
-
|
|
75
|
+
idAt: (position) => idAt(collectionModel, index, position),
|
|
76
|
+
pathAt: (position) => pathAt(collectionModel, position),
|
|
77
|
+
positionFor: (id) => positionFor(collectionModel, id),
|
|
78
|
+
positionsFor: (ids) => positionsFor(collectionModel, ids),
|
|
101
79
|
};
|
|
102
80
|
}
|
|
103
81
|
|
|
104
|
-
export { createCollection,
|
|
82
|
+
export { createCollection, isCollectionIdle, type Collection };
|