@panoramax/web-viewer 4.0.2-develop-9b499e28 → 4.0.2-develop-e389d775

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/build/index.css +1 -1
  3. package/build/index.css.map +1 -1
  4. package/build/index.js +274 -59
  5. package/build/index.js.map +1 -1
  6. package/config/jest/mocks.js +5 -0
  7. package/docs/reference/components/core/PhotoViewer.md +1 -0
  8. package/docs/reference/components/core/Viewer.md +1 -0
  9. package/docs/reference/components/menus/AnnotationsList.md +16 -0
  10. package/docs/reference/components/ui/HashTags.md +15 -0
  11. package/docs/reference/components/ui/ListItem.md +38 -0
  12. package/docs/reference/components/ui/Photo.md +53 -1
  13. package/docs/reference/components/ui/SemanticsTable.md +32 -0
  14. package/docs/reference/utils/PresetsManager.md +35 -0
  15. package/docs/reference.md +4 -0
  16. package/mkdocs.yml +4 -0
  17. package/package.json +2 -1
  18. package/src/components/core/Basic.css +2 -0
  19. package/src/components/core/PhotoViewer.js +11 -0
  20. package/src/components/core/Viewer.js +7 -0
  21. package/src/components/layout/Tabs.js +1 -1
  22. package/src/components/menus/AnnotationsList.js +146 -0
  23. package/src/components/menus/PictureLegend.js +2 -4
  24. package/src/components/menus/PictureMetadata.js +66 -2
  25. package/src/components/menus/index.js +1 -0
  26. package/src/components/styles.js +34 -0
  27. package/src/components/ui/HashTags.js +98 -0
  28. package/src/components/ui/ListItem.js +83 -0
  29. package/src/components/ui/Photo.js +137 -0
  30. package/src/components/ui/SemanticsTable.js +87 -0
  31. package/src/components/ui/index.js +3 -0
  32. package/src/img/osm.svg +49 -0
  33. package/src/img/wd.svg +1 -0
  34. package/src/translations/en.json +22 -0
  35. package/src/translations/fr.json +20 -0
  36. package/src/utils/PresetsManager.js +137 -0
  37. package/src/utils/index.js +3 -1
  38. package/src/utils/picture.js +28 -0
  39. package/src/utils/semantics.js +162 -0
  40. package/src/utils/services.js +38 -0
  41. package/src/utils/widgets.js +18 -1
  42. package/tests/components/core/__snapshots__/PhotoViewer.test.js.snap +10 -0
  43. package/tests/components/core/__snapshots__/Viewer.test.js.snap +10 -0
  44. package/tests/utils/PresetsManager.test.js +123 -0
  45. package/tests/utils/semantics.test.js +125 -0
@@ -9,7 +9,10 @@ import { faImages } from "@fortawesome/free-solid-svg-icons/faImages";
9
9
  import { faScroll } from "@fortawesome/free-solid-svg-icons/faScroll";
10
10
  import { faQuestion } from "@fortawesome/free-solid-svg-icons/faQuestion";
11
11
  import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
12
- import { titles, textarea } from "../styles";
12
+ import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
13
+ import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
14
+ import { faTags } from "@fortawesome/free-solid-svg-icons/faTags";
15
+ import { faSvg, titles, textarea, hidden } from "../styles";
13
16
  import { createWebComp } from "../../utils/widgets";
14
17
  import { getGPSPrecision } from "../../utils/picture";
15
18
  import {
@@ -31,7 +34,7 @@ const missing = () => fa(faQuestion, {styles: {height: "16px"}});
31
34
  */
32
35
  export default class PictureMetadata extends LitElement {
33
36
  /** @private */
34
- static styles = [ titles, textarea, css`
37
+ static styles = [ faSvg, titles, textarea, hidden, css`
35
38
  div[slot="content"] {
36
39
  padding: 5px 10px;
37
40
  background-color: #ededed;
@@ -56,13 +59,24 @@ export default class PictureMetadata extends LitElement {
56
59
  .data-block div {
57
60
  font-size: 0.8em;
58
61
  }
62
+
63
+ pnx-semantics-table {
64
+ overflow-x: auto;
65
+ display: block;
66
+ }
59
67
  ` ];
60
68
 
61
69
  /** @private */
62
70
  static properties = {
63
71
  _meta: {state: true},
72
+ _semanticsPicShowAll: {state: true},
64
73
  };
65
74
 
75
+ constructor() {
76
+ super();
77
+ this._semanticsPicShowAll = false;
78
+ }
79
+
66
80
  /** @private */
67
81
  connectedCallback() {
68
82
  super.connectedCallback();
@@ -205,6 +219,51 @@ export default class PictureMetadata extends LitElement {
205
219
  ];
206
220
  }
207
221
 
222
+ // Semantics data
223
+ const hasSemantics = (
224
+ (this._meta.properties.semantics || []).length > 0
225
+ || (this._meta.properties.annotations || []).length > 0
226
+ );
227
+ let semanticsData = [];
228
+ if(hasSemantics) {
229
+ // Full list of picture tags
230
+ semanticsData.push({
231
+ title: this._parent?._t.pnx.semantics_tags_picture,
232
+ style: "width: 100%",
233
+ content: html`${this._meta.properties.semantics?.length > 0
234
+ ? html`
235
+ <pnx-button
236
+ kind="outline"
237
+ size="sm"
238
+ style="width: 100%"
239
+ @click=${() => this._semanticsPicShowAll = !this._semanticsPicShowAll}
240
+ >
241
+ ${this._semanticsPicShowAll ? fa(faChevronUp) : fa(faChevronDown)}
242
+ ${this._semanticsPicShowAll ? this._parent?._t.pnx.semantics_hide_all_tags : this._parent?._t.pnx.semantics_show_all_tags}
243
+ </pnx-button>
244
+ <pnx-semantics-table
245
+ ._t=${this._parent?._t}
246
+ .source=${this._meta.properties}
247
+ style="margin-top: 5px"
248
+ class=${this._semanticsPicShowAll ? "":"pnx-hidden"}
249
+ />
250
+ `
251
+ : this._parent?._t.pnx.semantics_tags_picture_none
252
+ }`
253
+ });
254
+
255
+ // Annotations (features in picture)
256
+ semanticsData.push({
257
+ title: this._parent?._t.pnx.semantics_features,
258
+ style: "width: 100%",
259
+ content: html`
260
+ ${this._meta.properties.annotations?.length > 0
261
+ ? html`<pnx-annotations-list ._parent=${this._parent} />`
262
+ : this._parent?._t.pnx.semantics_features_none}
263
+ `
264
+ });
265
+ }
266
+
208
267
  // EXIF data
209
268
  const exifData = Object.entries(this._meta.properties.exif)
210
269
  .sort()
@@ -327,6 +386,11 @@ export default class PictureMetadata extends LitElement {
327
386
  qualityData
328
387
  ) : nothing}
329
388
 
389
+ ${hasSemantics ? this._toTab( // Semantics
390
+ html`${fa(faTags)} ${this._parent?._t.pnx.semantics_title}`,
391
+ semanticsData
392
+ ) : nothing}
393
+
330
394
  ${this._meta.properties?.exif ? this._toTab( // EXIF
331
395
  html`${fa(faScroll)} ${this._parent?._t.pnx.metadata_exif}`,
332
396
  exifData
@@ -3,6 +3,7 @@
3
3
  * @module Panoramax:components:menus
4
4
  */
5
5
 
6
+ export {default as AnnotationsList} from "./AnnotationsList";
6
7
  export {default as MapFilters} from "./MapFilters";
7
8
  export {default as MapLayers} from "./MapLayers";
8
9
  export {default as MapBackground} from "./MapBackground";
@@ -27,6 +27,11 @@ export const panel = css`
27
27
  }
28
28
  `;
29
29
 
30
+ // Hidden
31
+ export const hidden = css`
32
+ .pnx-hidden { display: none !important; }
33
+ `;
34
+
30
35
  // Font Awesome SVG
31
36
  export const faSvg = css`
32
37
  .svg-inline--fa {
@@ -442,3 +447,32 @@ export const noprint = css`
442
447
  .pnx-print-hidden { display: none !important; }
443
448
  }
444
449
  `;
450
+
451
+ // Data table
452
+ export const table = css`
453
+ table {
454
+ border-collapse: collapse;
455
+ font-size: 0.9rem;
456
+ width: 100%;
457
+ max-width: 100%;
458
+ font-family: var(--font-family);
459
+ background: var(--white);
460
+ }
461
+
462
+ th, td {
463
+ padding: 10px;
464
+ text-align: left;
465
+ border-bottom: 1px solid #ddd;
466
+ }
467
+
468
+ th { font-weight: 600; }
469
+ `;
470
+
471
+ // Iconify icons
472
+ export const iconify = css`
473
+ iconify-icon {
474
+ display: inline-block;
475
+ width: 1em;
476
+ height: 1em;
477
+ }
478
+ `;
@@ -0,0 +1,98 @@
1
+ import { LitElement, html, css, nothing } from "lit";
2
+ import { getHashTags, hasAnnotations } from "../../utils/picture";
3
+ import { fa } from "../../utils/widgets";
4
+ import { faDrawPolygon } from "@fortawesome/free-solid-svg-icons/faDrawPolygon";
5
+
6
+ /**
7
+ * HashTags component shows the list of hashtags associated to a picture.
8
+ * @class Panoramax.components.ui.HashTags
9
+ * @element pnx-hashtags
10
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
11
+ * @example
12
+ * ```html
13
+ * <pnx-hashtags ._parent=${viewer} />
14
+ * ```
15
+ */
16
+ export default class HashTags extends LitElement {
17
+ /** @private */
18
+ static styles = css`
19
+ div {
20
+ background: linear-gradient(to bottom left, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.2));
21
+ margin-top: -10px;
22
+ margin-right: -10px;
23
+ padding: 5px 10px;
24
+ border-bottom-left-radius: 10px;
25
+ font-family: var(--font-family);
26
+ color: white;
27
+ font-size: 0.8em;
28
+ }
29
+ `;
30
+
31
+ /** @private */
32
+ static properties = {
33
+ _tags: {state: true},
34
+ _visible: {state: true},
35
+ _annotationsToggled: {state: true},
36
+ };
37
+
38
+ constructor() {
39
+ super();
40
+ this._tags = [];
41
+ this._visible = false;
42
+ this._annotationsToggled = false;
43
+ }
44
+
45
+ /** @private */
46
+ connectedCallback() {
47
+ super.connectedCallback();
48
+
49
+ this._parent.onceReady().then(() => {
50
+ this._tags = getHashTags(this._parent?.psv?.getPictureMetadata?.());
51
+
52
+ // Component visibility : only if seen at least one pic with semantics
53
+ if(
54
+ !this._visible && (
55
+ this._tags.length > 0
56
+ || hasAnnotations(this._parent?.psv?.getPictureMetadata?.())
57
+ )
58
+ ) {
59
+ this._visible = true;
60
+ this._parent.psv.toggleAllAnnotations(true);
61
+ }
62
+
63
+ this._parent.psv.addEventListener("picture-loaded", () => {
64
+ this._tags = getHashTags(this._parent.psv.getPictureMetadata());
65
+ if(
66
+ !this._visible && (
67
+ this._tags.length > 0 ||
68
+ hasAnnotations(this._parent.psv.getPictureMetadata())
69
+ )
70
+ ) {
71
+ this._visible = true;
72
+ this._parent.psv.toggleAllAnnotations(true);
73
+ }
74
+ });
75
+
76
+ this._annotationsToggled = this._parent.psv.areAnnotationsVisible() || false;
77
+ this._parent.psv.addEventListener("annotations-toggled", e => {
78
+ this._annotationsToggled = e.detail.visible;
79
+ });
80
+ });
81
+ }
82
+
83
+ /** @private */
84
+ render() {
85
+ return this._visible ? html`<div>
86
+ ${this._tags.join(" ")}
87
+ <pnx-button
88
+ kind="outline"
89
+ style="vertical-align: middle"
90
+ title=${this._annotationsToggled ? this._parent._t?.pnx.semantics_hide_annotations : this._parent._t?.pnx.semantics_show_annotations}
91
+ active=${this._annotationsToggled ? "" : nothing}
92
+ @click=${() => this._parent.psv.toggleAllAnnotations(!this._annotationsToggled)}
93
+ >${fa(faDrawPolygon)}</pnx-button>
94
+ </div>` : nothing;
95
+ }
96
+ }
97
+
98
+ customElements.define("pnx-hashtags", HashTags);
@@ -0,0 +1,83 @@
1
+ import { LitElement, html, css } from "lit";
2
+
3
+ /**
4
+ * ListItem is a list entry, in a Material Design fashion.
5
+ * @class Panoramax.components.ui.ListItem
6
+ * @element pnx-list-item
7
+ * @slot `icon` The left icon (symbol for this item)
8
+ * @slot `action` The right icon (symbol for an interactive action)
9
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
10
+ * @example
11
+ * ```html
12
+ * <pnx-list-item title="My feature" subtitle="It is very cool">
13
+ * <img src="..." slot="icon" />
14
+ * <img src="..." slot="action" />
15
+ * </pnx-list-item>
16
+ * ```
17
+ */
18
+ export default class ListItem extends LitElement {
19
+ /** @private */
20
+ static styles = css`
21
+ .list-item {
22
+ display: flex;
23
+ align-items: center;
24
+ padding: 8px 16px;
25
+ cursor: pointer;
26
+ border-bottom: 1px solid #ddd;
27
+ font-family: var(--font-family);
28
+ background: var(--white);
29
+ min-height: 50px;
30
+ box-sizing: border-box;
31
+ }
32
+ .list-item:hover { background: var(--widget-bg-hover); }
33
+ .icon {
34
+ margin-right: 16px;
35
+ display: flex;
36
+ align-items: center;
37
+ gap: 10px;
38
+ }
39
+ .action { margin-left: 16px; }
40
+ .content {
41
+ flex: 1;
42
+ }
43
+ .title {
44
+ font-weight: 600;
45
+ }
46
+ .subtitle {
47
+ font-size: 0.9em;
48
+ color: var(--grey-dark);
49
+ }
50
+ `;
51
+
52
+ /**
53
+ * Component properties.
54
+ * @memberof Panoramax.components.ui.ListItem#
55
+ * @type {Object}
56
+ * @property {string} title The item title
57
+ * @property {string} [subtitle] The item subtitle
58
+ */
59
+ static properties = {
60
+ title: { type: String },
61
+ subtitle: { type: String },
62
+ };
63
+
64
+ /** @private */
65
+ render() {
66
+ return html`
67
+ <div class="list-item">
68
+ <div class="icon">
69
+ <slot name="icon"></slot>
70
+ </div>
71
+ <div class="content">
72
+ <div class="title">${this.title}</div>
73
+ <div class="subtitle">${this.subtitle}</div>
74
+ </div>
75
+ <div class="action">
76
+ <slot name="action"></slot>
77
+ </div>
78
+ </div>
79
+ `;
80
+ }
81
+ }
82
+
83
+ customElements.define("pnx-list-item", ListItem);
@@ -14,6 +14,7 @@ import "@photo-sphere-viewer/gallery-plugin/index.css";
14
14
  import "@photo-sphere-viewer/markers-plugin/index.css";
15
15
  import { Viewer as PSViewer } from "@photo-sphere-viewer/core";
16
16
  import { VirtualTourPlugin } from "@photo-sphere-viewer/virtual-tour-plugin";
17
+ import { MarkersPlugin } from "@photo-sphere-viewer/markers-plugin";
17
18
  import PhotoAdapter from "../../utils/PhotoAdapter";
18
19
 
19
20
 
@@ -77,6 +78,7 @@ PSViewer.useNewAnglesOrder = true;
77
78
  * @fires Panoramax.components.ui.Photo#sequence-stopped
78
79
  * @fires Panoramax.components.ui.Photo#pictures-navigation-changed
79
80
  * @fires Panoramax.components.ui.Photo#ready
81
+ * @fires Panoramax.components.ui.Photo#annotations-toggled
80
82
  * @example
81
83
  * const psv = new Panoramax.components.ui.Photo(viewer, psvNode, {transitionDuration: 500})
82
84
  */
@@ -112,6 +114,7 @@ export default class Photo extends PSViewer {
112
114
  linkOverlapAngle: Math.PI / 6,
113
115
  }
114
116
  }],
117
+ [MarkersPlugin, {}],
115
118
  ],
116
119
  ...options
117
120
  });
@@ -126,6 +129,8 @@ export default class Photo extends PSViewer {
126
129
  this._myVTour.config.transitionOptions = this._psvNodeTransition.bind(this);
127
130
  this._clearArrows = this._myVTour.arrowsRenderer.clear.bind(this._myVTour.arrowsRenderer);
128
131
  this._myVTour.arrowsRenderer.clear = () => {};
132
+ this._myMarkers = this.getPlugin(MarkersPlugin);
133
+ this._annotationsVisible = false;
129
134
  this._sequencePlaying = false;
130
135
  this._picturesNavigation = this._options.picturesNavigation || "any";
131
136
 
@@ -463,6 +468,11 @@ export default class Photo extends PSViewer {
463
468
  else {
464
469
  this.setOption("downloadUrl", null);
465
470
  }
471
+
472
+ // Show annotations
473
+ if(this._annotationsVisible) {
474
+ this.toggleAllAnnotations(true);
475
+ }
466
476
  }
467
477
 
468
478
  this._onTilesStartLoading();
@@ -888,6 +898,133 @@ export default class Photo extends PSViewer {
888
898
  }
889
899
  }
890
900
 
901
+ /**
902
+ * Are there any picture annotations shown ?
903
+ * @returns {boolean} True if annotations are visible
904
+ * @memberof Panoramax.components.ui.Photo#
905
+ */
906
+ areAnnotationsVisible() {
907
+ return this._annotationsVisible;
908
+ }
909
+
910
+ /**
911
+ * Toggle visibility of picture annotations
912
+ * @param {boolean} visible True to make visible, false to hide
913
+ * @memberof Panoramax.components.ui.Photo#
914
+ */
915
+ toggleAllAnnotations(visible) {
916
+ const meta = this.getPictureMetadata();
917
+ if(!meta) {
918
+ this._myMarkers.clearMarkers();
919
+ throw new Error("No picture currently selected");
920
+ }
921
+
922
+ if(!visible) { this._myMarkers.clearMarkers(); }
923
+ else {
924
+ let annotations = meta.properties.annotations;
925
+ if(annotations.length === 0) { console.warn("No annotation available on picture", meta.id); }
926
+
927
+ const picBData = this.state.textureData.panoData?.baseData;
928
+
929
+ annotations = annotations.map(a => {
930
+ // Get original HD picture dimensions
931
+ const origPicDim = this.getPictureMetadata().properties["pers:interior_orientation"].sensor_array_dimensions;
932
+
933
+ if(!origPicDim) {
934
+ console.warn("Picture lacks pers:interior_orientation.sensor_array_dimensions property, can't compute marker");
935
+ return null;
936
+ }
937
+
938
+ const shape = a.shape.coordinates.map(c1 => c1.map(c2 => {
939
+ // Px coordinates in shown image
940
+ const pxShown = [
941
+ c2[0] * picBData.croppedWidth / origPicDim[0],
942
+ c2[1] * picBData.croppedHeight / origPicDim[1]
943
+ ];
944
+
945
+ // Coords in %
946
+ const pct = [
947
+ (picBData.croppedX + pxShown[0]) / picBData.fullWidth,
948
+ (picBData.croppedY + pxShown[1]) / picBData.fullHeight
949
+ ];
950
+
951
+ // Coords in radians as center offset
952
+ return [
953
+ (pct[0] - 0.5) * 2 * Math.PI,
954
+ (0.5 - pct[1]) * Math.PI,
955
+ ];
956
+ }));
957
+
958
+ return {
959
+ id: `annotation-${a.id}`,
960
+ polygon: shape,
961
+ data: { id: a.id },
962
+ svgStyle: {
963
+ stroke: "var(--orange)",
964
+ strokeWidth: "3px",
965
+ fill: "var(--orange-transparent)",
966
+ cursor: "pointer",
967
+ },
968
+ };
969
+ });
970
+ this._myMarkers.setMarkers(annotations);
971
+ }
972
+
973
+ const sendEvent = this._annotationsVisible != visible;
974
+ this._annotationsVisible = visible;
975
+
976
+ if(sendEvent) {
977
+ /**
978
+ * Event for pictures annotation visibility change
979
+ * @event Panoramax.components.ui.Photo#annotations-toggled
980
+ * @type {CustomEvent}
981
+ * @property {boolean} detail.visible True if they are visible
982
+ */
983
+ this.dispatchEvent(new CustomEvent("annotations-toggled", { detail: { visible } }));
984
+ }
985
+ }
986
+
987
+ /**
988
+ * Make view centered and zoomed on given annotation.
989
+ * @param {string} id The annotation UUID
990
+ * @memberof Panoramax.components.ui.Photo#
991
+ */
992
+ focusOnAnnotation(id) {
993
+ if(!this.areAnnotationsVisible()) { this.toggleAllAnnotations(true); }
994
+ this.unfocusAnnotation();
995
+
996
+ const annotationId = `annotation-${id}`;
997
+ this._myMarkers.updateMarker({
998
+ id: annotationId,
999
+ svgStyle: {
1000
+ stroke: "var(--red)",
1001
+ strokeWidth: "3px",
1002
+ fill: "var(--red-transparent)",
1003
+ }
1004
+ });
1005
+ this._myMarkers.gotoMarker(annotationId, 0);
1006
+ this.zoom(65);
1007
+ }
1008
+
1009
+ /**
1010
+ * Remove focus styling on annotations.
1011
+ * @memberof Panoramax.components.ui.Photo#
1012
+ */
1013
+ unfocusAnnotation() {
1014
+ Object.keys(this._myMarkers.markers)
1015
+ .filter(id => id.startsWith("annotation-"))
1016
+ .forEach(id => {
1017
+ this._myMarkers.updateMarker({
1018
+ id,
1019
+ svgStyle: {
1020
+ stroke: "var(--orange)",
1021
+ strokeWidth: "3px",
1022
+ fill: "var(--orange-transparent)",
1023
+ }
1024
+ });
1025
+ });
1026
+ }
1027
+
891
1028
  /**
892
1029
  * Force reload of texture and tiles.
893
1030
  * @memberof Panoramax.components.ui.Photo#
@@ -0,0 +1,87 @@
1
+ import { LitElement, html, css, nothing } from "lit";
2
+ import { table } from "../styles";
3
+ import { groupByPrefix } from "../../utils/semantics";
4
+
5
+ /**
6
+ * Semantics Tables display all semantics tags from either a picture or an annotation.
7
+ *
8
+ * @class Panoramax.components.ui.SemanticsTable
9
+ * @element pnx-semantics-table
10
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
11
+ * @example
12
+ * ```html
13
+ * <pnx-semantics-table source=${picture.properties} ._t=${viewer._t} />
14
+ * ```
15
+ */
16
+ export default class SemanticsTable extends LitElement {
17
+ /** @private */
18
+ static styles = [ table, css`
19
+ th:first-child, td:first-child { width: 30%; }
20
+ td { vertical-align: top; }
21
+
22
+ td small { color: var(--grey-semi-dark); }
23
+ .qualifiers { margin-top: 5px; }
24
+ .qualifiers > b { margin: 8px 0; }
25
+ .qualifiers ul {
26
+ list-style: none;
27
+ margin: 0px 0px 0px 15px;
28
+ padding: 0;
29
+ }
30
+
31
+ img.prefix-logo {
32
+ max-width: 15px;
33
+ max-height: 15px;
34
+ aspect-ratio: 1;
35
+ vertical-align: middle;
36
+ }
37
+ ` ];
38
+
39
+ /**
40
+ * Component properties.
41
+ * @memberof Panoramax.components.ui.SemanticsTable#
42
+ * @type {Object}
43
+ * @property {object} [source] The picture or annotation feature (having a `semantics` property)
44
+ */
45
+ static properties = {
46
+ source: {type: Object},
47
+ };
48
+
49
+ render() {
50
+ /* eslint-disable indent */
51
+ if(!this.source) { return nothing; }
52
+
53
+ const groups = groupByPrefix(this.source.semantics);
54
+
55
+ return html`<table>
56
+ <thead>
57
+ <tr><th>${this._t.pnx.semantics_key}</th><th>${this._t.pnx.semantics_value}</th></tr>
58
+ </thead>
59
+ <tbody>
60
+ ${groups.map(g => g.tags.map(tag => html`
61
+ <tr>
62
+ <td>
63
+ ${g.prefix != ""
64
+ ? (g.logo
65
+ ? html`<img src=${g.logo} class="prefix-logo" alt=${g.prefix} title=${g.title} />`
66
+ : html`<small>(${g.prefix})</small>`)
67
+ : nothing
68
+ }
69
+ ${g.key_transform ? g.key_transform(tag, this._t) : tag.key}
70
+ </td>
71
+ <td>
72
+ ${g.value_transform ? g.value_transform(tag, this._t) : tag.value}
73
+ ${tag.qualifiers ? html`<div class="qualifiers">
74
+ <b>${this._t.pnx.semantics_qualifiers}</b>
75
+ <ul>
76
+ ${tag.qualifiers.map(q => html`<li>${q.subkey} = ${q.value}</li>`)}
77
+ </ul>
78
+ </div>` : nothing}
79
+ </td>
80
+ </tr>
81
+ `))}
82
+ </tbody>
83
+ </table>`;
84
+ }
85
+ }
86
+
87
+ customElements.define("pnx-semantics-table", SemanticsTable);
@@ -7,8 +7,10 @@ export {default as ButtonGroup} from "./ButtonGroup";
7
7
  export {default as Button} from "./Button";
8
8
  export {default as CopyButton} from "./CopyButton";
9
9
  export {default as Grade} from "./Grade";
10
+ export {default as HashTags} from "./HashTags";
10
11
  export {default as LinkButton} from "./LinkButton";
11
12
  export {default as ListGroup} from "./ListGroup";
13
+ export {default as ListItem} from "./ListItem";
12
14
  export {default as Loader} from "./Loader";
13
15
  export {default as Map} from "./Map";
14
16
  export {default as MapMore} from "./MapMore";
@@ -17,6 +19,7 @@ export {default as Popup} from "./Popup";
17
19
  export {default as ProgressBar} from "./ProgressBar";
18
20
  export {default as QualityScore} from "./QualityScore";
19
21
  export {default as SearchBar} from "./SearchBar";
22
+ export {default as SemanticsTable} from "./SemanticsTable";
20
23
  export {default as TogglableGroup} from "./TogglableGroup";
21
24
  import * as widgets from "./widgets";
22
25
  export {widgets};
@@ -0,0 +1,49 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+ <svg
5
+ width="240mm"
6
+ height="239.99869mm"
7
+ viewBox="0 0 240 239.99869"
8
+ id="svg6743"
9
+ version="1.1"
10
+ xmlns="http://www.w3.org/2000/svg"
11
+ xmlns:svg="http://www.w3.org/2000/svg">
12
+ <defs
13
+ id="defs1" />
14
+ <g
15
+ id="layer1"
16
+ transform="translate(-30,-27.000656)">
17
+ <g
18
+ id="g7426"
19
+ transform="translate(322.142,-53.972906)">
20
+ <path
21
+ style="display:inline;fill:#76c551;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.61532"
22
+ id="path33449"
23
+ d="m -81.063095,208.94609 c -11.906339,9.92421 -27.194885,15.8987 -43.860635,15.8987 -4.95854,0 -9.78728,-0.55158 -14.45059,-1.55504 l -4.79842,5.8446 c -10.66449,-2.85736 -20.38429,-7.94043 -28.6326,-14.74578 l -12.89559,12.92212 1.58177,9.54347 -47.96347,47.98965 4.63827,1.7685 45.04032,-17.15658 45.0402,17.15658 45.040312,-17.15658 17.158544,-45.03976 z" />
24
+ <path
25
+ d="m -272.48495,89.294626 17.1585,45.040274 -17.1585,45.03993 17.1585,45.03972 -17.1585,45.03977 10.53634,4.02267 57.2385,-57.21102 10.26764,1.58169 12.46673,-12.46669 c -7.35519,-8.52221 -12.86173,-18.6902 -15.87027,-29.92032 l 5.8446,-4.79842 c -1.00531,-4.66323 -1.55503,-9.49189 -1.55503,-14.45044 0,-24.76984 13.21043,-46.53472 32.94893,-58.606534 l -21.79693,-8.31063 -45.04019,17.158544 z"
26
+ id="path33447"
27
+ style="display:inline;fill:#76c551;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.61532" />
28
+ <path
29
+ style="display:inline;fill:#59a336;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.61532"
30
+ id="path33451"
31
+ d="m -81.063095,208.94609 c -11.906339,9.92421 -27.194885,15.8987 -43.860635,15.8987 -4.95854,0 -9.78728,-0.55158 -14.45059,-1.55504 l -4.79842,5.8446 -28.67744,-15.11228 -12.85075,13.28862 1.58177,9.54347 46.85284,7.05799 -0.0982,42.70016 45.040313,-17.15658 17.158544,-45.03976 z" />
32
+ <ellipse
33
+ style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:12.7048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
34
+ id="ellipse33453"
35
+ cx="-122.83165"
36
+ cy="155.70955"
37
+ rx="59.801929"
38
+ ry="60.929199" />
39
+ <path
40
+ style="opacity:1;fill:#cccccc;fill-opacity:1;stroke:none;stroke-width:12.7048;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none"
41
+ d="m -70.867543,179.77128 c -4.021615,-66.77094 -70.182367,-83.311142 -110.499567,-45.77004 21.03677,-28.56709 48.05279,-46.045197 78.56638,-33.40602 30.51362,12.63913 41.797793,45.98173 31.933187,79.17606 z"
42
+ id="path33455" />
43
+ <path
44
+ id="path33457"
45
+ d="m -73.395575,102.22726 c -28.338185,-28.338264 -74.283665,-28.338264 -102.621895,0 -17.93779,17.93776 -24.48523,42.91327 -19.71413,66.02883 l -11.27485,8.16937 c 5.13477,13.61756 12.1317,21.8574 16.91241,26.2963 l -10.22839,10.22834 h -10.80252 l -81.01705,81.01654 27.00583,27.00561 81.01701,-81.01683 v -10.80225 l 10.19464,-10.19425 c 4.56162,4.75244 12.69997,11.38138 25.75686,16.30469 l 7.86543,-10.90386 c 23.35858,5.12197 48.742245,-1.34701 66.906498,-19.5115 28.338386,-28.33732 28.33819,-74.28276 1.57e-4,-102.62099 z m -12.962835,12.96296 c 21.179095,21.17921 21.179095,55.51685 0,76.69579 -21.17902,21.17897 -55.51721,21.17897 -76.6963,0 -21.17898,-21.17894 -21.17898,-55.51658 0,-76.69579 21.17929,-21.179216 55.51728,-21.179216 76.6963,0 z"
46
+ style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:#2f2f2f;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:7.76059;marker:none;enable-background:accumulate" />
47
+ </g>
48
+ </g>
49
+ </svg>
package/src/img/wd.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 18"><path d="M2.5 1h1v16h-1zm2 0h3v16h-3zm4 0h3v16h-3z" fill="#900"/><path d="M12.5 1h1v16h-1zm2 0h1v16h-1zm12 0h1v16h-1zm2 0h1v16h-1z" fill="#396"/><path d="M16.5 1h3v16h-3zm4 0h1v16h-1zm2 0h3v16h-3z" fill="#069"/></svg>