@startinblox/boilerplate 3.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/.gitlab-ci.yml +57 -0
- package/.storybook/main.ts +15 -0
- package/.storybook/preview-head.html +8 -0
- package/.storybook/preview.ts +22 -0
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/biome.json +39 -0
- package/cypress/component/solid-boilerplate.cy.ts +9 -0
- package/cypress/cypress.d.ts +1 -0
- package/cypress/support/component-index.html +12 -0
- package/cypress/support/component.ts +17 -0
- package/cypress.config.ts +11 -0
- package/dist/boilerplate.css +1 -0
- package/dist/index.js +1213 -0
- package/lit-localize.json +15 -0
- package/locales/en.xlf +13 -0
- package/package.json +92 -0
- package/postcss.config.js +8 -0
- package/src/component.d.ts +161 -0
- package/src/components/solid-boilerplate.ts +79 -0
- package/src/components/ui/sample-object.ts +37 -0
- package/src/components/ui/sample-objects.ts +40 -0
- package/src/context.json +1 -0
- package/src/helpers/components/componentObjectHandler.ts +100 -0
- package/src/helpers/components/componentObjectsHandler.ts +44 -0
- package/src/helpers/components/orbitComponent.ts +241 -0
- package/src/helpers/components/setupCacheInvalidation.ts +37 -0
- package/src/helpers/components/setupCacheOnResourceReady.ts +32 -0
- package/src/helpers/components/setupComponentSubscriptions.ts +73 -0
- package/src/helpers/components/setupOnSaveReset.ts +20 -0
- package/src/helpers/datas/dataBuilder.ts +43 -0
- package/src/helpers/datas/filterGenerator.ts +29 -0
- package/src/helpers/datas/filterObjectByDateAfter.ts +80 -0
- package/src/helpers/datas/filterObjectById.ts +54 -0
- package/src/helpers/datas/filterObjectByInterval.ts +133 -0
- package/src/helpers/datas/filterObjectByNamedValue.ts +103 -0
- package/src/helpers/datas/filterObjectByType.ts +30 -0
- package/src/helpers/datas/filterObjectByValue.ts +81 -0
- package/src/helpers/datas/sort.ts +40 -0
- package/src/helpers/i18n/configureLocalization.ts +17 -0
- package/src/helpers/index.ts +41 -0
- package/src/helpers/ui/formatDate.ts +18 -0
- package/src/helpers/ui/lipsum.ts +12 -0
- package/src/helpers/utils/requestNavigation.ts +12 -0
- package/src/helpers/utils/uniq.ts +6 -0
- package/src/index.ts +7 -0
- package/src/initializer.ts +11 -0
- package/src/mocks/orbit.mock.ts +33 -0
- package/src/mocks/user.mock.ts +67 -0
- package/src/styles/component-sample.scss +4 -0
- package/src/styles/index.scss +16 -0
- package/stories/sample-objects.stories.ts +47 -0
- package/tsconfig.json +36 -0
- package/vite.config.ts +44 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { nothing } from "lit";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
CLIENT_CONTEXT,
|
|
6
|
+
requestNavigation,
|
|
7
|
+
setupComponentSubscriptions,
|
|
8
|
+
} from "@helpers";
|
|
9
|
+
|
|
10
|
+
import { ComponentObjectsHandler } from "@helpers/components/componentObjectsHandler";
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
Container,
|
|
14
|
+
LiveOrbit,
|
|
15
|
+
PropertiesPicker,
|
|
16
|
+
ProxyValue,
|
|
17
|
+
Resource,
|
|
18
|
+
UnknownResource,
|
|
19
|
+
} from "@src/component";
|
|
20
|
+
|
|
21
|
+
export default class extends ComponentObjectsHandler {
|
|
22
|
+
constructor({
|
|
23
|
+
defaultRoute = false,
|
|
24
|
+
setupSubscriptions = true,
|
|
25
|
+
ignoreRouter = false,
|
|
26
|
+
} = {}) {
|
|
27
|
+
super();
|
|
28
|
+
const attach = () => {
|
|
29
|
+
this._attach(defaultRoute, setupSubscriptions, ignoreRouter).then(
|
|
30
|
+
(attach: boolean) => {
|
|
31
|
+
if (attach) {
|
|
32
|
+
this.requestUpdate();
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
if (document.readyState === "loading") {
|
|
38
|
+
document.addEventListener("DOMContentLoaded", attach);
|
|
39
|
+
} else {
|
|
40
|
+
attach();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@property({ attribute: "default-data-src", reflect: true })
|
|
45
|
+
defaultDataSrc?: string;
|
|
46
|
+
|
|
47
|
+
@property({ attribute: "data-src", reflect: true })
|
|
48
|
+
dataSrc?: string;
|
|
49
|
+
|
|
50
|
+
@property({ attribute: "nested-field" })
|
|
51
|
+
nestedField?: string;
|
|
52
|
+
|
|
53
|
+
@property({ attribute: "uniq" })
|
|
54
|
+
uniq?: string;
|
|
55
|
+
|
|
56
|
+
@property({ attribute: "route" })
|
|
57
|
+
route: string | undefined;
|
|
58
|
+
|
|
59
|
+
@property({ attribute: false })
|
|
60
|
+
cherryPickedProperties: PropertiesPicker[] = [];
|
|
61
|
+
|
|
62
|
+
@state()
|
|
63
|
+
orbit: LiveOrbit | undefined;
|
|
64
|
+
|
|
65
|
+
@state()
|
|
66
|
+
currentRoute = "";
|
|
67
|
+
|
|
68
|
+
protected async _attach(
|
|
69
|
+
defaultRoute: boolean,
|
|
70
|
+
setupSubscriptions: boolean,
|
|
71
|
+
ignoreRouter: boolean
|
|
72
|
+
) {
|
|
73
|
+
if (!this.orbit) {
|
|
74
|
+
if (window.orbit) {
|
|
75
|
+
this.orbit = window.orbit;
|
|
76
|
+
if (setupSubscriptions) {
|
|
77
|
+
setupComponentSubscriptions({
|
|
78
|
+
component: this,
|
|
79
|
+
defaultRoute: defaultRoute,
|
|
80
|
+
ignoreRouter: ignoreRouter,
|
|
81
|
+
});
|
|
82
|
+
if (this.route) {
|
|
83
|
+
this.component = this.orbit.getComponentFromRoute(this.route);
|
|
84
|
+
if (this.component) {
|
|
85
|
+
this.orbit.components.map((c) => {
|
|
86
|
+
if (c.uniq === this.component.uniq) {
|
|
87
|
+
c.instance = this;
|
|
88
|
+
}
|
|
89
|
+
return c;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
await this._afterAttach();
|
|
94
|
+
return Promise.resolve(true);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return Promise.resolve(false);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async _afterAttach() {
|
|
102
|
+
return Promise.resolve();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_navigate(e: Event) {
|
|
106
|
+
window.sibRouter.previousRoute = this.currentRoute;
|
|
107
|
+
window.sibRouter.previousResource = window.sibRouter.currentResource;
|
|
108
|
+
const navigator = e.target?.closest("[navigation-target]");
|
|
109
|
+
let target = navigator.getAttribute("navigation-target");
|
|
110
|
+
const subrouter = navigator.getAttribute("navigation-subrouter");
|
|
111
|
+
const resource = navigator.getAttribute("navigation-resource");
|
|
112
|
+
const rdfType = navigator.getAttribute("navigation-rdf-type");
|
|
113
|
+
if (rdfType) {
|
|
114
|
+
const compatibleComponents = window.orbit?.components?.filter(
|
|
115
|
+
(c) => c?.routeAttributes?.["rdf-type"] === rdfType
|
|
116
|
+
);
|
|
117
|
+
if (compatibleComponents) target = compatibleComponents[0]?.uniq;
|
|
118
|
+
}
|
|
119
|
+
if (target) {
|
|
120
|
+
requestNavigation(
|
|
121
|
+
(window.orbit ? window.orbit.getRoute(target, true) : target) +
|
|
122
|
+
(subrouter ? `-${subrouter}` : ""),
|
|
123
|
+
resource
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
_normalizeLdpContains(value: Resource[] | Resource): Resource[] {
|
|
130
|
+
if (!Array.isArray(value) && value !== null) {
|
|
131
|
+
return [value];
|
|
132
|
+
}
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async _expandContainer(
|
|
137
|
+
value: Resource[],
|
|
138
|
+
recursive = true
|
|
139
|
+
): Promise<UnknownResource[]> {
|
|
140
|
+
const expandedContainer: UnknownResource[] = [];
|
|
141
|
+
for (const entry of value) {
|
|
142
|
+
const line = await this._getProxyValue(entry, recursive);
|
|
143
|
+
if (line) expandedContainer.push(line);
|
|
144
|
+
}
|
|
145
|
+
return expandedContainer;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async _getProperties(
|
|
149
|
+
resource: Resource,
|
|
150
|
+
recursive = true,
|
|
151
|
+
targetProperties: PropertiesPicker[] = this.cherryPickedProperties
|
|
152
|
+
) {
|
|
153
|
+
const properties = await resource.properties;
|
|
154
|
+
const response: Resource = {
|
|
155
|
+
"@id": resource["@id"],
|
|
156
|
+
"@type": resource["@type"],
|
|
157
|
+
"@context": resource.serverContext,
|
|
158
|
+
_originalResource: resource,
|
|
159
|
+
};
|
|
160
|
+
for (const prop of targetProperties) {
|
|
161
|
+
if (properties?.includes(prop.key)) {
|
|
162
|
+
response[prop.value] = await resource.get(prop.key);
|
|
163
|
+
if (prop.expand) {
|
|
164
|
+
response[prop.value] = await this._getProxyValue(
|
|
165
|
+
response[prop.value],
|
|
166
|
+
recursive,
|
|
167
|
+
targetProperties
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (prop.cast) {
|
|
171
|
+
response[prop.value] = prop.cast(response[prop.value]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return await this._responseAdaptator(response);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async _getProxyValue(
|
|
179
|
+
resource:
|
|
180
|
+
| string
|
|
181
|
+
| Resource
|
|
182
|
+
| ProxyValue<Resource | Container<ProxyValue<Resource> | Resource>>,
|
|
183
|
+
recursive = true,
|
|
184
|
+
targetProperties: PropertiesPicker[] = this.cherryPickedProperties
|
|
185
|
+
) {
|
|
186
|
+
try {
|
|
187
|
+
if (resource) {
|
|
188
|
+
let target = resource;
|
|
189
|
+
if (typeof resource === "string") {
|
|
190
|
+
target = await window.sibStore.getData(resource, CLIENT_CONTEXT);
|
|
191
|
+
}
|
|
192
|
+
if (typeof resource !== "string" && resource.isFullResource) {
|
|
193
|
+
if (!resource.isFullResource?.()) {
|
|
194
|
+
target = await window.sibStore.getData(
|
|
195
|
+
resource["@id"],
|
|
196
|
+
CLIENT_CONTEXT
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (typeof resource !== "string" && !resource.isFullResource) {
|
|
201
|
+
// Edge case when calling getProxyValue with an already
|
|
202
|
+
// fetched resource with server search, not a proxy
|
|
203
|
+
(target as Resource).properties = Object.keys(target);
|
|
204
|
+
(target as Resource).get = (property: any) =>
|
|
205
|
+
(target as Resource)[property];
|
|
206
|
+
}
|
|
207
|
+
if (!target) return { _originalResource: target };
|
|
208
|
+
if (typeof target === "object" && target !== null) {
|
|
209
|
+
if (target["ldp:contains"]) {
|
|
210
|
+
const value = this._normalizeLdpContains(target["ldp:contains"]);
|
|
211
|
+
return await this._expandContainer(value, recursive);
|
|
212
|
+
}
|
|
213
|
+
return await this._getProperties(target, recursive, targetProperties);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return;
|
|
217
|
+
} catch (e) {
|
|
218
|
+
if (import.meta.env.DEV) console.error(e);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async _responseAdaptator(response: Resource) {
|
|
223
|
+
return Promise.resolve(response);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
gatekeeper() {
|
|
227
|
+
if (
|
|
228
|
+
!this.orbit ||
|
|
229
|
+
(!this.noRouter &&
|
|
230
|
+
this.route &&
|
|
231
|
+
this.currentRoute &&
|
|
232
|
+
!this.route.startsWith(this.currentRoute))
|
|
233
|
+
) {
|
|
234
|
+
return nothing;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!this.dataSrc) {
|
|
238
|
+
return nothing;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Common code for components
|
|
3
|
+
Handle cache invalidation based on keywords
|
|
4
|
+
*/
|
|
5
|
+
const setupCacheInvalidation = (
|
|
6
|
+
component: any,
|
|
7
|
+
{ keywords = [] as string[], attributes = ["dataSrc"] } = {}
|
|
8
|
+
) => {
|
|
9
|
+
if (keywords && attributes) {
|
|
10
|
+
if (component.caching === undefined) {
|
|
11
|
+
component.caching = 0;
|
|
12
|
+
}
|
|
13
|
+
if (component.hasCachedDatas === undefined) {
|
|
14
|
+
component.hasCachedDatas = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
component.cacheListener = (e: Event) => {
|
|
18
|
+
const resource = e.detail.id || e.detail.resource["@id"];
|
|
19
|
+
if (keywords.some((keyword) => resource?.includes(keyword))) {
|
|
20
|
+
for (const attribute of attributes) {
|
|
21
|
+
if (component[attribute] && resource !== component[attribute]) {
|
|
22
|
+
window.sibStore.clearCache(component[attribute]);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
component.caching++;
|
|
26
|
+
component.hasCachedDatas = false;
|
|
27
|
+
component.requestUpdate();
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
component._subscriptions.add(["save", component.cacheListener]);
|
|
32
|
+
|
|
33
|
+
component._subscribe();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default setupCacheInvalidation;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Common code for components
|
|
3
|
+
Handle cache invalidation based on keywords
|
|
4
|
+
*/
|
|
5
|
+
const setupCacheOnResourceReady = (component: any, { keywords = [] } = {}) => {
|
|
6
|
+
if (keywords) {
|
|
7
|
+
if (component.caching === undefined) {
|
|
8
|
+
component.caching = 0;
|
|
9
|
+
}
|
|
10
|
+
if (component.hasCachedDatas === undefined) {
|
|
11
|
+
component.hasCachedDatas = false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
component.resourceCacheListener = (e: Event) => {
|
|
15
|
+
const resource = e.detail.id || e.detail.resource["@id"];
|
|
16
|
+
if (keywords.some((keyword) => resource?.includes(keyword))) {
|
|
17
|
+
component.caching++;
|
|
18
|
+
component.hasCachedDatas = false;
|
|
19
|
+
component.requestUpdate();
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
component._subscriptions.add([
|
|
24
|
+
"resourceReady",
|
|
25
|
+
component.resourceCacheListener,
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
component._subscribe();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default setupCacheOnResourceReady;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import uniq from "@helpers/utils/uniq";
|
|
2
|
+
/*
|
|
3
|
+
Common code for components
|
|
4
|
+
Handles uniq, route, optional orbit interface, subscriptions manager for each component
|
|
5
|
+
*/
|
|
6
|
+
const setupComponentSubscriptions = ({
|
|
7
|
+
component,
|
|
8
|
+
defaultRoute = false,
|
|
9
|
+
ignoreRouter = false,
|
|
10
|
+
}: {
|
|
11
|
+
component: any;
|
|
12
|
+
defaultRoute: boolean;
|
|
13
|
+
ignoreRouter: boolean;
|
|
14
|
+
}) => {
|
|
15
|
+
if (!component.uniq) {
|
|
16
|
+
component.uniq = uniq();
|
|
17
|
+
if (defaultRoute && !component.route && !ignoreRouter) {
|
|
18
|
+
component.route = defaultRoute;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
component._subscriptions = new Set();
|
|
22
|
+
if (!ignoreRouter) {
|
|
23
|
+
if (!component.route) {
|
|
24
|
+
component.route = component.uniq;
|
|
25
|
+
if (window.orbit) {
|
|
26
|
+
component.route = window.orbit.getRoute(component.uniq);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
component.noRouter = true;
|
|
30
|
+
let router = document.querySelector("solid-router");
|
|
31
|
+
while (router) {
|
|
32
|
+
component.noRouter = false;
|
|
33
|
+
component.currentRoute = router.currentRouteName;
|
|
34
|
+
component.currentResource = window.sibRouter.currentResource;
|
|
35
|
+
router = document.querySelector(
|
|
36
|
+
`[data-view="${router.currentRouteName}"] solid-router`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
component.navigationListener = () => {
|
|
41
|
+
// component.currentRoute = e.detail?.route;
|
|
42
|
+
// component.currentResource = window.sibRouter.currentResource;
|
|
43
|
+
let router = document.querySelector("solid-router");
|
|
44
|
+
while (router) {
|
|
45
|
+
component.noRouter = false;
|
|
46
|
+
component.currentRoute = router.currentRouteName;
|
|
47
|
+
component.currentResource = window.sibRouter.currentResource;
|
|
48
|
+
router = document.querySelector(
|
|
49
|
+
`[data-view="${router.currentRouteName}"] solid-router`,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
component.requestUpdate();
|
|
53
|
+
};
|
|
54
|
+
component._subscriptions.add(["navigate", component.navigationListener]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
component._subscribe = () => {
|
|
58
|
+
component._unsubscribe();
|
|
59
|
+
for (const subscription of component._subscriptions) {
|
|
60
|
+
document.addEventListener(subscription[0], subscription[1]);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
component._unsubscribe = () => {
|
|
65
|
+
for (const subscription of component._subscriptions) {
|
|
66
|
+
document.removeEventListener(subscription[0], subscription[1]);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
component._subscribe();
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default setupComponentSubscriptions;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Common code for components
|
|
3
|
+
Handle cache invalidation based on keywords
|
|
4
|
+
*/
|
|
5
|
+
const setupOnSaveReset = (component: any, { keywords = [] } = {}) => {
|
|
6
|
+
if (keywords) {
|
|
7
|
+
component.saveListener = (e: Event) => {
|
|
8
|
+
const resource = e.detail.id || e.detail.resource["@id"];
|
|
9
|
+
if (keywords.some((keyword) => resource?.includes(keyword))) {
|
|
10
|
+
component._setValue({ target: { value: "" } });
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
component._subscriptions.add(["save", component.saveListener]);
|
|
15
|
+
|
|
16
|
+
component._subscribe();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export default setupOnSaveReset;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Resource } from "@src/component";
|
|
2
|
+
|
|
3
|
+
export const recusiveRemovePath = (obj: Resource, pathArray: string[]) => {
|
|
4
|
+
if (!obj || pathArray.length === 0) return;
|
|
5
|
+
const key = pathArray.shift();
|
|
6
|
+
|
|
7
|
+
if (key !== undefined) {
|
|
8
|
+
if (pathArray.length === 0) {
|
|
9
|
+
delete obj[key];
|
|
10
|
+
} else if (obj[key] && typeof obj[key] === "object") {
|
|
11
|
+
recusiveRemovePath(obj[key], pathArray);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const dataBuilder = (
|
|
17
|
+
resource: Resource,
|
|
18
|
+
pathToRemove: string[] = [],
|
|
19
|
+
replacements: object = {},
|
|
20
|
+
removeThenReplace = false,
|
|
21
|
+
): Resource => {
|
|
22
|
+
const clone = structuredClone(resource);
|
|
23
|
+
|
|
24
|
+
if (!removeThenReplace) {
|
|
25
|
+
for (const path of pathToRemove) {
|
|
26
|
+
const splittedPath = path.split(".");
|
|
27
|
+
recusiveRemovePath(clone, splittedPath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const [key, value] of Object.entries(replacements)) {
|
|
32
|
+
clone[key] = value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (removeThenReplace) {
|
|
36
|
+
for (const path of pathToRemove) {
|
|
37
|
+
const splittedPath = path.split(".");
|
|
38
|
+
recusiveRemovePath(clone, splittedPath);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return clone;
|
|
43
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const filterGenerator = (objects: { [key: string]: any }[], field: string) => {
|
|
2
|
+
return objects
|
|
3
|
+
.flatMap((obj) => {
|
|
4
|
+
if (obj[field]) {
|
|
5
|
+
if (Array.isArray(obj[field])) {
|
|
6
|
+
return obj[field].map((v) => ({
|
|
7
|
+
label: v?.name || v,
|
|
8
|
+
value: v?.["@id"] || v,
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
label: obj[field]?.name || obj[field],
|
|
13
|
+
value: obj[field]?.["@id"] || obj[field],
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return;
|
|
17
|
+
})
|
|
18
|
+
.filter(
|
|
19
|
+
(value, index, self) =>
|
|
20
|
+
value?.label &&
|
|
21
|
+
value?.value &&
|
|
22
|
+
index ===
|
|
23
|
+
self.findIndex(
|
|
24
|
+
(t) => t?.label === value?.label && t?.value === value?.value,
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export default filterGenerator;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Resource } from "@src/component";
|
|
2
|
+
|
|
3
|
+
function isValidDateValue(value: unknown): value is string | number | Date {
|
|
4
|
+
return !Number.isNaN(new Date((value as Date)).getTime());
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const checkDateIsAfterRecursive = (
|
|
8
|
+
data: any,
|
|
9
|
+
propName: string,
|
|
10
|
+
thresholdDate: Date
|
|
11
|
+
): boolean => {
|
|
12
|
+
if (data === null || data === undefined) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const propPath = propName.split(".");
|
|
17
|
+
let current = data;
|
|
18
|
+
|
|
19
|
+
for (const segment of propPath) {
|
|
20
|
+
if (current && typeof current === "object" && segment in current) {
|
|
21
|
+
current = current[segment];
|
|
22
|
+
} else {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (isValidDateValue(current)) {
|
|
28
|
+
const date = new Date(current);
|
|
29
|
+
|
|
30
|
+
if (date > thresholdDate) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (Array.isArray(data)) {
|
|
38
|
+
return data.some((item) =>
|
|
39
|
+
checkDateIsAfterRecursive(item, propName, thresholdDate)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof data === "object") {
|
|
44
|
+
return Object.entries(data).some(([key, value]) => {
|
|
45
|
+
if (key === propName && isValidDateValue(value)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
return checkDateIsAfterRecursive(value, propName, thresholdDate);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return false;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const filterObjectByDateAfter = (
|
|
56
|
+
array: Resource[],
|
|
57
|
+
propName: string,
|
|
58
|
+
thresholdDateString: string
|
|
59
|
+
): Resource[] => {
|
|
60
|
+
if (
|
|
61
|
+
!propName ||
|
|
62
|
+
!thresholdDateString ||
|
|
63
|
+
typeof thresholdDateString !== "string"
|
|
64
|
+
) {
|
|
65
|
+
return array;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const thresholdDate = new Date(thresholdDateString);
|
|
69
|
+
|
|
70
|
+
if (Number.isNaN(thresholdDate.getTime())) {
|
|
71
|
+
console.warn(`Invalid threshold date provided: ${thresholdDateString}`);
|
|
72
|
+
return array;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return array.filter((obj) =>
|
|
76
|
+
checkDateIsAfterRecursive(obj, propName, thresholdDate)
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export default filterObjectByDateAfter;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { Resource } from "@src/component";
|
|
2
|
+
|
|
3
|
+
const filterObjectById = (
|
|
4
|
+
array: Resource[],
|
|
5
|
+
propName: string,
|
|
6
|
+
targetId: string
|
|
7
|
+
): Resource[] => {
|
|
8
|
+
if (!propName || !targetId) {
|
|
9
|
+
return array;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return array.filter((obj) => {
|
|
13
|
+
let current = obj;
|
|
14
|
+
const propPath = propName.split(".");
|
|
15
|
+
|
|
16
|
+
for (const segment of propPath) {
|
|
17
|
+
if (current && typeof current === "object" && segment in current) {
|
|
18
|
+
current = current[segment];
|
|
19
|
+
} else {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const propValue = current;
|
|
25
|
+
|
|
26
|
+
if (!propValue) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (
|
|
31
|
+
typeof propValue === "object" &&
|
|
32
|
+
!Array.isArray(propValue) &&
|
|
33
|
+
propValue !== null &&
|
|
34
|
+
"@id" in propValue &&
|
|
35
|
+
propValue["@id"] === targetId
|
|
36
|
+
) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (Array.isArray(propValue)) {
|
|
41
|
+
return propValue.some(
|
|
42
|
+
(item) =>
|
|
43
|
+
typeof item === "object" &&
|
|
44
|
+
item !== null &&
|
|
45
|
+
"@id" in item &&
|
|
46
|
+
item["@id"] === targetId
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return false;
|
|
51
|
+
});
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export default filterObjectById;
|