@panoramax/web-viewer
Version:
Panoramax web viewer for geolocated pictures
317 lines (278 loc) • 8.41 kB
JavaScript
import { LitElement, nothing, css } from "lit";
import { html, unsafeStatic } from "lit/static-html.js";
import { classMap } from "lit/directives/class-map.js";
import { fa, onceParentAvailable } from "../../utils/widgets";
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons/faArrowLeft";
import { faChevronUp } from "@fortawesome/free-solid-svg-icons/faChevronUp";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons/faChevronDown";
import { faUser } from "@fortawesome/free-solid-svg-icons/faUser";
import { faCalendarAlt } from "@fortawesome/free-solid-svg-icons/faCalendarAlt";
import { faTriangleExclamation } from "@fortawesome/free-solid-svg-icons/faTriangleExclamation";
import { faShareNodes } from "@fortawesome/free-solid-svg-icons/faShareNodes";
import { placeholder, panel, hidden } from "../styles";
import { reverseGeocodingNominatim } from "../../utils/geocoder";
/**
* Picture legend shows info about picture author, capture date, address, and access to metadata popup.
* @class Panoramax.components.menus.PictureLegend
* @element pnx-picture-legend
* @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/)
* @slot `editors` External links to map editors, or any tool that may be helpful. Defaults to OSM tools (iD & JOSM).
* @example
* ```html
* <pnx-picture-legend ._parent=${viewer} />
* ```
*/
export default class PictureLegend extends LitElement {
/** @private */
static styles = [placeholder, panel, hidden, css`
:host {
display: flex;
flex-direction: column;
margin: 0;
font-family: var(--font-family);
flex-wrap: nowrap;
}
@media screen and (min-width: 576px) {
:host {
max-height: 70vh;
}
}
/* Top bar */
.headline {
display: flex;
gap: 10px;
align-items: center;
margin: 10px 10px 5px 10px;
justify-content: space-between;
flex: 1;
}
.headline-buttons {
display: flex;
gap: 5px;
}
/* Address line */
#pic-legend-addr {
line-height: 1.2em;
font-size: 1em;
margin-bottom: 2px;
flex-grow: 5;
font-weight: 800;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
#pic-legend-addr span {
display: inline-block;
height: 100%;
width: 100%;
}
/* Minimal info block */
#pic-legend-info {
margin: 10px;
display: flex;
gap: 10px;
justify-content: space-around;
}
.info-block {
display: flex;
flex-shrink: 1;
gap: 5px;
font-weight: 600;
font-size: 0.85em;
align-items: center;
}
.info-block svg { height: 18px; }
/* Expand button */
#pic-legend-expand {
display: block;
margin-top: 5px;
max-width: 100%;
flex: 1;
}
#pic-legend-expand::part(btn) {
border-radius: 10px;
border-top-right-radius: 0;
border-top-left-radius: 0;
}
/* Details block */
pnx-picture-metadata {
margin: 5px 10px;
display: block;
box-sizing: border-box;
flex: 1;
overflow-y: auto;
}
/* Details actions */
#pic-legend-cta {
display: flex;
margin: 5px 10px;
border-bottom-left-radius: 10px;
border-bottom-right-radius: 10px;
gap: 5px;
flex-wrap: wrap;
flex: 1;
}
/* More options menu */
#pnx-legend-opts { min-width: unset; }
/* Editors */
#pic-legend-editors { margin: 0 10px; }
/* Light version */
.pnx-picture-legend-light {
width: max-content;
font-size: 10px;
}
`];
/**
* Component properties.
* @memberof Panoramax.components.menus.PictureLegend#
* @type {Object}
* @property {boolean} [light=false] Lighter version (for iframes)
*/
static properties = {
light: {type: Boolean},
_caption: { state: true },
_addr: { state: true },
_expanded: { state: true },
collapsable: { type: Boolean },
};
/** @private */
constructor() {
super();
this._expanded = true;
this.collapsable = false;
this.light = false;
}
/** @private */
connectedCallback() {
super.connectedCallback();
this._expanded = !this.collapsable;
this._prevSearches = {};
onceParentAvailable(this)
.then(() => this._parent.onceReady())
.then(() => {
this._onPicChange(this._parent.psv.getPictureMetadata());
this._parent.psv.addEventListener("picture-loaded", () => {
this._onPicChange(this._parent.psv.getPictureMetadata());
});
this._parent.psv.addEventListener("sequence-stopped", () => {
this._onPicChange(this._parent.psv.getPictureMetadata());
});
this._parent.psv.addEventListener("annotation-click", () => {
this._expanded = true;
});
});
}
/** @private */
_onPicChange(picMeta) {
clearTimeout(this._addrTimer1);
this._caption = picMeta?.caption;
if(picMeta) {
const coordsHash = `${picMeta.gps[0].toFixed(4)}/${picMeta.gps[1].toFixed(4)}`;
if(this._prevSearches[coordsHash]) {
this._addr = this._prevSearches[coordsHash];
}
else if(!this._parent.psv._sequencePlaying) {
this._addr = "";
this._addrTimer1 = setTimeout(() => {
reverseGeocodingNominatim(picMeta.gps[1], picMeta.gps[0])
.then(addr => {
clearTimeout(this._addrTimer2);
this._addr = addr;
this._prevSearches[coordsHash] = addr;
});
}, 500);
}
}
else {
this._addr = "";
}
}
/** @private */
_onBackClick() {
if(this._parent.isWidthSmall() && this._parent.focus === "map") {
this._parent.select();
}
else {
this._parent._setFocus?.("map");
}
}
/** @private */
render() {
if(!this._caption) { return nothing; }
const hiddenExpanded = classMap({"pnx-hidden": this._expanded});
const shownExpanded = classMap({"pnx-hidden": !this._expanded});
if(this.light) {
return html`<div class="pnx-picture-legend-light">
${this._caption.producer?.length > 0 ? html`
<a
href=${window.location.href}
target="_blank"
title=${this._parent?._t.pnx.share_page}
>${this._caption.producer[this._caption.producer.length-1]}</a>
</div>` : nothing}
${this._caption.producer?.length > 0 && this._caption?.license ? "|" : ""}
${this._caption?.license ? html`${unsafeStatic(this._caption.license)}` : nothing}
`;
}
return html`
<div class="headline">
${this._parent._setFocus ? html`
<pnx-button
kind="superinline"
@click=${this._onBackClick}
>
${fa(faArrowLeft)}
</pnx-button>
` : nothing}
<div id="pic-legend-addr" title=${this._addr || ""}>
${this._addr?.length > 0 ? this._addr : html`<span class="pnx-placeholder-loading"> </span>`}
</div>
<div class="headline-buttons">
<pnx-button
size="sm"
class=${hiddenExpanded}
title=${this._parent?._t.pnx.share}
@click=${() => this._parent._showShareOptions()}
>
${fa(faShareNodes)}
</pnx-button>
<pnx-picture-legend-actions
@click=${e => e.stopPropagation()}
._parent=${this._parent}
?full=${this._expanded}
></pnx-picture-legend-actions>
</div>
</div>
<div id="pic-legend-info" class=${hiddenExpanded}>
${this._caption.producer?.length > 0 ? html`<div class="info-block">
${fa(faUser)}
${this._caption.producer[this._caption.producer.length-1]}
</div>` : nothing}
${this._caption.date ? html`<div class="info-block">
${fa(faCalendarAlt)}
${this._caption.date.toLocaleDateString(this._parent?.lang || window.navigator.language, { year: "numeric", month: "long", day: "numeric" })} </div>` : nothing}
</div>
<div id="pic-legend-cta" class=${shownExpanded}>
<pnx-button size="sm" @click=${() => this._parent._showShareOptions()}>
${fa(faShareNodes)} ${this._parent?._t.pnx.share}
</pnx-button>
${this._parent.api._endpoints.report ? html`
<pnx-button kind="fullwarn" size="sm" @click=${() => this._parent._showReportForm()}>
${fa(faTriangleExclamation)} ${this._parent?._t.pnx.report}
</pnx-button>
` : nothing}
<slot name="editors">
<pnx-widget-osmeditors ._parent=${this._parent} />
</slot>
</div>
<pnx-picture-metadata class=${shownExpanded} ._parent=${this._parent}></pnx-picture-metadata>
${this.collapsable ? html`<pnx-button
kind="inline"
size="sm"
id="pic-legend-expand"
@click=${() => this._expanded = !this._expanded}
>${this._expanded ? fa(faChevronUp) : fa(faChevronDown)}</pnx-button>` : nothing}
`;
}
}
customElements.define("pnx-picture-legend", PictureLegend);