UNPKG

@panoramax/web-viewer

Version:

Panoramax web viewer for geolocated pictures

368 lines (330 loc) 11.5 kB
import { LitElement, html, nothing, css } from "lit"; import { fa, onceParentAvailable } from "../../utils/widgets"; import { faFloppyDisk } from "@fortawesome/free-solid-svg-icons/faFloppyDisk"; import { faTimes } from "@fortawesome/free-solid-svg-icons/faTimes"; import { faPlusSquare } from "@fortawesome/free-solid-svg-icons/faPlusSquare"; import { faEraser } from "@fortawesome/free-solid-svg-icons/faEraser"; import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle"; import { hidden, dataBlocks } from "../styles"; import { getUserAccount } from "../../utils/utils"; /** * Semantics metadata displays detailed info about semantic attributes of a single picture. * @class Panoramax.components.menus.SemanticsMetadata * @element pnx-semantics-metadata * @extends [lit.LitElement](https://lit.dev/docs/api/LitElement/) * @example * ```html * <pnx-semantics-metadata ._parent=${viewer}></pnx-semantics-metadata> * ``` */ export default class SemanticsMetadata extends LitElement { /** @private */ static styles = [ hidden, dataBlocks, css` .top-btns, pnx-semantics-list { font-size: 0.8em; } .top-btns { display: flex; margin: 5px 0; gap: 5px; } .top-btns > * { overflow: hidden; } .top-btns > *::part(btn) { height: unset; } .data-block { width: 100%; } .data-block h5 { justify-content: space-between; } ` ]; /** @private */ static properties = { _meta: {state: true}, _prevPSVPicNav: {state: true}, _authUrl: {state: true}, _showLinks: {state: true}, _addTag: {state: true}, _editTag: {state: true}, _addTagGeom: {state: true}, _editTagSem: {state: true}, }; constructor() { super(); this._meta = {}; this._prevPSVPicNav = null; this._authUrl = null; this._showLinks = true; this._addTag = false; this._editTag = null; this._addTagGeom = null; this._editTagSem = null; } /** @private */ connectedCallback() { super.connectedCallback(); onceParentAvailable(this).then(() => { this._meta = this._parent?.psv?.getPictureMetadata(); this._parent?.oncePSVReady?.().then(() => { this._parent.psv.addEventListener("picture-loaded", () => { this._meta = this._parent.psv.getPictureMetadata(); }); this._parent.psv.addEventListener("annotation-focused", () => { const tabs = this.shadowRoot.querySelector("pnx-tabs"); if(tabs) { tabs.setAttribute("activeTabIndex", 4); } }); }); this._parent.api.getAuthURL().then(res => this._authUrl = res); }); } /** @private */ _blockPicChange(block) { this._parent?.legend?.renderRoot.querySelector("pnx-picture-legend")?.blockOnEditing(block); if(this._parent?.mini) { this._parent.mini.style.display = block ? "none" : null; } const answ = this._parent.renderRoot.querySelector("pnx-annotations-switch"); if(answ) { answ.style.display = block ? "none": null; } if(block) { this._prevPSVPicNav = this._parent.psv.getPicturesNavigation(); this._parent.psv.setPicturesNavigation("pic"); this._parent._disableKeyboard(); window.addEventListener("beforeunload", this._onBrowserQuit); } else { this._parent.psv.setPicturesNavigation(this._prevPSVPicNav); this._parent._enableKeyboard(); window.removeEventListener("beforeunload", this._onBrowserQuit); } } /** @private */ _onBrowserQuit(e) { e.preventDefault(); e.returnValue = true; } /** @private */ _checkCanEdit() { if(!this._meta.origInstance && getUserAccount() !== null) { return true; } else { if(this._meta.origInstance && confirm((this._parent?._t.pnx.semantics_login_needed || "").replace("{n}", this._meta.origInstance.instance_name || this._meta.origInstance.href))) { window.location.href = this._meta.origInstance.href + window.location.search.replace(/&?nav=pic/, ""); } else if(!this._meta.origInstance && confirm((this._parent?._t.pnx.semantics_login_needed || "").replace("{n}", this._parent.api._metadata.name))) { window.location.href = this._authUrl.replace("<CBURL>", encodeURIComponent(window.location.href)); } else { return false; } } } /** @private */ _startAddTag() { if(!this._checkCanEdit()) { return; } this._blockPicChange(true); this._addTag = true; this._editTag = false; this._addTagGeom = null; this._editTagSem = null; this._addTagGeomListener = e => this._addTagGeom = e.detail.shape; this._parent.psv.addEventListener("annotation-drawn", this._addTagGeomListener); this._parent.psv.startDrawAnnotation(); } /** @private */ _onTagSemChange(e) { this._editTagSem = e.detail; } /** @private */ _onSaveTag() { const field = this.renderRoot.querySelector("#pnx-sem-editor"); // Check field validity if(!field || !field.checkValidity() || !this._editTagSem || !this._editTagSem.delta || this._editTagSem.delta.length === 0) { field.reportValidity(); return; } // Edit an annotation if(this._editTag?.type === "annotation") { this._parent?.api.editPictureAnnotation(this._editTag.annotation.id, this._editTagSem.delta).then(newAnnot => { const annotPos = this._meta.properties.annotations.findIndex(a => a.id === newAnnot.id); this._meta.properties.annotations[annotPos] = newAnnot; this._onCancelEditTag(newAnnot); }).catch(e => { console.error("Can't send annotation", e); this._onCancelEditTag(this._editTag.annotation); alert(this._parent?._t.pnx.semantics_send_fail); }); } // Create annotation if a geom exists else if(this._addTag && this._addTagGeom) { this._parent.psv.fixDrawAnnotation(); this._parent?.api.createPictureAnnotation(this._meta, this._addTagGeom, this._editTagSem.delta).then(newAnnot => { this._meta.properties.annotations.push(newAnnot); this._onCancelEditTag(newAnnot, true); }).catch(e => { console.error("Can't send annotation", e); alert(this._parent?._t.pnx.semantics_send_fail); this._onCancelEditTag(); }); } // Create picture tag or edit a tag group else if(this._addTag || (this._editTag && this._editTag.type !== "annotation")) { this._parent?.api.sendPictureSemantics(this._meta, this._editTagSem.delta).then(newPic => { this._meta.properties.semantics = newPic.properties.semantics; this._onCancelEditTag(this._editTagSem.delta); }).catch(e => { alert(this._parent?._t.pnx.semantics_send_fail); console.error("Can't send semantics", e); this._onCancelEditTag(); }); } } /** @private */ _onEraseDrawnAnnotation() { this._parent.psv.stopDrawAnnotation(); this._addTagGeom = null; this._parent.psv.startDrawAnnotation(); } /** @private */ _onEditTag(e) { if(!this._checkCanEdit()) { return; } this._blockPicChange(true); this._addTag = false; this._addTagGeom = null; this._editTagSem = e.detail.item?.type === "annotation" ? { semantics: e.detail.item.annotation.semantics } : e.detail.item.tagGroup; this._editTag = e.detail.item; if(e.detail.item?.type === "annotation") { this._parent.psv.focusOnAnnotation(e.detail.item.annotation.id, true); } } /** @private */ _onCancelEditTag(item = null, skipStopDraw = false) { // Force hiding of report status on tag editor this.renderRoot.querySelector("#pnx-sem-editor").reset(); this._addTag = false; this._addTagGeom = null; this._editTag = null; if(!skipStopDraw) { this._parent.psv.stopDrawAnnotation(); } this._blockPicChange(false); // Select back item in list this.getUpdateComplete().then(() => { const list = this.renderRoot.querySelector("#pnx-sem-list"); if(item && list) { if(item.id) { list.showAnnotation(item.id); } else { list.showTagsGroup(item); } } }); } /** @private */ _onDeleteTag(e) { if(!this._checkCanEdit()) { return; } if(confirm(this._parent?._t.pnx.semantics_delete_annotation_confirm)) { const item = e.detail.item; // Annotation deleting if(item?.type === "annotation") { this._parent?.api.deletePictureAnnotation(item.annotation.id).then(() => { // Remove from current metadata const newMeta = Object.assign({}, this._meta); newMeta.properties.annotations = this._meta.properties.annotations.filter(a => a.id != item.annotation.id); this._meta = newMeta; // Remove from PSV this._parent.psv.toggleAllAnnotations(true); // Remove from annotations list this.renderRoot.querySelector("pnx-semantics-list")?._onPicChange(); }).catch(e => { console.error("Can't send annotation", e); alert(this._parent?._t.pnx.semantics_send_fail); }); } // Tag group deletion else { const semDiff = item.tagGroup.semantics.map(t => Object.assign({action: "delete"}, t)); this._parent?.api.sendPictureSemantics(this._meta, semDiff).then(newPic => { this._meta.properties.semantics = newPic.properties.semantics; this.renderRoot.querySelector("pnx-semantics-list")?._onPicChange(); }).catch(e => { alert(this._parent?._t.pnx.semantics_send_fail); console.error("Can't send semantics", e); }); } } } /** @private */ render() { /* eslint-disable indent */ if(!this._meta?.properties) { return; } const canEdit = !this._meta.origInstance && (this._authUrl || getUserAccount() !== null); return html` ${(this._addTag || this._editTag) ? html` <div class="data-block"> <h5> ${this._addTag ? this._parent?._t.pnx.semantics_add_annotation : this._parent?._t.pnx.semantics_edit} <pnx-button-group> <pnx-button kind="fullsuccess" style="margin-right: 3px" title=${this._parent?._t.pnx.semantics_save} @click=${this._onSaveTag} > ${fa(faFloppyDisk)} </pnx-button> <pnx-button kind="fullwarn" title=${this._parent?._t.pnx.semantics_undo} @click=${() => this._onCancelEditTag(this._editTag?.annotation || this._editTag)} > ${fa(faTimes)} </pnx-button> </pnx-button-group> </h5> <div> ${this._addTag ? this._parent?._t.pnx.semantics_draw_annotation : nothing} ${this._addTag && this._addTagGeom ? html`<pnx-button kind="full" style="margin-top: 5px; width: 100%" @click=${this._onEraseDrawnAnnotation} > ${fa(faEraser)} ${this._parent?._t.pnx.semantics_erase_annotation} </pnx-button>` : nothing} <pnx-semantics-editor id="pnx-sem-editor" style="display: block; margin-top: 5px" .semantics=${this._editTagSem?.semantics || []} ._t=${this._parent._t} @change=${this._onTagSemChange} ></pnx-semantics-editor> </div> </div> ` : html` ${this._showLinks ? html ` <div class="top-btns"> <pnx-button kind="full" size="md" style="flex: 2 1" @click=${this._startAddTag} > ${fa(faPlusSquare)} ${this._parent?._t.pnx.semantics_add_annotation} </pnx-button> <pnx-button kind="full" size="md" style="flex: 1 1" @click=${() => this._parent?._showSemanticsDoc()} >${fa(faInfoCircle)} ${this._parent?._t.pnx.semantics_doc}</pnx-button> </div> ` : nothing} <pnx-semantics-list id="pnx-sem-list" .editable=${canEdit} @select=${e => this._showLinks = e.detail.item == null} @edit-click=${this._onEditTag} @delete-click=${this._onDeleteTag} ._parent=${this._parent} /> `} `; } } customElements.define("pnx-semantics-metadata", SemanticsMetadata);