@panoramax/web-viewer
Version:
Panoramax web viewer for geolocated pictures
368 lines (330 loc) • 11.5 kB
JavaScript
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}
=${this._onSaveTag}
>
${fa(faFloppyDisk)}
</pnx-button>
<pnx-button
kind="fullwarn"
title=${this._parent?._t.pnx.semantics_undo}
=${() => 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%"
=${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}
=${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"
=${this._startAddTag}
>
${fa(faPlusSquare)} ${this._parent?._t.pnx.semantics_add_annotation}
</pnx-button>
<pnx-button
kind="full"
size="md"
style="flex: 1 1"
=${() => this._parent?._showSemanticsDoc()}
>${fa(faInfoCircle)} ${this._parent?._t.pnx.semantics_doc}</pnx-button>
</div>
` : nothing}
<pnx-semantics-list
id="pnx-sem-list"
.editable=${canEdit}
=${e => this._showLinks = e.detail.item == null}
-click=${this._onEditTag}
-click=${this._onDeleteTag}
._parent=${this._parent}
/>
`}
`;
}
}
customElements.define("pnx-semantics-metadata", SemanticsMetadata);