@arrai-innovations/reactive-helpers 10.4.2 → 11.0.1
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/.circleci/config.yml +3 -3
- package/README.md +123 -146
- package/config/listCrud.js +0 -1
- package/docs.md +178 -34
- package/package.json +3 -1
- package/tests/unit/use/listFilter.spec.js +335 -165
- package/tests/unit/use/listInstance.spec.js +14 -1
- package/tests/unit/use/listSearch.spec.js +505 -0
- package/tests/unit/use/listSort.spec.js +17 -1
- package/tests/unit/use/listSubscription.spec.js +91 -108
- package/tests/unit/use/search.spec.js +217 -36
- package/tests/unit/utils/assignReactiveObject.spec.js +43 -1
- package/tests/unit/{use → utils}/watches.spec.js +27 -3
- package/use/cancellableIntent.js +8 -3
- package/use/index.js +2 -0
- package/use/list.js +41 -14
- package/use/listCalculated.js +22 -19
- package/use/listFilter.js +146 -152
- package/use/listInstance.js +26 -31
- package/use/listKeys.js +99 -0
- package/use/listRelated.js +22 -14
- package/use/listSearch.js +357 -0
- package/use/listSort.js +46 -49
- package/use/listSubscription.js +3 -13
- package/use/search.js +154 -64
- package/use/watchesRunning.js +1 -1
- package/utils/assignReactiveObject.js +58 -14
- package/utils/index.js +1 -0
- package/utils/keyDiff.js +13 -7
- package/utils/relatedCalculatedHelpers.js +17 -0
- package/utils/watches.js +14 -11
package/use/listKeys.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export const listInstanceStateKeys = [
|
|
2
|
+
"crud",
|
|
3
|
+
"retrieveArgs",
|
|
4
|
+
"listArgs",
|
|
5
|
+
"objects",
|
|
6
|
+
"loading",
|
|
7
|
+
"errored",
|
|
8
|
+
"error",
|
|
9
|
+
"objectsInOrder",
|
|
10
|
+
"objectsInOrderRefs",
|
|
11
|
+
"order",
|
|
12
|
+
// when paged
|
|
13
|
+
"totalRecords",
|
|
14
|
+
"totalPages",
|
|
15
|
+
"perPage",
|
|
16
|
+
];
|
|
17
|
+
export const listInstanceFunctions = [
|
|
18
|
+
"list",
|
|
19
|
+
"addListObject",
|
|
20
|
+
"updateListObject",
|
|
21
|
+
"deleteListObject",
|
|
22
|
+
"clearList",
|
|
23
|
+
"clearError",
|
|
24
|
+
"getFakeId",
|
|
25
|
+
"defaultPageCallback",
|
|
26
|
+
"pageCallback",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export const listSubscriptionStateKeys = [
|
|
30
|
+
"subscriptionLoading",
|
|
31
|
+
"subscriptionErrored",
|
|
32
|
+
"subscriptionError",
|
|
33
|
+
"intendToList",
|
|
34
|
+
"intendToSubscribe",
|
|
35
|
+
"subscribed",
|
|
36
|
+
];
|
|
37
|
+
export const listSubscriptionFunctions = ["subscribe", "unsubscribe", "clearError"];
|
|
38
|
+
|
|
39
|
+
export const listRelatedStateKeys = [
|
|
40
|
+
"relatedObjects",
|
|
41
|
+
"relatedObjectsRules",
|
|
42
|
+
"relatedObjectsWatchRunning",
|
|
43
|
+
"relatedObjectsParentStateObjectsWatchRunning",
|
|
44
|
+
"running",
|
|
45
|
+
];
|
|
46
|
+
export const listRelatedFunctions = [];
|
|
47
|
+
|
|
48
|
+
export const listCalculatedStateKeys = [
|
|
49
|
+
"calculatedObjects",
|
|
50
|
+
"calculatedObjectsParentStateObjectsWatchRunning",
|
|
51
|
+
"calculatedObjectsRules",
|
|
52
|
+
"calculatedObjectsWatchRunning",
|
|
53
|
+
"running",
|
|
54
|
+
];
|
|
55
|
+
export const listCalculatedFunctions = [];
|
|
56
|
+
|
|
57
|
+
export const listFilterStateKeys = [
|
|
58
|
+
"allowedFilter",
|
|
59
|
+
"excludedFilter",
|
|
60
|
+
"inResults",
|
|
61
|
+
"objects",
|
|
62
|
+
"objectsInOrder",
|
|
63
|
+
"objectsInOrderRefs",
|
|
64
|
+
"objectsWatchRunning",
|
|
65
|
+
"order",
|
|
66
|
+
"resultsWatchRunning",
|
|
67
|
+
"running",
|
|
68
|
+
];
|
|
69
|
+
export const listFilterFunctions = [];
|
|
70
|
+
|
|
71
|
+
export const listSearchStateKeys = [
|
|
72
|
+
"objectIndexes",
|
|
73
|
+
"objects",
|
|
74
|
+
"order",
|
|
75
|
+
"objectsInOrder",
|
|
76
|
+
"objectsInOrderRefs",
|
|
77
|
+
"textSearchRules",
|
|
78
|
+
"textSearchValue",
|
|
79
|
+
"searched",
|
|
80
|
+
"updateSearchIndexesRunning",
|
|
81
|
+
"customDocumentOptions",
|
|
82
|
+
"customSearchOptions",
|
|
83
|
+
"running",
|
|
84
|
+
"newSearchComputeds",
|
|
85
|
+
];
|
|
86
|
+
export const listSearchFunctions = [];
|
|
87
|
+
export const listSortStateKeys = [
|
|
88
|
+
"orderByRules",
|
|
89
|
+
"order",
|
|
90
|
+
"objectsInOrder",
|
|
91
|
+
"objectsInOrderRefs",
|
|
92
|
+
"sortCriteria",
|
|
93
|
+
"orderByDesc",
|
|
94
|
+
"sortCriteriaWatchRunning",
|
|
95
|
+
"sortWatchRunning",
|
|
96
|
+
"outstandingEffects",
|
|
97
|
+
"running",
|
|
98
|
+
];
|
|
99
|
+
export const listSortFunctions = [];
|
package/use/listRelated.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import { difference } from "../utils/index.js";
|
|
1
2
|
import { keyDiff } from "../utils/keyDiff.js";
|
|
2
3
|
import { loadingCombine } from "../utils/loadingCombine.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
4
|
+
import {
|
|
5
|
+
listSubscriptionStateKeys,
|
|
6
|
+
listInstanceStateKeys,
|
|
7
|
+
listRelatedStateKeys,
|
|
8
|
+
listCalculatedStateKeys,
|
|
9
|
+
listSortStateKeys,
|
|
10
|
+
listFilterStateKeys,
|
|
11
|
+
listSearchStateKeys,
|
|
12
|
+
} from "./listKeys.js";
|
|
5
13
|
import { useWatchesRunning } from "./watchesRunning.js";
|
|
6
14
|
import get from "lodash-es/get.js";
|
|
7
15
|
import identity from "lodash-es/identity.js";
|
|
@@ -11,14 +19,17 @@ import isEqual from "lodash-es/isEqual.js";
|
|
|
11
19
|
import isUndefined from "lodash-es/isUndefined.js";
|
|
12
20
|
import { computed, effectScope, onScopeDispose, reactive, toRef, unref, watch } from "vue";
|
|
13
21
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
const parentStateKeys = difference(
|
|
23
|
+
new Set([
|
|
24
|
+
...listInstanceStateKeys,
|
|
25
|
+
...listSubscriptionStateKeys,
|
|
26
|
+
...listCalculatedStateKeys,
|
|
27
|
+
...listFilterStateKeys,
|
|
28
|
+
...listSortStateKeys,
|
|
29
|
+
...listSearchStateKeys,
|
|
30
|
+
]),
|
|
31
|
+
new Set(listRelatedStateKeys)
|
|
32
|
+
);
|
|
22
33
|
|
|
23
34
|
export function useListRelateds(instances, args) {
|
|
24
35
|
for (const [key, value] of Object.entries(args)) {
|
|
@@ -151,10 +162,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
151
162
|
const es = effectScope();
|
|
152
163
|
|
|
153
164
|
es.run(() => {
|
|
154
|
-
for (const key of
|
|
155
|
-
state[key] = toRef(parentState, key);
|
|
156
|
-
}
|
|
157
|
-
for (const key of listSubscriptionStateKeys) {
|
|
165
|
+
for (const key of parentStateKeys) {
|
|
158
166
|
state[key] = toRef(parentState, key);
|
|
159
167
|
}
|
|
160
168
|
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
import { difference, loadingCombine, assignReactiveObject, keyDiff } from "../utils/index.js";
|
|
2
|
+
import { getObjectRelatedCalculatedByKey } from "../utils/relatedCalculatedHelpers.js";
|
|
3
|
+
import {
|
|
4
|
+
listInstanceStateKeys,
|
|
5
|
+
listSubscriptionStateKeys,
|
|
6
|
+
listRelatedStateKeys,
|
|
7
|
+
listCalculatedStateKeys,
|
|
8
|
+
listSortStateKeys,
|
|
9
|
+
listFilterStateKeys,
|
|
10
|
+
listSearchStateKeys,
|
|
11
|
+
} from "./listKeys.js";
|
|
12
|
+
import { useSearch } from "./search.js";
|
|
13
|
+
import get from "lodash-es/get.js";
|
|
14
|
+
import isEqual from "lodash-es/isEqual.js";
|
|
15
|
+
import { reactive, effectScope, toRef, computed, watch, unref, onScopeDispose } from "vue";
|
|
16
|
+
import { deepUnref } from "vue-deepunref";
|
|
17
|
+
|
|
18
|
+
const parentStateKeys = difference(
|
|
19
|
+
new Set([
|
|
20
|
+
...listInstanceStateKeys,
|
|
21
|
+
...listSubscriptionStateKeys,
|
|
22
|
+
...listRelatedStateKeys,
|
|
23
|
+
...listCalculatedStateKeys,
|
|
24
|
+
...listSortStateKeys,
|
|
25
|
+
...listFilterStateKeys,
|
|
26
|
+
]),
|
|
27
|
+
new Set(listSearchStateKeys)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
/* eslint-disable jsdoc/check-types */
|
|
31
|
+
// types valid for jsdoc-to-markdown, which uses the strict jsdoc.app. Object shorthand syntax doesn't work.
|
|
32
|
+
/**
|
|
33
|
+
* A Vue composition function that creates multiple list instances, and returns them as an object.
|
|
34
|
+
* @param {Object.<string, ListSearchInstanceOptions>} listInstanceArgs - each desired list instance options, keyed by an instance name.
|
|
35
|
+
* @returns {Object.<string, ListSearchInstance>} - each list instance, keyed by the instance name.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/* eslint-enable jsdoc/check-types */
|
|
39
|
+
export function useListSearches(args, instances) {
|
|
40
|
+
const searches = {};
|
|
41
|
+
for (const [key, value] of Object.entries(args)) {
|
|
42
|
+
searches[key] = useListSearch({ parentState: instances[key].state, ...value });
|
|
43
|
+
}
|
|
44
|
+
return searches;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {object} ListSearchProps
|
|
49
|
+
* @property {array} textSearchRules - rules for what to search for. Keys are the keys to search for, values are functions that take the object and return the value to search for.
|
|
50
|
+
* @property {string} textSearchValue - the value to search for.
|
|
51
|
+
* @property {object} customDocumentOptions - FlexSearch.Document options
|
|
52
|
+
* @property {object} customSearchOptions - FlexSearch.Search options
|
|
53
|
+
* @property {object} [customSearchOptions.limit=1000] - FlexSearch.Search options
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @typedef {object} ListSearchInstance
|
|
58
|
+
* @property {object} state - the state
|
|
59
|
+
* @property {SearchInstance} textSearchIndex - the text search index
|
|
60
|
+
* @property {object} effectScope - a Vue effect scope
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @typedef {object} ListSearchInstanceOptions
|
|
65
|
+
* @property {object} parentState - the list being filtered
|
|
66
|
+
* @property {ListSearchProps} props - reactive properties
|
|
67
|
+
* @property {number} [throttle=500] - throttle wait time
|
|
68
|
+
* @property {boolean} [showAllWhenEmpty=true] - whether to show all items when the search is empty
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Text filter for list items. This will not be performant for large lists, as each item will be watched.
|
|
73
|
+
* However, the results will reactively update.
|
|
74
|
+
* @param {ListSearchInstanceOptions} options - the arguments
|
|
75
|
+
* @returns {ListSearchInstance} - the instance
|
|
76
|
+
*/
|
|
77
|
+
export function useListSearch({ parentState, props, throttle = 500, showAllWhenEmpty = true }) {
|
|
78
|
+
if (!parentState) {
|
|
79
|
+
throw new Error("parentState is required");
|
|
80
|
+
}
|
|
81
|
+
const state = reactive({
|
|
82
|
+
objects: {},
|
|
83
|
+
objectsInOrder: [],
|
|
84
|
+
objectsInOrderRefs: [],
|
|
85
|
+
order: [],
|
|
86
|
+
textSearchRules: toRef(props, "textSearchRules"),
|
|
87
|
+
textSearchValue: toRef(props, "textSearchValue"),
|
|
88
|
+
objectIndexes: {},
|
|
89
|
+
updateSearchIndexesRunning: undefined,
|
|
90
|
+
customDocumentOptions: toRef(props, "customDocumentOptions"),
|
|
91
|
+
customSearchOptions: toRef(props, "customSearchOptions"),
|
|
92
|
+
searched: undefined,
|
|
93
|
+
running: undefined,
|
|
94
|
+
newSearchComputeds: undefined,
|
|
95
|
+
});
|
|
96
|
+
if (!state.customDocumentOptions) {
|
|
97
|
+
state.customDocumentOptions = {};
|
|
98
|
+
}
|
|
99
|
+
if (!state.customSearchOptions) {
|
|
100
|
+
state.customSearchOptions = {};
|
|
101
|
+
}
|
|
102
|
+
const textSearchIndexProps = reactive({
|
|
103
|
+
customDocumentOptions: computed(() => {
|
|
104
|
+
const options = {
|
|
105
|
+
tokenize: "forward",
|
|
106
|
+
minlength: 2,
|
|
107
|
+
...state.customDocumentOptions, // todo: not sure if this is ok inside a computed
|
|
108
|
+
};
|
|
109
|
+
if (!options.document) {
|
|
110
|
+
options.document = {
|
|
111
|
+
id: "id",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
options.document.index = state.textSearchRules;
|
|
115
|
+
return options;
|
|
116
|
+
}),
|
|
117
|
+
customSearchOptions: computed(() => ({
|
|
118
|
+
...state.customSearchOptions, // todo: not sure if this is ok inside a computed
|
|
119
|
+
limit: state.customSearchOptions.limit ?? 1000,
|
|
120
|
+
})),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const es = effectScope();
|
|
124
|
+
|
|
125
|
+
let textSearchIndex;
|
|
126
|
+
|
|
127
|
+
const objectEffectScopes = {};
|
|
128
|
+
const objectComputeds = {};
|
|
129
|
+
|
|
130
|
+
const previousTextSearchRules = [];
|
|
131
|
+
const previousObjectIndexes = {};
|
|
132
|
+
|
|
133
|
+
const doPassthrough = (cleanComputed = false) => {
|
|
134
|
+
// pass through the objects if there are no rules.
|
|
135
|
+
assignReactiveObject(state.objects, showAllWhenEmpty ? parentState.objects : {});
|
|
136
|
+
if (!cleanComputed) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// if there were indexes or computeds, there is no point in keeping them.
|
|
140
|
+
for (const objectKey of Object.keys(objectEffectScopes)) {
|
|
141
|
+
objectEffectScopes[objectKey].stop();
|
|
142
|
+
}
|
|
143
|
+
assignReactiveObject(objectEffectScopes, {});
|
|
144
|
+
assignReactiveObject(objectComputeds, {});
|
|
145
|
+
assignReactiveObject(state.objectIndexes, {});
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const makeComputeds = () => {
|
|
149
|
+
if (!state.textSearchRules?.length) {
|
|
150
|
+
doPassthrough(true);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const {
|
|
154
|
+
addedKeys: addedObjectIds,
|
|
155
|
+
removedKeys: removedObjectIds,
|
|
156
|
+
sameKeys: sameObjectIds,
|
|
157
|
+
} = keyDiff(Object.keys(parentState.objects), Object.keys(state.objects));
|
|
158
|
+
const { addedKeys: addedTextSearchRules, removedKeys: removedTextSearchRules } = keyDiff(
|
|
159
|
+
state.textSearchRules,
|
|
160
|
+
previousTextSearchRules
|
|
161
|
+
);
|
|
162
|
+
for (const removedObjectId of removedObjectIds) {
|
|
163
|
+
delete state.objectIndexes[removedObjectId];
|
|
164
|
+
// the effect scope will be stopped when the object is removed.
|
|
165
|
+
delete objectComputeds[removedObjectId];
|
|
166
|
+
if (objectEffectScopes[removedObjectId]) {
|
|
167
|
+
objectEffectScopes[removedObjectId].stop();
|
|
168
|
+
delete objectEffectScopes[removedObjectId];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
for (const addedObjectId of addedObjectIds) {
|
|
172
|
+
state.objectIndexes[addedObjectId] = { id: addedObjectId };
|
|
173
|
+
objectComputeds[addedObjectId] = {};
|
|
174
|
+
objectEffectScopes[addedObjectId] = effectScope();
|
|
175
|
+
const objectEffectScope = objectEffectScopes[addedObjectId];
|
|
176
|
+
const objectRef = toRef(parentState.objects, addedObjectId);
|
|
177
|
+
const relatedRef = parentState.relatedObjects
|
|
178
|
+
? toRef(parentState.relatedObjects, addedObjectId)
|
|
179
|
+
: undefined;
|
|
180
|
+
const calculatedRef = parentState.calculatedObjects
|
|
181
|
+
? toRef(parentState.calculatedObjects, addedObjectId)
|
|
182
|
+
: undefined;
|
|
183
|
+
objectEffectScope.run(() => {
|
|
184
|
+
for (const rule of state.textSearchRules || []) {
|
|
185
|
+
const [obj, key] = getObjectRelatedCalculatedByKey(objectRef, relatedRef, calculatedRef, rule);
|
|
186
|
+
state.newSearchComputeds = true;
|
|
187
|
+
state.objectIndexes[addedObjectId][rule] = objectComputeds[addedObjectId][rule] = computed(() => {
|
|
188
|
+
return get(unref(obj), key);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
for (const sameObjectId of sameObjectIds) {
|
|
194
|
+
const objectEffectScope = objectEffectScopes[sameObjectId];
|
|
195
|
+
const objectRef = toRef(parentState.objects, sameObjectId);
|
|
196
|
+
const relatedRef = parentState.relatedObjects ? toRef(parentState.relatedObjects, sameObjectId) : undefined;
|
|
197
|
+
const calculatedRef = parentState.calculatedObjects
|
|
198
|
+
? toRef(parentState.calculatedObjects, sameObjectId)
|
|
199
|
+
: undefined;
|
|
200
|
+
for (const key of removedTextSearchRules) {
|
|
201
|
+
delete state.objectIndexes[sameObjectId][key];
|
|
202
|
+
// stop a computed earlier than the effect scope
|
|
203
|
+
objectComputeds[sameObjectId][key].effect.stop();
|
|
204
|
+
delete objectComputeds[sameObjectId][key];
|
|
205
|
+
}
|
|
206
|
+
objectEffectScope.run(() => {
|
|
207
|
+
for (const rule of addedTextSearchRules) {
|
|
208
|
+
const [obj, key] = getObjectRelatedCalculatedByKey(objectRef, relatedRef, calculatedRef, rule);
|
|
209
|
+
state.newSearchComputeds = true;
|
|
210
|
+
state.objectIndexes[sameObjectId][rule] = objectComputeds[sameObjectId][rule] = computed(() => {
|
|
211
|
+
return get(unref(obj), key);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
previousTextSearchRules.length = 0;
|
|
217
|
+
if (state.textSearchRules?.length) {
|
|
218
|
+
previousTextSearchRules.push(...state.textSearchRules);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const updateSearchIndexes = async () => {
|
|
223
|
+
if (state.updateSearchIndexesRunning === undefined) {
|
|
224
|
+
state.updateSearchIndexesRunning = 0;
|
|
225
|
+
}
|
|
226
|
+
state.updateSearchIndexesRunning++;
|
|
227
|
+
try {
|
|
228
|
+
const { addedKeys, removedKeys, sameKeys } = keyDiff(
|
|
229
|
+
Object.keys(state.objectIndexes),
|
|
230
|
+
Object.keys(previousObjectIndexes)
|
|
231
|
+
);
|
|
232
|
+
const promises = [];
|
|
233
|
+
for (const removedKey of removedKeys) {
|
|
234
|
+
promises.push(textSearchIndex.removeIndex(removedKey));
|
|
235
|
+
delete previousObjectIndexes[removedKey];
|
|
236
|
+
}
|
|
237
|
+
for (const addedKey of addedKeys) {
|
|
238
|
+
promises.push(textSearchIndex.addIndex(state.objectIndexes[addedKey]));
|
|
239
|
+
previousObjectIndexes[addedKey] = deepUnref(state.objectIndexes[addedKey]);
|
|
240
|
+
}
|
|
241
|
+
for (const sameKey of sameKeys) {
|
|
242
|
+
if (!isEqual(previousObjectIndexes[sameKey], state.objectIndexes[sameKey])) {
|
|
243
|
+
promises.push(textSearchIndex.updateIndex(state.objectIndexes[sameKey]));
|
|
244
|
+
previousObjectIndexes[sameKey] = deepUnref(state.objectIndexes[sameKey]);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (promises.length) {
|
|
248
|
+
await Promise.all(promises);
|
|
249
|
+
}
|
|
250
|
+
} finally {
|
|
251
|
+
if (state.newSearchComputeds) {
|
|
252
|
+
state.newSearchComputeds = false;
|
|
253
|
+
}
|
|
254
|
+
state.updateSearchIndexesRunning--;
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const updateObjectsForResults = () => {
|
|
259
|
+
if (!state.textSearchRules?.length || !state.textSearchValue?.length) {
|
|
260
|
+
doPassthrough();
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
assignReactiveObject(
|
|
264
|
+
state.objects,
|
|
265
|
+
Object.fromEntries(
|
|
266
|
+
Object.entries(textSearchIndex.state.results)
|
|
267
|
+
.filter(([, value]) => !!value)
|
|
268
|
+
.map(([id]) => [id, toRef(parentState.objects, id)])
|
|
269
|
+
)
|
|
270
|
+
);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const updateOrder = () => {
|
|
274
|
+
state.order = parentState.order.filter((id) => !!state.objects[id]);
|
|
275
|
+
assignReactiveObject(
|
|
276
|
+
state.objectsInOrderRefs,
|
|
277
|
+
parentState.order.filter((id) => !!state.objects[id]).map((id) => toRef(state.objects, id))
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
let firstIndexWasCleared = false;
|
|
282
|
+
|
|
283
|
+
const indexWasCleared = async () => {
|
|
284
|
+
// skip the first time, preventing clearing the index after makeComputeds already ran.
|
|
285
|
+
if (firstIndexWasCleared) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
firstIndexWasCleared = true;
|
|
289
|
+
assignReactiveObject(previousObjectIndexes, {});
|
|
290
|
+
await makeComputeds();
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
let watchesRunning;
|
|
294
|
+
|
|
295
|
+
es.run(() => {
|
|
296
|
+
for (const key of parentStateKeys) {
|
|
297
|
+
state[key] = toRef(parentState, key);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
textSearchIndex = useSearch({
|
|
301
|
+
props: textSearchIndexProps,
|
|
302
|
+
throttle,
|
|
303
|
+
});
|
|
304
|
+
textSearchIndex.state.search = toRef(state, "textSearchValue");
|
|
305
|
+
textSearchIndex.events.addEventListener("newIndex", indexWasCleared);
|
|
306
|
+
state.searched = toRef(() => textSearchIndex.state.searched);
|
|
307
|
+
state.running = computed(() => {
|
|
308
|
+
return loadingCombine(parentState.running, state.newSearchComputeds, textSearchIndex.state.running);
|
|
309
|
+
});
|
|
310
|
+
state.objectsInOrder = computed(() => state.objectsInOrderRefs.map((ref) => unref(ref)));
|
|
311
|
+
|
|
312
|
+
watch([() => Object.keys(parentState.objects), toRef(state.textSearchRules)], makeComputeds, {
|
|
313
|
+
immediate: true,
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
watch(
|
|
317
|
+
toRef(state, "objectIndexes"),
|
|
318
|
+
() => {
|
|
319
|
+
updateSearchIndexes();
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
deep: true,
|
|
323
|
+
immediate: true,
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
watch(
|
|
328
|
+
[
|
|
329
|
+
toRef(state, "textSearchValue"),
|
|
330
|
+
() => Object.keys(textSearchIndex.state.results),
|
|
331
|
+
toRef(textSearchIndex.state, "running"),
|
|
332
|
+
],
|
|
333
|
+
updateObjectsForResults,
|
|
334
|
+
{
|
|
335
|
+
immediate: true,
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
watch(toRef(parentState, "order"), updateOrder, {
|
|
340
|
+
immediate: true,
|
|
341
|
+
deep: true,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
onScopeDispose(() => {
|
|
345
|
+
for (const objectKey of Object.keys(objectEffectScopes)) {
|
|
346
|
+
objectEffectScopes[objectKey].stop();
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
state,
|
|
353
|
+
effectScope: es,
|
|
354
|
+
textSearchIndex,
|
|
355
|
+
watchesRunning,
|
|
356
|
+
};
|
|
357
|
+
}
|
package/use/listSort.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { assignReactiveObject, keyDiff, loadingCombine } from "../utils/index.js";
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import { assignReactiveObject, keyDiff, loadingCombine, difference } from "../utils/index.js";
|
|
2
|
+
import {
|
|
3
|
+
listSortStateKeys,
|
|
4
|
+
listFilterStateKeys,
|
|
5
|
+
listRelatedStateKeys,
|
|
6
|
+
listCalculatedStateKeys,
|
|
7
|
+
listSubscriptionStateKeys,
|
|
8
|
+
listInstanceStateKeys,
|
|
9
|
+
listSearchStateKeys,
|
|
10
|
+
} from "./listKeys.js";
|
|
7
11
|
import { useWatchesRunning } from "./watchesRunning.js";
|
|
8
|
-
import cloneDeep from "lodash-es/cloneDeep.js";
|
|
9
12
|
import get from "lodash-es/get.js";
|
|
10
13
|
import identity from "lodash-es/identity.js";
|
|
11
14
|
import isEmpty from "lodash-es/isEmpty.js";
|
|
@@ -15,21 +18,7 @@ import isUndefined from "lodash-es/isUndefined.js";
|
|
|
15
18
|
import throttle from "lodash-es/throttle.js";
|
|
16
19
|
import zip from "lodash-es/zip.js";
|
|
17
20
|
import { effectScope, reactive, toRef, unref, watch, computed } from "vue";
|
|
18
|
-
|
|
19
|
-
export const listSortStateKeys = [
|
|
20
|
-
"orderByRules",
|
|
21
|
-
// "order",
|
|
22
|
-
// "objectsInOrder",
|
|
23
|
-
"sortCriteria",
|
|
24
|
-
"sortCriteriaEffectScopes",
|
|
25
|
-
"orderByDesc",
|
|
26
|
-
"sortCriteriaWatchRunning",
|
|
27
|
-
"sortWatchRunning",
|
|
28
|
-
"outstandingEffects",
|
|
29
|
-
// "running",
|
|
30
|
-
];
|
|
31
|
-
|
|
32
|
-
export const listSortFunctions = [];
|
|
21
|
+
import { deepUnref } from "vue-deepunref";
|
|
33
22
|
|
|
34
23
|
const collator = new Intl.Collator(undefined, { numeric: true });
|
|
35
24
|
|
|
@@ -39,6 +28,18 @@ const defaultOptions = {
|
|
|
39
28
|
sortThrottleWait: 100,
|
|
40
29
|
};
|
|
41
30
|
|
|
31
|
+
const parentStateKeys = difference(
|
|
32
|
+
new Set([
|
|
33
|
+
...listInstanceStateKeys,
|
|
34
|
+
...listSubscriptionStateKeys,
|
|
35
|
+
...listRelatedStateKeys,
|
|
36
|
+
...listCalculatedStateKeys,
|
|
37
|
+
...listFilterStateKeys,
|
|
38
|
+
...listSearchStateKeys,
|
|
39
|
+
]),
|
|
40
|
+
new Set(listSortStateKeys)
|
|
41
|
+
);
|
|
42
|
+
|
|
42
43
|
export function setListSortDefaultOptions({ sortThrottleWait }) {
|
|
43
44
|
defaultOptions.sortThrottleWait = sortThrottleWait;
|
|
44
45
|
}
|
|
@@ -55,12 +56,15 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
55
56
|
if (sortThrottleWait === defaultSortThrottleWait) {
|
|
56
57
|
sortThrottleWait = defaultOptions.sortThrottleWait;
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
const sortCriteriaEffectScopes = {};
|
|
61
|
+
|
|
58
62
|
const state = reactive({
|
|
59
63
|
orderByRules,
|
|
60
64
|
order: [],
|
|
65
|
+
objectsInOrderRefs: [],
|
|
61
66
|
objectsInOrder: [],
|
|
62
67
|
sortCriteria: {},
|
|
63
|
-
sortCriteriaEffectScopes: {},
|
|
64
68
|
orderByDesc: [],
|
|
65
69
|
sortCriteriaWatchRunning: false,
|
|
66
70
|
sortWatchRunning: false,
|
|
@@ -69,16 +73,16 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
69
73
|
const es = effectScope();
|
|
70
74
|
|
|
71
75
|
function removeSortCriteria(removedKey) {
|
|
72
|
-
const oldScope =
|
|
76
|
+
const oldScope = sortCriteriaEffectScopes[removedKey];
|
|
73
77
|
if (oldScope) {
|
|
74
78
|
oldScope.stop();
|
|
75
|
-
delete
|
|
79
|
+
delete sortCriteriaEffectScopes[removedKey];
|
|
76
80
|
}
|
|
77
81
|
delete state.sortCriteria[removedKey];
|
|
78
82
|
}
|
|
79
83
|
|
|
80
84
|
function addSortCriteria(object, relatedObject, calculatedObject, key) {
|
|
81
|
-
const oldScope =
|
|
85
|
+
const oldScope = sortCriteriaEffectScopes[key];
|
|
82
86
|
if (oldScope) {
|
|
83
87
|
oldScope.stop();
|
|
84
88
|
}
|
|
@@ -123,7 +127,7 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
123
127
|
}
|
|
124
128
|
);
|
|
125
129
|
});
|
|
126
|
-
|
|
130
|
+
sortCriteriaEffectScopes[key] = newScope;
|
|
127
131
|
}
|
|
128
132
|
|
|
129
133
|
function sortCriteriaWatch() {
|
|
@@ -134,8 +138,8 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
134
138
|
removeSortCriteria(removedKey);
|
|
135
139
|
}
|
|
136
140
|
}
|
|
137
|
-
assignReactiveObject(state.order,
|
|
138
|
-
assignReactiveObject(state.objectsInOrder,
|
|
141
|
+
assignReactiveObject(state.order, deepUnref(parentState.order));
|
|
142
|
+
assignReactiveObject(state.objectsInOrder, deepUnref(parentState.objectsInOrder));
|
|
139
143
|
return;
|
|
140
144
|
}
|
|
141
145
|
const { removedKeys, addedKeys } = keyDiff(
|
|
@@ -166,8 +170,12 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
166
170
|
function sortWatch() {
|
|
167
171
|
try {
|
|
168
172
|
if (!state.orderByRules || !state.orderByRules.length) {
|
|
169
|
-
|
|
170
|
-
assignReactiveObject(
|
|
173
|
+
state.order = [...parentState.order];
|
|
174
|
+
assignReactiveObject(
|
|
175
|
+
state.objectsInOrderRefs,
|
|
176
|
+
state.order.map((e) => toRef(parentState.objects, e))
|
|
177
|
+
);
|
|
178
|
+
3;
|
|
171
179
|
return;
|
|
172
180
|
}
|
|
173
181
|
|
|
@@ -207,8 +215,11 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
207
215
|
}
|
|
208
216
|
return 0;
|
|
209
217
|
});
|
|
210
|
-
|
|
211
|
-
assignReactiveObject(
|
|
218
|
+
state.order = idList;
|
|
219
|
+
assignReactiveObject(
|
|
220
|
+
state.objectsInOrderRefs,
|
|
221
|
+
idList.map((e) => toRef(parentState.objects, e))
|
|
222
|
+
);
|
|
212
223
|
} finally {
|
|
213
224
|
state.sortWatchRunning = false;
|
|
214
225
|
state.outstandingEffects = false;
|
|
@@ -220,22 +231,7 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
220
231
|
let watchesRunning = null;
|
|
221
232
|
|
|
222
233
|
es.run(() => {
|
|
223
|
-
for (const key of
|
|
224
|
-
if (["order", "objectsInOrder"].includes(key)) {
|
|
225
|
-
continue;
|
|
226
|
-
}
|
|
227
|
-
state[key] = toRef(parentState, key);
|
|
228
|
-
}
|
|
229
|
-
for (const key of listSubscriptionStateKeys) {
|
|
230
|
-
state[key] = toRef(parentState, key);
|
|
231
|
-
}
|
|
232
|
-
for (const key of listRelatedStateKeys) {
|
|
233
|
-
state[key] = toRef(parentState, key);
|
|
234
|
-
}
|
|
235
|
-
for (const key of listCalculatedStateKeys) {
|
|
236
|
-
state[key] = toRef(parentState, key);
|
|
237
|
-
}
|
|
238
|
-
for (const key of listFilterStateKeys) {
|
|
234
|
+
for (const key of parentStateKeys) {
|
|
239
235
|
state[key] = toRef(parentState, key);
|
|
240
236
|
}
|
|
241
237
|
// this watch must come first or be immediate.
|
|
@@ -255,6 +251,7 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
255
251
|
watchSentinelRefs: [toRef(state, "sortCriteriaWatchRunning"), toRef(state, "sortWatchRunning")],
|
|
256
252
|
});
|
|
257
253
|
|
|
254
|
+
state.objectsInOrder = computed(() => state.objectsInOrderRefs.map((e) => unref(e)));
|
|
258
255
|
state.sortRunning = computed(() => loadingCombine(watchesRunning.state.running, state.outstandingEffects));
|
|
259
256
|
state.running = computed(() =>
|
|
260
257
|
loadingCombine(watchesRunning.state.running, state.outstandingEffects, parentState.running)
|