@startinblox/components-ds4go 3.3.8 → 4.1.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/dist/components-ds4go.css +1 -1
- package/dist/index.js +3534 -3602
- package/locales/en.xlf +187 -3
- package/package.json +2 -2
- package/src/component.d.ts +0 -5
- package/src/components/cards/ds4go-card-dataspace-catalog.ts +82 -227
- package/src/components/cards/ds4go-card-fact.ts +128 -0
- package/src/components/catalog/ds4go-catalog-data-holder.ts +158 -0
- package/src/components/catalog/ds4go-fact-holder.ts +149 -0
- package/src/components/modal/catalog-modal/agreement-info.ts +110 -0
- package/src/components/modal/catalog-modal/index.ts +4 -0
- package/src/components/modal/catalog-modal/negotiation-button.ts +111 -0
- package/src/components/modal/catalog-modal/policy-display.ts +66 -0
- package/src/components/modal/catalog-modal/policy-selection.ts +71 -0
- package/src/components/modal/ds4go-catalog-modal.ts +158 -1105
- package/src/components/modal/ds4go-fact-modal.ts +217 -0
- package/src/components/odrl/policy-composer.ts +1 -1
- package/src/components/odrl-policy-viewer.ts +0 -21
- package/src/components/solid-dsp-catalog.ts +2 -43
- package/src/components/solid-fact-list.ts +307 -0
- package/src/ds4go.d.ts +78 -1
- package/src/helpers/dsp/agreementStorage.ts +243 -0
- package/src/helpers/dsp/policyHelpers.ts +223 -0
- package/src/helpers/index.ts +7 -0
- package/src/styles/cards/ds4go-card-catalog.scss +1 -1
- package/src/styles/cards/ds4go-card-dataspace-catalog.scss +22 -165
- package/src/styles/cards/ds4go-card-fact.scss +112 -0
- package/src/styles/index.scss +42 -0
- package/src/styles/modal/ds4go-catalog-modal.scss +1 -1
- package/src/styles/modal/ds4go-fact-modal.scss +161 -0
- package/src/components/modal/ds4go-catalog-data-holder.ts +0 -349
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import Ds4goCardFactStyle from "@styles/cards/ds4go-card-fact.scss?inline";
|
|
2
|
+
import { formatDate } from "@helpers";
|
|
3
|
+
import type { Category, Fact } from "@src/ds4go";
|
|
4
|
+
import { css, html, LitElement, nothing, unsafeCSS } from "lit";
|
|
5
|
+
import { customElement, property } from "lit/decorators.js";
|
|
6
|
+
import { msg, str } from "@lit/localize";
|
|
7
|
+
|
|
8
|
+
@customElement("ds4go-card-fact")
|
|
9
|
+
export class Ds4goCardFact extends LitElement {
|
|
10
|
+
static styles = css`
|
|
11
|
+
${unsafeCSS(Ds4goCardFactStyle)};
|
|
12
|
+
`;
|
|
13
|
+
|
|
14
|
+
@property({ attribute: false })
|
|
15
|
+
object?: Fact;
|
|
16
|
+
|
|
17
|
+
private _getThumbnail(): string {
|
|
18
|
+
if (this.object?.enclosure) {
|
|
19
|
+
return String(this.object.enclosure);
|
|
20
|
+
}
|
|
21
|
+
// Default placeholder image
|
|
22
|
+
return "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='354' height='167' viewBox='0 0 354 167'%3E%3Crect width='354' height='167' fill='%23f0f0f0'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' font-family='sans-serif' font-size='14' fill='%23999'%3ENo Image%3C/text%3E%3C/svg%3E";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private _getFormattedDate(): string {
|
|
26
|
+
if (!this.object?.created_at) return "";
|
|
27
|
+
return formatDate(this.object.created_at, {
|
|
28
|
+
year: "numeric",
|
|
29
|
+
month: "long",
|
|
30
|
+
day: "numeric",
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private _getTags() {
|
|
35
|
+
const tags: {
|
|
36
|
+
label: string;
|
|
37
|
+
type: string;
|
|
38
|
+
color?: string;
|
|
39
|
+
}[] = [];
|
|
40
|
+
|
|
41
|
+
// Add categories as tags (max 3, with overflow indicator)
|
|
42
|
+
(this.object?.categories || [])?.slice(0, 3).forEach((c: Category) => {
|
|
43
|
+
if (c.name) {
|
|
44
|
+
tags.push({
|
|
45
|
+
label: c.name,
|
|
46
|
+
type: "neutral",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const totalCategories = this.object?.categories?.length || 0;
|
|
52
|
+
if (totalCategories > 3) {
|
|
53
|
+
tags.push({
|
|
54
|
+
label: msg(str`+${totalCategories - 3} ${msg("more")}`),
|
|
55
|
+
type: "neutral",
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Add media count if available
|
|
60
|
+
const mediaCount = this.object?.medias?.length || 0;
|
|
61
|
+
if (mediaCount > 0) {
|
|
62
|
+
tags.push({
|
|
63
|
+
label: msg(
|
|
64
|
+
str`${mediaCount} ${mediaCount > 1 ? msg("medias") : msg("media")}`,
|
|
65
|
+
),
|
|
66
|
+
type: "information",
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return tags;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
render() {
|
|
74
|
+
if (!this.object) return nothing;
|
|
75
|
+
const tags = this._getTags();
|
|
76
|
+
const formattedDate = this._getFormattedDate();
|
|
77
|
+
|
|
78
|
+
return html`<article class="vertical">
|
|
79
|
+
<header
|
|
80
|
+
class="image"
|
|
81
|
+
style='background-image: url("${this._getThumbnail()}")'
|
|
82
|
+
></header>
|
|
83
|
+
<main>
|
|
84
|
+
${formattedDate
|
|
85
|
+
? html`<div class="meta-info">
|
|
86
|
+
<span class="date">${formattedDate}</span>
|
|
87
|
+
</div>`
|
|
88
|
+
: nothing}
|
|
89
|
+
<div class="content">
|
|
90
|
+
${this.object?.name
|
|
91
|
+
? html`<div class="card-header">
|
|
92
|
+
<tems-division
|
|
93
|
+
label=${this.object?.name}
|
|
94
|
+
type="h4"
|
|
95
|
+
></tems-division>
|
|
96
|
+
</div>`
|
|
97
|
+
: nothing}
|
|
98
|
+
${this.object?.description
|
|
99
|
+
? html`<div class="card-content">
|
|
100
|
+
<tems-division
|
|
101
|
+
label=${this.object?.description}
|
|
102
|
+
type="body-m"
|
|
103
|
+
></tems-division>
|
|
104
|
+
</div>`
|
|
105
|
+
: nothing}
|
|
106
|
+
${tags.length > 0
|
|
107
|
+
? html`<div class="tags">
|
|
108
|
+
${tags.map(
|
|
109
|
+
(tag) =>
|
|
110
|
+
html`<tems-badge
|
|
111
|
+
size="sm"
|
|
112
|
+
label=${tag.label}
|
|
113
|
+
type=${tag.type}
|
|
114
|
+
color=${tag.color || nothing}
|
|
115
|
+
></tems-badge>`,
|
|
116
|
+
)}
|
|
117
|
+
</div>`
|
|
118
|
+
: nothing}
|
|
119
|
+
${this.object.author
|
|
120
|
+
? html`<div class="meta-info">
|
|
121
|
+
<span class="author">${msg("By")} ${this.object.author}</span>
|
|
122
|
+
</div>`
|
|
123
|
+
: nothing}
|
|
124
|
+
</div>
|
|
125
|
+
</main>
|
|
126
|
+
</article>`;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { DSPOffer } from "@src/ds4go";
|
|
2
|
+
import {
|
|
3
|
+
filterObjectByDateAfter,
|
|
4
|
+
filterObjectById,
|
|
5
|
+
filterObjectByInterval,
|
|
6
|
+
filterObjectByNamedValue,
|
|
7
|
+
filterObjectByType,
|
|
8
|
+
filterObjectByValue,
|
|
9
|
+
} from "@helpers";
|
|
10
|
+
import type { Resource } from "@src/component";
|
|
11
|
+
import {
|
|
12
|
+
TemsObjectsHandler,
|
|
13
|
+
type TemsSearchObject,
|
|
14
|
+
} from "@startinblox/solid-tems-shared";
|
|
15
|
+
import { css, html, nothing, type PropertyValues } from "lit";
|
|
16
|
+
import { customElement, state } from "lit/decorators.js";
|
|
17
|
+
|
|
18
|
+
@customElement("ds4go-catalog-data-holder")
|
|
19
|
+
export class Ds4goCatalogDataHolder extends TemsObjectsHandler {
|
|
20
|
+
static styles = css`
|
|
21
|
+
.card-grid {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: row;
|
|
24
|
+
flex-wrap: wrap;
|
|
25
|
+
gap: 20px;
|
|
26
|
+
}
|
|
27
|
+
.card-grid-vertical {
|
|
28
|
+
justify-content: stretch;
|
|
29
|
+
}
|
|
30
|
+
.card-grid-vertical ds4go-card-dataspace-catalog {
|
|
31
|
+
width: 354px;
|
|
32
|
+
height: auto;
|
|
33
|
+
}
|
|
34
|
+
ds4go-card-dataspace-catalog {
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
@state()
|
|
40
|
+
view: "card" | "list" | "table" | "map" = "card";
|
|
41
|
+
|
|
42
|
+
@state()
|
|
43
|
+
search: TemsSearchObject[] = [];
|
|
44
|
+
|
|
45
|
+
objects: Resource[] = [];
|
|
46
|
+
|
|
47
|
+
@state()
|
|
48
|
+
protected _displayObjects: DSPOffer[] = [];
|
|
49
|
+
|
|
50
|
+
protected filter(objects: any[], filters: TemsSearchObject[] = []) {
|
|
51
|
+
if (!filters || filters.length === 0 || !objects || objects.length === 0) {
|
|
52
|
+
return objects;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const groupedFilters = new Map<string, TemsSearchObject[]>();
|
|
56
|
+
for (const filter of filters) {
|
|
57
|
+
const groupKey = filter.name;
|
|
58
|
+
const group = groupedFilters.get(groupKey) || [];
|
|
59
|
+
group.push(filter);
|
|
60
|
+
groupedFilters.set(groupKey, group);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let currentFilteredObjects = [...objects];
|
|
64
|
+
|
|
65
|
+
for (const filterGroup of groupedFilters.values()) {
|
|
66
|
+
if (currentFilteredObjects.length === 0) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const tempResults = [];
|
|
71
|
+
for (const filter of filterGroup) {
|
|
72
|
+
switch (filter.type) {
|
|
73
|
+
case "interval":
|
|
74
|
+
tempResults.push(
|
|
75
|
+
filterObjectByInterval(
|
|
76
|
+
currentFilteredObjects,
|
|
77
|
+
filter.name,
|
|
78
|
+
filter.value,
|
|
79
|
+
),
|
|
80
|
+
);
|
|
81
|
+
break;
|
|
82
|
+
case "dateAfter":
|
|
83
|
+
tempResults.push(
|
|
84
|
+
filterObjectByDateAfter(
|
|
85
|
+
currentFilteredObjects,
|
|
86
|
+
filter.name,
|
|
87
|
+
filter.value,
|
|
88
|
+
),
|
|
89
|
+
);
|
|
90
|
+
break;
|
|
91
|
+
case "matchId":
|
|
92
|
+
tempResults.push(
|
|
93
|
+
filterObjectById(
|
|
94
|
+
currentFilteredObjects,
|
|
95
|
+
filter.name,
|
|
96
|
+
filter.value,
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
break;
|
|
100
|
+
case "matchType":
|
|
101
|
+
tempResults.push(
|
|
102
|
+
filterObjectByType(currentFilteredObjects, filter.value),
|
|
103
|
+
);
|
|
104
|
+
break;
|
|
105
|
+
case "exact":
|
|
106
|
+
tempResults.push(
|
|
107
|
+
filterObjectByNamedValue(
|
|
108
|
+
currentFilteredObjects,
|
|
109
|
+
filter.name,
|
|
110
|
+
filter.value,
|
|
111
|
+
),
|
|
112
|
+
);
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
tempResults.push(
|
|
116
|
+
filterObjectByValue(currentFilteredObjects, filter.value),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
currentFilteredObjects = [...tempResults.flat()];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return currentFilteredObjects;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
protected _handleClickEvent(originalObj: Resource) {
|
|
127
|
+
this.dispatchEvent(new CustomEvent("clicked", { detail: originalObj }));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
willUpdate(changedProperties: PropertyValues) {
|
|
131
|
+
if (changedProperties.has("objects") || changedProperties.has("search")) {
|
|
132
|
+
const filteredObjects = this.filter(this.objects, this.search);
|
|
133
|
+
this.dispatchEvent(
|
|
134
|
+
new CustomEvent("result-count", { detail: filteredObjects.length }),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
this._displayObjects = filteredObjects as DSPOffer[];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
render() {
|
|
142
|
+
if (!this._displayObjects || this._displayObjects.length === 0) {
|
|
143
|
+
return nothing;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return html`<div
|
|
147
|
+
class="card-grid${this.view === "card" ? " card-grid-vertical" : ""}"
|
|
148
|
+
>
|
|
149
|
+
${this._displayObjects.map((dspOffer) => {
|
|
150
|
+
return html`<ds4go-card-dataspace-catalog
|
|
151
|
+
.object=${dspOffer}
|
|
152
|
+
type=${this.view === "card" ? "card" : "horizontal"}
|
|
153
|
+
@click=${() => this._handleClickEvent(dspOffer as Resource)}
|
|
154
|
+
></ds4go-card-dataspace-catalog>`;
|
|
155
|
+
})}
|
|
156
|
+
</div>`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ComponentObjectsHandler,
|
|
3
|
+
filterObjectByDateAfter,
|
|
4
|
+
filterObjectById,
|
|
5
|
+
filterObjectByInterval,
|
|
6
|
+
filterObjectByNamedValue,
|
|
7
|
+
filterObjectByType,
|
|
8
|
+
filterObjectByValue,
|
|
9
|
+
} from "@helpers";
|
|
10
|
+
import type { Fact } from "@src/ds4go";
|
|
11
|
+
import type { Resource, SearchObject, UnknownResource } from "@src/component";
|
|
12
|
+
import { css, html, nothing, type PropertyValues } from "lit";
|
|
13
|
+
import { customElement, state } from "lit/decorators.js";
|
|
14
|
+
|
|
15
|
+
@customElement("ds4go-fact-holder")
|
|
16
|
+
export class Ds4goFactHolder extends ComponentObjectsHandler {
|
|
17
|
+
static styles = css`
|
|
18
|
+
.card-grid {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: row;
|
|
21
|
+
flex-wrap: wrap;
|
|
22
|
+
gap: 20px;
|
|
23
|
+
}
|
|
24
|
+
.card-grid-vertical {
|
|
25
|
+
justify-content: stretch;
|
|
26
|
+
}
|
|
27
|
+
.card-grid-vertical ds4go-card-fact {
|
|
28
|
+
width: 354px;
|
|
29
|
+
height: auto;
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
@state()
|
|
34
|
+
search: SearchObject[] = [];
|
|
35
|
+
|
|
36
|
+
@state()
|
|
37
|
+
protected _displayObjects: Resource[] = [];
|
|
38
|
+
|
|
39
|
+
protected filter(objects: Resource[], filters: SearchObject[] = []) {
|
|
40
|
+
if (!filters || filters.length === 0 || !objects || objects.length === 0) {
|
|
41
|
+
return objects;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const groupedFilters = new Map<string, SearchObject[]>();
|
|
45
|
+
for (const filter of filters) {
|
|
46
|
+
const groupKey = filter.name;
|
|
47
|
+
const group = groupedFilters.get(groupKey) || [];
|
|
48
|
+
group.push(filter);
|
|
49
|
+
groupedFilters.set(groupKey, group);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let currentFilteredObjects = [...objects];
|
|
53
|
+
|
|
54
|
+
for (const filterGroup of groupedFilters.values()) {
|
|
55
|
+
if (currentFilteredObjects.length === 0) {
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const tempResults: UnknownResource[] = [];
|
|
60
|
+
for (const filter of filterGroup) {
|
|
61
|
+
switch (filter.type) {
|
|
62
|
+
case "interval":
|
|
63
|
+
tempResults.push(
|
|
64
|
+
filterObjectByInterval(
|
|
65
|
+
currentFilteredObjects,
|
|
66
|
+
filter.name,
|
|
67
|
+
filter.value,
|
|
68
|
+
),
|
|
69
|
+
);
|
|
70
|
+
break;
|
|
71
|
+
case "dateAfter":
|
|
72
|
+
tempResults.push(
|
|
73
|
+
filterObjectByDateAfter(
|
|
74
|
+
currentFilteredObjects,
|
|
75
|
+
filter.name,
|
|
76
|
+
filter.value,
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
break;
|
|
80
|
+
case "matchId":
|
|
81
|
+
tempResults.push(
|
|
82
|
+
filterObjectById(
|
|
83
|
+
currentFilteredObjects,
|
|
84
|
+
filter.name,
|
|
85
|
+
filter.value,
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
break;
|
|
89
|
+
case "matchType":
|
|
90
|
+
tempResults.push(
|
|
91
|
+
filterObjectByType(currentFilteredObjects, filter.value),
|
|
92
|
+
);
|
|
93
|
+
break;
|
|
94
|
+
case "exact":
|
|
95
|
+
tempResults.push(
|
|
96
|
+
filterObjectByNamedValue(
|
|
97
|
+
currentFilteredObjects,
|
|
98
|
+
filter.name,
|
|
99
|
+
filter.value,
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
tempResults.push(
|
|
105
|
+
filterObjectByValue(currentFilteredObjects, filter.value),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
currentFilteredObjects = [...tempResults.flat()] as Resource[];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return currentFilteredObjects;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
protected _handleClickEvent(originalObj: Resource) {
|
|
116
|
+
this.dispatchEvent(new CustomEvent("clicked", { detail: originalObj }));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
willUpdate(changedProperties: PropertyValues) {
|
|
120
|
+
if (changedProperties.has("objects") || changedProperties.has("search")) {
|
|
121
|
+
if (this.objects) {
|
|
122
|
+
this._displayObjects = this.filter(this.objects, this.search);
|
|
123
|
+
} else {
|
|
124
|
+
this._displayObjects = [];
|
|
125
|
+
}
|
|
126
|
+
this.dispatchEvent(
|
|
127
|
+
new CustomEvent("result-count", {
|
|
128
|
+
detail: this._displayObjects.length,
|
|
129
|
+
}),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
render() {
|
|
135
|
+
if (!this._displayObjects || this._displayObjects.length === 0) {
|
|
136
|
+
return nothing;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return html`<div class="card-grid card-grid-vertical">
|
|
140
|
+
${this._displayObjects.map((displayObj) => {
|
|
141
|
+
const fact = displayObj as Fact;
|
|
142
|
+
return html`<ds4go-card-fact
|
|
143
|
+
.object=${import.meta.env.DEV ? fact : nothing}
|
|
144
|
+
@click=${() => this._handleClickEvent(fact)}
|
|
145
|
+
></ds4go-card-fact>`;
|
|
146
|
+
})}
|
|
147
|
+
</div>`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { localized, msg } from "@lit/localize";
|
|
2
|
+
import { html, nothing, type TemplateResult } from "lit";
|
|
3
|
+
import { customElement, property } from "lit/decorators.js";
|
|
4
|
+
import { LitElement } from "lit";
|
|
5
|
+
import type { DSPOffer, AgreementInfo } from "@src/ds4go";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Agreement info component for displaying contract agreement details
|
|
9
|
+
*/
|
|
10
|
+
@customElement("catalog-modal-agreement-info")
|
|
11
|
+
@localized()
|
|
12
|
+
export class CatalogModalAgreementInfo extends LitElement {
|
|
13
|
+
@property({ attribute: false })
|
|
14
|
+
offer?: DSPOffer;
|
|
15
|
+
|
|
16
|
+
@property({ attribute: false })
|
|
17
|
+
agreementInfo?: AgreementInfo;
|
|
18
|
+
|
|
19
|
+
@property({ attribute: false })
|
|
20
|
+
contractId?: string;
|
|
21
|
+
|
|
22
|
+
private _handleRenewContract(): void {
|
|
23
|
+
this.dispatchEvent(
|
|
24
|
+
new CustomEvent("renew-contract", {
|
|
25
|
+
bubbles: true,
|
|
26
|
+
composed: true,
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
render(): TemplateResult | typeof nothing {
|
|
32
|
+
// Only show if we have agreement info
|
|
33
|
+
if (!this.contractId || !this.agreementInfo) {
|
|
34
|
+
return nothing;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const agreementDate = this.agreementInfo.timestamp
|
|
38
|
+
? new Date(this.agreementInfo.timestamp).toLocaleString()
|
|
39
|
+
: null;
|
|
40
|
+
|
|
41
|
+
// Get endpoint URL from asset
|
|
42
|
+
const endpointUrl = this.offer?.["dcat:endpointUrl"];
|
|
43
|
+
|
|
44
|
+
return html`<div
|
|
45
|
+
style="margin-top: 24px; padding: 16px; background: #e8f5e9; border-radius: 8px;"
|
|
46
|
+
>
|
|
47
|
+
<tems-division type="h4"
|
|
48
|
+
><div>${msg("Contract Agreement")}</div></tems-division
|
|
49
|
+
>
|
|
50
|
+
<div style="font-size: 0.9em; margin-top: 12px;">
|
|
51
|
+
<div style="margin-bottom: 8px;">
|
|
52
|
+
<strong>✅ ${msg("Agreement ID:")}</strong>
|
|
53
|
+
<div
|
|
54
|
+
style="font-family: monospace; background: white; padding: 8px; border-radius: 4px; margin-top: 4px; word-break: break-all;"
|
|
55
|
+
>
|
|
56
|
+
${this.contractId}
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
|
|
60
|
+
${endpointUrl
|
|
61
|
+
? html`
|
|
62
|
+
<div style="margin-bottom: 8px;">
|
|
63
|
+
<strong>🔗 ${msg("Endpoint URL:")}</strong>
|
|
64
|
+
<div
|
|
65
|
+
style="font-family: monospace; background: white; padding: 8px; border-radius: 4px; margin-top: 4px; word-break: break-all;"
|
|
66
|
+
>
|
|
67
|
+
${endpointUrl}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
`
|
|
71
|
+
: nothing}
|
|
72
|
+
${agreementDate
|
|
73
|
+
? html`
|
|
74
|
+
<div style="opacity: 0.8; font-size: 0.85em;">
|
|
75
|
+
<strong>${msg("Agreed on:")}</strong> ${agreementDate}
|
|
76
|
+
</div>
|
|
77
|
+
`
|
|
78
|
+
: nothing}
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div
|
|
82
|
+
style="margin-top: 12px; padding: 12px; background: rgba(0,0,0,0.05); border-radius: 4px; font-size: 0.85em;"
|
|
83
|
+
>
|
|
84
|
+
<div style="margin-bottom: 4px;">
|
|
85
|
+
<strong>ℹ️ ${msg("Note:")}</strong>
|
|
86
|
+
</div>
|
|
87
|
+
<div>
|
|
88
|
+
${msg(
|
|
89
|
+
"You can now use this agreement ID to access the service through the provider's API or data gateway.",
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
${this.agreementInfo
|
|
95
|
+
? html`
|
|
96
|
+
<div
|
|
97
|
+
style="margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(0,0,0,0.1);"
|
|
98
|
+
>
|
|
99
|
+
<button
|
|
100
|
+
@click=${this._handleRenewContract}
|
|
101
|
+
style="font-size: 0.85em; color: #666; background: none; border: none; cursor: pointer; text-decoration: underline; padding: 0;"
|
|
102
|
+
>
|
|
103
|
+
🔄 ${msg("Renegotiate contract")}
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
`
|
|
107
|
+
: nothing}
|
|
108
|
+
</div>`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { localized, msg } from "@lit/localize";
|
|
2
|
+
import { html, type TemplateResult } from "lit";
|
|
3
|
+
import { customElement, property } from "lit/decorators.js";
|
|
4
|
+
import { LitElement } from "lit";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Negotiation button component for handling DSP contract negotiation UI
|
|
8
|
+
*/
|
|
9
|
+
@customElement("catalog-modal-negotiation-button")
|
|
10
|
+
@localized()
|
|
11
|
+
export class CatalogModalNegotiationButton extends LitElement {
|
|
12
|
+
@property({ attribute: false })
|
|
13
|
+
dspStore?: any;
|
|
14
|
+
|
|
15
|
+
@property({ attribute: false })
|
|
16
|
+
showPolicySelection = false;
|
|
17
|
+
|
|
18
|
+
@property()
|
|
19
|
+
negotiationStatus:
|
|
20
|
+
| "idle"
|
|
21
|
+
| "negotiating"
|
|
22
|
+
| "pending"
|
|
23
|
+
| "granted"
|
|
24
|
+
| "failed"
|
|
25
|
+
| "transferring" = "idle";
|
|
26
|
+
|
|
27
|
+
@property()
|
|
28
|
+
currentState?: string;
|
|
29
|
+
|
|
30
|
+
@property()
|
|
31
|
+
attempt?: number;
|
|
32
|
+
|
|
33
|
+
@property()
|
|
34
|
+
maxAttempts?: number;
|
|
35
|
+
|
|
36
|
+
@property()
|
|
37
|
+
negotiationError?: string;
|
|
38
|
+
|
|
39
|
+
private _handleNegotiate(): void {
|
|
40
|
+
this.dispatchEvent(
|
|
41
|
+
new CustomEvent("negotiate", {
|
|
42
|
+
bubbles: true,
|
|
43
|
+
composed: true,
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private _handleRetry(): void {
|
|
49
|
+
this.dispatchEvent(
|
|
50
|
+
new CustomEvent("retry", {
|
|
51
|
+
bubbles: true,
|
|
52
|
+
composed: true,
|
|
53
|
+
}),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
render(): TemplateResult {
|
|
58
|
+
const hasDspConnector =
|
|
59
|
+
this.dspStore !== undefined && this.dspStore !== null;
|
|
60
|
+
|
|
61
|
+
if (!hasDspConnector) {
|
|
62
|
+
return html`<tems-button disabled=""
|
|
63
|
+
>${msg("Activate this service")}</tems-button
|
|
64
|
+
>`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
switch (this.negotiationStatus) {
|
|
68
|
+
case "idle":
|
|
69
|
+
return html`<tems-button @click=${this._handleNegotiate}
|
|
70
|
+
>${msg("Negotiate access")}</tems-button
|
|
71
|
+
>`;
|
|
72
|
+
|
|
73
|
+
case "negotiating":
|
|
74
|
+
return html`<tems-button disabled="">
|
|
75
|
+
${msg("Negotiating...")}
|
|
76
|
+
</tems-button>`;
|
|
77
|
+
|
|
78
|
+
case "pending":
|
|
79
|
+
return html`<tems-button disabled="">
|
|
80
|
+
${this.currentState || msg("Pending")}
|
|
81
|
+
${this.attempt ? `(${this.attempt}/${this.maxAttempts})` : ""}
|
|
82
|
+
</tems-button>`;
|
|
83
|
+
|
|
84
|
+
case "granted": {
|
|
85
|
+
return html`
|
|
86
|
+
<tems-button disabled="" type="success">
|
|
87
|
+
✅ ${msg("Access Granted")}
|
|
88
|
+
</tems-button>
|
|
89
|
+
`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
case "failed":
|
|
93
|
+
return html`<div
|
|
94
|
+
style="display: flex; flex-direction: column; gap: 8px;"
|
|
95
|
+
>
|
|
96
|
+
<tems-button disabled="" type="error">
|
|
97
|
+
❌ ${msg("Failed")}:
|
|
98
|
+
${this.negotiationError || msg("Unknown error")}
|
|
99
|
+
</tems-button>
|
|
100
|
+
<tems-button @click=${this._handleRetry} type="outline-gray">
|
|
101
|
+
${msg("Retry")}
|
|
102
|
+
</tems-button>
|
|
103
|
+
</div>`;
|
|
104
|
+
|
|
105
|
+
default:
|
|
106
|
+
return html`<tems-button disabled=""
|
|
107
|
+
>${msg("Activate this service")}</tems-button
|
|
108
|
+
>`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|