@arrai-innovations/reactive-helpers 10.1.0 → 10.3.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/.circleci/config.yml +1 -3
- package/docs.md +5 -4
- package/package.json +1 -1
- package/tests/unit/use/listCalculated.spec.js +61 -1
- package/tests/unit/use/listRelated.spec.js +116 -2
- package/tests/unit/use/listSort.spec.js +40 -16
- package/use/listCalculated.js +5 -1
- package/use/listRelated.js +23 -4
- package/use/listSort.js +103 -62
- package/use/listSubscription.js +2 -2
- package/utils/watches.js +42 -16
package/.circleci/config.yml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
version: 2.1
|
|
2
2
|
orbs:
|
|
3
3
|
eslint: arrai/eslint@7.4.0
|
|
4
|
-
prettier: arrai/prettier@5.
|
|
4
|
+
prettier: arrai/prettier@5.4.0
|
|
5
5
|
npm: arrai/npm@2.4.1
|
|
6
6
|
github: arrai/github@1.1.0
|
|
7
7
|
workflows:
|
|
@@ -75,14 +75,12 @@ workflows:
|
|
|
75
75
|
- prettier/code_style:
|
|
76
76
|
name: prettier
|
|
77
77
|
context: arrai-global
|
|
78
|
-
version: 2.6.2
|
|
79
78
|
filters:
|
|
80
79
|
branches:
|
|
81
80
|
only:
|
|
82
81
|
- main
|
|
83
82
|
- prettier/code_style:
|
|
84
83
|
name: prettier-no-badge
|
|
85
|
-
version: 2.6.2
|
|
86
84
|
create_badges: false
|
|
87
85
|
filters:
|
|
88
86
|
branches:
|
package/docs.md
CHANGED
|
@@ -494,11 +494,11 @@ It tracks the objects in the list, and their added order.
|
|
|
494
494
|
causing the list to be re-fetched as needed and listening for updates to the list.
|
|
495
495
|
|
|
496
496
|
**Kind**: global function
|
|
497
|
-
**Returns**: ListSubscription
|
|
497
|
+
**Returns**: [`ListSubscription`] - - the list subscription
|
|
498
498
|
|
|
499
|
-
| Param | Type |
|
|
500
|
-
| ------- | --------------------------- |
|
|
501
|
-
| options | [`ListSubscriptionOptions`] |
|
|
499
|
+
| Param | Type | Description |
|
|
500
|
+
| ------- | --------------------------- | --------------------------------------------------- |
|
|
501
|
+
| options | [`ListSubscriptionOptions`] | the configuration options for the list subscription |
|
|
502
502
|
|
|
503
503
|
## useObjectInstances(instanceArgs)
|
|
504
504
|
|
|
@@ -832,6 +832,7 @@ A CSS object or a space-separated list of CSS classes.
|
|
|
832
832
|
[`cssclasseswithrefs`]: #cssclasseswithrefs
|
|
833
833
|
[`listinstance`]: #listinstance
|
|
834
834
|
[`listinstanceoptions`]: #listinstanceoptions
|
|
835
|
+
[`listsubscription`]: #listsubscription
|
|
835
836
|
[`listsubscriptionoptions`]: #listsubscriptionoptions
|
|
836
837
|
[`objectinstanceinstance`]: #objectinstanceinstance
|
|
837
838
|
[`listinstancestate`]: #listinstancestate
|
package/package.json
CHANGED
|
@@ -2,12 +2,16 @@ import { nextTick } from "vue";
|
|
|
2
2
|
import { deepUnref } from "vue-deepunref";
|
|
3
3
|
|
|
4
4
|
describe("use/listCalculated", () => {
|
|
5
|
-
let useListInstance, useListCalculated;
|
|
5
|
+
let useListInstance, useListCalculated, useListRelated, AwaitNot;
|
|
6
6
|
beforeEach(async () => {
|
|
7
7
|
const listInstanceModule = await import("../../../use/listInstance");
|
|
8
8
|
useListInstance = listInstanceModule.useListInstance;
|
|
9
9
|
const listCalculatedModule = await import("../../../use/listCalculated");
|
|
10
10
|
useListCalculated = listCalculatedModule.useListCalculated;
|
|
11
|
+
const listRelatedModule = await import("../../../use/listRelated");
|
|
12
|
+
useListRelated = listRelatedModule.useListRelated;
|
|
13
|
+
const watchesModule = await import("../../../utils/watches.js");
|
|
14
|
+
AwaitNot = watchesModule.AwaitNot;
|
|
11
15
|
});
|
|
12
16
|
it("should return a list of calculated items", async () => {
|
|
13
17
|
const mainListInstance = useListInstance({ props: {} });
|
|
@@ -71,4 +75,60 @@ describe("use/listCalculated", () => {
|
|
|
71
75
|
},
|
|
72
76
|
});
|
|
73
77
|
});
|
|
78
|
+
it("should allow calculated objects to return results based on related objects", async () => {
|
|
79
|
+
const mainListInstance = useListInstance({ props: {} });
|
|
80
|
+
const relatedListInstance = useListInstance({ props: {} });
|
|
81
|
+
mainListInstance.addListObject({
|
|
82
|
+
id: "1",
|
|
83
|
+
name: "main",
|
|
84
|
+
related_items: ["2", "3"],
|
|
85
|
+
related_id: "4",
|
|
86
|
+
calculated_items: ["2", "3"],
|
|
87
|
+
calculated_id: "4",
|
|
88
|
+
});
|
|
89
|
+
relatedListInstance.addListObject({
|
|
90
|
+
id: "2",
|
|
91
|
+
name: "related1",
|
|
92
|
+
});
|
|
93
|
+
relatedListInstance.addListObject({
|
|
94
|
+
id: "3",
|
|
95
|
+
name: "related2",
|
|
96
|
+
});
|
|
97
|
+
relatedListInstance.addListObject({
|
|
98
|
+
id: "4",
|
|
99
|
+
name: "related3",
|
|
100
|
+
});
|
|
101
|
+
const listRelated = useListRelated({
|
|
102
|
+
parentState: mainListInstance.state,
|
|
103
|
+
relatedObjectsRules: {
|
|
104
|
+
relatedItems: {
|
|
105
|
+
objects: relatedListInstance.state.objects,
|
|
106
|
+
pkKey: "related_items",
|
|
107
|
+
},
|
|
108
|
+
relatedItem: {
|
|
109
|
+
objects: relatedListInstance.state.objects,
|
|
110
|
+
pkKey: "related_id",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
const listCalculated = useListCalculated({
|
|
115
|
+
parentState: listRelated.state,
|
|
116
|
+
calculatedObjectsRules: {
|
|
117
|
+
calculatedItems: (obj, relatedObj) => relatedObj.relatedItems?.map((x) => x.name + "-modified"),
|
|
118
|
+
calculatedItem: (obj, relatedObj) => relatedObj.relatedItem?.name + "-modified",
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
const anr = new AwaitNot({
|
|
122
|
+
obj: listCalculated.state,
|
|
123
|
+
prop: "running",
|
|
124
|
+
});
|
|
125
|
+
anr.start();
|
|
126
|
+
await anr.promise;
|
|
127
|
+
expect(deepUnref(listCalculated.state.calculatedObjects)).toEqual({
|
|
128
|
+
1: {
|
|
129
|
+
calculatedItems: ["related1-modified", "related2-modified"],
|
|
130
|
+
calculatedItem: "related3-modified",
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
});
|
|
74
134
|
});
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
// import { useListInstance, useListRelated } from "../../../use/listRelated.js";
|
|
2
1
|
import { nextTick } from "vue";
|
|
3
2
|
import { deepUnref } from "vue-deepunref";
|
|
4
3
|
|
|
5
4
|
describe("use/listRelated", () => {
|
|
6
|
-
let useListInstance, useListRelated;
|
|
5
|
+
let useListInstance, useListRelated, AwaitNot;
|
|
7
6
|
beforeEach(async () => {
|
|
8
7
|
const listInstanceModule = await import("../../../use/listInstance");
|
|
9
8
|
useListInstance = listInstanceModule.useListInstance;
|
|
10
9
|
const listRelatedModule = await import("../../../use/listRelated");
|
|
11
10
|
useListRelated = listRelatedModule.useListRelated;
|
|
11
|
+
const watchesModule = await import("../../../utils/watches.js");
|
|
12
|
+
AwaitNot = watchesModule.AwaitNot;
|
|
12
13
|
});
|
|
13
14
|
it("should return a list of related items", async () => {
|
|
14
15
|
const mainListInstance = useListInstance({ props: {} });
|
|
@@ -78,4 +79,117 @@ describe("use/listRelated", () => {
|
|
|
78
79
|
},
|
|
79
80
|
});
|
|
80
81
|
});
|
|
82
|
+
it('should allow related objects pkKey to be prefixed with "relatedItem." to reference previously related objects', async () => {
|
|
83
|
+
//
|
|
84
|
+
const mainListInstance = useListInstance({ props: {} });
|
|
85
|
+
const intermediateListInstance = useListInstance({ props: {} });
|
|
86
|
+
const relatedListInstance = useListInstance({ props: {} });
|
|
87
|
+
mainListInstance.addListObject({
|
|
88
|
+
id: "1",
|
|
89
|
+
name: "main",
|
|
90
|
+
intermediate_ids: ["2", "3"],
|
|
91
|
+
intermediate_id: "2",
|
|
92
|
+
});
|
|
93
|
+
intermediateListInstance.addListObject({
|
|
94
|
+
id: "2",
|
|
95
|
+
name: "intermediate1",
|
|
96
|
+
related_ids: ["4", "5"],
|
|
97
|
+
related_id: "4",
|
|
98
|
+
});
|
|
99
|
+
intermediateListInstance.addListObject({
|
|
100
|
+
id: "3",
|
|
101
|
+
name: "intermediate2",
|
|
102
|
+
related_ids: ["6", "7"],
|
|
103
|
+
related_id: "6",
|
|
104
|
+
});
|
|
105
|
+
relatedListInstance.addListObject({
|
|
106
|
+
id: "4",
|
|
107
|
+
name: "related1",
|
|
108
|
+
});
|
|
109
|
+
relatedListInstance.addListObject({
|
|
110
|
+
id: "5",
|
|
111
|
+
name: "related2",
|
|
112
|
+
});
|
|
113
|
+
relatedListInstance.addListObject({
|
|
114
|
+
id: "6",
|
|
115
|
+
name: "related3",
|
|
116
|
+
});
|
|
117
|
+
relatedListInstance.addListObject({
|
|
118
|
+
id: "7",
|
|
119
|
+
name: "related4",
|
|
120
|
+
});
|
|
121
|
+
const listRelated = useListRelated({
|
|
122
|
+
parentState: mainListInstance.state,
|
|
123
|
+
relatedObjectsRules: {
|
|
124
|
+
intermediateItems: {
|
|
125
|
+
objects: intermediateListInstance.state.objects,
|
|
126
|
+
pkKey: "intermediate_ids",
|
|
127
|
+
},
|
|
128
|
+
intermediateItem: {
|
|
129
|
+
objects: intermediateListInstance.state.objects,
|
|
130
|
+
pkKey: "intermediate_id",
|
|
131
|
+
},
|
|
132
|
+
relatedItems: {
|
|
133
|
+
objects: relatedListInstance.state.objects,
|
|
134
|
+
pkKey: "relatedItem.intermediateItems.related_ids",
|
|
135
|
+
},
|
|
136
|
+
relatedItem: {
|
|
137
|
+
objects: relatedListInstance.state.objects,
|
|
138
|
+
pkKey: "relatedItem.intermediateItem.related_id",
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
const anr = new AwaitNot({
|
|
143
|
+
obj: listRelated.state,
|
|
144
|
+
prop: "running",
|
|
145
|
+
});
|
|
146
|
+
anr.start();
|
|
147
|
+
await anr.promise;
|
|
148
|
+
expect(deepUnref(listRelated.state.relatedObjects)).toEqual({
|
|
149
|
+
1: {
|
|
150
|
+
intermediateItems: [
|
|
151
|
+
{
|
|
152
|
+
id: "2",
|
|
153
|
+
name: "intermediate1",
|
|
154
|
+
related_ids: ["4", "5"],
|
|
155
|
+
related_id: "4",
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "3",
|
|
159
|
+
name: "intermediate2",
|
|
160
|
+
related_ids: ["6", "7"],
|
|
161
|
+
related_id: "6",
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
intermediateItem: {
|
|
165
|
+
id: "2",
|
|
166
|
+
name: "intermediate1",
|
|
167
|
+
related_ids: ["4", "5"],
|
|
168
|
+
related_id: "4",
|
|
169
|
+
},
|
|
170
|
+
relatedItems: [
|
|
171
|
+
{
|
|
172
|
+
id: "4",
|
|
173
|
+
name: "related1",
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: "5",
|
|
177
|
+
name: "related2",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: "6",
|
|
181
|
+
name: "related3",
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "7",
|
|
185
|
+
name: "related4",
|
|
186
|
+
},
|
|
187
|
+
],
|
|
188
|
+
relatedItem: {
|
|
189
|
+
id: "4",
|
|
190
|
+
name: "related1",
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
});
|
|
81
195
|
});
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { doAwaitTimeout } from "../../../utils/index.js";
|
|
2
|
-
import { nextTick } from "vue";
|
|
3
2
|
import { deepUnref } from "vue-deepunref";
|
|
4
3
|
|
|
5
4
|
describe("use/useListSort", () => {
|
|
@@ -10,7 +9,8 @@ describe("use/useListSort", () => {
|
|
|
10
9
|
useListInstances,
|
|
11
10
|
useListSort,
|
|
12
11
|
useListSorts,
|
|
13
|
-
setListSortDefaultOptions
|
|
12
|
+
setListSortDefaultOptions,
|
|
13
|
+
AwaitNot;
|
|
14
14
|
const contactsResolved = [
|
|
15
15
|
{
|
|
16
16
|
id: 15,
|
|
@@ -58,10 +58,22 @@ describe("use/useListSort", () => {
|
|
|
58
58
|
setListSortDefaultOptions({
|
|
59
59
|
sortThrottleWait: 0,
|
|
60
60
|
});
|
|
61
|
+
const importedUtils = await import("../../../utils");
|
|
62
|
+
AwaitNot = importedUtils.AwaitNot;
|
|
61
63
|
});
|
|
62
64
|
|
|
63
65
|
afterEach(() => vi.resetAllMocks());
|
|
64
66
|
|
|
67
|
+
const waitForListSort = async (listSort) => {
|
|
68
|
+
const anr = new AwaitNot({
|
|
69
|
+
obj: listSort.state,
|
|
70
|
+
prop: "running",
|
|
71
|
+
timeout: 2000,
|
|
72
|
+
});
|
|
73
|
+
anr.start();
|
|
74
|
+
await anr.promise;
|
|
75
|
+
};
|
|
76
|
+
|
|
65
77
|
it("generates initial values from inputs", () => {
|
|
66
78
|
const listSort = useListSort({ parentState: listInstance.state, orderByRules, sortThrottleWait });
|
|
67
79
|
expect(listSort.state.orderByRules).toEqual(orderByRules);
|
|
@@ -89,13 +101,14 @@ describe("use/useListSort", () => {
|
|
|
89
101
|
listInstance.addListObject(contact);
|
|
90
102
|
}
|
|
91
103
|
const listSort = useListSort({ parentState: listInstance.state, orderByRules });
|
|
92
|
-
|
|
104
|
+
// sorts immediately
|
|
93
105
|
expect(listSort.state.order).toEqual(testOrder1);
|
|
106
|
+
await waitForListSort(listSort);
|
|
94
107
|
listInstance.addListObject(addObject);
|
|
95
|
-
await
|
|
108
|
+
await waitForListSort(listSort);
|
|
96
109
|
expect(listSort.state.order).toEqual(testOrder2);
|
|
97
110
|
listInstance.deleteListObject(12);
|
|
98
|
-
await
|
|
111
|
+
await waitForListSort(listSort);
|
|
99
112
|
expect(listSort.state.order).toEqual(testOrder3);
|
|
100
113
|
});
|
|
101
114
|
});
|
|
@@ -115,17 +128,17 @@ describe("use/useListSort", () => {
|
|
|
115
128
|
listSort.state.orderByRules.pop();
|
|
116
129
|
listSort.state.orderByRules.push({ key: "lexical_name", desc: false, localeCompare: true });
|
|
117
130
|
expect(listSort.state.order).toEqual(testOrder1);
|
|
118
|
-
await
|
|
131
|
+
await waitForListSort(listSort);
|
|
119
132
|
expect(listSort.state.order).toEqual(testOrder2);
|
|
120
133
|
listSort.state.orderByRules.pop();
|
|
121
134
|
listSort.state.orderByRules.push({ key: "organization", desc: true, localeCompare: true });
|
|
122
|
-
await
|
|
135
|
+
await waitForListSort(listSort);
|
|
123
136
|
expect(listSort.state.order).toEqual(testOrder3);
|
|
124
137
|
listSort.state.orderByRules.pop();
|
|
125
|
-
await
|
|
138
|
+
await waitForListSort(listSort);
|
|
126
139
|
expect(listSort.state.order).toEqual(testOrder4);
|
|
127
140
|
listSort.state.orderByRules.push({ key: "organization", desc: false, localeCompare: false });
|
|
128
|
-
await
|
|
141
|
+
await waitForListSort(listSort);
|
|
129
142
|
expect(listSort.state.order).toEqual(testOrder2);
|
|
130
143
|
});
|
|
131
144
|
});
|
|
@@ -212,20 +225,31 @@ describe("use/useListSort", () => {
|
|
|
212
225
|
}
|
|
213
226
|
|
|
214
227
|
const testOrder1 = [];
|
|
215
|
-
const testOrder2 = ["
|
|
216
|
-
const testOrder3 = ["35", "15", "9"];
|
|
228
|
+
const testOrder2 = ["12", "15", "9"];
|
|
229
|
+
const testOrder3 = ["35", "12", "15", "9"];
|
|
230
|
+
const testOrder4 = ["35", "15", "9"];
|
|
217
231
|
|
|
218
232
|
const listSort = useListSort({ parentState: listInstance.state, orderByRules, sortThrottleWait });
|
|
219
|
-
await nextTick();
|
|
220
233
|
expect(listSort.state.order).toEqual(testOrder1);
|
|
234
|
+
|
|
235
|
+
// wait for the original throttle to finish
|
|
236
|
+
await doAwaitTimeout(250);
|
|
237
|
+
|
|
238
|
+
expect(listSort.state.order).toEqual(testOrder2);
|
|
221
239
|
listInstance.addListObject(addObject);
|
|
222
|
-
await nextTick();
|
|
223
240
|
expect(listSort.state.order).toEqual(testOrder2);
|
|
241
|
+
// trigger the leading edge of the throttle
|
|
242
|
+
await doAwaitTimeout(10);
|
|
243
|
+
expect(listSort.state.order).toEqual(testOrder3);
|
|
244
|
+
// trigger again before the 200ms throttle
|
|
224
245
|
listInstance.deleteListObject(12);
|
|
225
|
-
await nextTick();
|
|
226
|
-
expect(listSort.state.order).toEqual(testOrder2);
|
|
227
|
-
await doAwaitTimeout(200);
|
|
228
246
|
expect(listSort.state.order).toEqual(testOrder3);
|
|
247
|
+
// this should trigger before the 200ms throttle
|
|
248
|
+
await doAwaitTimeout(100);
|
|
249
|
+
expect(listSort.state.order).toEqual(testOrder3);
|
|
250
|
+
// this should trigger after the 200ms throttle
|
|
251
|
+
await doAwaitTimeout(200);
|
|
252
|
+
expect(listSort.state.order).toEqual(testOrder4);
|
|
229
253
|
});
|
|
230
254
|
});
|
|
231
255
|
});
|
package/use/listCalculated.js
CHANGED
|
@@ -87,7 +87,11 @@ export function useListCalculated({ parentState, calculatedObjectsRules }) {
|
|
|
87
87
|
calculatedObjectsEffectScopes[objectKey].run(() => {
|
|
88
88
|
for (const addedRuleKey of addedRuleKeys) {
|
|
89
89
|
calculatedObjectsObject[addedRuleKey] = computed(() =>
|
|
90
|
-
state.calculatedObjectsRules?.[addedRuleKey]?.(
|
|
90
|
+
state.calculatedObjectsRules?.[addedRuleKey]?.(
|
|
91
|
+
unref(originalObject),
|
|
92
|
+
unref(relatedObject),
|
|
93
|
+
calculatedObjectsObject
|
|
94
|
+
)
|
|
91
95
|
);
|
|
92
96
|
}
|
|
93
97
|
});
|
package/use/listRelated.js
CHANGED
|
@@ -58,6 +58,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
58
58
|
for (const objectKey of Object.keys(state.relatedObjects)) {
|
|
59
59
|
const relatedObjectsObject = state.relatedObjects[objectKey];
|
|
60
60
|
const originalObjectRef = toRef(parentState.objects, objectKey);
|
|
61
|
+
const relatedObjectRef = toRef(state.relatedObjects, objectKey);
|
|
61
62
|
let removedRuleKeys, addedRuleKeys;
|
|
62
63
|
if (!relatedObjectsRulesIsEmpty) {
|
|
63
64
|
({ removedKeys: removedRuleKeys, addedKeys: addedRuleKeys } = keyDiff(
|
|
@@ -86,12 +87,30 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
86
87
|
const rulePkKey = state.relatedObjectsRules?.[addedRuleKey]?.pkKey || addedRuleKey;
|
|
87
88
|
const ruleOrder = unref(state.relatedObjectsRules?.[addedRuleKey]?.order);
|
|
88
89
|
if (!ruleObjects || !rulePkKey) {
|
|
89
|
-
relatedObjectsObject[addedRuleKey]
|
|
90
|
+
if (!isUndefined(relatedObjectsObject[addedRuleKey])) {
|
|
91
|
+
relatedObjectsObject[addedRuleKey] = undefined;
|
|
92
|
+
}
|
|
90
93
|
return;
|
|
91
94
|
}
|
|
92
|
-
let value
|
|
95
|
+
let value;
|
|
96
|
+
if (rulePkKey.startsWith("relatedItem.")) {
|
|
97
|
+
value = get(unref(relatedObjectRef), rulePkKey.slice(12));
|
|
98
|
+
if (isUndefined(value)) {
|
|
99
|
+
// is the first level an array?
|
|
100
|
+
const firstLevelKey = rulePkKey.slice(12).split(".")[0];
|
|
101
|
+
const firstLevelItem = get(unref(relatedObjectRef), firstLevelKey);
|
|
102
|
+
if (isArray(firstLevelItem)) {
|
|
103
|
+
const restOfKey = rulePkKey.slice(12 + firstLevelKey.length + 1);
|
|
104
|
+
value = firstLevelItem.map((e) => get(e, restOfKey)).flat();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
value = get(unref(originalObjectRef), rulePkKey);
|
|
109
|
+
}
|
|
93
110
|
if (isUndefined(value)) {
|
|
94
|
-
relatedObjectsObject[addedRuleKey]
|
|
111
|
+
if (!isUndefined(relatedObjectsObject[addedRuleKey])) {
|
|
112
|
+
relatedObjectsObject[addedRuleKey] = undefined;
|
|
113
|
+
}
|
|
95
114
|
return;
|
|
96
115
|
}
|
|
97
116
|
if (isArray(value)) {
|
|
@@ -114,7 +133,7 @@ export function useListRelated({ parentState, relatedObjectsRules }) {
|
|
|
114
133
|
}
|
|
115
134
|
};
|
|
116
135
|
watch(
|
|
117
|
-
[toRef(state.relatedObjectsRules, addedRuleKey), originalObjectRef],
|
|
136
|
+
[toRef(state.relatedObjectsRules, addedRuleKey), originalObjectRef, relatedObjectRef],
|
|
118
137
|
relatedObjectsObjectWatchFn,
|
|
119
138
|
{
|
|
120
139
|
deep: true,
|
package/use/listSort.js
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { assignReactiveObject, keyDiff } from "../utils/index.js";
|
|
1
|
+
import { assignReactiveObject, keyDiff, loadingCombine } from "../utils/index.js";
|
|
2
2
|
import { listCalculatedStateKeys } from "./listCalculated.js";
|
|
3
3
|
import { listFilterStateKeys } from "./listFilter.js";
|
|
4
4
|
import { listInstanceStateKeys } from "./listInstance.js";
|
|
5
5
|
import { listRelatedStateKeys } from "./listRelated.js";
|
|
6
6
|
import { listSubscriptionStateKeys } from "./listSubscription.js";
|
|
7
|
+
import { useWatchesRunning } from "./watchesRunning.js";
|
|
7
8
|
import cloneDeep from "lodash-es/cloneDeep.js";
|
|
8
9
|
import get from "lodash-es/get.js";
|
|
9
10
|
import identity from "lodash-es/identity.js";
|
|
10
11
|
import isEmpty from "lodash-es/isEmpty.js";
|
|
12
|
+
import isEqual from "lodash-es/isEqual.js";
|
|
11
13
|
import isNull from "lodash-es/isNull.js";
|
|
12
14
|
import isUndefined from "lodash-es/isUndefined.js";
|
|
13
15
|
import throttle from "lodash-es/throttle.js";
|
|
14
16
|
import zip from "lodash-es/zip.js";
|
|
15
|
-
import { effectScope, reactive, toRef, unref, watch } from "vue";
|
|
17
|
+
import { effectScope, reactive, toRef, unref, watch, computed } from "vue";
|
|
16
18
|
|
|
17
19
|
export const listSortStateKeys = [
|
|
18
20
|
"orderByRules",
|
|
@@ -21,6 +23,10 @@ export const listSortStateKeys = [
|
|
|
21
23
|
"sortCriteria",
|
|
22
24
|
"sortCriteriaEffectScopes",
|
|
23
25
|
"orderByDesc",
|
|
26
|
+
"sortCriteriaWatchRunning",
|
|
27
|
+
"sortWatchRunning",
|
|
28
|
+
"outstandingEffects",
|
|
29
|
+
// "running",
|
|
24
30
|
];
|
|
25
31
|
|
|
26
32
|
export const listSortFunctions = [];
|
|
@@ -56,6 +62,9 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
56
62
|
sortCriteria: {},
|
|
57
63
|
sortCriteriaEffectScopes: {},
|
|
58
64
|
orderByDesc: [],
|
|
65
|
+
sortCriteriaWatchRunning: false,
|
|
66
|
+
sortWatchRunning: false,
|
|
67
|
+
outstandingEffects: false,
|
|
59
68
|
});
|
|
60
69
|
const es = effectScope();
|
|
61
70
|
|
|
@@ -92,7 +101,13 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
92
101
|
}
|
|
93
102
|
newSearchCriteria.push(newItem);
|
|
94
103
|
}
|
|
104
|
+
if (isEqual(newSearchCriteria, state.sortCriteria[key])) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
95
107
|
assignReactiveObject(state.sortCriteria[key], newSearchCriteria);
|
|
108
|
+
if (!state.outstandingEffects) {
|
|
109
|
+
state.outstandingEffects = true;
|
|
110
|
+
}
|
|
96
111
|
},
|
|
97
112
|
{
|
|
98
113
|
deep: true,
|
|
@@ -104,82 +119,96 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
104
119
|
}
|
|
105
120
|
|
|
106
121
|
function sortCriteriaWatch() {
|
|
107
|
-
|
|
108
|
-
if (!
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
try {
|
|
123
|
+
if (!state.orderByRules || !state.orderByRules.filter(identity).length) {
|
|
124
|
+
if (!isEmpty(state.sortCriteria)) {
|
|
125
|
+
for (const removedKey of Object.keys(state.sortCriteria)) {
|
|
126
|
+
removeSortCriteria(removedKey);
|
|
127
|
+
}
|
|
111
128
|
}
|
|
129
|
+
assignReactiveObject(state.order, cloneDeep(parentState.order));
|
|
130
|
+
assignReactiveObject(state.objectsInOrder, cloneDeep(parentState.objectsInOrder));
|
|
131
|
+
return;
|
|
112
132
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
removeSortCriteria(removedKey);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
es.run(() => {
|
|
123
|
-
for (const addedKey of addedKeys) {
|
|
124
|
-
const object = toRef(() => parentState.objects[addedKey]);
|
|
125
|
-
addSortCriteria(object, addedKey);
|
|
133
|
+
const { removedKeys, addedKeys } = keyDiff(
|
|
134
|
+
Object.keys(parentState.objects),
|
|
135
|
+
Object.keys(state.sortCriteria)
|
|
136
|
+
);
|
|
137
|
+
for (const removedKey of removedKeys) {
|
|
138
|
+
removeSortCriteria(removedKey);
|
|
126
139
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
140
|
+
|
|
141
|
+
es.run(() => {
|
|
142
|
+
for (const addedKey of addedKeys) {
|
|
143
|
+
const object = toRef(() => parentState.objects[addedKey]);
|
|
144
|
+
addSortCriteria(object, addedKey);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
assignReactiveObject(
|
|
148
|
+
state.orderByDesc,
|
|
149
|
+
state.orderByRules.filter(identity).map((e) => e.desc || false)
|
|
150
|
+
);
|
|
151
|
+
} finally {
|
|
152
|
+
state.sortCriteriaWatchRunning = false;
|
|
153
|
+
}
|
|
132
154
|
}
|
|
133
155
|
|
|
134
156
|
function sortWatch() {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
157
|
+
try {
|
|
158
|
+
if (!state.orderByRules || !state.orderByRules.length) {
|
|
159
|
+
assignReactiveObject(state.order, cloneDeep(parentState.order));
|
|
160
|
+
assignReactiveObject(state.objectsInOrder, cloneDeep(parentState.objectsInOrder));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
140
163
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
if (orderByObj.desc) {
|
|
150
|
-
[x, y] = [y, x];
|
|
151
|
-
}
|
|
152
|
-
const isUndefinedX = isUndefined(x) || isNull(x);
|
|
153
|
-
const isUndefinedY = isUndefined(y) || isNull(y);
|
|
154
|
-
if (isUndefinedX && isUndefinedY) {
|
|
155
|
-
continue;
|
|
156
|
-
} else if (isUndefinedX) {
|
|
157
|
-
return -1;
|
|
158
|
-
} else if (isUndefinedY) {
|
|
159
|
-
return 1;
|
|
160
|
-
}
|
|
161
|
-
if (orderByObj.localeCompare) {
|
|
162
|
-
const strComp = collator.compare(x, y);
|
|
163
|
-
if (strComp) {
|
|
164
|
-
return strComp;
|
|
164
|
+
let idList = Object.keys(parentState.objects);
|
|
165
|
+
idList.sort((xKey, yKey) => {
|
|
166
|
+
const xCriteria = state.sortCriteria[xKey];
|
|
167
|
+
const yCriteria = state.sortCriteria[yKey];
|
|
168
|
+
for (let [x, y, orderByObj] of zip(xCriteria, yCriteria, state.orderByRules)) {
|
|
169
|
+
if (!orderByObj) {
|
|
170
|
+
continue;
|
|
165
171
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return -1;
|
|
172
|
+
if (orderByObj.desc) {
|
|
173
|
+
[x, y] = [y, x];
|
|
169
174
|
}
|
|
170
|
-
|
|
175
|
+
const isUndefinedX = isUndefined(x) || isNull(x);
|
|
176
|
+
const isUndefinedY = isUndefined(y) || isNull(y);
|
|
177
|
+
if (isUndefinedX && isUndefinedY) {
|
|
178
|
+
continue;
|
|
179
|
+
} else if (isUndefinedX) {
|
|
180
|
+
return -1;
|
|
181
|
+
} else if (isUndefinedY) {
|
|
171
182
|
return 1;
|
|
172
183
|
}
|
|
184
|
+
if (orderByObj.localeCompare) {
|
|
185
|
+
const strComp = collator.compare(x, y);
|
|
186
|
+
if (strComp) {
|
|
187
|
+
return strComp;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
if (x < y) {
|
|
191
|
+
return -1;
|
|
192
|
+
}
|
|
193
|
+
if (x > y) {
|
|
194
|
+
return 1;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
173
197
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
return 0;
|
|
199
|
+
});
|
|
200
|
+
assignReactiveObject(state.order, idList);
|
|
201
|
+
assignReactiveObject(state.objectsInOrder, idList.map((e) => parentState.objects[e]).filter(identity));
|
|
202
|
+
} finally {
|
|
203
|
+
state.sortWatchRunning = false;
|
|
204
|
+
state.outstandingEffects = false;
|
|
205
|
+
}
|
|
179
206
|
}
|
|
180
207
|
|
|
181
208
|
const throttledSortWatch = throttle(sortWatch, sortThrottleWait);
|
|
182
209
|
|
|
210
|
+
let watchesRunning = null;
|
|
211
|
+
|
|
183
212
|
es.run(() => {
|
|
184
213
|
for (const key of listInstanceStateKeys) {
|
|
185
214
|
if (["order", "objectsInOrder"].includes(key)) {
|
|
@@ -209,11 +238,23 @@ export function useListSort({ parentState, orderByRules, sortThrottleWait = defa
|
|
|
209
238
|
deep: true,
|
|
210
239
|
immediate: true,
|
|
211
240
|
});
|
|
241
|
+
watchesRunning = useWatchesRunning({
|
|
242
|
+
triggerRefs: [
|
|
243
|
+
computed(() => (state.orderByRules && !isEmpty(state.orderByRules) ? parentState.loading : false)),
|
|
244
|
+
],
|
|
245
|
+
watchSentinelRefs: [toRef(state, "sortCriteriaWatchRunning"), toRef(state, "sortWatchRunning")],
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
state.sortRunning = computed(() => loadingCombine(watchesRunning.state.running, state.outstandingEffects));
|
|
249
|
+
state.running = computed(() =>
|
|
250
|
+
loadingCombine(watchesRunning.state.running, state.outstandingEffects, parentState.running)
|
|
251
|
+
);
|
|
212
252
|
});
|
|
213
253
|
|
|
214
254
|
return {
|
|
215
255
|
state,
|
|
216
256
|
parentState,
|
|
217
257
|
effectScope: es,
|
|
258
|
+
watchesRunning,
|
|
218
259
|
};
|
|
219
260
|
}
|
package/use/listSubscription.js
CHANGED
|
@@ -77,8 +77,8 @@ export function useListSubscriptions(args, listInstances = {}) {
|
|
|
77
77
|
/**
|
|
78
78
|
* `useListSubscription` creates a reactive object that manages a list of objects, as returned by `useListInstance`,
|
|
79
79
|
* causing the list to be re-fetched as needed and listening for updates to the list.
|
|
80
|
-
* @param {ListSubscriptionOptions} options
|
|
81
|
-
* @returns ListSubscription
|
|
80
|
+
* @param {ListSubscriptionOptions} options - the configuration options for the list subscription
|
|
81
|
+
* @returns {ListSubscription} - the list subscription
|
|
82
82
|
*/
|
|
83
83
|
export function useListSubscription({ listInstance, props, functions, keepOldPages = false }) {
|
|
84
84
|
if (!listInstance && !props) {
|
package/utils/watches.js
CHANGED
|
@@ -86,7 +86,9 @@ export function doAwaitTimeout(timeout) {
|
|
|
86
86
|
|
|
87
87
|
export class AwaitNot {
|
|
88
88
|
constructor({ obj, prop, couldAlreadyBeFalse = false, timeout = 1000 }) {
|
|
89
|
-
|
|
89
|
+
if (timeout > 0) {
|
|
90
|
+
this.timeout = new AwaitTimeout({ timeout });
|
|
91
|
+
}
|
|
90
92
|
this.promise = new Promise((resolve, reject) => {
|
|
91
93
|
this.resolve = resolve;
|
|
92
94
|
this.reject = reject;
|
|
@@ -97,22 +99,32 @@ export class AwaitNot {
|
|
|
97
99
|
this.trueISW = new ImmediateStopWatch();
|
|
98
100
|
this.falseISW = new ImmediateStopWatch();
|
|
99
101
|
// prebuild the exception for a more useful stack.
|
|
100
|
-
this.
|
|
102
|
+
if (this.timeout) {
|
|
103
|
+
this.timeoutError = new AwaitNotError("Timeout", "timeout");
|
|
104
|
+
}
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
start() {
|
|
104
|
-
this.timeout
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
if (this.timeout) {
|
|
109
|
+
this.timeout.promise
|
|
110
|
+
.then(() => {
|
|
111
|
+
if (this.reject) {
|
|
112
|
+
this.reject(this.timeoutError);
|
|
113
|
+
}
|
|
114
|
+
this.stop();
|
|
115
|
+
this.cleanPromise();
|
|
116
|
+
})
|
|
117
|
+
.catch((err) => {
|
|
118
|
+
if (!(err instanceof AwaitTimeoutError)) {
|
|
119
|
+
if (this.reject) {
|
|
120
|
+
this.reject(err);
|
|
121
|
+
}
|
|
122
|
+
this.cleanPromise();
|
|
123
|
+
}
|
|
124
|
+
this.stop();
|
|
125
|
+
});
|
|
126
|
+
this.timeout.start();
|
|
127
|
+
}
|
|
116
128
|
if (this.obj[this.prop] === false && this.couldAlreadyBeFalse) {
|
|
117
129
|
this.waitForTrue();
|
|
118
130
|
} else {
|
|
@@ -146,7 +158,10 @@ export class AwaitNot {
|
|
|
146
158
|
(newValue) => {
|
|
147
159
|
if (newValue === false) {
|
|
148
160
|
this.stop();
|
|
149
|
-
this.resolve
|
|
161
|
+
if (this.resolve) {
|
|
162
|
+
this.resolve();
|
|
163
|
+
}
|
|
164
|
+
this.cleanPromise();
|
|
150
165
|
}
|
|
151
166
|
},
|
|
152
167
|
[this.obj[this.prop]]
|
|
@@ -161,10 +176,21 @@ export class AwaitNot {
|
|
|
161
176
|
}
|
|
162
177
|
|
|
163
178
|
stop() {
|
|
164
|
-
this.timeout
|
|
179
|
+
if (this.timeout) {
|
|
180
|
+
this.timeout.stop();
|
|
181
|
+
}
|
|
165
182
|
this.stopTrue();
|
|
166
183
|
this.stopFalse();
|
|
167
184
|
}
|
|
185
|
+
|
|
186
|
+
cleanPromise() {
|
|
187
|
+
if (this.resolve) {
|
|
188
|
+
delete this.resolve;
|
|
189
|
+
}
|
|
190
|
+
if (this.reject) {
|
|
191
|
+
delete this.reject;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
168
194
|
}
|
|
169
195
|
|
|
170
196
|
export function doAwaitNot({ obj, prop, couldAlreadyBeFalse = true, timeout = 1000 }) {
|