UNPKG

@panoramax/web-viewer

Version:

Panoramax web viewer for geolocated pictures

347 lines (305 loc) 9.59 kB
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"; import { PanoramaxMetaCatalogURL } from "../../utils/services"; /** * 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 }, _blockOnEditor: { 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-focused", () => { this._expanded = true; }); this._parent.psv.addEventListener("annotations-toggled", e => { if(e.detail.visible) { this._expanded = true; } }); if(this._parent.psv.getSelectedAnnotations().length > 0) { 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"); } } /** * Force legend to not go anywhere beyond tags editor. * @memberof Panoramax.components.menus.PictureLegend# * @param {boolean} enabled Set to true to block legend, false to unblock */ blockOnEditing(enabled) { this._blockOnEditor = enabled; } /** @private */ render() { /* eslint-disable indent */ if(!this._caption) { return nothing; } const lang = this._parent?.lang || window.navigator.language; 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=${PanoramaxMetaCatalogURL()+"/?pic="+this._parent.psv.getPictureMetadata().id} 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.date ? "-" : ""} ${this._caption.date ? new Intl.DateTimeFormat(lang, { timeZone: this._caption.tz, dateStyle: "short" }).format(this._caption.date) : nothing} ${(this._caption.producer?.length > 0 || this._caption.date) && this._caption?.license ? "|" : ""} ${this._caption?.license ? html`${unsafeStatic(this._caption.license)}` : nothing} `; } return html` <div class=${classMap({ "headline": true, "pnx-hidden": this._blockOnEditor })}> ${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">&nbsp;</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=${classMap({ "pnx-hidden": !this._expanded || this._blockOnEditor })}> <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} .block-editor=${this._blockOnEditor} ._parent=${this._parent} ></pnx-picture-metadata> ${this.collapsable && !this._blockOnEditor ? 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);