UNPKG

@itk-viewer/element

Version:
216 lines (194 loc) 7.06 kB
import { LitElement, PropertyValues, css, html } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { ViewControls, ViewActor } from './view-controls-controller.js'; import { ref } from 'lit/directives/ref.js'; import '@shoelace-style/shoelace/dist/components/card/card.js'; import '@shoelace-style/shoelace/dist/components/range/range.js'; import '@shoelace-style/shoelace/dist/components/radio-group/radio-group.js'; import '@shoelace-style/shoelace/dist/components/radio-button/radio-button.js'; import '@shoelace-style/shoelace/dist/components/tab-group/tab-group.js'; import '@shoelace-style/shoelace/dist/components/tab/tab.js'; import '@shoelace-style/shoelace/dist/components/tab-panel/tab-panel.js'; // not used, but needed for selected tab underline to appear at the start import '@shoelace-style/shoelace/dist/components/dropdown/dropdown.js'; import '@shoelace-style/shoelace/dist/components/button/button.js'; import '@shoelace-style/shoelace/dist/components/menu/menu.js'; import '@shoelace-style/shoelace/dist/components/menu-item/menu-item.js'; @customElement('itk-view-controls-shoelace') export class ViewControlsShoelace extends LitElement { @property({ type: String }) view: '2d' | '3d' = '2d'; @property({ type: Boolean }) hideSliceUi: boolean = false; @property({ type: Boolean }) hideScaleUi: boolean = false; @property({ type: Boolean }) hideColorUi: boolean = false; @property({ type: Boolean }) hideTransferFunctionUi: boolean = false; actor: ViewActor | undefined; private controls = new ViewControls(this); @state() selectedComponent = 0; setActor(actor: ViewActor) { this.actor = actor; this.controls.setActor(actor); } transferFunctionContainerChanged(container: Element | undefined) { this.controls.setTransferFunctionContainer(container); } willUpdate(changedProperties: PropertyValues<this>) { if (changedProperties.has('view')) { this.controls.setView(this.view); } } render() { const slice = this.controls.slice?.value; const axis = this.controls.axis?.value; const imageDimension = this.controls.imageDimension?.value ?? 0; const scale = this.controls.scale?.value; const scaleCount = this.controls.scaleCount?.value ?? 1; const scaleOptions = Array.from( { length: scaleCount }, (_, i) => i, ).reverse(); const colorMapOptions = this.controls.colorMapsOptions?.value ?? {}; const componentCount = this.controls.componentCount?.value ?? 1; const showComponentSelector = !(this.hideColorUi && this.hideTransferFunctionUi) && componentCount > 1; const components = Array.from({ length: componentCount }, (_, i) => i); const colorMap = this.controls.colorMaps?.value[this.selectedComponent] ?? ''; const isImage3D = imageDimension >= 3; const showScale = !this.hideScaleUi && scaleCount >= 2; const tfEditorHeight = this.view === '2d' ? '2rem' : '8rem'; return html` <sl-card> ${isImage3D && this.view === '2d' && !this.hideSliceUi ? html` <sl-range value=${Number(slice)} @sl-change="${this.controls.onSlice}" min="0" max="1" step=".01" label="Slice" style="min-width: 8rem;" ></sl-range> <sl-radio-group label="Slice Axis" value=${axis} @sl-change="${this.controls.onAxis}" > <sl-radio-button value="I">X</sl-radio-button> <sl-radio-button value="J">Y</sl-radio-button> <sl-radio-button value="K">Z</sl-radio-button> </sl-radio-group> ` : ''} ${showScale ? html` <sl-radio-group label="Image Scale" value=${scale} @sl-change="${this.controls.onScale}" > ${scaleOptions.map( (option) => html`<sl-radio-button value=${option}> ${option} </sl-radio-button>`, )} </sl-radio-group> ` : ''} ${showComponentSelector ? html` <sl-tab-group style="max-width: 18rem" value=${this.controls.selectedComponent} @sl-tab-show="${(e: CustomEvent) => { const component = Number(e.detail.name); this.controls.onSelectedComponent(component); // trigger re-render to update color map value this.selectedComponent = Number(e.detail.name); }}" > ${components.map( (option) => html`<sl-tab panel="${option}" slot="nav"> Component ${option} </sl-tab>`, )} </sl-tab-group> ` : ''} ${this.hideColorUi ? '' : html` <sl-dropdown style="padding-top: .4rem; width: 100%"> <sl-button slot="trigger" caret class="color-button"> <sl-tooltip content=${colorMap}> <img src=${ colorMapOptions[colorMap] ?? '' } class='selected-swatch'></img> </sl-tooltip> </sl-button> <sl-menu @sl-select=${(e: CustomEvent) => { this.controls.onColorMap(e.detail.item.value); }} class="color-button" > ${Object.entries(colorMapOptions).map( ([value, icon]) => html`<sl-menu-item value=${value}> <div class='swatch'> <img src=${icon}></img> ${value} </div> </sl-menu-item>`, )} </sl-menu> </sl-dropdown> `} ${this.hideTransferFunctionUi ? '' : html` <div style="padding-top: 0.4rem;"> ${this.view === '2d' ? 'Color Range' : 'Opacity and Color'} </div> <div ${ref(this.transferFunctionContainerChanged)} style=${`width: 100%; min-width: 12rem; height: ${tfEditorHeight};`} ></div> `} </sl-card> `; } static styles = css` .color-button { width: 100%; } .color-button::part(label) { width: 100%; max-height: var(--sl-input-height-medium); padding: 0; padding-right: var(--sl-spacing-medium); } .selected-swatch { width: 100%; height: 100%; } .swatch { height: 100%; display: flex; } .swatch img { width: 8rem; height: 100%; padding-right: var(--sl-spacing-small); } `; } declare global { interface HTMLElementTagNameMap { 'itk-view-2d-controls-shoelace': ViewControlsShoelace; } }