@panoramax/web-viewer 4.0.2 → 4.0.3-develop-54221cf0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -1
- package/build/index.css +2 -2
- package/build/index.css.map +1 -1
- package/build/index.js +287 -72
- package/build/index.js.map +1 -1
- package/config/jest/mocks.js +5 -0
- package/docs/09_Develop.md +6 -0
- package/docs/reference/components/core/PhotoViewer.md +1 -0
- package/docs/reference/components/core/Viewer.md +2 -1
- package/docs/reference/components/menus/AnnotationsList.md +16 -0
- package/docs/reference/components/ui/HashTags.md +15 -0
- package/docs/reference/components/ui/ListItem.md +38 -0
- package/docs/reference/components/ui/Photo.md +78 -1
- package/docs/reference/components/ui/SemanticsTable.md +32 -0
- package/docs/reference/components/ui/widgets/GeoSearch.md +5 -1
- package/docs/reference/utils/PresetsManager.md +35 -0
- package/docs/reference.md +4 -0
- package/mkdocs.yml +4 -0
- package/package.json +2 -1
- package/src/components/core/Basic.css +2 -0
- package/src/components/core/PhotoViewer.js +11 -0
- package/src/components/core/Viewer.js +8 -1
- package/src/components/layout/Tabs.js +1 -1
- package/src/components/menus/AnnotationsList.js +151 -0
- package/src/components/menus/PictureLegend.js +6 -5
- package/src/components/menus/PictureMetadata.js +80 -8
- package/src/components/menus/index.js +1 -0
- package/src/components/styles.js +34 -0
- package/src/components/ui/HashTags.js +98 -0
- package/src/components/ui/ListItem.js +83 -0
- package/src/components/ui/Photo.js +188 -0
- package/src/components/ui/SemanticsTable.js +87 -0
- package/src/components/ui/index.js +3 -0
- package/src/components/ui/widgets/GeoSearch.js +13 -5
- package/src/img/osm.svg +49 -0
- package/src/img/wd.svg +1 -0
- package/src/translations/de.json +50 -3
- package/src/translations/en.json +23 -0
- package/src/translations/fr.json +21 -0
- package/src/translations/it.json +28 -1
- package/src/translations/nl.json +14 -4
- package/src/translations/zh_Hant.json +6 -1
- package/src/utils/PresetsManager.js +137 -0
- package/src/utils/URLHandler.js +1 -1
- package/src/utils/geocoder.js +135 -83
- package/src/utils/index.js +3 -1
- package/src/utils/picture.js +28 -0
- package/src/utils/semantics.js +162 -0
- package/src/utils/services.js +39 -1
- package/src/utils/widgets.js +18 -1
- package/tests/components/core/__snapshots__/PhotoViewer.test.js.snap +10 -0
- package/tests/components/core/__snapshots__/Viewer.test.js.snap +10 -0
- package/tests/data/Map_geocoder_nominatim.json +25 -40
- package/tests/utils/PresetsManager.test.js +123 -0
- package/tests/utils/URLHandler.test.js +42 -0
- package/tests/utils/__snapshots__/geocoder.test.js.snap +5 -16
- package/tests/utils/geocoder.test.js +1 -1
- package/tests/utils/semantics.test.js +125 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { LitElement, html, css, nothing } from "lit";
|
|
2
|
+
import { faSvg, iconify } from "../styles";
|
|
3
|
+
import { fa, moreIcons } from "../../utils/widgets";
|
|
4
|
+
import { faChevronRight } from "@fortawesome/free-solid-svg-icons/faChevronRight";
|
|
5
|
+
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons/faArrowLeft";
|
|
6
|
+
import "iconify-icon";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Annotations list shows listing of tagged picture areas.
|
|
10
|
+
* It uses the parent component currently selected picture.
|
|
11
|
+
*
|
|
12
|
+
* @class Panoramax.components.menus.AnnotationsList
|
|
13
|
+
* @element pnx-annotations-list
|
|
14
|
+
* @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
|
|
15
|
+
* @example
|
|
16
|
+
* ```html
|
|
17
|
+
* <pnx-annotations-list _parent=${viewer} />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export default class AnnotationsList extends LitElement {
|
|
21
|
+
/** @private */
|
|
22
|
+
static styles = [ faSvg, iconify, css`
|
|
23
|
+
.annotation { background: var(--white); }
|
|
24
|
+
` ];
|
|
25
|
+
|
|
26
|
+
/** @private */
|
|
27
|
+
static properties = {
|
|
28
|
+
_meta: {state: true},
|
|
29
|
+
_selectedAnnotation: {state: true},
|
|
30
|
+
_presets: {state: true},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
super();
|
|
35
|
+
this._selectedAnnotation = null;
|
|
36
|
+
this._presets = {};
|
|
37
|
+
moreIcons();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** @private */
|
|
41
|
+
connectedCallback() {
|
|
42
|
+
super.connectedCallback();
|
|
43
|
+
|
|
44
|
+
this._onPicChange();
|
|
45
|
+
this._parent?.psv?.addEventListener("picture-loaded", this._onPicChange.bind(this));
|
|
46
|
+
|
|
47
|
+
this._parent?.psv?.addEventListener("annotation-click", e => {
|
|
48
|
+
const aPos = this._meta.properties?.annotations?.findIndex(a => a.id === e.detail.annotationId);
|
|
49
|
+
if(aPos >= 0) { this._onListItemClick(Object.assign({nb: aPos+1}, this._meta.properties.annotations[aPos])); }
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
this._parent?.psv?.addEventListener("annotations-unfocused", () => {
|
|
53
|
+
this._onListItemClick(null);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @private */
|
|
58
|
+
_onPicChange() {
|
|
59
|
+
this._meta = this._parent?.psv?.getPictureMetadata();
|
|
60
|
+
delete this._prevPsvView;
|
|
61
|
+
this._selectedAnnotation = null;
|
|
62
|
+
|
|
63
|
+
// Load presets for annotations
|
|
64
|
+
if(this._meta && this._parent?.presetsManager) {
|
|
65
|
+
this._presets = {};
|
|
66
|
+
if(this._meta.properties?.annotations?.length > 0) {
|
|
67
|
+
this._meta.properties.annotations.map(a => this._parent.presetsManager.getPreset(a).then(p => {
|
|
68
|
+
this._presets[a.id] = p;
|
|
69
|
+
this.requestUpdate();
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** @private */
|
|
76
|
+
_onListItemHover(a) {
|
|
77
|
+
if(a) {
|
|
78
|
+
// Save position before hover to allow reset after
|
|
79
|
+
if(!this._prevPsvView) { this._prevPsvView = [this._parent.psv.getZoomLevel(), this._parent.psv.getPosition()]; }
|
|
80
|
+
|
|
81
|
+
this._parent.psv.focusOnAnnotation(a.id);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
this._parent.psv.unfocusAnnotation();
|
|
85
|
+
|
|
86
|
+
// Restore previous PSV position
|
|
87
|
+
if(this._prevPsvView) {
|
|
88
|
+
this._parent.psv.zoom(this._prevPsvView[0]);
|
|
89
|
+
this._parent.psv.rotate(this._prevPsvView[1]);
|
|
90
|
+
delete this._prevPsvView;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** @private */
|
|
96
|
+
_onListItemClick(a) {
|
|
97
|
+
this._selectedAnnotation = a;
|
|
98
|
+
this._onListItemHover(a);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** @private */
|
|
102
|
+
render() {
|
|
103
|
+
/* eslint-disable indent */
|
|
104
|
+
if(!this._meta) { return nothing; }
|
|
105
|
+
|
|
106
|
+
return this._selectedAnnotation === null
|
|
107
|
+
? html`<div class="list">
|
|
108
|
+
${this._meta.properties.annotations.map((a,i) => html`
|
|
109
|
+
<pnx-list-item
|
|
110
|
+
title=${
|
|
111
|
+
this._presets[a.id]?.name
|
|
112
|
+
|| this._parent?._t.pnx.semantics_features_default_title.replace("{nb}", i+1)
|
|
113
|
+
}
|
|
114
|
+
subtitle=${this._parent?._t.pnx.semantics_features_subtitle.replace("{nb}", a.semantics.length)}
|
|
115
|
+
@click=${() => this._onListItemClick(Object.assign({nb: i+1}, a))}
|
|
116
|
+
@mouseover=${() => this._onListItemHover(Object.assign({nb: i+1}, a))}
|
|
117
|
+
@mouseout=${() => this._onListItemHover(null)}
|
|
118
|
+
>
|
|
119
|
+
<iconify-icon
|
|
120
|
+
slot="icon"
|
|
121
|
+
icon=${this._presets[a.id]?.iconify || "fa6-solid:cube"}
|
|
122
|
+
style="font-size: 1.5em"
|
|
123
|
+
></iconify-icon>
|
|
124
|
+
${fa(faChevronRight, {transform: {size: 24}, attributes: {slot: "action"}})}
|
|
125
|
+
</pnx-list-item>
|
|
126
|
+
`)}
|
|
127
|
+
</div>`
|
|
128
|
+
: html`<div class="annotation">
|
|
129
|
+
<pnx-list-item
|
|
130
|
+
title=${
|
|
131
|
+
this._presets[this._selectedAnnotation.id]?.name
|
|
132
|
+
|| this._parent?._t.pnx.semantics_features_default_title.replace("{nb}", this._selectedAnnotation.nb)
|
|
133
|
+
}
|
|
134
|
+
@click=${() => this._onListItemClick(null)}
|
|
135
|
+
>
|
|
136
|
+
${fa(faArrowLeft, {transform: {size: 24}, attributes: {slot: "icon"}})}
|
|
137
|
+
<iconify-icon
|
|
138
|
+
slot="icon"
|
|
139
|
+
icon=${this._presets[this._selectedAnnotation.id]?.iconify || "fa6-solid:cube"}
|
|
140
|
+
style="font-size: 1.5em"
|
|
141
|
+
></iconify-icon>
|
|
142
|
+
</pnx-list-item>
|
|
143
|
+
<pnx-semantics-table
|
|
144
|
+
._t=${this._parent?._t}
|
|
145
|
+
.source=${this._selectedAnnotation}
|
|
146
|
+
/>
|
|
147
|
+
</div>`;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
customElements.define("pnx-annotations-list", AnnotationsList);
|
|
@@ -8,7 +8,7 @@ import { faUser } from "@fortawesome/free-solid-svg-icons/faUser";
|
|
|
8
8
|
import { faCalendarAlt } from "@fortawesome/free-solid-svg-icons/faCalendarAlt";
|
|
9
9
|
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons/faTriangleExclamation";
|
|
10
10
|
import { faShareNodes } from "@fortawesome/free-solid-svg-icons/faShareNodes";
|
|
11
|
-
import { placeholder, panel } from "../styles";
|
|
11
|
+
import { placeholder, panel, hidden } from "../styles";
|
|
12
12
|
import { reverseGeocodingNominatim } from "../../utils/geocoder";
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -24,7 +24,7 @@ import { reverseGeocodingNominatim } from "../../utils/geocoder";
|
|
|
24
24
|
*/
|
|
25
25
|
export default class PictureLegend extends LitElement {
|
|
26
26
|
/** @private */
|
|
27
|
-
static styles = [placeholder, panel, css`
|
|
27
|
+
static styles = [placeholder, panel, hidden, css`
|
|
28
28
|
:host {
|
|
29
29
|
overflow-y: auto;
|
|
30
30
|
overflow-x: hidden;
|
|
@@ -38,8 +38,6 @@ export default class PictureLegend extends LitElement {
|
|
|
38
38
|
pnx-picture-metadata { width: 30vw; }
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
.pnx-hidden { display: none !important; }
|
|
42
|
-
|
|
43
41
|
/* Top bar */
|
|
44
42
|
.headline {
|
|
45
43
|
display: flex;
|
|
@@ -148,6 +146,9 @@ export default class PictureLegend extends LitElement {
|
|
|
148
146
|
this._parent.psv.addEventListener("sequence-stopped", () => {
|
|
149
147
|
this._onPicChange(this._parent.psv.getPictureMetadata());
|
|
150
148
|
});
|
|
149
|
+
this._parent.psv.addEventListener("annotation-click", () => {
|
|
150
|
+
this._expanded = true;
|
|
151
|
+
});
|
|
151
152
|
});
|
|
152
153
|
}
|
|
153
154
|
|
|
@@ -225,7 +226,7 @@ export default class PictureLegend extends LitElement {
|
|
|
225
226
|
|
|
226
227
|
${this._caption.date ? html`<div class="info-block">
|
|
227
228
|
${fa(faCalendarAlt)}
|
|
228
|
-
${this._caption.date.toLocaleDateString(
|
|
229
|
+
${this._caption.date.toLocaleDateString(this._parent?.lang || window.navigator.language, { year: "numeric", month: "long" })}
|
|
229
230
|
</div>` : nothing}
|
|
230
231
|
</div>
|
|
231
232
|
|
|
@@ -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 {
|
|
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,20 +59,37 @@ 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();
|
|
69
83
|
|
|
70
84
|
this._meta = this._parent?.psv?.getPictureMetadata();
|
|
71
|
-
this._parent?.
|
|
72
|
-
this.
|
|
85
|
+
this._parent?.oncePSVReady?.().then(() => {
|
|
86
|
+
this._parent.psv.addEventListener("picture-loaded", () => {
|
|
87
|
+
this._meta = this._parent.psv.getPictureMetadata();
|
|
88
|
+
});
|
|
89
|
+
this._parent.psv.addEventListener("annotation-click", () => {
|
|
90
|
+
const tabs = this.shadowRoot.querySelector("pnx-tabs");
|
|
91
|
+
if(tabs) { tabs.setAttribute("activeTabIndex", 4); }
|
|
92
|
+
});
|
|
73
93
|
});
|
|
74
94
|
}
|
|
75
95
|
|
|
@@ -99,6 +119,8 @@ export default class PictureMetadata extends LitElement {
|
|
|
99
119
|
/* eslint-disable indent */
|
|
100
120
|
if(!this._meta) { return nothing; }
|
|
101
121
|
|
|
122
|
+
const lang = this._parent?.lang || window.navigator.language;
|
|
123
|
+
|
|
102
124
|
// Generic information
|
|
103
125
|
const persOrient = this._meta?.properties?.["pers:interior_orientation"];
|
|
104
126
|
const makeModel = [persOrient.camera_manufacturer, persOrient.camera_model].filter(v => v).join(" ");
|
|
@@ -127,11 +149,11 @@ export default class PictureMetadata extends LitElement {
|
|
|
127
149
|
this._meta?.caption?.date && {
|
|
128
150
|
title: this._parent?._t.pnx.metadata_general_date,
|
|
129
151
|
content: html`
|
|
130
|
-
<strong>${new Intl.DateTimeFormat(
|
|
152
|
+
<strong>${new Intl.DateTimeFormat(lang, {
|
|
131
153
|
timeZone: this._meta.caption.tz,
|
|
132
154
|
dateStyle: "short"
|
|
133
155
|
}).format(this._meta.caption.date)}</strong>
|
|
134
|
-
<br />${new Intl.DateTimeFormat(
|
|
156
|
+
<br />${new Intl.DateTimeFormat(lang, {
|
|
135
157
|
timeZone: this._meta.caption.tz,
|
|
136
158
|
hour: "numeric",
|
|
137
159
|
minute: "numeric",
|
|
@@ -205,6 +227,51 @@ export default class PictureMetadata extends LitElement {
|
|
|
205
227
|
];
|
|
206
228
|
}
|
|
207
229
|
|
|
230
|
+
// Semantics data
|
|
231
|
+
const hasSemantics = (
|
|
232
|
+
(this._meta.properties.semantics || []).length > 0
|
|
233
|
+
|| (this._meta.properties.annotations || []).length > 0
|
|
234
|
+
);
|
|
235
|
+
let semanticsData = [];
|
|
236
|
+
if(hasSemantics) {
|
|
237
|
+
// Full list of picture tags
|
|
238
|
+
semanticsData.push({
|
|
239
|
+
title: this._parent?._t.pnx.semantics_tags_picture,
|
|
240
|
+
style: "width: 100%",
|
|
241
|
+
content: html`${this._meta.properties.semantics?.length > 0
|
|
242
|
+
? html`
|
|
243
|
+
<pnx-button
|
|
244
|
+
kind="outline"
|
|
245
|
+
size="sm"
|
|
246
|
+
style="width: 100%"
|
|
247
|
+
@click=${() => this._semanticsPicShowAll = !this._semanticsPicShowAll}
|
|
248
|
+
>
|
|
249
|
+
${this._semanticsPicShowAll ? fa(faChevronUp) : fa(faChevronDown)}
|
|
250
|
+
${this._semanticsPicShowAll ? this._parent?._t.pnx.semantics_hide_all_tags : this._parent?._t.pnx.semantics_show_all_tags}
|
|
251
|
+
</pnx-button>
|
|
252
|
+
<pnx-semantics-table
|
|
253
|
+
._t=${this._parent?._t}
|
|
254
|
+
.source=${this._meta.properties}
|
|
255
|
+
style="margin-top: 5px"
|
|
256
|
+
class=${this._semanticsPicShowAll ? "":"pnx-hidden"}
|
|
257
|
+
/>
|
|
258
|
+
`
|
|
259
|
+
: this._parent?._t.pnx.semantics_tags_picture_none
|
|
260
|
+
}`
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Annotations (features in picture)
|
|
264
|
+
semanticsData.push({
|
|
265
|
+
title: this._parent?._t.pnx.semantics_features,
|
|
266
|
+
style: "width: 100%",
|
|
267
|
+
content: html`
|
|
268
|
+
${this._meta.properties.annotations?.length > 0
|
|
269
|
+
? html`<pnx-annotations-list ._parent=${this._parent} />`
|
|
270
|
+
: this._parent?._t.pnx.semantics_features_none}
|
|
271
|
+
`
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
208
275
|
// EXIF data
|
|
209
276
|
const exifData = Object.entries(this._meta.properties.exif)
|
|
210
277
|
.sort()
|
|
@@ -247,8 +314,8 @@ export default class PictureMetadata extends LitElement {
|
|
|
247
314
|
this._meta?.caption?.date && {
|
|
248
315
|
title: this._parent?._t.pnx.metadata_general_date,
|
|
249
316
|
content: html`
|
|
250
|
-
<strong>${new Intl.DateTimeFormat(
|
|
251
|
-
<br />${new Intl.DateTimeFormat(
|
|
317
|
+
<strong>${new Intl.DateTimeFormat(lang, {timeZone: this._meta.caption.tz, dateStyle: "long"}).format(this._meta.caption.date)}</strong>
|
|
318
|
+
<br />${new Intl.DateTimeFormat(lang, {timeZone: this._meta.caption.tz, hour: "numeric",minute:"numeric"}).format(this._meta.caption.date)}
|
|
252
319
|
`
|
|
253
320
|
},
|
|
254
321
|
// Camera
|
|
@@ -327,6 +394,11 @@ export default class PictureMetadata extends LitElement {
|
|
|
327
394
|
qualityData
|
|
328
395
|
) : nothing}
|
|
329
396
|
|
|
397
|
+
${hasSemantics ? this._toTab( // Semantics
|
|
398
|
+
html`${fa(faTags)} ${this._parent?._t.pnx.semantics_title}`,
|
|
399
|
+
semanticsData
|
|
400
|
+
) : nothing}
|
|
401
|
+
|
|
330
402
|
${this._meta.properties?.exif ? this._toTab( // EXIF
|
|
331
403
|
html`${fa(faScroll)} ${this._parent?._t.pnx.metadata_exif}`,
|
|
332
404
|
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";
|
package/src/components/styles.js
CHANGED
|
@@ -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);
|