@gtkx/components 1.0.0-rc.3 → 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 +8 -9
- package/dist/adw/combo-row.d.ts +1 -0
- package/dist/adw/combo-row.d.ts.map +1 -1
- package/dist/adw/combo-row.js.map +1 -1
- package/dist/adw/types.d.ts +2 -1
- package/dist/adw/types.d.ts.map +1 -1
- package/dist/adw/types.js.map +1 -1
- package/dist/column-view.d.ts +2 -2
- package/dist/column-view.d.ts.map +1 -1
- package/dist/column-view.js +15 -13
- package/dist/column-view.js.map +1 -1
- package/dist/drop-down.d.ts +1 -0
- package/dist/drop-down.d.ts.map +1 -1
- package/dist/drop-down.js.map +1 -1
- package/dist/grid-view.js +2 -2
- package/dist/grid-view.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +32 -24
- 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 +9 -13
- package/dist/internal/collection-index.d.ts.map +1 -1
- package/dist/internal/collection-index.js +33 -70
- 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 -98
- package/dist/internal/collection-model.js.map +1 -1
- package/dist/internal/collection.d.ts +9 -12
- 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 +5 -5
- package/dist/internal/use-collection.d.ts.map +1 -1
- package/dist/internal/use-collection.js +6 -6
- 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 +127 -54
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +7 -7
- package/src/adw/combo-row.tsx +1 -0
- package/src/adw/types.ts +2 -1
- package/src/column-view.tsx +38 -35
- package/src/drop-down.tsx +1 -0
- package/src/grid-view.tsx +2 -2
- package/src/index.ts +10 -11
- package/src/internal/cells.tsx +345 -123
- package/src/internal/collection-index.ts +51 -99
- package/src/internal/collection-model.ts +259 -110
- package/src/internal/collection.ts +46 -68
- package/src/internal/controlled-sync.ts +34 -0
- package/src/internal/drop-down.tsx +44 -23
- 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 +11 -11
- package/src/list-view.tsx +14 -10
- package/src/types.ts +140 -65
- package/dist/size-group.d.ts +0 -12
- package/dist/size-group.d.ts.map +0 -1
- package/dist/size-group.js +0 -41
- package/dist/size-group.js.map +0 -1
- package/src/size-group.tsx +0 -77
|
@@ -1,181 +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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if (state.objects.size <= OBJECT_CACHE_MAX) {
|
|
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)) {
|
|
146
230
|
return;
|
|
147
231
|
}
|
|
148
232
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
233
|
+
const level = context.index.children.get(slotPathAt(store, slot));
|
|
234
|
+
|
|
235
|
+
if (level !== undefined) {
|
|
236
|
+
syncLevel(context, child, level);
|
|
153
237
|
}
|
|
154
238
|
}
|
|
155
239
|
|
|
156
|
-
function
|
|
157
|
-
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
|
+
}
|
|
158
253
|
|
|
159
|
-
|
|
254
|
+
function childStoreFor(object: GObject.Object): Gio.ListModel | null {
|
|
255
|
+
const ref = slotRefFor(object);
|
|
256
|
+
|
|
257
|
+
if (ref === null) {
|
|
160
258
|
return null;
|
|
161
259
|
}
|
|
162
260
|
|
|
163
|
-
return
|
|
261
|
+
return ref.store.childStores[ref.slot] ?? null;
|
|
164
262
|
}
|
|
165
263
|
|
|
166
|
-
function ensureTree(state: ModelState): Gtk.TreeListModel {
|
|
167
|
-
state.tree
|
|
168
|
-
|
|
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
|
+
}
|
|
169
269
|
|
|
170
270
|
return state.tree;
|
|
171
271
|
}
|
|
172
272
|
|
|
173
273
|
function desiredRootModels(state: ModelState, index: CollectionIndex): GObject.Object[] {
|
|
174
|
-
|
|
175
|
-
|
|
274
|
+
const [first] = state.groupStores;
|
|
275
|
+
|
|
276
|
+
if (first !== undefined && index.isTree) {
|
|
277
|
+
return [ensureTree(state, first)];
|
|
176
278
|
}
|
|
177
279
|
|
|
178
|
-
return
|
|
280
|
+
return [...state.groupStores];
|
|
179
281
|
}
|
|
180
282
|
|
|
181
283
|
function hasSameModels(previous: GObject.Object[], next: GObject.Object[]): boolean {
|
|
@@ -191,51 +293,63 @@ function syncRoot(state: ModelState, index: CollectionIndex): void {
|
|
|
191
293
|
|
|
192
294
|
state.root.splice(0, state.rootModels.length, next);
|
|
193
295
|
state.rootModels = next;
|
|
296
|
+
resetExpansion(state.expansion);
|
|
194
297
|
}
|
|
195
298
|
|
|
196
|
-
function
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
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);
|
|
200
302
|
|
|
201
|
-
for (const level of
|
|
202
|
-
|
|
203
|
-
|
|
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);
|
|
204
307
|
}
|
|
308
|
+
}
|
|
205
309
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
}
|
|
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];
|
|
210
313
|
|
|
211
|
-
|
|
212
|
-
|
|
314
|
+
if (store !== undefined && group < surviving) {
|
|
315
|
+
syncLevel(context, store, level);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
213
318
|
}
|
|
214
319
|
|
|
215
|
-
function
|
|
216
|
-
|
|
217
|
-
|
|
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;
|
|
218
331
|
}
|
|
219
332
|
|
|
220
|
-
|
|
221
|
-
refreshExpandable(levelFor(state, level.key), level);
|
|
222
|
-
}
|
|
333
|
+
adoptIndex(state.expansion, index);
|
|
223
334
|
}
|
|
224
335
|
|
|
225
336
|
function createCollectionModel(): CollectionModel {
|
|
226
|
-
|
|
337
|
+
registeredStoreClass();
|
|
338
|
+
const root = newRootStore();
|
|
227
339
|
|
|
228
340
|
const state: ModelState = {
|
|
229
341
|
root,
|
|
230
342
|
rootModels: [],
|
|
231
343
|
model: Gtk.FlattenListModel.new(root),
|
|
232
|
-
|
|
233
|
-
levels: new Map(),
|
|
344
|
+
groupStores: [],
|
|
234
345
|
tree: null,
|
|
346
|
+
treeRoot: null,
|
|
347
|
+
expansion: createTreeExpansion(EMPTY_INDEX),
|
|
235
348
|
};
|
|
236
349
|
|
|
237
350
|
return {
|
|
238
351
|
model: state.model,
|
|
352
|
+
expansion: state.expansion,
|
|
239
353
|
treeModel: () => state.tree,
|
|
240
354
|
sync: (index) => {
|
|
241
355
|
syncModel(state, index);
|
|
@@ -243,4 +357,39 @@ function createCollectionModel(): CollectionModel {
|
|
|
243
357
|
};
|
|
244
358
|
}
|
|
245
359
|
|
|
246
|
-
|
|
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
|
|
2
|
-
import type * as Gtk from "@gtkx/gi/gtk";
|
|
3
|
-
import type { Item } from "../types.js";
|
|
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) => Item | 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 };
|