@startinblox/components-ds4go 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/.gitlab-ci.yml +57 -0
- package/.storybook/main.ts +17 -0
- package/.storybook/preview-head.html +8 -0
- package/.storybook/preview.ts +22 -0
- package/LICENSE +21 -0
- package/README.md +129 -0
- package/biome.json +50 -0
- package/cypress/component/solid-boilerplate.cy.ts +9 -0
- package/cypress/support/component-index.html +12 -0
- package/cypress/support/component.ts +17 -0
- package/cypress.config.ts +11 -0
- package/dist/components-ds4go.css +1 -0
- package/dist/index.js +1634 -0
- package/lit-localize.json +15 -0
- package/locales/en.xlf +28 -0
- package/package.json +93 -0
- package/postcss.config.js +8 -0
- package/src/component.d.ts +167 -0
- package/src/components/catalog/ds4go-fact-bundle-holder.ts +162 -0
- package/src/components/modal/ds4go-fact-bundle-modal.ts +82 -0
- package/src/components/solid-fact-bundle.ts +225 -0
- package/src/context.json +1 -0
- package/src/helpers/components/ResourceMapper.ts +450 -0
- package/src/helpers/components/componentObjectHandler.ts +22 -0
- package/src/helpers/components/componentObjectsHandler.ts +14 -0
- package/src/helpers/components/dspComponent.ts +243 -0
- package/src/helpers/components/orbitComponent.ts +273 -0
- package/src/helpers/components/setupCacheInvalidation.ts +44 -0
- package/src/helpers/components/setupCacheOnResourceReady.ts +39 -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 +43 -0
- package/src/helpers/mappings/dsp-mapping-config.ts +545 -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 +14 -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/_helpers/flex.scss +39 -0
- package/src/styles/index.scss +14 -0
- package/src/styles/modal/ds4go-fact-bundle-modal.scss +89 -0
- package/tsconfig.json +36 -0
- package/vite.config.ts +48 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { OrbitComponent, setupComponentSubscriptions } from "@helpers";
|
|
2
|
+
import { ResourceMapper } from "@helpers/components/ResourceMapper";
|
|
3
|
+
import { dspMappingConfig } from "@helpers/mappings/dsp-mapping-config";
|
|
4
|
+
import type { Resource } from "@src/component.d.ts";
|
|
5
|
+
import { StoreService, StoreType } from "@startinblox/core";
|
|
6
|
+
import { property, state } from "lit/decorators.js";
|
|
7
|
+
|
|
8
|
+
export interface DSPProviderConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
address: string;
|
|
11
|
+
color?: string;
|
|
12
|
+
participantId?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default class extends OrbitComponent {
|
|
16
|
+
@property({ attribute: "participant-connector-uri", reflect: true })
|
|
17
|
+
participantConnectorUri?: string;
|
|
18
|
+
|
|
19
|
+
@property({ attribute: "participant-id", reflect: true })
|
|
20
|
+
participantId?: string;
|
|
21
|
+
|
|
22
|
+
@property({ attribute: "participant-api-key", reflect: true })
|
|
23
|
+
participantApiKey?: string;
|
|
24
|
+
|
|
25
|
+
@property({ attribute: "providers", type: Array })
|
|
26
|
+
private _providers: DSPProviderConfig[] = [];
|
|
27
|
+
|
|
28
|
+
public get providers(): DSPProviderConfig[] {
|
|
29
|
+
return this._providers;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public set providers(value: DSPProviderConfig[]) {
|
|
33
|
+
if (typeof value === "string") {
|
|
34
|
+
try {
|
|
35
|
+
this._providers = JSON.parse(value);
|
|
36
|
+
} catch (_e) {
|
|
37
|
+
this._providers = [];
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
this._providers = value || [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@property({ attribute: "api-gateway-config", reflect: true })
|
|
45
|
+
apiGatewayConfig?: string;
|
|
46
|
+
|
|
47
|
+
@state()
|
|
48
|
+
storeService?: any;
|
|
49
|
+
|
|
50
|
+
@state()
|
|
51
|
+
dspStoreService?: any;
|
|
52
|
+
|
|
53
|
+
@state()
|
|
54
|
+
_apiGatewayConfigParsed?: any;
|
|
55
|
+
|
|
56
|
+
protected async _attach(
|
|
57
|
+
defaultRoute: boolean,
|
|
58
|
+
setupSubscriptions: boolean,
|
|
59
|
+
ignoreRouter: boolean,
|
|
60
|
+
) {
|
|
61
|
+
if (!this.orbit) {
|
|
62
|
+
if (window.orbit) {
|
|
63
|
+
this.orbit = window.orbit;
|
|
64
|
+
if (setupSubscriptions) {
|
|
65
|
+
setupComponentSubscriptions({
|
|
66
|
+
component: this,
|
|
67
|
+
defaultRoute: defaultRoute,
|
|
68
|
+
ignoreRouter: ignoreRouter,
|
|
69
|
+
});
|
|
70
|
+
if (this.route) {
|
|
71
|
+
this.component = this.orbit.getComponentFromRoute(this.route);
|
|
72
|
+
if (this.component) {
|
|
73
|
+
this.orbit.components.map((c) => {
|
|
74
|
+
if (c.uniq === this.component.uniq) {
|
|
75
|
+
c.instance = this;
|
|
76
|
+
}
|
|
77
|
+
return c;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Get connector configuration from attributes or component params
|
|
83
|
+
if (!this.participantConnectorUri) {
|
|
84
|
+
const params = this.component?.parameters as any;
|
|
85
|
+
if (params?.["participant-connector-uri"]) {
|
|
86
|
+
this.participantConnectorUri =
|
|
87
|
+
params["participant-connector-uri"];
|
|
88
|
+
}
|
|
89
|
+
if (params?.["participant-api-key"]) {
|
|
90
|
+
this.participantApiKey = params["participant-api-key"];
|
|
91
|
+
}
|
|
92
|
+
if (params?.providers) {
|
|
93
|
+
this.providers = params.providers;
|
|
94
|
+
}
|
|
95
|
+
if (params?.["participant-id"]) {
|
|
96
|
+
this.participantId = params["participant-id"];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Parse API Gateway configuration (OPTIONAL)
|
|
101
|
+
const params = this.component?.parameters as any;
|
|
102
|
+
|
|
103
|
+
if (params?.["api-gateway-config"]) {
|
|
104
|
+
this.apiGatewayConfig = params["api-gateway-config"];
|
|
105
|
+
try {
|
|
106
|
+
this._apiGatewayConfigParsed =
|
|
107
|
+
typeof this.apiGatewayConfig === "string"
|
|
108
|
+
? JSON.parse(this.apiGatewayConfig)
|
|
109
|
+
: this.apiGatewayConfig;
|
|
110
|
+
} catch (_error) {
|
|
111
|
+
this._apiGatewayConfigParsed = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Initialize DataspaceConnectorStore
|
|
116
|
+
if (!this.storeService && this.participantConnectorUri) {
|
|
117
|
+
try {
|
|
118
|
+
// Use unique store name based on component ID to avoid singleton conflicts
|
|
119
|
+
const storeName = `dsp-connector-${this.component.uniq}`;
|
|
120
|
+
|
|
121
|
+
const config = {
|
|
122
|
+
type: StoreType.DataspaceConnector,
|
|
123
|
+
endpoint: this.participantConnectorUri,
|
|
124
|
+
catalogEndpoint: `${this.participantConnectorUri}/management/v3/catalog/request`,
|
|
125
|
+
contractNegotiationEndpoint: `${this.participantConnectorUri}/management/v3/contractnegotiations`,
|
|
126
|
+
transferProcessEndpoint: `${this.participantConnectorUri}/management/v3/transferprocesses`,
|
|
127
|
+
edrsEndpoint: `${this.participantConnectorUri}/management/v3/edrs`,
|
|
128
|
+
authMethod: "dsp-api-key",
|
|
129
|
+
dspApiKey: this.participantApiKey || "password",
|
|
130
|
+
participantId: this.participantId || "stbx-consumer",
|
|
131
|
+
retryAttempts: 10,
|
|
132
|
+
timeout: 30000,
|
|
133
|
+
// Include API Gateway configuration ONLY if available
|
|
134
|
+
...(this._apiGatewayConfigParsed && {
|
|
135
|
+
apiGatewayConfig: this._apiGatewayConfigParsed,
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
this.storeService = StoreService.addStore(storeName, config);
|
|
140
|
+
// Also set dspStoreService for contract negotiation in tems-modal
|
|
141
|
+
this.dspStoreService = this.storeService;
|
|
142
|
+
// Expose globally for FederatedIndexManager auto-negotiation
|
|
143
|
+
if (!window.dspStore) {
|
|
144
|
+
window.dspStore = this.storeService;
|
|
145
|
+
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error("DSP Store initialization error:", error);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await this._afterAttach();
|
|
151
|
+
return Promise.resolve(true);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return Promise.resolve(false);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Fetch catalog from multiple DSP providers
|
|
160
|
+
*/
|
|
161
|
+
async fetchFederatedCatalog(): Promise<Resource[]> {
|
|
162
|
+
if (!this.storeService || !this.providers || this.providers.length === 0) {
|
|
163
|
+
console.warn("DSP store or providers not configured");
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
try {
|
|
168
|
+
// FIXME: Avoid magic strings
|
|
169
|
+
// Use default base URLs for TEMS resources
|
|
170
|
+
const baseURL = "https://api.tems.example.com/services/";
|
|
171
|
+
|
|
172
|
+
// Initialize ResourceMapper with DSP configuration
|
|
173
|
+
const mapper = new ResourceMapper(dspMappingConfig);
|
|
174
|
+
|
|
175
|
+
const catalogPromises = this.providers.map(async (provider) => {
|
|
176
|
+
try {
|
|
177
|
+
const catalog = await this.storeService.getCatalog(provider.address);
|
|
178
|
+
|
|
179
|
+
if (catalog?.["dcat:dataset"]) {
|
|
180
|
+
// Normalize datasets to array - catalog may return single object if only one dataset
|
|
181
|
+
const rawDatasets = catalog["dcat:dataset"];
|
|
182
|
+
const datasets = Array.isArray(rawDatasets)
|
|
183
|
+
? rawDatasets
|
|
184
|
+
: [rawDatasets];
|
|
185
|
+
// IMPORTANT: Prefer config's participantId over catalog response
|
|
186
|
+
// This ensures assets from different providers are distinguishable
|
|
187
|
+
// even if EDC catalogs return the same participantId (misconfiguration)
|
|
188
|
+
const catalogResponseParticipantId =
|
|
189
|
+
catalog.participantId ||
|
|
190
|
+
catalog["edc:participantId"] ||
|
|
191
|
+
catalog["https://w3id.org/edc/v0.0.1/ns/participantId"];
|
|
192
|
+
|
|
193
|
+
// Use config's participantId as authoritative (user configured it intentionally)
|
|
194
|
+
// Only fall back to catalog response if config doesn't have participantId
|
|
195
|
+
const effectiveParticipantId =
|
|
196
|
+
provider.participantId || catalogResponseParticipantId;
|
|
197
|
+
|
|
198
|
+
// Map each dataset using ResourceMapper
|
|
199
|
+
return datasets.map((dataset: Resource) => {
|
|
200
|
+
const context = {
|
|
201
|
+
temsServiceBase: baseURL,
|
|
202
|
+
temsCategoryBase: baseURL.replace(
|
|
203
|
+
"/services/",
|
|
204
|
+
"/providers/categories/",
|
|
205
|
+
),
|
|
206
|
+
temsImageBase: baseURL.replace(
|
|
207
|
+
"/services/",
|
|
208
|
+
"/objects/images/",
|
|
209
|
+
),
|
|
210
|
+
temsProviderBase: baseURL.replace("/services/", "/providers/"),
|
|
211
|
+
// Provider context for mapping - use config's participantId as authoritative
|
|
212
|
+
providerName: provider.name,
|
|
213
|
+
providerAddress: provider.address,
|
|
214
|
+
providerColor: provider.color,
|
|
215
|
+
providerParticipantId: effectiveParticipantId,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
return mapper.map(dataset, context);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return [];
|
|
223
|
+
} catch (_error) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const results = await Promise.all(catalogPromises);
|
|
229
|
+
const allDatasets = results.flat();
|
|
230
|
+
|
|
231
|
+
return allDatasets;
|
|
232
|
+
} catch (_error) {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get current DataspaceConnectorStore instance
|
|
239
|
+
*/
|
|
240
|
+
getStoreService() {
|
|
241
|
+
return this.storeService;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLIENT_CONTEXT,
|
|
3
|
+
requestNavigation,
|
|
4
|
+
setupComponentSubscriptions,
|
|
5
|
+
} from "@helpers";
|
|
6
|
+
import { ComponentObjectsHandler } from "@helpers/components/componentObjectsHandler";
|
|
7
|
+
import type {
|
|
8
|
+
Container,
|
|
9
|
+
LiveOrbit,
|
|
10
|
+
PropertiesPicker,
|
|
11
|
+
ProxyValue,
|
|
12
|
+
Resource,
|
|
13
|
+
UnknownResource,
|
|
14
|
+
} from "@src/component";
|
|
15
|
+
import { nothing } from "lit";
|
|
16
|
+
import { property, state } from "lit/decorators.js";
|
|
17
|
+
|
|
18
|
+
export default class extends ComponentObjectsHandler {
|
|
19
|
+
constructor({
|
|
20
|
+
defaultRoute = false,
|
|
21
|
+
setupSubscriptions = true,
|
|
22
|
+
ignoreRouter = false,
|
|
23
|
+
} = {}) {
|
|
24
|
+
super();
|
|
25
|
+
const attach = () => {
|
|
26
|
+
if (document.readyState === "complete") {
|
|
27
|
+
this._attach(defaultRoute, setupSubscriptions, ignoreRouter).then(
|
|
28
|
+
(attach: boolean) => {
|
|
29
|
+
if (attach) {
|
|
30
|
+
this.requestUpdate();
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
if (document.readyState === "complete") {
|
|
37
|
+
attach();
|
|
38
|
+
} else {
|
|
39
|
+
document.addEventListener("readystatechange", attach);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@property({ attribute: "default-data-src", reflect: true })
|
|
44
|
+
defaultDataSrc?: string;
|
|
45
|
+
|
|
46
|
+
@property({ attribute: "data-src", reflect: true })
|
|
47
|
+
dataSrc?: string;
|
|
48
|
+
|
|
49
|
+
@property({ attribute: "nested-field" })
|
|
50
|
+
nestedField?: string;
|
|
51
|
+
|
|
52
|
+
@property({ attribute: "uniq" })
|
|
53
|
+
uniq?: string;
|
|
54
|
+
|
|
55
|
+
@property({ attribute: "route" })
|
|
56
|
+
route: string | undefined;
|
|
57
|
+
|
|
58
|
+
@property({ attribute: false })
|
|
59
|
+
cherryPickedProperties: PropertiesPicker[] = [];
|
|
60
|
+
|
|
61
|
+
@state()
|
|
62
|
+
orbit: LiveOrbit | undefined;
|
|
63
|
+
|
|
64
|
+
@state()
|
|
65
|
+
currentRoute = "";
|
|
66
|
+
|
|
67
|
+
protected async _attach(
|
|
68
|
+
defaultRoute: boolean,
|
|
69
|
+
setupSubscriptions: boolean,
|
|
70
|
+
ignoreRouter: boolean,
|
|
71
|
+
) {
|
|
72
|
+
if (!this.orbit) {
|
|
73
|
+
if (window.orbit) {
|
|
74
|
+
this.orbit = window.orbit;
|
|
75
|
+
if (setupSubscriptions) {
|
|
76
|
+
setupComponentSubscriptions({
|
|
77
|
+
component: this,
|
|
78
|
+
defaultRoute: defaultRoute,
|
|
79
|
+
ignoreRouter: ignoreRouter,
|
|
80
|
+
});
|
|
81
|
+
if (this.route) {
|
|
82
|
+
this.component = this.orbit.getComponentFromRoute(this.route);
|
|
83
|
+
if (this.component) {
|
|
84
|
+
this.orbit.components.map((c) => {
|
|
85
|
+
if (c.uniq === this.component.uniq) {
|
|
86
|
+
c.instance = this;
|
|
87
|
+
}
|
|
88
|
+
return c;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
await this._afterAttach();
|
|
93
|
+
return Promise.resolve(true);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return Promise.resolve(false);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async _afterAttach() {
|
|
101
|
+
return Promise.resolve();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_navigate(e: Event) {
|
|
105
|
+
window.sibRouter.previousRoute = this.currentRoute;
|
|
106
|
+
window.sibRouter.previousResource = window.sibRouter.currentResource;
|
|
107
|
+
const navigator = e.target?.closest("[navigation-target]");
|
|
108
|
+
let target = navigator.getAttribute("navigation-target");
|
|
109
|
+
const subrouter = navigator.getAttribute("navigation-subrouter");
|
|
110
|
+
const resource = navigator.getAttribute("navigation-resource");
|
|
111
|
+
const rdfType = navigator.getAttribute("navigation-rdf-type");
|
|
112
|
+
if (rdfType) {
|
|
113
|
+
const compatibleComponents = window.orbit?.components?.filter(
|
|
114
|
+
(c) => c?.routeAttributes?.["rdf-type"] === rdfType,
|
|
115
|
+
);
|
|
116
|
+
if (compatibleComponents) target = compatibleComponents[0]?.uniq;
|
|
117
|
+
}
|
|
118
|
+
if (target) {
|
|
119
|
+
requestNavigation(
|
|
120
|
+
(window.orbit ? window.orbit.getRoute(target, true) : target) +
|
|
121
|
+
(subrouter ? `-${subrouter}` : ""),
|
|
122
|
+
resource,
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
e.preventDefault();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
_normalizeLdpContains(value: Resource[] | Resource): Resource[] {
|
|
129
|
+
if (!Array.isArray(value) && value !== null) {
|
|
130
|
+
return [value];
|
|
131
|
+
}
|
|
132
|
+
return value;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async _expandContainer(
|
|
136
|
+
value: Resource[],
|
|
137
|
+
recursive = true,
|
|
138
|
+
targetProperties: PropertiesPicker[] = this.cherryPickedProperties,
|
|
139
|
+
): Promise<UnknownResource[]> {
|
|
140
|
+
const expandedContainer: UnknownResource[] = [];
|
|
141
|
+
for (const entry of value) {
|
|
142
|
+
const line = await this._getProxyValue(
|
|
143
|
+
await entry,
|
|
144
|
+
recursive,
|
|
145
|
+
targetProperties,
|
|
146
|
+
);
|
|
147
|
+
if (line) expandedContainer.push(line);
|
|
148
|
+
}
|
|
149
|
+
return expandedContainer;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async _getProperties(
|
|
153
|
+
resource: Resource,
|
|
154
|
+
recursive = true,
|
|
155
|
+
targetProperties: PropertiesPicker[] = this.cherryPickedProperties,
|
|
156
|
+
) {
|
|
157
|
+
const properties = await resource.properties;
|
|
158
|
+
const response: Resource = {
|
|
159
|
+
"@id": resource["@id"],
|
|
160
|
+
"@type": resource["@type"],
|
|
161
|
+
"@context": resource.serverContext,
|
|
162
|
+
_originalResource: resource,
|
|
163
|
+
};
|
|
164
|
+
for (const prop of targetProperties) {
|
|
165
|
+
if (properties?.includes(prop.key)) {
|
|
166
|
+
response[prop.value] = await resource.get(prop.key);
|
|
167
|
+
if (prop.expand) {
|
|
168
|
+
response[prop.value] = await this._getProxyValue(
|
|
169
|
+
response[prop.value],
|
|
170
|
+
recursive,
|
|
171
|
+
targetProperties,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (prop.cast) {
|
|
175
|
+
response[prop.value] = await prop.cast(response[prop.value]);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return await this._responseAdaptator(response);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async _hasCherryPickedProperties(resource: Resource) {
|
|
183
|
+
const properties = await resource.properties;
|
|
184
|
+
for (const prop of this.cherryPickedProperties) {
|
|
185
|
+
if (properties?.includes(prop.key)) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async _getProxyValue(
|
|
193
|
+
resource:
|
|
194
|
+
| string
|
|
195
|
+
| Resource
|
|
196
|
+
| ProxyValue<Resource | Container<ProxyValue<Resource> | Resource>>,
|
|
197
|
+
recursive = true,
|
|
198
|
+
targetProperties: PropertiesPicker[] = this.cherryPickedProperties,
|
|
199
|
+
) {
|
|
200
|
+
try {
|
|
201
|
+
if (resource) {
|
|
202
|
+
let target = resource;
|
|
203
|
+
if (typeof resource === "string") {
|
|
204
|
+
target = await window.sibStore.getData(resource, CLIENT_CONTEXT);
|
|
205
|
+
}
|
|
206
|
+
if (typeof resource !== "string" && resource.isFullResource) {
|
|
207
|
+
if (!resource.isFullResource?.()) {
|
|
208
|
+
target = await window.sibStore.getData(
|
|
209
|
+
resource["@id"],
|
|
210
|
+
CLIENT_CONTEXT,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// TEMPFIX: weird store behaviour? (await await getData?)
|
|
216
|
+
// target = await target;
|
|
217
|
+
|
|
218
|
+
if (typeof resource !== "string" && !resource.isFullResource) {
|
|
219
|
+
// Edge case when calling getProxyValue with an already
|
|
220
|
+
// fetched resource with server search, not a proxy
|
|
221
|
+
(target as Resource).properties = Object.keys(target);
|
|
222
|
+
(target as Resource).get = (property: any) =>
|
|
223
|
+
(target as Resource)[property];
|
|
224
|
+
}
|
|
225
|
+
if (!target) return { _originalResource: target };
|
|
226
|
+
if (typeof target === "object" && target !== null) {
|
|
227
|
+
if (target.isContainer?.() && target["ldp:contains"]) {
|
|
228
|
+
// Allow ldp:containers to be treated manually by a component
|
|
229
|
+
if (await this._hasCherryPickedProperties(target)) {
|
|
230
|
+
return await this._getProperties(
|
|
231
|
+
target,
|
|
232
|
+
recursive,
|
|
233
|
+
targetProperties,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const value = this._normalizeLdpContains(
|
|
237
|
+
await target["ldp:contains"],
|
|
238
|
+
);
|
|
239
|
+
return await this._expandContainer(
|
|
240
|
+
value,
|
|
241
|
+
recursive,
|
|
242
|
+
targetProperties,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
return await this._getProperties(target, recursive, targetProperties);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return;
|
|
249
|
+
} catch (e) {
|
|
250
|
+
if (import.meta.env.DEV) console.error(e);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async _responseAdaptator(response: Resource) {
|
|
255
|
+
return Promise.resolve(response);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
gatekeeper() {
|
|
259
|
+
if (
|
|
260
|
+
!this.orbit ||
|
|
261
|
+
(!this.noRouter &&
|
|
262
|
+
this.route &&
|
|
263
|
+
this.currentRoute &&
|
|
264
|
+
!this.route.startsWith(this.currentRoute))
|
|
265
|
+
) {
|
|
266
|
+
return nothing;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!this.dataSrc) {
|
|
270
|
+
return nothing;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
const setup = () => {
|
|
10
|
+
if (keywords && attributes) {
|
|
11
|
+
if (component.caching === undefined) {
|
|
12
|
+
component.caching = 0;
|
|
13
|
+
}
|
|
14
|
+
if (component.hasCachedDatas === undefined) {
|
|
15
|
+
component.hasCachedDatas = false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
component.cacheListener = (e: Event) => {
|
|
19
|
+
const resource = e.detail.id || e.detail.resource["@id"];
|
|
20
|
+
if (keywords.some((keyword) => resource?.includes(keyword))) {
|
|
21
|
+
for (const attribute of attributes) {
|
|
22
|
+
if (component[attribute] && resource !== component[attribute]) {
|
|
23
|
+
window.sibStore.clearCache(component[attribute]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
component.caching++;
|
|
27
|
+
component.hasCachedDatas = false;
|
|
28
|
+
component.requestUpdate();
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
component._subscriptions.add(["save", component.cacheListener]);
|
|
33
|
+
|
|
34
|
+
component._subscribe();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
if (document.readyState !== "complete") {
|
|
38
|
+
document.addEventListener("DOMContentLoaded", setup);
|
|
39
|
+
} else {
|
|
40
|
+
setup();
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default setupCacheInvalidation;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Common code for components
|
|
3
|
+
Handle cache invalidation based on keywords
|
|
4
|
+
*/
|
|
5
|
+
const setupCacheOnResourceReady = (component: any, { keywords = [] } = {}) => {
|
|
6
|
+
const setup = () => {
|
|
7
|
+
if (keywords) {
|
|
8
|
+
if (component.caching === undefined) {
|
|
9
|
+
component.caching = 0;
|
|
10
|
+
}
|
|
11
|
+
if (component.hasCachedDatas === undefined) {
|
|
12
|
+
component.hasCachedDatas = false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
component.resourceCacheListener = (e: Event) => {
|
|
16
|
+
const resource = e.detail.id || e.detail.resource["@id"];
|
|
17
|
+
if (keywords.some((keyword) => resource?.includes(keyword))) {
|
|
18
|
+
component.caching++;
|
|
19
|
+
component.hasCachedDatas = false;
|
|
20
|
+
component.requestUpdate();
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
component._subscriptions.add([
|
|
25
|
+
"resourceReady",
|
|
26
|
+
component.resourceCacheListener,
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
component._subscribe();
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
if (document.readyState !== "complete") {
|
|
33
|
+
document.addEventListener("DOMContentLoaded", setup);
|
|
34
|
+
} else {
|
|
35
|
+
setup();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
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;
|