@panoramax/web-viewer 5.0.0-develop-d7206514 → 5.0.0-develop-d26305dd

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 (152) hide show
  1. package/build/bundle.cjs +3399 -0
  2. package/build/bundle.cjs.map +1 -0
  3. package/build/bundle_photoviewer.cjs +2510 -0
  4. package/build/bundle_photoviewer.cjs.map +1 -0
  5. package/build/cjs/index.js +1 -1
  6. package/build/cjs/index_photoviewer.js +1 -1
  7. package/build/components/core/Basic.css +56 -0
  8. package/build/components/core/Basic.js +378 -0
  9. package/build/components/core/CoverageMap.css +10 -0
  10. package/build/components/core/CoverageMap.js +169 -0
  11. package/build/components/core/Editor.css +33 -0
  12. package/build/components/core/Editor.js +398 -0
  13. package/build/components/core/PhotoViewer.css +70 -0
  14. package/build/components/core/PhotoViewer.js +650 -0
  15. package/build/components/core/Viewer.css +130 -0
  16. package/build/components/core/Viewer.js +711 -0
  17. package/build/components/core/index.js +10 -0
  18. package/build/components/index.js +11 -0
  19. package/build/components/index_photoviewer.js +6 -0
  20. package/build/components/layout/BottomDrawer.js +258 -0
  21. package/build/components/layout/CorneredGrid.js +143 -0
  22. package/build/components/layout/Mini.js +121 -0
  23. package/build/components/layout/Tabs.js +140 -0
  24. package/build/components/layout/index.js +9 -0
  25. package/build/components/menus/LocationPrecisionDoc.js +42 -0
  26. package/build/components/menus/MapBackground.js +110 -0
  27. package/build/components/menus/MapFilters.js +567 -0
  28. package/build/components/menus/MapLayers.js +238 -0
  29. package/build/components/menus/MapLegend.js +68 -0
  30. package/build/components/menus/MiniPictureLegend.js +73 -0
  31. package/build/components/menus/PictureLegend.js +379 -0
  32. package/build/components/menus/PictureMetadata.js +380 -0
  33. package/build/components/menus/PlayerOptions.js +93 -0
  34. package/build/components/menus/QualityScoreDoc.js +42 -0
  35. package/build/components/menus/ReportForm.js +132 -0
  36. package/build/components/menus/SemanticsDoc.js +38 -0
  37. package/build/components/menus/SemanticsDownload.js +33 -0
  38. package/build/components/menus/SemanticsFilters.js +153 -0
  39. package/build/components/menus/SemanticsList.js +413 -0
  40. package/build/components/menus/SemanticsMetadata.js +368 -0
  41. package/build/components/menus/Share.js +105 -0
  42. package/build/components/menus/index.js +22 -0
  43. package/build/components/menus/index_photoviewer.js +11 -0
  44. package/build/components/styles.js +557 -0
  45. package/build/components/ui/AnnotationsSwitch.js +159 -0
  46. package/build/components/ui/Button.js +77 -0
  47. package/build/components/ui/ButtonGroup.css +59 -0
  48. package/build/components/ui/ButtonGroup.js +69 -0
  49. package/build/components/ui/CopyButton.js +110 -0
  50. package/build/components/ui/Grade.js +54 -0
  51. package/build/components/ui/GradeFilter.js +122 -0
  52. package/build/components/ui/IconSwitch.js +193 -0
  53. package/build/components/ui/LinkButton.js +67 -0
  54. package/build/components/ui/ListGroup.js +66 -0
  55. package/build/components/ui/ListItem.js +90 -0
  56. package/build/components/ui/Loader.js +203 -0
  57. package/build/components/ui/Map.css +63 -0
  58. package/build/components/ui/Map.js +853 -0
  59. package/build/components/ui/MapMore.js +175 -0
  60. package/build/components/ui/Photo.css +50 -0
  61. package/build/components/ui/Photo.js +1502 -0
  62. package/build/components/ui/Popup.js +145 -0
  63. package/build/components/ui/ProgressBar.js +104 -0
  64. package/build/components/ui/QualityScore.js +147 -0
  65. package/build/components/ui/SearchBar.js +374 -0
  66. package/build/components/ui/SemanticsEditor.js +191 -0
  67. package/build/components/ui/SemanticsTable.js +88 -0
  68. package/build/components/ui/Switch.js +139 -0
  69. package/build/components/ui/TogglableGroup.js +157 -0
  70. package/build/components/ui/index.js +29 -0
  71. package/build/components/ui/index_photoviewer.js +21 -0
  72. package/build/components/ui/widgets/CopyCoordinates.js +75 -0
  73. package/build/components/ui/widgets/GeoSearch.css +21 -0
  74. package/build/components/ui/widgets/GeoSearch.js +150 -0
  75. package/build/components/ui/widgets/Legend.js +190 -0
  76. package/build/components/ui/widgets/LevelSelect.css +51 -0
  77. package/build/components/ui/widgets/LevelSelect.js +143 -0
  78. package/build/components/ui/widgets/MapFiltersButton.js +114 -0
  79. package/build/components/ui/widgets/MapLayersButton.js +79 -0
  80. package/build/components/ui/widgets/OSMEditors.js +155 -0
  81. package/build/components/ui/widgets/PictureLegendActions.js +99 -0
  82. package/build/components/ui/widgets/Player.css +7 -0
  83. package/build/components/ui/widgets/Player.js +154 -0
  84. package/build/components/ui/widgets/SemanticsFiltersButton.js +65 -0
  85. package/build/components/ui/widgets/Zoom.js +84 -0
  86. package/build/components/ui/widgets/index.js +16 -0
  87. package/build/components/ui/widgets/index_photoviewer.js +7 -0
  88. package/build/esm/components/core/Basic.js +1 -1
  89. package/build/img/arrow_360.svg +14 -0
  90. package/build/img/arrow_flat.svg +11 -0
  91. package/build/img/arrow_triangle.svg +9 -0
  92. package/build/img/arrow_turn.svg +8 -0
  93. package/build/img/bg_aerial.jpg +0 -0
  94. package/build/img/bg_streets.jpg +0 -0
  95. package/build/img/loader_base.jpg +0 -0
  96. package/build/img/logo_dead.svg +91 -0
  97. package/build/img/marker.svg +17 -0
  98. package/build/img/marker_blue.svg +20 -0
  99. package/build/img/osm.svg +49 -0
  100. package/build/img/panoramax.svg +13 -0
  101. package/build/img/switch_big.svg +54 -0
  102. package/build/img/switch_mini.svg +48 -0
  103. package/build/img/wd.svg +1 -0
  104. package/build/index_photoviewer.js +4 -0
  105. package/build/package.json +148 -0
  106. package/build/servers.js +14 -0
  107. package/build/translations/ar.json +1 -0
  108. package/build/translations/be.json +257 -0
  109. package/build/translations/br.json +81 -0
  110. package/build/translations/cy.json +117 -0
  111. package/build/translations/da.json +300 -0
  112. package/build/translations/de.json +309 -0
  113. package/build/translations/en.json +294 -0
  114. package/build/translations/eo.json +235 -0
  115. package/build/translations/es.json +292 -0
  116. package/build/translations/fi.json +1 -0
  117. package/build/translations/fr.json +294 -0
  118. package/build/translations/hr.json +294 -0
  119. package/build/translations/hu.json +294 -0
  120. package/build/translations/it.json +306 -0
  121. package/build/translations/ja.json +182 -0
  122. package/build/translations/ko.json +1 -0
  123. package/build/translations/nl.json +305 -0
  124. package/build/translations/nn.json +1 -0
  125. package/build/translations/pl.json +169 -0
  126. package/build/translations/pt.json +296 -0
  127. package/build/translations/pt_BR.json +304 -0
  128. package/build/translations/sv.json +182 -0
  129. package/build/translations/ti.json +9 -0
  130. package/build/translations/tr.json +297 -0
  131. package/build/translations/uk.json +268 -0
  132. package/build/translations/zh_Hant.json +309 -0
  133. package/build/utils/API.js +928 -0
  134. package/build/utils/InitParameters.js +521 -0
  135. package/build/utils/MapStyleComposer.js +889 -0
  136. package/build/utils/PanoraMapProtocol.js +49 -0
  137. package/build/utils/PhotoAdapter.js +49 -0
  138. package/build/utils/PresetsManager.js +148 -0
  139. package/build/utils/SemanticsMapProtocol.js +144 -0
  140. package/build/utils/URLHandler.js +426 -0
  141. package/build/utils/geocoder.js +203 -0
  142. package/build/utils/i18n.js +128 -0
  143. package/build/utils/index.js +17 -0
  144. package/build/utils/index_photoviewer.js +14 -0
  145. package/build/utils/indoor.js +200 -0
  146. package/build/utils/map.js +788 -0
  147. package/build/utils/picture.js +507 -0
  148. package/build/utils/semantics.js +321 -0
  149. package/build/utils/services.js +148 -0
  150. package/build/utils/utils.js +433 -0
  151. package/build/utils/widgets.js +110 -0
  152. package/package.json +1 -1
@@ -0,0 +1,33 @@
1
+ import { LitElement, html } from "lit";
2
+ import { faSvg, titles } from "../styles.js";
3
+ import { fa } from "../../utils/widgets.js";
4
+ import { faDownload } from "@fortawesome/free-solid-svg-icons";
5
+
6
+ /**
7
+ * Semantics Download docs displays a synthetic summary of you can download semantics metadata.
8
+ * @class Panoramax.components.menus.SemanticsDownload
9
+ * @element pnx-semantics-download
10
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
11
+ * @example
12
+ * ```html
13
+ * <pnx-semantics-download ._t=${viewer._t} />
14
+ * ```
15
+ */
16
+ export default class SemanticsDownload extends LitElement {
17
+ static styles = [ titles, faSvg ];
18
+
19
+ render() {
20
+ return html`
21
+ <h4>${fa(faDownload)} ${this._t?.pnx.semantics_download_title}</h4>
22
+ <p>${this._t?.pnx.semantics_download_info}</p>
23
+ <pnx-link-button
24
+ href="https://docs.panoramax.fr/federated-catalog/data_export/"
25
+ target="_blank"
26
+ kind="outline"
27
+ style="width: 100%"
28
+ >${this._t?.pnx.semantics_download_cta}</pnx-link-button>
29
+ `;
30
+ }
31
+ }
32
+
33
+ customElements.define("pnx-semantics-download", SemanticsDownload);
@@ -0,0 +1,153 @@
1
+ import { LitElement, css, html } from "lit";
2
+ import { fa, onceParentAvailable } from "../../utils/widgets.js";
3
+ import { faDownload, faInfoCircle } from "@fortawesome/free-solid-svg-icons";
4
+ import { SEMANTICS_OVERLAY_MINZOOM } from "../../utils/SemanticsMapProtocol.js";
5
+
6
+ /**
7
+ * Semantics Filters menu allows user to show/hide specific semantic tags over the map.
8
+ * @class Panoramax.components.menus.SemanticsFilters
9
+ * @element pnx-map-semantics-filters-menu
10
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
11
+ * @fires Panoramax.components.menus.SemanticsFilters#change
12
+ * @example
13
+ * ```html
14
+ * <pnx-map-semantics-filters-menu _parent=${viewer} />
15
+ * ```
16
+ */
17
+ export default class SemanticsFilters extends LitElement {
18
+ /** @private */
19
+ static styles = css`
20
+ .pnx-tags-zoomin {
21
+ z-index: 131;
22
+ background-color: rgba(255,255,255,0.8);
23
+ text-align: center;
24
+ font-weight: 800;
25
+ position: absolute;
26
+ top: 0;
27
+ right: 0;
28
+ left: 0;
29
+ bottom: 0;
30
+ display: flex;
31
+ justify-content: center;
32
+ align-items: center;
33
+ border-radius: 25px;
34
+ }
35
+ .pnx-tags-zoomin.hidden {
36
+ display: none;
37
+ }
38
+ .pnx-tags-cta {
39
+ display: flex;
40
+ align-items: center;
41
+ justify-content: end;
42
+ }
43
+ pnx-switch {
44
+ display: block;
45
+ margin: 5px 0;
46
+ }
47
+ `;
48
+
49
+ /** @private */
50
+ static properties = {
51
+ _showZoomIn: {state: true},
52
+ _layers: {state: true},
53
+ };
54
+
55
+ constructor() {
56
+ super();
57
+ this.showZoomIn = true;
58
+ this._layers = [];
59
+
60
+ // Map zoom
61
+ onceParentAvailable(this).then(() => this._parent.onceMapReady?.().then(() => {
62
+ const postUpdate = () => {
63
+ this._onLayersChange();
64
+ this.requestUpdate();
65
+ };
66
+ this._parent.map.on("zoomend", this._onMapZoom.bind(this));
67
+ this._parent.map.on("styledata", postUpdate);
68
+ this._parent.mapStyleComposer.addEventListener("dataoverlay-added", postUpdate);
69
+ this._onMapZoom();
70
+ postUpdate();
71
+ }));
72
+ }
73
+
74
+ /** @private */
75
+ _layersToSet(layers) {
76
+ return layers
77
+ .map(l => l.source+"/"+this._parent.mapStyleComposer.dataOverlays.has(l.source))
78
+ .sort()
79
+ .join("|");
80
+ }
81
+
82
+ /** @private */
83
+ _onLayersChange() {
84
+ clearTimeout(this._debouncedChange);
85
+ this._debouncedChange = setTimeout(
86
+ () => {
87
+ const newlayers = this._parent?.map?.getSemanticOverlays();
88
+ // eslint-disable-next-line eqeqeq
89
+ if(this._layersToSet(newlayers) != this._layersToSet(this._layers)) {
90
+ this._layers = newlayers;
91
+
92
+ /**
93
+ * Event for semantics overlay visibility changes
94
+ * @event Panoramax.components.menus.SemanticsFilters#change
95
+ * @type {CustomEvent}
96
+ * @property {object[]} detail.layers List of semantics layers, from MapLibre
97
+ */
98
+ this.dispatchEvent(new CustomEvent("change", {
99
+ bubbles: true,
100
+ composed: true,
101
+ detail: { layers: newlayers }
102
+ }));
103
+ }
104
+ },
105
+ 10
106
+ );
107
+ }
108
+
109
+ /**
110
+ * Map zoom event handler: show/hide "zoom in" labels
111
+ * @private
112
+ */
113
+ _onMapZoom() {
114
+ this._showZoomIn = this._parent.map.getZoom() < SEMANTICS_OVERLAY_MINZOOM;
115
+ }
116
+
117
+ /** @private */
118
+ render() {
119
+ if(!this._layers || this._layers.length === 0) { return; }
120
+
121
+ return html`
122
+ <div class="pnx-tags-zoomin ${this._showZoomIn ? "" : "hidden"}">
123
+ ${this._parent?._t.pnx.semantics_zoom_in}
124
+ </div>
125
+
126
+ <div class="pnx-tags-cta">
127
+ <pnx-button
128
+ kind="full"
129
+ size="sm"
130
+ @click=${() => this._parent?._showSemanticsDownload()}
131
+ >${fa(faDownload)} ${this._parent?._t.pnx.semantics_download}</pnx-button>
132
+
133
+ <pnx-button
134
+ id="pnx-semfilt-doc"
135
+ kind="superinline"
136
+ size="md"
137
+ title=${this._parent?._t.pnx.semantics_doc}
138
+ @click=${() => this._parent?._showSemanticsDoc()}
139
+ >${fa(faInfoCircle)}</pnx-button>
140
+ </div>
141
+
142
+ ${this._layers.filter(l => l.metadata).map(l => html`
143
+ <pnx-switch
144
+ name=${"pnx-semfilt-"+l.id}
145
+ .checked=${this._parent.mapStyleComposer.dataOverlays.has(l.source)}
146
+ @change=${e => this._parent.mapStyleComposer.switchDataOverlayVisibility(l.source, e.target.checked)}
147
+ >${this._parent?._t.pnx.semantics_overlays[l.id] || l.metadata.name || l.id}</pnx-switch>
148
+ `)}
149
+ `;
150
+ }
151
+ }
152
+
153
+ customElements.define("pnx-map-semantics-filters-menu", SemanticsFilters);
@@ -0,0 +1,413 @@
1
+ import { LitElement, css, html, nothing } from "lit";
2
+ import { badgeCount, faSvg, iconify } from "../styles.js";
3
+ import { fa, moreIcons, onceParentAvailable } from "../../utils/widgets.js";
4
+ import { groupByPrefix, semanticsToString } from "../../utils/semantics.js";
5
+ import {
6
+ faArrowLeft, faCheck, faChevronRight, faCopy,
7
+ faImages, faPen, faTrash, faVectorSquare
8
+ } from "@fortawesome/free-solid-svg-icons";
9
+ import "iconify-icon";
10
+
11
+ /**
12
+ * Semantics list shows listing of both picture tags and annotations.
13
+ * It uses the parent component currently selected picture.
14
+ *
15
+ * @class Panoramax.components.menus.SemanticsList
16
+ * @element pnx-semantics-list
17
+ * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
18
+ * @fires Panoramax.components.menus.SemanticsList#select
19
+ * @fires Panoramax.components.menus.SemanticsList#edit-click
20
+ * @fires Panoramax.components.menus.SemanticsList#delete-click
21
+ * @example
22
+ * ```html
23
+ * <pnx-semantics-list _parent=${viewer} />
24
+ * ```
25
+ */
26
+ export default class SemanticsList extends LitElement {
27
+ /** @private */
28
+ static styles = [ faSvg, iconify, badgeCount, css`
29
+ .annotation { background: var(--white); }
30
+ pnx-list-item {
31
+ display: block;
32
+ margin: 5px 0;
33
+ }
34
+ ` ];
35
+
36
+ /**
37
+ * Component properties.
38
+ * @memberof Panoramax.components.menus.SemanticsList#
39
+ * @type {Object}
40
+ * @property {boolean} [editable=false] Show an "Edit" button ?
41
+ */
42
+ static properties = {
43
+ editable: {type: Boolean},
44
+ _meta: {state: true},
45
+ _seqMeta: {state: true},
46
+ _entries: {state: true},
47
+ _selectedEntry: {state: true},
48
+ _annotPresets: {state: true},
49
+ _picPresets: {state: true},
50
+ _seqPresets: {state: true},
51
+ };
52
+
53
+ constructor() {
54
+ super();
55
+ this._meta = {};
56
+ this._seqMeta = {};
57
+ this._selectedEntry = null;
58
+ this._annotPresets = {};
59
+ this._picPresets = [];
60
+ this._seqPresets = [];
61
+ this._entries = null;
62
+ this.editable = false;
63
+ moreIcons();
64
+ }
65
+
66
+ /** @private */
67
+ connectedCallback() {
68
+ super.connectedCallback();
69
+
70
+ this._onPicChange();
71
+
72
+ onceParentAvailable(this).then(() => {
73
+ this._parent.psv?.addEventListener("picture-loaded", this._onPicChange.bind(this));
74
+ this._parent.psv?.addEventListener("annotation-focused", e => this.showAnnotation(e.detail.annotationId));
75
+ this._parent.psv?.addEventListener("annotations-unfocused", () => this._onListItemClick(null));
76
+ this._parent.psv?.addEventListener("semantics-unfocused", () => this._onListItemClick(null));
77
+
78
+ const selectedAnnots = this._parent.psv.getSelectedAnnotations();
79
+ if(selectedAnnots.length > 0) {
80
+ this.showAnnotation(selectedAnnots[0]);
81
+ }
82
+ });
83
+ }
84
+
85
+ /**
86
+ * Display a specific annotation details.
87
+ * @param {string} id The annotation UUID
88
+ * @memberof Panoramax.components.menus.SemanticsList#
89
+ */
90
+ showAnnotation(id) {
91
+ if(!this._entries) { setTimeout(() => this.showAnnotation(id), 200); }
92
+ else { this._onListItemClick(this._entries.find(e => e.type === "annotation" && e.annotation.id == id)); }
93
+ }
94
+
95
+ /**
96
+ * Display a specific tag group details.
97
+ * @param {object} tags The tags to find
98
+ * @memberof Panoramax.components.menus.SemanticsList#
99
+ */
100
+ showTagsGroup(tags) {
101
+ if(!this._entries) { setTimeout(() => this.showTagsGroup(tags), 200); }
102
+ else {
103
+ this._onListItemClick(this._entries.find(e => {
104
+ if(e?.type === "annotation") { return false; }
105
+ for(let i=0; i < tags.length; i++) {
106
+ if(!e.tagGroup.semantics.find(s => tags[i].key === s.key && tags[i].value === s.value)) {
107
+ return false;
108
+ }
109
+ }
110
+ return true;
111
+ }));
112
+ }
113
+ }
114
+
115
+ /** @private */
116
+ _onPicChange() {
117
+ this._meta = this._parent?.psv?.getPictureMetadata();
118
+ this._getSeqMeta();
119
+ delete this._prevPsvView;
120
+ this._selectedEntry = null;
121
+ this._entries = null;
122
+ this.dispatchEvent(new CustomEvent("select", { detail: { item: null } }));
123
+
124
+ // Load presets
125
+ if(this._meta && this._parent?.presetsManager) {
126
+ this._annotPresets = {};
127
+ this._picPresets = [];
128
+
129
+ // Picture tags
130
+ this._parent.presetsManager.getPresets(this._meta.properties).then(p => {
131
+ this._picPresets = p;
132
+ this._computeEntries();
133
+ });
134
+
135
+ // Annotations
136
+ if(this._meta.properties?.annotations?.length > 0) {
137
+ Promise.all(this._meta.properties.annotations.map(a => this._parent.presetsManager.getPreset(a).then(p => {
138
+ this._annotPresets[a.id] = p;
139
+ }))).then(() => this._computeEntries());
140
+ }
141
+ }
142
+ }
143
+
144
+ /** @private */
145
+ async _getSeqMeta() {
146
+ const seqId = this._meta?.sequence?.id;
147
+
148
+ // If no cache, call API
149
+ if(seqId && !this._parent._sequencesMetadata[seqId]) {
150
+ this._parent._sequencesMetadata[seqId] = await this._parent.getAPI().getSequenceMetadata(seqId);
151
+ }
152
+
153
+ this._seqMeta = this._parent._sequencesMetadata[seqId];
154
+ this._seqPresets = [];
155
+
156
+ if(this._seqMeta?.semantics?.length > 0) {
157
+ const p = await this._parent.presetsManager.getPresets(this._seqMeta);
158
+ this._seqPresets = p;
159
+ this._computeEntries();
160
+ }
161
+ }
162
+
163
+ /** @private */
164
+ _getGroupedTags(semantics, type, unknownTagId, presets) {
165
+ const entries = [];
166
+ const groupToList = (t, pkey) => {
167
+ const g = [ {key: pkey, value: t.value} ];
168
+ if(t.qualifiers) { t.qualifiers.forEach(q => g.push({key: q.key, value: q.value})); }
169
+ return g;
170
+ };
171
+
172
+ const tagsGroups = groupByPrefix(semantics);
173
+ const nonMatchingTags = [];
174
+ tagsGroups.forEach(g => {
175
+ const usedTags = {};
176
+ g.tags.forEach(t => {
177
+ const pkey = g.prefix != "" ? `${g.prefix}|${t.key}` : t.key;
178
+
179
+ // Skip if tag found in a previous preset
180
+ if(usedTags[`${pkey}=${t.value}`]) {
181
+ groupToList(t, pkey).forEach(v => usedTags[`${pkey}=${t.value}`].push(v));
182
+ return;
183
+ }
184
+
185
+ // Check if a picture preset matches this tag
186
+ const preset = presets.find(p => ["*", t.value].includes(p.tags[pkey]));
187
+ if(preset) {
188
+ const groupTags = groupToList(t, pkey);
189
+
190
+ // Add all used tags for this preset into used ones
191
+ let usedTagsCount = 0;
192
+ Object.entries(preset.tags).forEach(([prk, prv]) => {
193
+ let utv = prv;
194
+ if(prv === "*") {
195
+ utv = this._meta.properties.semantics.find(s => s.key === pkey)?.value;
196
+ }
197
+ usedTags[prk+"="+utv] = groupTags;
198
+ usedTagsCount++;
199
+ });
200
+
201
+ entries.push({
202
+ title: preset.name,
203
+ icon: preset.iconify || "fa6-solid:cube",
204
+ associatedTags: (t.qualifiers?.length || 0) + usedTagsCount,
205
+ tagGroup: { semantics: groupTags },
206
+ type: type,
207
+ });
208
+ }
209
+ else {
210
+ nonMatchingTags.push({key: pkey, value: t.value, qualifiers: t.qualifiers});
211
+ }
212
+ });
213
+ });
214
+
215
+ if(nonMatchingTags.length > 0) {
216
+ entries.push({
217
+ title: this._parent?._t.pnx.semantics_features_default_title.replace("{nb}", unknownTagId++),
218
+ icon: "fa6-solid:tag",
219
+ associatedTags: nonMatchingTags.length,
220
+ tagGroup: { semantics: nonMatchingTags },
221
+ type: type,
222
+ });
223
+ }
224
+
225
+ return [ entries, unknownTagId ];
226
+ }
227
+
228
+ /** @private */
229
+ _computeEntries() {
230
+ let unknownTagId = 1;
231
+ let entries = [];
232
+ let entries2 = [];
233
+
234
+ // Sequence tags
235
+ if(this._seqMeta?.semantics?.length > 0) {
236
+ [ entries, unknownTagId ] = this._getGroupedTags(this._seqMeta.semantics, "seqtags", unknownTagId, this._seqPresets);
237
+ }
238
+
239
+ // Picture tags
240
+ [ entries2, unknownTagId ] = this._getGroupedTags(this._meta.properties?.semantics, "pictags", unknownTagId, this._picPresets);
241
+ entries = entries.concat(entries2);
242
+
243
+ // Annotations
244
+ entries = entries.concat(this._meta.properties?.annotations?.map(a => ({
245
+ title: this._annotPresets[a.id]?.name || this._parent?._t.pnx.semantics_features_default_title.replace("{nb}", unknownTagId++),
246
+ icon: this._annotPresets[a.id]?.iconify || "fa6-solid:cube",
247
+ associatedTags: a.semantics.filter(s => s.action !== "delete").length,
248
+ type: "annotation",
249
+ annotation: a,
250
+ })) || []);
251
+
252
+ this._entries = entries;
253
+ }
254
+
255
+ /** @private */
256
+ _onListItemHover(item) {
257
+ if(item?.type === "annotation") {
258
+ // Save position before hover to allow reset after
259
+ if(!this._prevPsvView) { this._prevPsvView = [this._parent.psv.getZoomLevel(), this._parent.psv.getPosition()]; }
260
+
261
+ this._parent.psv.focusOnAnnotation(item.annotation.id, undefined, true);
262
+ }
263
+ else {
264
+ this._parent.psv.unfocusAnnotation();
265
+
266
+ // Restore previous PSV position
267
+ if(this._prevPsvView) {
268
+ this._parent.psv.zoom(this._prevPsvView[0]);
269
+ this._parent.psv.rotate(this._prevPsvView[1]);
270
+ delete this._prevPsvView;
271
+ }
272
+ }
273
+ }
274
+
275
+ /** @private */
276
+ _onListItemClick(item) {
277
+ this._selectedEntry = item;
278
+ this._onListItemHover(item);
279
+
280
+ /**
281
+ * Event for item selection
282
+ *
283
+ * @event Panoramax.components.menus.SemanticsList#select
284
+ * @type {CustomEvent}
285
+ * @property {object} detail.item The selected annotation/tag group (or null if none)
286
+ */
287
+ this.dispatchEvent(new CustomEvent("select", {
288
+ detail: { item }
289
+ }));
290
+ }
291
+
292
+ /** @private */
293
+ _onItemEditClick(e) {
294
+ e.stopPropagation();
295
+
296
+ /**
297
+ * Event for edit button click
298
+ *
299
+ * @event Panoramax.components.menus.SemanticsList#edit-click
300
+ * @type {CustomEvent}
301
+ * @property {object} detail.item The annotation/tag group to edit
302
+ */
303
+ this.dispatchEvent(new CustomEvent("edit-click", {
304
+ detail: { item: this._selectedEntry }
305
+ }));
306
+ }
307
+
308
+ /** @private */
309
+ _onItemDeleteClick(e) {
310
+ e.stopPropagation();
311
+
312
+ /**
313
+ * Event for delete button click
314
+ *
315
+ * @event Panoramax.components.menus.SemanticsList#delete-click
316
+ * @type {CustomEvent}
317
+ * @property {object} detail.item The annotation/tag group to delete
318
+ */
319
+ this.dispatchEvent(new CustomEvent("delete-click", {
320
+ detail: { item: this._selectedEntry }
321
+ }));
322
+ }
323
+
324
+ /** @private */
325
+ render() {
326
+ if(!this._meta) { return nothing; }
327
+
328
+ // Selected entry
329
+ if(this._selectedEntry) {
330
+ return html`<div class="annotation">
331
+ <pnx-list-item
332
+ title=${this._selectedEntry.title}
333
+ @click=${() => this._onListItemClick(null)}
334
+ >
335
+ ${fa(faArrowLeft, {transform: {size: 24}, attributes: {slot: "icon"}})}
336
+ <iconify-icon
337
+ slot="icon"
338
+ icon=${this._selectedEntry.icon}
339
+ style="font-size: 1.5em"
340
+ ></iconify-icon>
341
+
342
+ <pnx-copy-button
343
+ kind="superinline"
344
+ text=${semanticsToString((this._selectedEntry?.annotation || this._selectedEntry?.tagGroup)?.semantics)}
345
+ slot="action"
346
+ ._t=${this._parent?._t}
347
+ title=${this._parent?._t.pnx.semantics_copy}
348
+ >
349
+ ${fa(faCopy, {transform: {size: 18}})}
350
+ ${fa(faCheck, {attributes: {slot: "oncopy"}, transform: {size: 18}})}
351
+ </pnx-copy-button>
352
+
353
+ ${this.editable && this._selectedEntry.type !== "seqtags" ? html`
354
+ <pnx-button
355
+ kind="superinline"
356
+ title=${this._parent?._t.pnx.semantics_edit}
357
+ slot="action"
358
+ @click=${this._onItemEditClick}
359
+ >
360
+ ${fa(faPen, {transform: {size: 18}})}
361
+ </pnx-button>
362
+ <pnx-button
363
+ kind="superinline"
364
+ title=${this._parent?._t.pnx.semantics_delete_annotation}
365
+ slot="action"
366
+ @click=${this._onItemDeleteClick}
367
+ >
368
+ ${fa(faTrash, {transform: {size: 18}})}
369
+ </pnx-button>`
370
+ : nothing}
371
+ </pnx-list-item>
372
+ <pnx-semantics-table
373
+ ._t=${this._parent?._t}
374
+ .source=${this._selectedEntry?.type === "annotation" ? this._selectedEntry.annotation : this._selectedEntry.tagGroup}
375
+ />
376
+ </div>`;
377
+ }
378
+ // General listing
379
+ else {
380
+ const titles = {
381
+ "seqtags": this._parent?._t.pnx.semantics_sequence_tags,
382
+ "pictags": this._parent?._t.pnx.semantics_picture_tags,
383
+ "annotation": this._parent?._t.pnx.semantics_annotation_tags
384
+ };
385
+
386
+ return html`<div class="list">
387
+ ${(this._entries || []).map(e => html`
388
+ <pnx-list-item
389
+ title=${e.title}
390
+ tooltip=${titles[e.type]}
391
+ @click=${() => this._onListItemClick(e)}
392
+ @mouseover=${() => this._onListItemHover(e)}
393
+ @mouseout=${() => this._onListItemHover(null)}
394
+ >
395
+ <iconify-icon
396
+ slot="icon"
397
+ icon=${e.icon}
398
+ style="font-size: 1.5em"
399
+ ></iconify-icon>
400
+ ${e.associatedTags > 1 ? html`
401
+ <span slot="icon" class="pnx-badge-count">${e.associatedTags}</span>
402
+ ` : nothing}
403
+ ${e?.type === "annotation" ? fa(faVectorSquare, {transform: {size: 20}, styles: { "margin-right": "20px" }, attributes: {slot: "action"}}) : nothing}
404
+ ${e?.type === "seqtags" ? fa(faImages, {transform: {size: 20}, styles: { "margin-right": "20px" }, attributes: {slot: "action"}}) : nothing}
405
+ ${fa(faChevronRight, {transform: {size: 20}, attributes: {slot: "action"}})}
406
+ </pnx-list-item>
407
+ `)}
408
+ </div>`;
409
+ }
410
+ }
411
+ }
412
+
413
+ customElements.define("pnx-semantics-list", SemanticsList);