import { AvailableThermalPalettes, Instance, TimeFormat } from "@labir/core"; import { provide } from "@lit/context"; import { t } from "i18next"; import { css, CSSResultGroup, html, nothing, PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { cache } from 'lit/directives/cache.js'; import { ifDefined } from "lit/directives/if-defined.js"; import { createRef, ref, Ref } from "lit/directives/ref.js"; import { unsafeSVG } from "lit/directives/unsafe-svg.js"; import { version } from "../../../package.json"; import { BaseElement } from "../../hierarchy/BaseElement"; import { FileProviderElement } from "../../hierarchy/providers/FileProvider"; import { T } from "../../translations/Languages"; import { initLocalesInTopLevelElement, localeContext, localeConverter, Locales } from "../../translations/localeContext"; import { interactiveAnalysisContext } from "../../utils/context"; import { booleanConverter } from "../../utils/converters/booleanConverter"; import { BaseAppWithPngExportContext, pngExportFsContext, pngExportFsSetterContext, pngExportWidthContext, pngExportWidthSetterContext } from "../../utils/converters/pngExportContext"; enum Layout { NOGUI = "nogui", SIMPLE = "simple", ADVANCED = "advanced", LESSON = "lesson" } type LayoutItem = { key: string, icon: string } const layouts: LayoutItem[] = [ { key: Layout.SIMPLE, icon: ` ` }, { key: Layout.ADVANCED, icon: ` ` }, { key: Layout.LESSON, icon: ' layout_11_line ' } ]; const analysisSlotProperty = ["analysis1", "analysis2", "analysis3", "analysis4", "analysis5", "analysis6", "analysis7"]; @customElement("thermal-file-app") export class FileApp extends BaseAppWithPngExportContext { protected fileProviderRef: Ref = createRef(); @property({ type: String, reflect: true }) public layout: Layout = Layout.SIMPLE; @property({ type: String, reflect: true }) url?: string; @property({ type: String, reflect: true }) visible?: string; @property({ type: String, reflect: true, attribute: true }) palette: AvailableThermalPalettes = "jet"; @property({ type: Number, reflect: true }) from?: number; @property({ type: Number, reflect: true }) to?: number; @property({ type: Number, reflect: true }) opacity?: number = 1; @property() author?: string; @state() recorded?: string; @property() license?: string; @property() label?: string; @property({ type: String, reflect: false, converter: booleanConverter(true) }) showfullscreen: boolean = true; @property({ type: Boolean, reflect: true, converter: booleanConverter(true) }) showscale: boolean = true; @property({ type: Boolean, reflect: true, converter: booleanConverter(true) }) showhistogram: boolean = true; @property({ type: Boolean, reflect: true, converter: booleanConverter(false) }) showlayout: boolean = false; @property({ type: Boolean, reflect: true, converter: booleanConverter(false) }) showshare: boolean = false; @property({ type: String, reflect: true }) analysis1?: string; @property({ type: String, reflect: true }) analysis2?: string; @property({ type: String, reflect: true }) analysis3?: string; @property({ type: String, reflect: true }) analysis4?: string; @property({ type: String, reflect: true }) analysis5?: string; @property({ type: String, reflect: true }) analysis6?: string; @property({ type: String, reflect: true }) analysis7?: string; @provide({ context: localeContext }) @property({ reflect: true, converter: localeConverter }) public locale!: Locales; @provide({ context: interactiveAnalysisContext }) @property({ type: String, reflect: true, converter: booleanConverter(true) }) interactiveanalysis: boolean = true; @state() protected loading: boolean = true; @state() protected hasVisible: boolean = false; @state() ms: number = 0; @provide({ context: pngExportWidthContext }) protected pngExportWidth: number = 1200; @provide({ context: pngExportWidthSetterContext }) protected pngExportWidthSetterContext = (value: number) => { this.pngExportWidth = value; } @provide({ context: pngExportFsContext }) protected pngExportFs: number = 20; @provide({ context: pngExportFsSetterContext }) protected pngExportFsSetterContext = (value: number) => { this.pngExportFs = value; } protected _file?: Instance; @state() protected outerHTMLSnapshot?: string; protected get file() { if (this.fileProviderRef.value === undefined) return undefined; else return this.fileProviderRef.value.file; } protected firstUpdated(_changedProperties: PropertyValues): void { super.firstUpdated(_changedProperties); // Register intl listeners initLocalesInTopLevelElement(this); // Register listeners to internal properties of the file, group, registry this.hydrateInternalListeners(); } /** Listen to changes in @labir/core and reflect them to the webcomponent state */ protected hydrateInternalListeners() { if (this.fileProviderRef.value) { this.fileProviderRef.value.onSuccess.set(this.UUID, instance => { this.loading = false; this.recorded = TimeFormat.human(instance.timestamp); this.hasVisible = instance.visibleUrl !== undefined; /** Range changes */ instance.group.registry.range.addListener(this.UUID + "mirror_changes", value => { if (value === undefined) { this.from = undefined; this.to = undefined; } else { if (this.from !== value.from) this.from = value.from; if (this.to !== value.to) this.to = value.to; } }); /** Opacity changes */ instance.group.registry.opacity.addListener(this.UUID + "mirror_changes", value => { if (value !== this.opacity) { this.opacity = value; } }); /** Palette changes */ instance.group.registry.manager.palette.addListener(this.UUID + "mirror_changes", value => { if (this.palette !== value) { this.palette = value; } }); /** Analysis 1 changes */ instance.slots.onSlot1Serialize.set(this.UUID, value => { if (this.analysis1 !== value) { this.analysis1 = value; } }); /** Analysis 2 changes */ instance.slots.onSlot2Serialize.set(this.UUID, value => { if (this.analysis2 !== value) { this.analysis2 = value; } }); /** Analysis 3 changes */ instance.slots.onSlot3Serialize.set(this.UUID, value => { if (this.analysis3 !== value) { this.analysis3 = value; } }); /** Analysis 4 changes */ instance.slots.onSlot4Serialize.set(this.UUID, value => { if (this.analysis4 !== value) { this.analysis4 = value; } }); /** Analysis 5 changes */ instance.slots.onSlot5Serialize.set(this.UUID, value => { if (this.analysis5 !== value) { this.analysis5 = value; } }); /** Analysis 6 changes */ instance.slots.onSlot6Serialize.set(this.UUID, value => { if (this.analysis6 !== value) { this.analysis6 = value; } }); /** Analysis 7 changes */ instance.slots.onSlot7Serialize.set(this.UUID, value => { if (this.analysis7 !== value) { this.analysis7 = value; } }); }); } } protected updated(_changedProperties: PropertyValues): void { super.updated(_changedProperties); if (this.file !== undefined) { const group = this.file.group; const registry = group.registry; const manager = registry.manager; /** Propagate the range if necessary */ if (_changedProperties.has("from") && _changedProperties.has("to")) { if (this.from !== undefined && this.to !== undefined) { this.file.group.registry.range.imposeRange({ from: this.from, to: this.to }); } else { this.file.group.registry.range.imposeRange(undefined); } } /** Propagate the opacity if necessary */ if (_changedProperties.has("opacity")) { if (this.opacity !== undefined && this.opacity !== registry.opacity.value) { this.file.group.registry.opacity.imposeOpacity(this.opacity); } } /** Propagate the palette if necessary */ if (_changedProperties.has("palette")) { if (this.palette !== manager.palette.value) { manager.palette.setPalette(this.palette); } } /** Propagate the analyses if necessary */ analysisSlotProperty.forEach((slotParameterName, index) => { if (this.file === undefined) { return; } if (_changedProperties.has(slotParameterName as keyof FileApp)) { const slotNum = index + 1; const localSlotValue = this[slotParameterName as keyof FileApp] as string | undefined; const internalSlotValue = this.file.slots.getSlot(slotNum)?.serialized; if (localSlotValue !== internalSlotValue) { const slotObject = this.file.slots.getSlot(slotNum); if (localSlotValue !== undefined) { if (slotObject !== undefined) { slotObject.recieveSerialized(localSlotValue); } else { this.file.slots.createFromSerialized(localSlotValue, slotNum) } } else { if (this.file.slots.hasSlot(slotNum)) { this.file.slots.removeSlotAndAnalysis(slotNum); } } } } }); } this.outerHTMLSnapshot = this.outerHTML; } protected getLabel() { if (this.loading === true) return t(T.loading); else if (this.label !== undefined) return this.label; else if (this.label === undefined && this.file !== undefined) return this.file.fileName; else return t(T.file); } protected setLayout(value: Layout) { this.layout = value; setTimeout(() => { if (this.fileProviderRef.value && this.file) { this.fileProviderRef.value.redraw(); // this.updateNotationsMs(0); } }, 0); } protected renderNogui() { return html` ${this.renderScale()} `; } /** Render the */ protected renderApp() { return html` ${this.showlayout ? this.renderLayoutSwitch() : nothing} ${cache(html` ${this.hasVisible ? html`` : nothing} `)} ${this.showshare ? html`` : nothing} ${cache(html` ${t(T.config)}
` )}
${this.layout === Layout.ADVANCED || this.layout === Layout.LESSON ? this.renderScale() : nothing} ${cache(html``)}
${this.layout === Layout.ADVANCED ? html`` : html` ` }
${this.layout === Layout.SIMPLE ? html`` : nothing}
`; } protected renderScale() { return html`${this.showhistogram ? cache(html``) : nothing} ${this.showscale ? html`` : nothing} ${this.showhistogram || this.showscale ? html`` : nothing}`; } protected renderOneLayoutItem(icon: string, key: string, hasLabel: boolean = false) { return html`
${unsafeSVG(icon)} ${hasLabel ? html`${t(T[`layout_${key}` as T])}` : nothing}
`; } protected renderLayoutSwitch() { const currentLayout = layouts.find(layout => layout.key === this.layout); if (!currentLayout) { return nothing; } const otherLayouts = layouts.map(layout => { return { ...layout, action: layout.key !== this.layout ? () => this.setLayout(layout.key as Layout) : undefined } }); return html`
${this.renderOneLayoutItem(currentLayout.icon, currentLayout.key, false)}
${otherLayouts.map(l => html`
${this.renderOneLayoutItem(l.icon, l.key, true)}
`)}
`; } static styles?: CSSResultGroup | undefined = css` .layout-option { &.current { .layout-item { cursor: pointer; color: var(--thermal-foreground); &:hover { color: var(--thermal-primary); } } } &.available { .layout-item { opacity: .5; } } } .layout-item { display: flex; flex-wrap: nowrap; align-items: center; gap: 5px; svg { width: 1em; } span { font-size: 12px; } } .layout { display: grid; gap: var(--thermal-gap); .toolbar { grid-area: toolbar; } .thermogram { grid-area: thermogram; } .complex { grid-area: complex; } .notations { grid-area: notations; } .graph { grid-area: graph; } .analysis { grid-area: analysis; } &.layout__advanced { grid-template-columns: 2em 1fr calc(50% - var(--thermal-gap) ); grid-template-areas: "toolbar thermogram complex" "toolbar thermogram complex" "notations notations notations"; @media(max-width: 800px) { grid-template-columns: 2em calc(100% - 3em); grid-template-areas: "toolbar thermogram" "toolbar notations" "toolbar complex"; } } &.layout__simple { gap: 0px; grid-template-columns: 2.5em 1fr; grid-template-rows: auto; grid-template-areas: "toolbar thermogram" "toolbar analysis" "toolbar graph" "toolbar notations" "toolbar complex"; .analysis, .graph, .complex, .notations { // border: 10px solid red; } } &.layout__lesson { grid-template-columns: 2em 1fr calc(40% - var(--thermal-gap) ); grid-template-areas: "toolbar thermogram notations" "toolbar analysis graph"; @media(max-width: 800px) { grid-template-columns: 2em calc(100% - 3em); grid-template-areas: "toolbar thermogram" "toolbar notations" "toolbar analysis" "toolbar graph"; } .thermogram { padding: var(--thermal-gap); border: 1px solid var(--thermal-slate); border-radius: var(--thermal-radius); background: var(--thermal-background); } } } .share { svg { width: 1em; translateY: 3px; } pre { padding: var(--thermal-gap); border-radius: var(--thermal-radius); background: var(--thermal-background); color: var(--thermal-foreground); border: 1px solid var(--thermal-slate); white-space: pre-wrap; } } .button-fix { width: calc(var(--thermal-fs) * 1.2); height: calc(var(--thermal-fs) * 1.2); } `; protected render(): unknown { return html` ${this.layout === Layout.NOGUI ? this.renderNogui() : this.renderApp() } `; } }