Skip to content

Commit 7b03053

Browse files
committed
feat(pos-relations): allow adding a new relation via pos-add-relation
1 parent 7efe5bd commit 7b03053

File tree

7 files changed

+144
-8
lines changed

7 files changed

+144
-8
lines changed

docs/elements/apps/pos-app-generic/readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@ graph TD;
3434
pos-add-literal-value --> pos-select-term
3535
pos-relations --> pos-predicate
3636
pos-relations --> pos-rich-link
37+
pos-relations --> pos-add-relation
3738
pos-rich-link --> pos-label
3839
pos-rich-link --> pos-description
3940
pos-rich-link --> pos-resource
41+
pos-add-relation --> pos-select-term
4042
pos-reverse-relations --> pos-predicate
4143
pos-reverse-relations --> pos-rich-link
4244
style pos-app-generic fill:#f9f,stroke:#333,stroke-width:4px

docs/elements/components/pos-relations/readme.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,18 @@
2222

2323
- [pos-predicate](../pos-predicate)
2424
- [pos-rich-link](../pos-rich-link)
25+
- [pos-add-relation](../pos-add-relation)
2526

2627
### Graph
2728
```mermaid
2829
graph TD;
2930
pos-relations --> pos-predicate
3031
pos-relations --> pos-rich-link
32+
pos-relations --> pos-add-relation
3133
pos-rich-link --> pos-label
3234
pos-rich-link --> pos-description
3335
pos-rich-link --> pos-resource
36+
pos-add-relation --> pos-select-term
3437
pos-app-generic --> pos-relations
3538
style pos-relations fill:#f9f,stroke:#333,stroke-width:4px
3639
```

docs/elements/components/pos-select-term/readme.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,14 @@
3434
### Used by
3535

3636
- [pos-add-literal-value](../pos-add-literal-value)
37+
- [pos-add-relation](../pos-add-relation)
3738
- [pos-new-thing-form](../pos-new-thing-form)
3839

3940
### Graph
4041
```mermaid
4142
graph TD;
4243
pos-add-literal-value --> pos-select-term
44+
pos-add-relation --> pos-select-term
4345
pos-new-thing-form --> pos-select-term
4446
style pos-select-term fill:#f9f,stroke:#333,stroke-width:4px
4547
```

elements/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
1111
- `@pod-os/elements` now includes a [Custom Elements Manifest](https://github.com/webcomponents/custom-elements-manifest) at `dist/custom-elements.json` that can be used in IDEs to
1212
enable auto-completion for web components.
1313
- [pos-add-relation](https://pod-os.org/reference/elements/components/pos-add-relation/): New component to add a relation (link) to a thing
14+
- [pos-relations](https://pod-os.org/reference/elements/components/pos-relations/): Allows to add a new relation, if resource is editable
1415

1516
### Fixed
1617

elements/src/components.d.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
* It contains typing information for all components that exist in this project.
66
*/
77
import { HTMLStencilElement, JSXBase } from "@stencil/core/internal";
8-
import { Attachment, HttpProblem, LdpContainer, NetworkProblem, PodOS, Problem, SolidFile, Thing } from "@pod-os/core";
8+
import { Attachment, HttpProblem, LdpContainer, Literal, NetworkProblem, PodOS, Problem, Relation, SolidFile, Thing } from "@pod-os/core";
99
import { ToolConfig } from "./components/pos-type-router/selectToolsForTypes";
1010
import { ResultAsync } from "neverthrow";
11-
export { Attachment, HttpProblem, LdpContainer, NetworkProblem, PodOS, Problem, SolidFile, Thing } from "@pod-os/core";
11+
export { Attachment, HttpProblem, LdpContainer, Literal, NetworkProblem, PodOS, Problem, Relation, SolidFile, Thing } from "@pod-os/core";
1212
export { ToolConfig } from "./components/pos-type-router/selectToolsForTypes";
1313
export { ResultAsync } from "neverthrow";
1414
export namespace Components {
@@ -373,6 +373,10 @@ export interface PosAddLiteralValueCustomEvent<T> extends CustomEvent<T> {
373373
detail: T;
374374
target: HTMLPosAddLiteralValueElement;
375375
}
376+
export interface PosAddRelationCustomEvent<T> extends CustomEvent<T> {
377+
detail: T;
378+
target: HTMLPosAddRelationElement;
379+
}
376380
export interface PosAppCustomEvent<T> extends CustomEvent<T> {
377381
detail: T;
378382
target: HTMLPosAppElement;
@@ -517,7 +521,7 @@ declare global {
517521
interface HTMLPosAddLiteralValueElementEventMap {
518522
"pod-os:init": any;
519523
"pod-os:resource": any;
520-
"pod-os:added-literal-value": any;
524+
"pod-os:added-literal-value": Literal;
521525
"pod-os:error": any;
522526
}
523527
interface HTMLPosAddLiteralValueElement extends Components.PosAddLiteralValue, HTMLStencilElement {
@@ -540,10 +544,22 @@ declare global {
540544
prototype: HTMLPosAddNewThingElement;
541545
new (): HTMLPosAddNewThingElement;
542546
};
547+
interface HTMLPosAddRelationElementEventMap {
548+
"pod-os:added-relation": Relation;
549+
"pod-os:error": any;
550+
}
543551
/**
544552
* Add a new relation from the current resource to another one
545553
*/
546554
interface HTMLPosAddRelationElement extends Components.PosAddRelation, HTMLStencilElement {
555+
addEventListener<K extends keyof HTMLPosAddRelationElementEventMap>(type: K, listener: (this: HTMLPosAddRelationElement, ev: PosAddRelationCustomEvent<HTMLPosAddRelationElementEventMap[K]>) => any, options?: boolean | AddEventListenerOptions): void;
556+
addEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
557+
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
558+
addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
559+
removeEventListener<K extends keyof HTMLPosAddRelationElementEventMap>(type: K, listener: (this: HTMLPosAddRelationElement, ev: PosAddRelationCustomEvent<HTMLPosAddRelationElementEventMap[K]>) => any, options?: boolean | EventListenerOptions): void;
560+
removeEventListener<K extends keyof DocumentEventMap>(type: K, listener: (this: Document, ev: DocumentEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
561+
removeEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
562+
removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
547563
}
548564
var HTMLPosAddRelationElement: {
549565
prototype: HTMLPosAddRelationElement;
@@ -1407,7 +1423,7 @@ declare namespace LocalJSX {
14071423
/**
14081424
* The entered literal value has been added to the resource and successfully stored to the Pod.
14091425
*/
1410-
"onPod-os:added-literal-value"?: (event: PosAddLiteralValueCustomEvent<any>) => void;
1426+
"onPod-os:added-literal-value"?: (event: PosAddLiteralValueCustomEvent<Literal>) => void;
14111427
/**
14121428
* Something went wrong while adding the literal value.
14131429
*/
@@ -1422,6 +1438,14 @@ declare namespace LocalJSX {
14221438
* Add a new relation from the current resource to another one
14231439
*/
14241440
interface PosAddRelation {
1441+
/**
1442+
* The relation has been added to the resource and successfully stored to the Pod.
1443+
*/
1444+
"onPod-os:added-relation"?: (event: PosAddRelationCustomEvent<Relation>) => void;
1445+
/**
1446+
* Something went wrong while adding the relation.
1447+
*/
1448+
"onPod-os:error"?: (event: PosAddRelationCustomEvent<any>) => void;
14251449
}
14261450
interface PosApp {
14271451
/**

elements/src/components/pos-relations/pos-relations.spec.tsx

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { newSpecPage } from '@stencil/core/testing';
22

3-
import { getByText } from '@testing-library/dom';
3+
import { fireEvent } from '@testing-library/dom';
44

55
import { PosRelations } from './pos-relations';
6+
import { Relation } from '@pod-os/core';
67

78
describe('pos-relations', () => {
89
it('are empty initially', async () => {
@@ -12,7 +13,9 @@ describe('pos-relations', () => {
1213
});
1314
expect(page.root).toEqualHtml(`
1415
<pos-relations>
15-
<mock:shadow-root></mock:shadow-root>
16+
<mock:shadow-root>
17+
<pos-add-relation></pos-add-relation>
18+
</mock:shadow-root>
1619
</pos-relations>
1720
`);
1821
});
@@ -78,4 +81,82 @@ describe('pos-relations', () => {
7881
);
7982
expect(linkToAttachment).not.toBeNull();
8083
});
84+
85+
it('adds newly added relation to the list', async () => {
86+
// given
87+
const page = await newSpecPage({
88+
components: [PosRelations],
89+
html: `<pos-relations />`,
90+
supportsShadowDom: false,
91+
});
92+
await page.rootInstance.receiveResource({
93+
relations: () => [],
94+
});
95+
await page.waitForChanges();
96+
97+
// when
98+
const input = page.root.querySelector('pos-add-relation');
99+
const relation: Relation = {
100+
predicate: 'http://xmlns.com/foaf/0.1/knows',
101+
label: 'knows',
102+
uris: ['https://alice.test/profile/card#me'],
103+
};
104+
fireEvent(
105+
input,
106+
new CustomEvent('pod-os:added-relation', {
107+
detail: relation,
108+
}),
109+
);
110+
111+
await page.waitForChanges();
112+
113+
const url = page.root.querySelector('pos-predicate[uri="http://xmlns.com/foaf/0.1/knows"]');
114+
expect(url).toEqualAttribute('label', 'knows');
115+
const linkToAlice = page.root.querySelector('pos-rich-link[uri="https://alice.test/profile/card#me"]');
116+
expect(linkToAlice).not.toBeNull();
117+
});
118+
119+
it('adds newly added relation to the existing list without duplicating the predicate', async () => {
120+
// given
121+
const page = await newSpecPage({
122+
components: [PosRelations],
123+
html: `<pos-relations />`,
124+
supportsShadowDom: false,
125+
});
126+
await page.rootInstance.receiveResource({
127+
relations: () => [
128+
{
129+
predicate: 'http://xmlns.com/foaf/0.1/knows',
130+
label: 'knows',
131+
uris: ['https://alice.test/profile/card#me'],
132+
},
133+
],
134+
});
135+
await page.waitForChanges();
136+
137+
// when
138+
const input = page.root.querySelector('pos-add-relation');
139+
const relation: Relation = {
140+
predicate: 'http://xmlns.com/foaf/0.1/knows',
141+
label: 'knows',
142+
uris: ['https://bernadette.test/profile/card#me'],
143+
};
144+
fireEvent(
145+
input,
146+
new CustomEvent('pod-os:added-relation', {
147+
detail: relation,
148+
}),
149+
);
150+
151+
await page.waitForChanges();
152+
153+
// then
154+
const knows = page.root.querySelectorAll('pos-predicate[uri="http://xmlns.com/foaf/0.1/knows"]');
155+
expect(knows).toHaveLength(1);
156+
expect(knows[0]).toEqualAttribute('label', 'knows');
157+
const linkToAlice = page.root.querySelector('pos-rich-link[uri="https://alice.test/profile/card#me"]');
158+
expect(linkToAlice).not.toBeNull();
159+
const linkToBernadette = page.root.querySelector('pos-rich-link[uri="https://bernadette.test/profile/card#me"]');
160+
expect(linkToBernadette).not.toBeNull();
161+
});
81162
});

elements/src/components/pos-relations/pos-relations.tsx

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Relation, Thing } from '@pod-os/core';
2-
import { Component, Event, EventEmitter, h, State } from '@stencil/core';
2+
import { Component, Event, EventEmitter, h, Host, State } from '@stencil/core';
33
import { ResourceAware, subscribeResource } from '../events/ResourceAware';
44

55
@Component({
@@ -21,6 +21,24 @@ export class PosRelations implements ResourceAware {
2121
this.data = resource.relations();
2222
};
2323

24+
relationAdded(newRelation: Relation) {
25+
const existing = this.data.find(it => it.predicate === newRelation.predicate);
26+
27+
if (!existing) {
28+
this.data = [...this.data, newRelation];
29+
} else {
30+
this.data = this.data.map(it => {
31+
return it.predicate === existing.predicate
32+
? {
33+
predicate: existing.predicate,
34+
label: existing.label,
35+
uris: [...existing.uris, ...newRelation.uris],
36+
}
37+
: it;
38+
});
39+
}
40+
}
41+
2442
render() {
2543
const items = this.data.map(it => (
2644
<div class="predicate-values">
@@ -36,6 +54,11 @@ export class PosRelations implements ResourceAware {
3654
</div>
3755
</div>
3856
));
39-
return this.data.length > 0 ? <dl>{items}</dl> : null;
57+
return (
58+
<Host>
59+
{this.data.length > 0 ? <dl>{items}</dl> : null}
60+
<pos-add-relation onPod-os:added-relation={ev => this.relationAdded(ev.detail)}></pos-add-relation>
61+
</Host>
62+
);
4063
}
4164
}

0 commit comments

Comments
 (0)