@startinblox/components-ds4go 3.2.2 → 3.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@startinblox/components-ds4go",
3
- "version": "3.2.2",
3
+ "version": "3.3.1",
4
4
  "description": "Startin'blox DS4GO",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -0,0 +1,256 @@
1
+ import Ds4goCardCatalogStyle from "@styles/cards/ds4go-card-dataspace-catalog.scss?inline";
2
+ import { css, html, LitElement, nothing, unsafeCSS } from "lit";
3
+ import { customElement, property } from "lit/decorators.js";
4
+ import { classMap } from "lit/directives/class-map.js";
5
+ import { ifDefined } from "lit/directives/if-defined.js";
6
+
7
+ export type Ds4goCardCatalogProps = {
8
+ cardType?: string;
9
+ isFullSize: boolean;
10
+ logoOnTop: boolean;
11
+
12
+ backgroundImg?: string;
13
+ orgLogo?: string;
14
+ address?: string;
15
+ language?: string;
16
+ date?: string;
17
+ source?: string;
18
+ header?: string;
19
+ content?: string;
20
+ badge?: string;
21
+ tags: string[] | { name: string; type?: string }[];
22
+ };
23
+
24
+ @customElement("ds4go-card-dataspace-catalog")
25
+ export class Ds4goCardCatalog extends LitElement {
26
+ static styles = css`
27
+ ${unsafeCSS(Ds4goCardCatalogStyle)};
28
+ `;
29
+
30
+ @property({ attribute: "type", type: String, reflect: true })
31
+ cardType: Ds4goCardCatalogProps["cardType"] = "vertical";
32
+
33
+ @property({ attribute: "address", type: String })
34
+ address: Ds4goCardCatalogProps["address"];
35
+
36
+ @property({ attribute: "language", type: String })
37
+ language: Ds4goCardCatalogProps["language"];
38
+
39
+ @property({ attribute: "date", type: String })
40
+ date: Ds4goCardCatalogProps["date"];
41
+
42
+ @property({ attribute: "source", type: String })
43
+ source: Ds4goCardCatalogProps["source"];
44
+
45
+ @property({ attribute: "background-img", type: String })
46
+ backgroundImg: Ds4goCardCatalogProps["backgroundImg"];
47
+
48
+ @property({ attribute: "org-logo", type: String })
49
+ orgLogo: Ds4goCardCatalogProps["orgLogo"];
50
+
51
+ @property({ attribute: "display-logo", type: Boolean })
52
+ logoOnTop: Ds4goCardCatalogProps["logoOnTop"] = false;
53
+
54
+ @property({ attribute: "full-size", type: Boolean })
55
+ isFullSize: Ds4goCardCatalogProps["isFullSize"] = false;
56
+
57
+ @property({ attribute: "header", type: String })
58
+ header: Ds4goCardCatalogProps["header"];
59
+
60
+ @property({ attribute: "content", type: String })
61
+ content: Ds4goCardCatalogProps["content"];
62
+
63
+ @property({ attribute: "badge", type: String })
64
+ badge: Ds4goCardCatalogProps["badge"];
65
+
66
+ @property({ attribute: false })
67
+ tags: Ds4goCardCatalogProps["tags"] = [];
68
+
69
+ private _getType() {
70
+ const allowedTypes = ["vertical", "horizontal", "bill-image"];
71
+ if (!allowedTypes.includes(String(this.cardType))) {
72
+ this.cardType = "vertical";
73
+ }
74
+ return {
75
+ vertical: this.cardType === "vertical",
76
+ horizontal: this.cardType === "horizontal",
77
+ billImage: this.cardType === "bill-image",
78
+ };
79
+ }
80
+
81
+ private renderImageHeader() {
82
+ if (this.isFullSize && (this.backgroundImg || this.badge)) {
83
+ if (this.backgroundImg) {
84
+ return html`<header
85
+ class="image"
86
+ style="background-image: url(${this.backgroundImg})"
87
+ >
88
+ ${
89
+ this.badge
90
+ ? html`<tems-badge label=${this.badge}></tems-badge>`
91
+ : nothing
92
+ }
93
+ </header>`;
94
+ }
95
+ return html`<header>
96
+ ${
97
+ this.badge
98
+ ? html`<tems-badge label=${this.badge}></tems-badge>`
99
+ : nothing
100
+ }
101
+ </header>`;
102
+ }
103
+ return nothing;
104
+ }
105
+
106
+ private renderLogo() {
107
+ if (this.logoOnTop && this.orgLogo) {
108
+ return html`<div
109
+ class="orgLogo"
110
+ style="background-image: url(${this.orgLogo})"
111
+ ></div>`;
112
+ }
113
+ return nothing;
114
+ }
115
+
116
+ private renderTags() {
117
+ if (!this.tags || this.tags.length === 0) return nothing;
118
+
119
+ return html`<div class="tags">
120
+ ${this.tags
121
+ .filter((t) => typeof t === "string" || t.name)
122
+ .map(
123
+ (tag: string | { name: string; type?: string; color?: string }) =>
124
+ html`<tems-badge
125
+ size="sm"
126
+ label=${typeof tag === "string" ? tag : tag.name}
127
+ type=${typeof tag !== "string" && ifDefined(tag.type)}
128
+ color=${typeof tag !== "string" && ifDefined(tag.color)}
129
+ ></tems-badge>`,
130
+ )}
131
+ </div>`;
132
+ }
133
+
134
+ private renderContent() {
135
+ if (this.header || this.content) {
136
+ return html`<div class="content">
137
+ ${
138
+ this.header
139
+ ? html`<div class="card-header">
140
+ <tems-division label=${this.header} type="h4"></tems-division>
141
+ </div>`
142
+ : nothing
143
+ }
144
+ ${
145
+ this.content
146
+ ? html`<div class="card-content">
147
+ <tems-division
148
+ label=${this.content}
149
+ type="body-m"
150
+ ></tems-division>
151
+ </div>`
152
+ : nothing
153
+ }
154
+ </div>`;
155
+ }
156
+ return nothing;
157
+ }
158
+
159
+ private renderLangDate() {
160
+ if (!this.date && !this.language) return nothing;
161
+
162
+ const date = this.date ? html`<span>${this.date}</span>` : nothing;
163
+ const language = this.language
164
+ ? html`<span>${this.language}</span>`
165
+ : nothing;
166
+
167
+ return html`<div class="lang-date">
168
+ ${
169
+ this.cardType === "bill-image"
170
+ ? html`${language}${date}`
171
+ : html`${date}${language}`
172
+ }
173
+ </div>`;
174
+ }
175
+
176
+ private renderSource() {
177
+ if (!this.source) return nothing;
178
+
179
+ return html`<div class="source-logo">
180
+ <div class="source">
181
+ <icon-material-symbols-rss-feed-rounded></icon-material-symbols-rss-feed-rounded>
182
+ ${this.source}
183
+ </div>
184
+ ${
185
+ this.orgLogo && !this.logoOnTop
186
+ ? html`<div
187
+ class="logo"
188
+ style="background-image: url(${this.orgLogo})"
189
+ ></div>`
190
+ : nothing
191
+ }
192
+ </div>`;
193
+ }
194
+
195
+ private renderAddressAndSource() {
196
+ if (!this.address && !this.source) return nothing;
197
+
198
+ if (this.cardType === "vertical" || this.cardType === "bill-image") {
199
+ if (!this.address) return nothing;
200
+ return html`<div class="address">
201
+ <icon-ic-outline-place></icon-ic-outline-place> ${this.address}
202
+ </div>`;
203
+ }
204
+
205
+ return html`<div class="address-line">
206
+ <div>
207
+ ${
208
+ this.address
209
+ ? html`<div class="address">
210
+ <icon-ic-outline-place></icon-ic-outline-place> ${this.address}
211
+ </div>`
212
+ : nothing
213
+ }
214
+ ${this.renderSource()}
215
+ </div>
216
+ ${this.renderLangDate()}
217
+ </div>`;
218
+ }
219
+
220
+ render() {
221
+ const classes = {
222
+ ...this._getType(),
223
+ };
224
+
225
+ if (this.cardType === "horizontal") {
226
+ return html`<article class=${classMap(classes)}>
227
+ ${this.renderImageHeader()}
228
+ <main>
229
+ ${this.renderLogo()} ${this.renderTags()} ${this.renderContent()}
230
+ ${this.renderAddressAndSource()}
231
+ </main>
232
+ </article>`;
233
+ }
234
+
235
+ if (this.cardType === "bill-image") {
236
+ return html`<article class=${classMap(classes)}>
237
+ ${this.renderImageHeader()}
238
+ <main>
239
+ ${this.renderLogo()} ${this.renderTags()} ${this.renderSource()}
240
+ ${this.renderContent()} ${this.renderAddressAndSource()}
241
+ ${this.renderLangDate()}
242
+ </main>
243
+ </article>`;
244
+ }
245
+
246
+ return html`<article class=${classMap(classes)}>
247
+ ${this.renderImageHeader()}
248
+ <main>
249
+ ${this.renderLogo()} ${this.renderLangDate()} ${this.renderContent()}
250
+ ${this.renderAddressAndSource()} ${this.renderSource()}
251
+ ${this.renderTags()}
252
+ </main>
253
+ <div class="action"><slot></slot></div>
254
+ </article>`;
255
+ }
256
+ }
@@ -0,0 +1,349 @@
1
+ import type { Ds4goCardCatalogProps } from "@components/cards/ds4go-card-dataspace-catalog";
2
+ import {
3
+ filterObjectByDateAfter,
4
+ filterObjectById,
5
+ filterObjectByInterval,
6
+ filterObjectByNamedValue,
7
+ filterObjectByType,
8
+ filterObjectByValue,
9
+ } from "@helpers";
10
+ import { msg } from "@lit/localize";
11
+ import type { Resource } from "@src/component";
12
+ import {
13
+ rdf,
14
+ TemsObjectsHandler,
15
+ type TemsSearchObject,
16
+ } from "@startinblox/solid-tems-shared";
17
+ import { css, html, nothing, type PropertyValues } from "lit";
18
+ import { customElement, state } from "lit/decorators.js";
19
+
20
+ @customElement("ds4go-catalog-data-holder")
21
+ export class Ds4goCatalogDataHolder extends TemsObjectsHandler {
22
+ static styles = css`
23
+ .card-grid {
24
+ display: flex;
25
+ flex-direction: row;
26
+ flex-wrap: wrap;
27
+ gap: 20px;
28
+ }
29
+ .card-grid-vertical {
30
+ justify-content: stretch;
31
+ }
32
+ .card-grid-vertical ds4go-card-dataspace-catalog {
33
+ width: 354px;
34
+ height: auto;
35
+ }
36
+ ds4go-card-dataspace-catalog {
37
+ cursor: pointer;
38
+ }
39
+ `;
40
+
41
+ @state()
42
+ view: "card" | "list" | "table" | "map" = "card";
43
+
44
+ @state()
45
+ search: TemsSearchObject[] = [];
46
+
47
+ objects: (
48
+ | rdf.Object_3DObject
49
+ | rdf.Object_MediaObject_CivilSociety
50
+ | rdf.Object_MediaObject_FactChecking
51
+ | rdf.Object_MediaObject_InteractiveInfographics
52
+ | rdf.Object_MediaObject_Stories
53
+ | rdf.Provider
54
+ | rdf.Service
55
+ | rdf.DataOffer
56
+ )[] = [];
57
+
58
+ @state()
59
+ protected _displayObjects: (Ds4goCardCatalogProps & { original: Resource })[] =
60
+ [];
61
+
62
+ protected filter(objects: any[], filters: TemsSearchObject[] = []) {
63
+ if (!filters || filters.length === 0 || !objects || objects.length === 0) {
64
+ return objects;
65
+ }
66
+
67
+ const groupedFilters = new Map<string, TemsSearchObject[]>();
68
+ for (const filter of filters) {
69
+ const groupKey = filter.name;
70
+ const group = groupedFilters.get(groupKey) || [];
71
+ group.push(filter);
72
+ groupedFilters.set(groupKey, group);
73
+ }
74
+
75
+ let currentFilteredObjects = [...objects];
76
+
77
+ for (const filterGroup of groupedFilters.values()) {
78
+ if (currentFilteredObjects.length === 0) {
79
+ break;
80
+ }
81
+
82
+ const tempResults = [];
83
+ for (const filter of filterGroup) {
84
+ switch (filter.type) {
85
+ case "interval":
86
+ tempResults.push(
87
+ filterObjectByInterval(
88
+ currentFilteredObjects,
89
+ filter.name,
90
+ filter.value,
91
+ ),
92
+ );
93
+ break;
94
+ case "dateAfter":
95
+ tempResults.push(
96
+ filterObjectByDateAfter(
97
+ currentFilteredObjects,
98
+ filter.name,
99
+ filter.value,
100
+ ),
101
+ );
102
+ break;
103
+ case "matchId":
104
+ tempResults.push(
105
+ filterObjectById(
106
+ currentFilteredObjects,
107
+ filter.name,
108
+ filter.value,
109
+ ),
110
+ );
111
+ break;
112
+ case "matchType":
113
+ tempResults.push(
114
+ filterObjectByType(currentFilteredObjects, filter.value),
115
+ );
116
+ break;
117
+ case "exact":
118
+ tempResults.push(
119
+ filterObjectByNamedValue(
120
+ currentFilteredObjects,
121
+ filter.name,
122
+ filter.value,
123
+ ),
124
+ );
125
+ break;
126
+ default:
127
+ tempResults.push(
128
+ filterObjectByValue(currentFilteredObjects, filter.value),
129
+ );
130
+ }
131
+ }
132
+ currentFilteredObjects = [...tempResults.flat()];
133
+ }
134
+
135
+ return currentFilteredObjects;
136
+ }
137
+
138
+ protected _getProp(obj: Resource, ...names: string[]) {
139
+ if (!obj) return undefined;
140
+ for (const name of names) {
141
+ if (name in obj) {
142
+ return obj[name];
143
+ }
144
+ }
145
+ return undefined;
146
+ }
147
+
148
+ protected _areSameType(objects: Resource[] = this.objects): boolean {
149
+ if (!objects || objects.length === 0) {
150
+ return true;
151
+ }
152
+
153
+ const firstType = objects[0]["@type"];
154
+ for (const obj of objects) {
155
+ const objType = obj["@type"];
156
+
157
+ if (Array.isArray(firstType) && Array.isArray(objType)) {
158
+ if (
159
+ firstType.length !== objType.length ||
160
+ !firstType.every((val, index) => val === objType[index])
161
+ ) {
162
+ return false;
163
+ }
164
+ } else if (Array.isArray(firstType) && !Array.isArray(objType)) {
165
+ return false;
166
+ } else if (!Array.isArray(firstType) && Array.isArray(objType)) {
167
+ return false;
168
+ } else if (objType !== firstType) {
169
+ return false;
170
+ }
171
+ }
172
+
173
+ return true;
174
+ }
175
+
176
+ protected _typeToString(types: string[]) {
177
+ return types
178
+ .map((type) => rdf.typeMap[type])
179
+ .filter(String)
180
+ .join("");
181
+ }
182
+
183
+ protected _handleClickEvent(originalObj: Resource) {
184
+ this.dispatchEvent(new CustomEvent("clicked", { detail: originalObj }));
185
+ }
186
+
187
+ willUpdate(changedProperties: PropertyValues) {
188
+ if (changedProperties.has("objects") || changedProperties.has("search")) {
189
+ const filteredObjects = this.filter(this.objects, this.search);
190
+ this.dispatchEvent(
191
+ new CustomEvent("result-count", { detail: filteredObjects.length }),
192
+ );
193
+ const objectsAreSameType = this._areSameType(filteredObjects);
194
+
195
+ this._displayObjects = filteredObjects.map((obj) => {
196
+ const getter = (propName: string, ...fallbackNames: string[]) =>
197
+ this._getProp(obj, propName, ...fallbackNames);
198
+
199
+ const tags: (
200
+ | string
201
+ | { name: string; type?: string; color?: string }
202
+ )[] = [];
203
+
204
+ // Add provider badge for DSP datasets (provider discrimination)
205
+ const providerName = getter("_provider");
206
+ const providerColor = getter("_providerColor");
207
+ if (providerName) {
208
+ tags.push({
209
+ name: providerName,
210
+ type: "provider",
211
+ color: providerColor,
212
+ });
213
+ }
214
+
215
+ let categories = getter("categories", "keywords");
216
+ let firstCategory = "";
217
+ if (categories) {
218
+ if (!Array.isArray(categories)) categories = [categories];
219
+
220
+ if (
221
+ !(!objectsAreSameType && this.hasType(rdf.RDFTYPE_OBJECT, [obj])) &&
222
+ categories.length > 0 &&
223
+ !this.hasType(rdf.RDFTYPE_PROVIDER, [obj]) &&
224
+ !this.hasType(rdf.RDFTYPE_DATAOFFER, [obj])
225
+ ) {
226
+ firstCategory = categories[0]?.name;
227
+ tags.push(
228
+ ...categories.slice(1).map((c: rdf.NamedResource) => c.name),
229
+ );
230
+ } else {
231
+ tags.push(...categories.map((c: rdf.NamedResource) => c.name));
232
+ }
233
+ }
234
+
235
+ let location = "";
236
+ if (this.hasType(rdf.RDFTYPE_3D_OBJECT, [obj])) {
237
+ if (getter("is_low_polygons")) {
238
+ tags.push(msg("Low-poly (<10k polygones)"));
239
+ }
240
+ if (getter("ai")) {
241
+ tags.push(msg("AI-generated"));
242
+ }
243
+ if (getter("texture")) {
244
+ tags.push(getter("texture"));
245
+ }
246
+ location = getter("location")?.country;
247
+ }
248
+
249
+ let images = getter("images", "image");
250
+ if (!Array.isArray(images)) images = [images];
251
+ images = images.filter((p: rdf.Image) => p?.url && !p?.iframe);
252
+
253
+ let language = getter("language", "original_languages", "prices");
254
+ if (typeof language !== "string") {
255
+ language = language?.name || "";
256
+ }
257
+
258
+ // Get endpoint URL and content type for DSP assets
259
+ const endpointUrl = getter("endpointUrl", "url");
260
+ const contentType = getter("contentType", "contenttype");
261
+
262
+ // Add content type as tag if available
263
+ if (
264
+ contentType &&
265
+ !tags.some((t) =>
266
+ typeof t === "string" ? t === contentType : t.name === contentType,
267
+ )
268
+ ) {
269
+ tags.push({
270
+ name: contentType,
271
+ type: "contentType",
272
+ color: "#607d8b",
273
+ });
274
+ }
275
+
276
+ // Build description with endpoint URL for DSP assets
277
+ let description = getter("description") || "";
278
+ if (endpointUrl && !description) {
279
+ description = endpointUrl;
280
+ }
281
+
282
+ const cardProps: Ds4goCardCatalogProps = {
283
+ backgroundImg: this.hasType(rdf.RDFTYPE_PROVIDER, [obj])
284
+ ? false
285
+ : images?.filter((p: rdf.Image) => p?.url && !p?.iframe)?.[0]?.url,
286
+ orgLogo: this.hasType(rdf.RDFTYPE_PROVIDER, [obj])
287
+ ? images?.filter((p: rdf.Image) => p?.url && !p?.iframe)?.[0]?.url
288
+ : getter("providers")?.filter(
289
+ (p: rdf.Provider) => p.image?.url && !p.image?.iframe,
290
+ )?.[0]?.image?.url,
291
+ logoOnTop: !this.hasType(rdf.RDFTYPE_OBJECT, [obj]),
292
+ badge:
293
+ !objectsAreSameType && this.hasType(rdf.RDFTYPE_OBJECT, [obj])
294
+ ? this._typeToString([getter("@type")].flat())
295
+ : firstCategory,
296
+ tags: tags.filter((t) => t) as string[],
297
+ header: getter("name", "title"),
298
+ content: description,
299
+ isFullSize: true,
300
+ date: this.hasType(rdf.RDFTYPE_3D_OBJECT, [obj])
301
+ ? getter("licences")
302
+ ?.map((l: rdf.Licence) => l.name)
303
+ .join(", ")
304
+ : getter("publication_date", "release_date", "creation_date"),
305
+ language: language,
306
+ address: endpointUrl || location,
307
+ source: this.hasType(rdf.RDFTYPE_OBJECT, [obj])
308
+ ? getter("affiliation")?.name ||
309
+ getter("providers")
310
+ ?.map((p: rdf.Provider) => p.name)
311
+ .join(", ")
312
+ : "",
313
+ };
314
+
315
+ return { ...cardProps, original: obj };
316
+ });
317
+ }
318
+ }
319
+
320
+ render() {
321
+ if (!this._displayObjects || this._displayObjects.length === 0) {
322
+ return nothing;
323
+ }
324
+
325
+ return html`<div
326
+ class="card-grid${this.view === "card" ? " card-grid-vertical" : ""}"
327
+ >
328
+ ${this._displayObjects.map((displayObj) => {
329
+ return html`<ds4go-card-dataspace-catalog
330
+ .object=${import.meta.env.DEV ? displayObj.original : nothing}
331
+ type=${this.view === "card" ? "card" : "horizontal"}
332
+ background-img=${displayObj.backgroundImg || nothing}
333
+ org-logo=${displayObj.orgLogo || nothing}
334
+ .logoOnTop=${displayObj.logoOnTop || nothing}
335
+ .badge=${displayObj.badge || nothing}
336
+ .tags=${displayObj.tags || nothing}
337
+ .header=${displayObj.header || nothing}
338
+ .content=${displayObj.content || nothing}
339
+ .isFullSize=${displayObj.isFullSize || nothing}
340
+ date=${displayObj.date || nothing}
341
+ language=${displayObj.language || nothing}
342
+ address=${displayObj.address || nothing}
343
+ source=${displayObj.source || nothing}
344
+ @click=${() => this._handleClickEvent(displayObj.original)}
345
+ ></ds4go-card-dataspace-catalog>`;
346
+ })}
347
+ </div>`;
348
+ }
349
+ }