diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 5856e1fd..decf80e5 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- `Thing.reverseRelations()`: Now takes optional predicate to filter by + ## 0.17.0 ### Changed diff --git a/core/src/thing/Thing.reverseRelations.spec.ts b/core/src/thing/Thing.reverseRelations.spec.ts index 2109e761..2fa9e639 100644 --- a/core/src/thing/Thing.reverseRelations.spec.ts +++ b/core/src/thing/Thing.reverseRelations.spec.ts @@ -142,5 +142,30 @@ describe("Thing", function () { }, ]); }); + + it("only follows the given predicate if provided", () => { + const store = graph(); + const uri = "https://jane.doe.example/container/file.ttl#fragment"; + store.add( + sym("https://pod.example/first"), + sym("http://vocab.test/first"), + + sym(uri), + ); + store.add( + sym("https://pod.example/second"), + sym("http://vocab.test/second"), + sym(uri), + ); + const it = new Thing(uri, store); + const result = it.reverseRelations("http://vocab.test/first"); + expect(result).toEqual([ + { + predicate: "http://vocab.test/first", + label: "first", + uris: ["https://pod.example/first"], + }, + ]); + }); }); }); diff --git a/core/src/thing/Thing.ts b/core/src/thing/Thing.ts index 3cb8d64f..b53b0fb1 100644 --- a/core/src/thing/Thing.ts +++ b/core/src/thing/Thing.ts @@ -89,10 +89,10 @@ export class Thing { })); } - reverseRelations(): Relation[] { + reverseRelations(predicate?: string): Relation[] { const statements = this.store.statementsMatching( undefined, - undefined, + predicate ? sym(predicate) : null, sym(this.uri), ); diff --git a/docs/elements/apps/pos-app-browser/readme.md b/docs/elements/apps/pos-app-browser/readme.md index 219a2574..3834dc2c 100644 --- a/docs/elements/apps/pos-app-browser/readme.md +++ b/docs/elements/apps/pos-app-browser/readme.md @@ -7,10 +7,10 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | -------------- | -| `mode` | `mode` | The mode the app is running in: - standalone: use this when you deploy it as a standalone web application - pod: use this when you host this app as a default interface for you pod | `"pod" \| "standalone"` | `'standalone'` | -| `restorePreviousSession` | `restore-previous-session` | | `boolean` | `false` | +| Property | Attribute | Description | Type | Default | +| ------------------------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | -------------- | +| `mode` | `mode` | The mode the app is running in: - standalone: use this when you deploy it as a standalone web application - pod: use this when you host this app as a default interface for you pod | `"pod" \| "standalone"` | `'standalone'` | +| `restorePreviousSession` | `restore-previous-session` | | `boolean` | `false` | ## Dependencies @@ -55,9 +55,9 @@ graph TD; pos-make-findable --> pos-label pos-resource --> ion-progress-bar ion-searchbar --> ion-icon - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-login --> pos-resource pos-login --> pos-picture pos-login --> pos-label diff --git a/docs/elements/apps/pos-app-dashboard/pos-example-resources/readme.md b/docs/elements/apps/pos-app-dashboard/pos-example-resources/readme.md index be8580d5..e5129690 100644 --- a/docs/elements/apps/pos-app-dashboard/pos-example-resources/readme.md +++ b/docs/elements/apps/pos-app-dashboard/pos-example-resources/readme.md @@ -16,9 +16,9 @@ ```mermaid graph TD; pos-example-resources --> pos-rich-link - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-resource --> ion-progress-bar pos-app-dashboard --> pos-example-resources style pos-example-resources fill:#f9f,stroke:#333,stroke-width:4px diff --git a/docs/elements/apps/pos-app-dashboard/readme.md b/docs/elements/apps/pos-app-dashboard/readme.md index bfd7c2ed..6b7ea98b 100644 --- a/docs/elements/apps/pos-app-dashboard/readme.md +++ b/docs/elements/apps/pos-app-dashboard/readme.md @@ -19,9 +19,9 @@ graph TD; pos-app-dashboard --> pos-getting-started pos-app-dashboard --> pos-example-resources pos-example-resources --> pos-rich-link - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-resource --> ion-progress-bar pos-internal-router --> pos-app-dashboard style pos-app-dashboard fill:#f9f,stroke:#333,stroke-width:4px diff --git a/docs/elements/apps/pos-app-generic/readme.md b/docs/elements/apps/pos-app-generic/readme.md index bb20fa58..19fbdd54 100644 --- a/docs/elements/apps/pos-app-generic/readme.md +++ b/docs/elements/apps/pos-app-generic/readme.md @@ -39,9 +39,9 @@ graph TD; pos-add-literal-value --> pos-select-term pos-relations --> pos-predicate pos-relations --> pos-rich-link - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-resource --> ion-progress-bar pos-reverse-relations --> pos-predicate pos-reverse-relations --> pos-rich-link diff --git a/docs/elements/apps/pos-app-ldp-container/readme.md b/docs/elements/apps/pos-app-ldp-container/readme.md index c292a1ab..71e6610b 100644 --- a/docs/elements/apps/pos-app-ldp-container/readme.md +++ b/docs/elements/apps/pos-app-ldp-container/readme.md @@ -28,9 +28,9 @@ graph TD; pos-resource --> ion-progress-bar pos-container-item --> ion-icon pos-subjects --> pos-rich-link - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-type-badges --> ion-badge pos-type-badges --> ion-icon pos-literals --> pos-predicate diff --git a/docs/elements/apps/pos-app-rdf-document/readme.md b/docs/elements/apps/pos-app-rdf-document/readme.md index c144f960..02389038 100644 --- a/docs/elements/apps/pos-app-rdf-document/readme.md +++ b/docs/elements/apps/pos-app-rdf-document/readme.md @@ -22,9 +22,9 @@ graph TD; pos-app-rdf-document --> pos-type-badges pos-app-rdf-document --> pos-literals pos-subjects --> pos-rich-link - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-resource --> ion-progress-bar pos-type-badges --> ion-badge pos-type-badges --> ion-icon diff --git a/docs/elements/components/pos-internal-router/readme.md b/docs/elements/components/pos-internal-router/readme.md index 497c0353..9a069e59 100644 --- a/docs/elements/components/pos-internal-router/readme.md +++ b/docs/elements/components/pos-internal-router/readme.md @@ -29,9 +29,9 @@ graph TD; pos-app-dashboard --> pos-getting-started pos-app-dashboard --> pos-example-resources pos-example-resources --> pos-rich-link - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-resource --> ion-progress-bar pos-app-browser --> pos-internal-router style pos-internal-router fill:#f9f,stroke:#333,stroke-width:4px diff --git a/docs/elements/components/pos-navigation-bar/readme.md b/docs/elements/components/pos-navigation-bar/readme.md index 48ecfdda..f96a8a6a 100644 --- a/docs/elements/components/pos-navigation-bar/readme.md +++ b/docs/elements/components/pos-navigation-bar/readme.md @@ -42,9 +42,9 @@ graph TD; pos-make-findable --> pos-label pos-resource --> ion-progress-bar ion-searchbar --> ion-icon - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-app-browser --> pos-navigation-bar style pos-navigation-bar fill:#f9f,stroke:#333,stroke-width:4px ``` diff --git a/docs/elements/components/pos-relations/readme.md b/docs/elements/components/pos-relations/readme.md index a7d3807e..3017addb 100644 --- a/docs/elements/components/pos-relations/readme.md +++ b/docs/elements/components/pos-relations/readme.md @@ -29,9 +29,9 @@ graph TD; pos-relations --> pos-predicate pos-relations --> pos-rich-link pos-predicate --> ion-icon - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-resource --> ion-progress-bar pos-app-generic --> pos-relations style pos-relations fill:#f9f,stroke:#333,stroke-width:4px diff --git a/docs/elements/components/pos-reverse-relations/readme.md b/docs/elements/components/pos-reverse-relations/readme.md index 2292c79a..b0f9b1eb 100644 --- a/docs/elements/components/pos-reverse-relations/readme.md +++ b/docs/elements/components/pos-reverse-relations/readme.md @@ -29,9 +29,9 @@ graph TD; pos-reverse-relations --> pos-predicate pos-reverse-relations --> pos-rich-link pos-predicate --> ion-icon - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-resource --> ion-progress-bar pos-app-generic --> pos-reverse-relations style pos-reverse-relations fill:#f9f,stroke:#333,stroke-width:4px diff --git a/docs/elements/components/pos-rich-link/readme.md b/docs/elements/components/pos-rich-link/readme.md index b9563d04..650b3f32 100644 --- a/docs/elements/components/pos-rich-link/readme.md +++ b/docs/elements/components/pos-rich-link/readme.md @@ -7,16 +7,20 @@ ## Properties -| Property | Attribute | Description | Type | Default | -| -------- | --------- | ----------- | -------- | ----------- | -| `uri` | `uri` | | `string` | `undefined` | +| Property | Attribute | Description | Type | Default | +| -------- | --------- | ----------------------------------------------------------------------------------------- | -------- | ----------- | +| `rel` | `rel` | Link will be obtained by following the predicate with this URI forward from a resource | `string` | `undefined` | +| `rev` | `rev` | Link will be obtained by following the predicate with this URI in reverse from a resource | `string` | `undefined` | +| `uri` | `uri` | Link will use this URI | `string` | `undefined` | ## Events -| Event | Description | Type | -| ------------- | ----------- | ------------------ | -| `pod-os:link` | | `CustomEvent` | +| Event | Description | Type | +| ----------------- | ----------- | ------------------ | +| `pod-os:error` | | `CustomEvent` | +| `pod-os:link` | | `CustomEvent` | +| `pod-os:resource` | | `CustomEvent` | ## Dependencies @@ -31,16 +35,16 @@ ### Depends on -- [pos-resource](../pos-resource) - [pos-label](../pos-label) - [pos-description](../pos-description) +- [pos-resource](../pos-resource) ### Graph ```mermaid graph TD; - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-resource --> ion-progress-bar pos-example-resources --> pos-rich-link pos-navigation-bar --> pos-rich-link diff --git a/docs/elements/components/pos-subjects/readme.md b/docs/elements/components/pos-subjects/readme.md index 8e300153..7d1e2101 100644 --- a/docs/elements/components/pos-subjects/readme.md +++ b/docs/elements/components/pos-subjects/readme.md @@ -27,9 +27,9 @@ ```mermaid graph TD; pos-subjects --> pos-rich-link - pos-rich-link --> pos-resource pos-rich-link --> pos-label pos-rich-link --> pos-description + pos-rich-link --> pos-resource pos-resource --> ion-progress-bar pos-app-ldp-container --> pos-subjects pos-app-rdf-document --> pos-subjects diff --git a/elements/CHANGELOG.md b/elements/CHANGELOG.md index 1396b84d..6ab9677d 100644 --- a/elements/CHANGELOG.md +++ b/elements/CHANGELOG.md @@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## Unreleased +### Changed + +- [pos-rich-link](../docs/elements/components/pos-rich-link): + - can now receive a resource to use for the link + - can now follow `rel` and `rev` to discover a resource to use for the link + ### Fixed - [pos-app-browser](../docs/elements/apps/pos-app-browser): prevent error message flashing up while uri is unset on hard refresh diff --git a/elements/src/components.d.ts b/elements/src/components.d.ts index a2885085..ac1acfa3 100644 --- a/elements/src/components.d.ts +++ b/elements/src/components.d.ts @@ -121,7 +121,18 @@ export namespace Components { interface PosReverseRelations { } interface PosRichLink { - "uri": string; + /** + * Link will be obtained by following the predicate with this URI forward from a resource + */ + "rel"?: string; + /** + * Link will be obtained by following the predicate with this URI in reverse from a resource + */ + "rev"?: string; + /** + * Link will use this URI + */ + "uri"?: string; } /** * The responsibility of pos-router is to handle the `uri` query param, that specifies the URI of the resource that is currently opened. @@ -724,6 +735,8 @@ declare global { }; interface HTMLPosRichLinkElementEventMap { "pod-os:link": any; + "pod-os:resource": any; + "pod-os:error": any; } interface HTMLPosRichLinkElement extends Components.PosRichLink, HTMLStencilElement { addEventListener(type: K, listener: (this: HTMLPosRichLinkElement, ev: PosRichLinkCustomEvent) => any, options?: boolean | AddEventListenerOptions): void; @@ -1066,7 +1079,20 @@ declare namespace LocalJSX { "onPod-os:resource"?: (event: PosReverseRelationsCustomEvent) => void; } interface PosRichLink { + "onPod-os:error"?: (event: PosRichLinkCustomEvent) => void; "onPod-os:link"?: (event: PosRichLinkCustomEvent) => void; + "onPod-os:resource"?: (event: PosRichLinkCustomEvent) => void; + /** + * Link will be obtained by following the predicate with this URI forward from a resource + */ + "rel"?: string; + /** + * Link will be obtained by following the predicate with this URI in reverse from a resource + */ + "rev"?: string; + /** + * Link will use this URI + */ "uri"?: string; } /** diff --git a/elements/src/components/pos-rich-link/pos-rich-link.integration.spec.tsx b/elements/src/components/pos-rich-link/pos-rich-link.integration.spec.tsx new file mode 100644 index 00000000..0784add9 --- /dev/null +++ b/elements/src/components/pos-rich-link/pos-rich-link.integration.spec.tsx @@ -0,0 +1,158 @@ +import { newSpecPage } from '@stencil/core/testing'; +import { mockPodOS } from '../../test/mockPodOS'; +import { PosApp } from '../pos-app/pos-app'; +import { PosDescription } from '../pos-description/pos-description'; +import { PosLabel } from '../pos-label/pos-label'; +import { PosResource } from '../pos-resource/pos-resource'; +import { PosRichLink } from './pos-rich-link'; +import { when } from 'jest-when'; +import { Component, h } from '@stencil/core'; + +describe('pos-rich-link', () => { + let os; + beforeEach(async () => { + os = mockPodOS(); + when(os.store.get) + .calledWith('https://resource.test') + .mockReturnValue({ + uri: 'https://resource.test', + label: () => 'Test label', + description: () => 'Test description', + relations: () => [{ predicate: 'https://schema.org/video', uris: ['https://video.test/video-1'] }], + }); + when(os.store.get) + .calledWith('https://video.test/video-1') + .mockReturnValue({ + uri: 'https://video.test/video-1', + label: () => 'Video 1', + description: () => 'Description of Video 1', + reverseRelations: () => [{ predicate: 'https://schema.org/video', uris: ['https://resource.test'] }], + }); + }); + + it('can be used outside resource', async () => { + const page = await newSpecPage({ + components: [PosApp, PosDescription, PosLabel, PosResource, PosRichLink], + supportsShadowDom: false, + html: ` + + + `, + }); + + const link = page.root?.querySelector('pos-rich-link'); + expect(link).toEqualHtml(` + + +

+ + + Test label + + + resource.test + + Test description + +

+
+
+ `); + }); + + it('receives and renders resource', async () => { + const page = await newSpecPage({ + components: [PosApp, PosDescription, PosLabel, PosResource, PosRichLink], + supportsShadowDom: false, + html: ` + + + + + `, + }); + + expect(os.store.get.mock.calls).toHaveLength(1); + + const link = page.root?.querySelector('pos-rich-link'); + expect(link).toEqualHtml(` + +

+ + + Test label + + + resource.test + + Test description + +

+
+ `); + }); + + it('uses label and description of the matching rel', async () => { + const page = await newSpecPage({ + components: [PosApp, PosDescription, PosLabel, PosResource, PosRichLink], + supportsShadowDom: false, + html: ` + + + + + `, + }); + + const link = page.root?.querySelector('pos-rich-link'); + expect(link).toEqualHtml(` + + +

+ + + Video 1 + + + video.test + + Description of Video 1 + +

+
+
+ `); + }); + + it('uses label and description of the matching rev', async () => { + const page = await newSpecPage({ + components: [PosApp, PosDescription, PosLabel, PosResource, PosRichLink], + supportsShadowDom: false, + html: ` + + + + + `, + }); + + const link = page.root?.querySelector('pos-rich-link'); + expect(link).toEqualHtml(` + + +

+ + + Test label + + + resource.test + + Test description + +

+
+
+ `); + }); +}); diff --git a/elements/src/components/pos-rich-link/pos-rich-link.spec.tsx b/elements/src/components/pos-rich-link/pos-rich-link.spec.tsx index 3f0a0dea..92c7f392 100644 --- a/elements/src/components/pos-rich-link/pos-rich-link.spec.tsx +++ b/elements/src/components/pos-rich-link/pos-rich-link.spec.tsx @@ -1,8 +1,9 @@ import { newSpecPage } from '@stencil/core/testing'; import { PosRichLink } from './pos-rich-link'; import { getByText } from '@testing-library/dom'; +import { when } from 'jest-when'; -describe('pos-rich-link', () => { +describe('pos-rich-link with uri', () => { let page; beforeEach(async () => { page = await newSpecPage({ @@ -49,3 +50,137 @@ describe('pos-rich-link', () => { }); }); }); + +describe('pos-rich-link without uri', () => { + it('does not emit pod-os:resource event if uri is present', async () => { + const onResource = jest.fn(); + const page = await newSpecPage({ + components: [PosRichLink], + }); + page.body.addEventListener('pod-os:resource', onResource); + await page.setContent(''); + expect(onResource).toHaveBeenCalledTimes(0); + }); + + it('receives resource and sets it as link if uri is not present', async () => { + const onResource = jest.fn(); + const page = await newSpecPage({ + components: [PosRichLink], + }); + page.body.addEventListener('pod-os:resource', onResource); + await page.setContent(''); + expect(onResource).toHaveBeenCalledTimes(1); + + await page.rootInstance.receiveResource({ + uri: 'https://pod.example/resource', + }); + await page.waitForChanges(); + const link = page.root?.shadowRoot?.querySelector('a'); + expect(link).toEqualAttribute('href', 'https://pod.example/resource'); + }); + + it('is empty if neither uri nor resource are received', async () => { + const page = await newSpecPage({ + components: [PosRichLink], + html: ``, + }); + expect(page.root?.innerHTML).toBe(''); + }); + + it('does not use pos-resource if uri is not present', async () => { + const page = await newSpecPage({ + components: [PosRichLink], + html: ``, + }); + await page.rootInstance.receiveResource({ + uri: 'https://pod.example/resource', + }); + await page.waitForChanges(); + expect(page.root?.shadowRoot?.querySelector('pos-resource')).toBeNull(); + }); + + it('uses the matching relation if rel prop is defined', async () => { + const page = await newSpecPage({ + components: [PosRichLink], + html: ``, + }); + const thing = { + uri: 'https://pod.example/resource', + relations: jest.fn(), + }; + when(thing.relations) + .calledWith('https://schema.org/video') + .mockReturnValue([{ predicate: 'https://schema.org/video', uris: ['https://video.test/video-1'] }]); + + await page.rootInstance.receiveResource(thing); + await page.waitForChanges(); + const link = page.root?.shadowRoot?.querySelector('a'); + expect(link).toEqualAttribute('href', 'https://video.test/video-1'); + }); + + it('uses the matching relation if rev prop is defined', async () => { + const page = await newSpecPage({ + components: [PosRichLink], + html: ``, + }); + const thing = { + uri: 'https://video.test/video-1', + reverseRelations: jest.fn(), + }; + when(thing.reverseRelations) + .calledWith('https://schema.org/video') + .mockReturnValue([{ predicate: 'https://schema.org/video', uris: ['https://pod.example/resource'] }]); + + await page.rootInstance.receiveResource(thing); + await page.waitForChanges(); + const link = page.root?.shadowRoot?.querySelector('a'); + expect(link).toEqualAttribute('href', 'https://pod.example/resource'); + }); + + it('displays and emits an error if no link is found', async () => { + const page = await newSpecPage({ + components: [PosRichLink], + html: ``, + }); + const errorListener = jest.fn(); + page.body.addEventListener('pod-os:error', errorListener); + await page.rootInstance.receiveResource({ + uri: 'https://pod.example/resource', + relations: () => [], + }); + await page.waitForChanges(); + expect(page.root?.shadowRoot?.textContent).toEqual('No matching link found'); + expect(errorListener).toHaveBeenCalledWith( + expect.objectContaining({ + detail: new Error( + 'pos-rich-link: No matching link found from https://pod.example/resource rel=https://schema.org/video', + ), + }), + ); + }); + + it('displays and emits an error if more than one link is found', async () => { + const page = await newSpecPage({ + components: [PosRichLink], + html: ``, + }); + const errorListener = jest.fn(); + page.body.addEventListener('pod-os:error', errorListener); + + await page.rootInstance.receiveResource({ + uri: 'https://pod.example/resource', + relations: () => [ + { predicate: 'https://schema.org/video', uris: ['https://video.test/video-1', 'https://video.test/video-2'] }, + ], + }); + await page.waitForChanges(); + expect(page.root?.shadowRoot?.textContent).toEqual('More than one matching link found'); + expect(errorListener).toHaveBeenCalledWith( + expect.objectContaining({ + detail: new Error( + 'pos-rich-link: More than one matching link found from https://pod.example/resource rel=https://schema.org/video', + ), + }), + ); + }); +}); diff --git a/elements/src/components/pos-rich-link/pos-rich-link.tsx b/elements/src/components/pos-rich-link/pos-rich-link.tsx index fe58f7e4..f66f050a 100644 --- a/elements/src/components/pos-rich-link/pos-rich-link.tsx +++ b/elements/src/components/pos-rich-link/pos-rich-link.tsx @@ -1,32 +1,103 @@ -import { Component, Event, EventEmitter, h, Prop } from '@stencil/core'; +import { Relation, Thing } from '@pod-os/core'; +import { Component, Event, EventEmitter, h, Prop, State } from '@stencil/core'; +import { ResourceAware, ResourceEventEmitter, subscribeResource } from '../events/ResourceAware'; @Component({ tag: 'pos-rich-link', shadow: true, styleUrl: 'pos-rich-link.css', }) -export class PosRichLink { - @Prop() uri: string; +export class PosRichLink implements ResourceAware { + /** + * Link will use this URI + */ + @Prop() uri?: string; + /** + * Link will be obtained by following the predicate with this URI forward from a resource + */ + @Prop() rel?: string; + /** + * Link will be obtained by following the predicate with this URI in reverse from a resource + */ + @Prop() rev?: string; @Event({ eventName: 'pod-os:link' }) linkEmitter: EventEmitter; + @Event({ eventName: 'pod-os:resource' }) + subscribeResource: ResourceEventEmitter; + + @Event({ eventName: 'pod-os:error' }) errorEmitter: EventEmitter; + + @State() link?: string; + @State() followPredicate: boolean = false; + @State() error: string = null; + + componentWillLoad() { + if (!this.uri) subscribeResource(this); + } + + receiveResource = (resource: Thing) => { + const addLink = (links: Relation[], resource: Thing, predicate: string, direction: string) => { + if (links.length == 0) { + this.error = 'No matching link found'; + this.errorEmitter.emit( + new Error(`pos-rich-link: No matching link found from ${resource.uri} ${direction}=${predicate}`), + ); + } else if (links[0].uris.length > 1) { + this.error = 'More than one matching link found'; + this.errorEmitter.emit( + new Error(`pos-rich-link: More than one matching link found from ${resource.uri} ${direction}=${predicate}`), + ); + } else { + this.link = links[0].uris[0]; + this.followPredicate = true; + } + }; + + if (this.rel) { + addLink(resource.relations(this.rel), resource, this.rel, 'rel'); + } else if (this.rev) { + addLink(resource.reverseRelations(this.rev), resource, this.rev, 'rev'); + } else { + this.link = resource.uri; + } + }; + render() { - return ( - -

- { - e.preventDefault(); - this.linkEmitter.emit(this.uri); - }} - > - - - {new URL(this.uri).host} - -

-
+ const content = (uri: string) => ( +

+ { + e.preventDefault(); + this.linkEmitter.emit(uri); + }} + > + + + {new URL(uri).host} + +

); + + if (this.error) { + return this.error; + } else if (this.followPredicate) { + return ( + + {content(this.link)} + + ); + } else if (this.link) { + return content(this.link); + } else if (this.uri) { + return ( + + {content(this.uri)} + + ); + } else { + return null; + } } } diff --git a/storybook/stories/navigation/0_pos-rich-link.stories.mdx b/storybook/stories/navigation/0_pos-rich-link.stories.mdx index 201d3acc..d35b3c82 100644 --- a/storybook/stories/navigation/0_pos-rich-link.stories.mdx +++ b/storybook/stories/navigation/0_pos-rich-link.stories.mdx @@ -1,35 +1,84 @@ -import { - html -} from "lit-html"; +import { html } from "lit-html"; -import { - Canvas, - Meta, - Story -} from '@storybook/addon-docs/blocks'; +import { Canvas, Meta, Story } from "@storybook/addon-docs/blocks"; - ## pos-rich-link -Links to the given URI and shows a label and description of the resource identified by that URI, if available. +Links to the given URI and shows a label and description of the resource +identified by that URI, if available. + + + + {({ uri }) => html` `} + + + +Link can be obtained by receiving a resource from an ancestor element + + + + {({ uri }) => html` + + + + `} + + + +Link can be obtained by following the predicate forward from a resource + + + + {({ uri }) => html` + + + + `} + + + +Link can be obtained by following a predicate in reverse from a resource + + + + {({ uri }) => html` + + + + `} + + + +Error is shown if there is no matching link + + + + {({ uri }) => html` + + + + `} + + + +Error is shown if there is more than one link - - - {({uri}) => html` - + + + {({ uri }) => html` + + + `}