583 lines
23 KiB
TypeScript
583 lines
23 KiB
TypeScript
|
|
import './index.css';
|
||
|
|
import type { ThemeConfig } from '../../themes/types';
|
||
|
|
import { IBimComponent } from '../../types/component';
|
||
|
|
import { t } from '../../services/locale';
|
||
|
|
import { MEASURE_TYPES, type ClearHeightDirection, type ClearHeightSelectType, type MeasureMode } from '../../types/measure';
|
||
|
|
import { getIcon } from '../../utils/icon-manager';
|
||
|
|
import type { MeasureConfig, MeasurePrecision, MeasureUnit } from '../measure-panel/types';
|
||
|
|
|
||
|
|
const PRIMARY_MODES: MeasureMode[] = ['distance', 'clearHeight', 'clearDistance', 'elevation'];
|
||
|
|
const SECONDARY_MODES: MeasureMode[] = ['point', 'angle', 'area', 'slope'];
|
||
|
|
const CONFIG_CACHE_KEY = 'bim-engine:measure:config';
|
||
|
|
const DEFAULT_CONFIG: MeasureConfig = {
|
||
|
|
unit: 'mm',
|
||
|
|
precision: 2
|
||
|
|
};
|
||
|
|
|
||
|
|
export interface MeasureDockPanelOptions {
|
||
|
|
defaultMode?: MeasureMode;
|
||
|
|
defaultExpanded?: boolean;
|
||
|
|
defaultClearHeightDirection?: ClearHeightDirection;
|
||
|
|
defaultClearHeightSelectType?: ClearHeightSelectType;
|
||
|
|
onModeChange?: (mode: MeasureMode) => void;
|
||
|
|
onClearAll?: () => void;
|
||
|
|
onSettings?: () => void;
|
||
|
|
onConfigSave?: (config: MeasureConfig) => void;
|
||
|
|
onClearHeightDirectionChange?: (direction: ClearHeightDirection) => void;
|
||
|
|
onClearHeightSelectTypeChange?: (selectType: ClearHeightSelectType) => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
export class MeasureDockPanel implements IBimComponent {
|
||
|
|
public readonly element: HTMLElement;
|
||
|
|
private readonly options: MeasureDockPanelOptions;
|
||
|
|
|
||
|
|
private readonly modeButtons: Map<MeasureMode, HTMLButtonElement> = new Map();
|
||
|
|
private readonly clearBtn: HTMLButtonElement;
|
||
|
|
private readonly expandBtn: HTMLButtonElement;
|
||
|
|
private readonly settingsBtn: HTMLButtonElement;
|
||
|
|
private readonly secondaryRow: HTMLElement;
|
||
|
|
private readonly clearHeightOptions: HTMLElement;
|
||
|
|
private readonly clearHeightDirectionLabel: HTMLElement;
|
||
|
|
private readonly clearHeightSelectTypeLabel: HTMLElement;
|
||
|
|
private readonly directionButtons: Map<ClearHeightDirection, HTMLButtonElement> = new Map();
|
||
|
|
private readonly selectTypeButtons: Map<ClearHeightSelectType, HTMLButtonElement> = new Map();
|
||
|
|
private readonly mainView: HTMLElement;
|
||
|
|
private readonly settingsView: HTMLElement;
|
||
|
|
private readonly settingsUnitLabel: HTMLElement;
|
||
|
|
private readonly settingsPrecisionLabel: HTMLElement;
|
||
|
|
private readonly settingsUnitSelect: HTMLSelectElement;
|
||
|
|
private readonly settingsPrecisionSelect: HTMLSelectElement;
|
||
|
|
private readonly settingsSaveBtn: HTMLButtonElement;
|
||
|
|
private readonly settingsBackBtn: HTMLButtonElement;
|
||
|
|
|
||
|
|
private activeMode: MeasureMode;
|
||
|
|
private isExpanded: boolean;
|
||
|
|
private clearHeightDirection: ClearHeightDirection;
|
||
|
|
private clearHeightSelectType: ClearHeightSelectType;
|
||
|
|
private view: 'main' | 'settings' = 'main';
|
||
|
|
private config: MeasureConfig;
|
||
|
|
private lockedWidthPx: number | null = null;
|
||
|
|
|
||
|
|
constructor(options: MeasureDockPanelOptions = {}) {
|
||
|
|
this.options = options;
|
||
|
|
this.activeMode = options.defaultMode ?? 'distance';
|
||
|
|
this.isExpanded = options.defaultExpanded ?? false;
|
||
|
|
this.clearHeightDirection = options.defaultClearHeightDirection ?? 1;
|
||
|
|
this.clearHeightSelectType = options.defaultClearHeightSelectType ?? 'point';
|
||
|
|
this.config = this.loadConfigFromCache() ?? { ...DEFAULT_CONFIG };
|
||
|
|
|
||
|
|
const {
|
||
|
|
root,
|
||
|
|
clearBtn,
|
||
|
|
expandBtn,
|
||
|
|
settingsBtn,
|
||
|
|
secondaryRow,
|
||
|
|
clearHeightOptions,
|
||
|
|
clearHeightDirectionLabel,
|
||
|
|
clearHeightSelectTypeLabel,
|
||
|
|
mainView,
|
||
|
|
settingsView,
|
||
|
|
settingsUnitLabel,
|
||
|
|
settingsPrecisionLabel,
|
||
|
|
settingsUnitSelect,
|
||
|
|
settingsPrecisionSelect,
|
||
|
|
settingsSaveBtn,
|
||
|
|
settingsBackBtn
|
||
|
|
} = this.createDom();
|
||
|
|
this.element = root;
|
||
|
|
this.clearBtn = clearBtn;
|
||
|
|
this.expandBtn = expandBtn;
|
||
|
|
this.settingsBtn = settingsBtn;
|
||
|
|
this.secondaryRow = secondaryRow;
|
||
|
|
this.clearHeightOptions = clearHeightOptions;
|
||
|
|
this.clearHeightDirectionLabel = clearHeightDirectionLabel;
|
||
|
|
this.clearHeightSelectTypeLabel = clearHeightSelectTypeLabel;
|
||
|
|
this.mainView = mainView;
|
||
|
|
this.settingsView = settingsView;
|
||
|
|
this.settingsUnitLabel = settingsUnitLabel;
|
||
|
|
this.settingsPrecisionLabel = settingsPrecisionLabel;
|
||
|
|
this.settingsUnitSelect = settingsUnitSelect;
|
||
|
|
this.settingsPrecisionSelect = settingsPrecisionSelect;
|
||
|
|
this.settingsSaveBtn = settingsSaveBtn;
|
||
|
|
this.settingsBackBtn = settingsBackBtn;
|
||
|
|
}
|
||
|
|
|
||
|
|
public init(): void {
|
||
|
|
this.setLocales();
|
||
|
|
this.syncSettingsFormFromConfig();
|
||
|
|
this.applyExpandedState();
|
||
|
|
this.applyClearHeightOptionsState();
|
||
|
|
this.applyViewState();
|
||
|
|
this.syncActiveMode(this.activeMode);
|
||
|
|
}
|
||
|
|
|
||
|
|
public setTheme(theme: ThemeConfig): void {
|
||
|
|
const style = this.element.style;
|
||
|
|
style.setProperty('--bim-text-primary', theme.textPrimary);
|
||
|
|
style.setProperty('--bim-text-secondary', theme.textSecondary);
|
||
|
|
style.setProperty('--bim-text-tertiary', theme.textTertiary);
|
||
|
|
style.setProperty('--bim-border-default', theme.borderDefault);
|
||
|
|
style.setProperty('--bim-border-strong', theme.borderStrong);
|
||
|
|
style.setProperty('--bim-bg-inset', theme.bgInset);
|
||
|
|
style.setProperty('--bim-bg-elevated', theme.bgElevated);
|
||
|
|
style.setProperty('--bim-primary', theme.primary);
|
||
|
|
style.setProperty('--bim-primary-subtle', theme.primarySubtle);
|
||
|
|
style.setProperty('--bim-danger', theme.danger);
|
||
|
|
style.setProperty('--bim-component-bg-hover', theme.componentBgHover);
|
||
|
|
}
|
||
|
|
|
||
|
|
public setLocales(): void {
|
||
|
|
for (const [mode, btn] of this.modeButtons.entries()) {
|
||
|
|
const text = t(`measure.modes.${mode}`);
|
||
|
|
btn.dataset.tooltip = text;
|
||
|
|
btn.setAttribute('aria-label', text);
|
||
|
|
}
|
||
|
|
|
||
|
|
const clearText = t('measure.actions.clearAll');
|
||
|
|
this.clearBtn.dataset.tooltip = clearText;
|
||
|
|
this.clearBtn.setAttribute('aria-label', clearText);
|
||
|
|
|
||
|
|
const expandText = this.isExpanded ? t('measure.actions.collapse') : t('measure.actions.expand');
|
||
|
|
delete this.expandBtn.dataset.tooltip;
|
||
|
|
this.expandBtn.setAttribute('aria-label', expandText);
|
||
|
|
|
||
|
|
const settingsText = t('measure.actions.settings');
|
||
|
|
this.settingsBtn.dataset.tooltip = settingsText;
|
||
|
|
this.settingsBtn.setAttribute('aria-label', settingsText);
|
||
|
|
|
||
|
|
this.clearHeightDirectionLabel.textContent = t('measure.clearHeight.direction');
|
||
|
|
this.clearHeightSelectTypeLabel.textContent = t('measure.clearHeight.selectType');
|
||
|
|
|
||
|
|
this.directionButtons.get(0)!.textContent = t('measure.clearHeight.directionDown');
|
||
|
|
this.directionButtons.get(1)!.textContent = t('measure.clearHeight.directionUp');
|
||
|
|
this.selectTypeButtons.get('point')!.textContent = t('measure.clearHeight.selectPoint');
|
||
|
|
this.selectTypeButtons.get('element')!.textContent = t('measure.clearHeight.selectElement');
|
||
|
|
|
||
|
|
this.settingsUnitLabel.textContent = t('measure.settings.unit');
|
||
|
|
this.settingsPrecisionLabel.textContent = t('measure.settings.precision');
|
||
|
|
this.settingsSaveBtn.textContent = t('measure.settings.save');
|
||
|
|
this.settingsBackBtn.textContent = t('measure.settings.cancel');
|
||
|
|
}
|
||
|
|
|
||
|
|
public switchMode(mode: MeasureMode, triggerCallback: boolean = true): void {
|
||
|
|
this.activeMode = mode;
|
||
|
|
this.syncActiveMode(mode);
|
||
|
|
this.applyClearHeightOptionsState();
|
||
|
|
this.closeSettingsView();
|
||
|
|
if (triggerCallback) {
|
||
|
|
this.options.onModeChange?.(mode);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (mode === 'clearHeight') {
|
||
|
|
this.options.onClearHeightDirectionChange?.(this.clearHeightDirection);
|
||
|
|
this.options.onClearHeightSelectTypeChange?.(this.clearHeightSelectType);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
public destroy(): void {
|
||
|
|
this.element.remove();
|
||
|
|
}
|
||
|
|
|
||
|
|
public getConfig(): MeasureConfig {
|
||
|
|
return { ...this.config };
|
||
|
|
}
|
||
|
|
|
||
|
|
private createDom(): {
|
||
|
|
root: HTMLElement;
|
||
|
|
clearBtn: HTMLButtonElement;
|
||
|
|
expandBtn: HTMLButtonElement;
|
||
|
|
settingsBtn: HTMLButtonElement;
|
||
|
|
secondaryRow: HTMLElement;
|
||
|
|
clearHeightOptions: HTMLElement;
|
||
|
|
clearHeightDirectionLabel: HTMLElement;
|
||
|
|
clearHeightSelectTypeLabel: HTMLElement;
|
||
|
|
mainView: HTMLElement;
|
||
|
|
settingsView: HTMLElement;
|
||
|
|
settingsUnitLabel: HTMLElement;
|
||
|
|
settingsPrecisionLabel: HTMLElement;
|
||
|
|
settingsUnitSelect: HTMLSelectElement;
|
||
|
|
settingsPrecisionSelect: HTMLSelectElement;
|
||
|
|
settingsSaveBtn: HTMLButtonElement;
|
||
|
|
settingsBackBtn: HTMLButtonElement;
|
||
|
|
} {
|
||
|
|
const root = document.createElement('div');
|
||
|
|
root.className = 'measure-dock-panel';
|
||
|
|
|
||
|
|
const mainView = document.createElement('div');
|
||
|
|
mainView.className = 'measure-dock-panel-main';
|
||
|
|
|
||
|
|
const clearHeightOptions = document.createElement('div');
|
||
|
|
clearHeightOptions.className = 'measure-dock-clearheight-options';
|
||
|
|
|
||
|
|
const directionGroup = document.createElement('div');
|
||
|
|
directionGroup.className = 'measure-dock-clearheight-group';
|
||
|
|
const clearHeightDirectionLabel = document.createElement('span');
|
||
|
|
clearHeightDirectionLabel.className = 'measure-dock-clearheight-label';
|
||
|
|
const directionButtons = document.createElement('div');
|
||
|
|
directionButtons.className = 'measure-dock-clearheight-buttons';
|
||
|
|
|
||
|
|
const directionDown = this.createClearHeightOptionButton(() => {
|
||
|
|
this.setClearHeightDirection(0);
|
||
|
|
});
|
||
|
|
const directionUp = this.createClearHeightOptionButton(() => {
|
||
|
|
this.setClearHeightDirection(1);
|
||
|
|
});
|
||
|
|
this.directionButtons.set(0, directionDown);
|
||
|
|
this.directionButtons.set(1, directionUp);
|
||
|
|
directionButtons.appendChild(directionDown);
|
||
|
|
directionButtons.appendChild(directionUp);
|
||
|
|
directionGroup.appendChild(clearHeightDirectionLabel);
|
||
|
|
directionGroup.appendChild(directionButtons);
|
||
|
|
|
||
|
|
const selectTypeGroup = document.createElement('div');
|
||
|
|
selectTypeGroup.className = 'measure-dock-clearheight-group';
|
||
|
|
const clearHeightSelectTypeLabel = document.createElement('span');
|
||
|
|
clearHeightSelectTypeLabel.className = 'measure-dock-clearheight-label';
|
||
|
|
const selectTypeButtons = document.createElement('div');
|
||
|
|
selectTypeButtons.className = 'measure-dock-clearheight-buttons';
|
||
|
|
|
||
|
|
const selectPoint = this.createClearHeightOptionButton(() => {
|
||
|
|
this.setClearHeightSelectType('point');
|
||
|
|
});
|
||
|
|
const selectElement = this.createClearHeightOptionButton(() => {
|
||
|
|
this.setClearHeightSelectType('element');
|
||
|
|
});
|
||
|
|
this.selectTypeButtons.set('point', selectPoint);
|
||
|
|
this.selectTypeButtons.set('element', selectElement);
|
||
|
|
selectTypeButtons.appendChild(selectPoint);
|
||
|
|
selectTypeButtons.appendChild(selectElement);
|
||
|
|
selectTypeGroup.appendChild(clearHeightSelectTypeLabel);
|
||
|
|
selectTypeGroup.appendChild(selectTypeButtons);
|
||
|
|
|
||
|
|
clearHeightOptions.appendChild(directionGroup);
|
||
|
|
clearHeightOptions.appendChild(selectTypeGroup);
|
||
|
|
|
||
|
|
const top = document.createElement('div');
|
||
|
|
top.className = 'measure-dock-panel-top';
|
||
|
|
|
||
|
|
const modeZone = document.createElement('div');
|
||
|
|
modeZone.className = 'measure-dock-panel-mode-zone';
|
||
|
|
|
||
|
|
const primaryRow = document.createElement('div');
|
||
|
|
primaryRow.className = 'measure-dock-panel-mode-row';
|
||
|
|
PRIMARY_MODES.forEach((mode) => {
|
||
|
|
primaryRow.appendChild(this.createModeButton(mode));
|
||
|
|
});
|
||
|
|
|
||
|
|
const secondaryRow = document.createElement('div');
|
||
|
|
secondaryRow.className = 'measure-dock-panel-mode-row measure-dock-panel-mode-row-secondary';
|
||
|
|
SECONDARY_MODES.forEach((mode) => {
|
||
|
|
secondaryRow.appendChild(this.createModeButton(mode));
|
||
|
|
});
|
||
|
|
|
||
|
|
const clearBtn = this.createIconButton('measure-dock-panel-action-clear', getIcon('delete'));
|
||
|
|
clearBtn.addEventListener('click', () => {
|
||
|
|
this.options.onClearAll?.();
|
||
|
|
});
|
||
|
|
|
||
|
|
const settingsBtn = this.createIconButton('measure-dock-panel-action-settings', getIcon('settings'));
|
||
|
|
settingsBtn.addEventListener('click', () => {
|
||
|
|
this.openSettingsView();
|
||
|
|
this.options.onSettings?.();
|
||
|
|
});
|
||
|
|
|
||
|
|
primaryRow.appendChild(clearBtn);
|
||
|
|
secondaryRow.appendChild(settingsBtn);
|
||
|
|
|
||
|
|
modeZone.appendChild(primaryRow);
|
||
|
|
modeZone.appendChild(secondaryRow);
|
||
|
|
|
||
|
|
const actionZone = document.createElement('div');
|
||
|
|
actionZone.className = 'measure-dock-panel-actions';
|
||
|
|
|
||
|
|
const expandBtn = this.createIconButton('measure-dock-panel-action-expand', getIcon('expand'));
|
||
|
|
expandBtn.addEventListener('click', () => {
|
||
|
|
this.isExpanded = !this.isExpanded;
|
||
|
|
this.applyExpandedState();
|
||
|
|
this.setLocales();
|
||
|
|
});
|
||
|
|
|
||
|
|
actionZone.appendChild(expandBtn);
|
||
|
|
|
||
|
|
top.appendChild(modeZone);
|
||
|
|
top.appendChild(actionZone);
|
||
|
|
|
||
|
|
mainView.appendChild(clearHeightOptions);
|
||
|
|
mainView.appendChild(top);
|
||
|
|
|
||
|
|
const {
|
||
|
|
settingsView,
|
||
|
|
settingsUnitLabel,
|
||
|
|
settingsPrecisionLabel,
|
||
|
|
settingsUnitSelect,
|
||
|
|
settingsPrecisionSelect,
|
||
|
|
settingsSaveBtn,
|
||
|
|
settingsBackBtn
|
||
|
|
} = this.createSettingsView();
|
||
|
|
|
||
|
|
root.appendChild(mainView);
|
||
|
|
root.appendChild(settingsView);
|
||
|
|
|
||
|
|
return {
|
||
|
|
root,
|
||
|
|
clearBtn,
|
||
|
|
expandBtn,
|
||
|
|
settingsBtn,
|
||
|
|
secondaryRow,
|
||
|
|
clearHeightOptions,
|
||
|
|
clearHeightDirectionLabel,
|
||
|
|
clearHeightSelectTypeLabel,
|
||
|
|
mainView,
|
||
|
|
settingsView,
|
||
|
|
settingsUnitLabel,
|
||
|
|
settingsPrecisionLabel,
|
||
|
|
settingsUnitSelect,
|
||
|
|
settingsPrecisionSelect,
|
||
|
|
settingsSaveBtn,
|
||
|
|
settingsBackBtn
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
private createSettingsView(): {
|
||
|
|
settingsView: HTMLElement;
|
||
|
|
settingsUnitLabel: HTMLElement;
|
||
|
|
settingsPrecisionLabel: HTMLElement;
|
||
|
|
settingsUnitSelect: HTMLSelectElement;
|
||
|
|
settingsPrecisionSelect: HTMLSelectElement;
|
||
|
|
settingsSaveBtn: HTMLButtonElement;
|
||
|
|
settingsBackBtn: HTMLButtonElement;
|
||
|
|
} {
|
||
|
|
const settingsView = document.createElement('div');
|
||
|
|
settingsView.className = 'measure-dock-panel-settings';
|
||
|
|
|
||
|
|
const unitRow = document.createElement('div');
|
||
|
|
unitRow.className = 'measure-dock-settings-row';
|
||
|
|
const settingsUnitLabel = document.createElement('span');
|
||
|
|
settingsUnitLabel.className = 'measure-dock-settings-label';
|
||
|
|
const settingsUnitSelect = document.createElement('select');
|
||
|
|
settingsUnitSelect.className = 'measure-dock-settings-select';
|
||
|
|
['m', 'cm', 'mm', 'km'].forEach((unit) => {
|
||
|
|
const option = document.createElement('option');
|
||
|
|
option.value = unit;
|
||
|
|
option.textContent = unit;
|
||
|
|
settingsUnitSelect.appendChild(option);
|
||
|
|
});
|
||
|
|
unitRow.appendChild(settingsUnitLabel);
|
||
|
|
unitRow.appendChild(settingsUnitSelect);
|
||
|
|
|
||
|
|
const precisionRow = document.createElement('div');
|
||
|
|
precisionRow.className = 'measure-dock-settings-row';
|
||
|
|
const settingsPrecisionLabel = document.createElement('span');
|
||
|
|
settingsPrecisionLabel.className = 'measure-dock-settings-label';
|
||
|
|
const settingsPrecisionSelect = document.createElement('select');
|
||
|
|
settingsPrecisionSelect.className = 'measure-dock-settings-select';
|
||
|
|
[0, 1, 2, 3].forEach((precision) => {
|
||
|
|
const option = document.createElement('option');
|
||
|
|
option.value = String(precision);
|
||
|
|
option.textContent = precision === 0 ? '0' : `0.${'0'.repeat(precision)}`;
|
||
|
|
settingsPrecisionSelect.appendChild(option);
|
||
|
|
});
|
||
|
|
precisionRow.appendChild(settingsPrecisionLabel);
|
||
|
|
precisionRow.appendChild(settingsPrecisionSelect);
|
||
|
|
|
||
|
|
const actions = document.createElement('div');
|
||
|
|
actions.className = 'measure-dock-settings-actions';
|
||
|
|
const settingsSaveBtn = document.createElement('button');
|
||
|
|
settingsSaveBtn.type = 'button';
|
||
|
|
settingsSaveBtn.className = 'measure-dock-settings-btn is-save';
|
||
|
|
settingsSaveBtn.addEventListener('click', () => {
|
||
|
|
this.saveSettings();
|
||
|
|
});
|
||
|
|
const settingsBackBtn = document.createElement('button');
|
||
|
|
settingsBackBtn.type = 'button';
|
||
|
|
settingsBackBtn.className = 'measure-dock-settings-btn is-back';
|
||
|
|
settingsBackBtn.addEventListener('click', () => {
|
||
|
|
this.closeSettingsView();
|
||
|
|
});
|
||
|
|
actions.appendChild(settingsSaveBtn);
|
||
|
|
actions.appendChild(settingsBackBtn);
|
||
|
|
|
||
|
|
settingsView.appendChild(unitRow);
|
||
|
|
settingsView.appendChild(precisionRow);
|
||
|
|
settingsView.appendChild(actions);
|
||
|
|
|
||
|
|
return {
|
||
|
|
settingsView,
|
||
|
|
settingsUnitLabel,
|
||
|
|
settingsPrecisionLabel,
|
||
|
|
settingsUnitSelect,
|
||
|
|
settingsPrecisionSelect,
|
||
|
|
settingsSaveBtn,
|
||
|
|
settingsBackBtn
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
private createClearHeightOptionButton(onClick: () => void): HTMLButtonElement {
|
||
|
|
const button = document.createElement('button');
|
||
|
|
button.type = 'button';
|
||
|
|
button.className = 'measure-dock-clearheight-btn';
|
||
|
|
button.addEventListener('click', onClick);
|
||
|
|
return button;
|
||
|
|
}
|
||
|
|
|
||
|
|
private createModeButton(mode: MeasureMode): HTMLButtonElement {
|
||
|
|
const button = document.createElement('button');
|
||
|
|
button.type = 'button';
|
||
|
|
button.className = 'measure-dock-panel-mode-btn';
|
||
|
|
button.dataset.mode = mode;
|
||
|
|
button.innerHTML = `<span class="measure-dock-panel-mode-icon">${MEASURE_TYPES[mode].icon}</span>`;
|
||
|
|
button.addEventListener('click', () => {
|
||
|
|
this.switchMode(mode);
|
||
|
|
});
|
||
|
|
this.modeButtons.set(mode, button);
|
||
|
|
return button;
|
||
|
|
}
|
||
|
|
|
||
|
|
private createIconButton(className: string, iconSvg: string): HTMLButtonElement {
|
||
|
|
const button = document.createElement('button');
|
||
|
|
button.type = 'button';
|
||
|
|
button.className = `measure-dock-panel-action-btn ${className}`;
|
||
|
|
button.innerHTML = iconSvg;
|
||
|
|
return button;
|
||
|
|
}
|
||
|
|
|
||
|
|
private applyExpandedState(): void {
|
||
|
|
this.secondaryRow.style.display = this.isExpanded ? 'grid' : 'none';
|
||
|
|
this.expandBtn.classList.toggle('is-expanded', this.isExpanded);
|
||
|
|
this.expandBtn.classList.toggle('is-collapsed', !this.isExpanded);
|
||
|
|
}
|
||
|
|
|
||
|
|
private openSettingsView(): void {
|
||
|
|
this.lockPanelWidth();
|
||
|
|
this.view = 'settings';
|
||
|
|
this.syncSettingsFormFromConfig();
|
||
|
|
this.applyViewState();
|
||
|
|
}
|
||
|
|
|
||
|
|
private closeSettingsView(): void {
|
||
|
|
if (this.view !== 'settings') {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.view = 'main';
|
||
|
|
this.unlockPanelWidth();
|
||
|
|
this.applyViewState();
|
||
|
|
}
|
||
|
|
|
||
|
|
private saveSettings(): void {
|
||
|
|
const nextUnit = this.settingsUnitSelect.value as MeasureUnit;
|
||
|
|
const nextPrecision = Number(this.settingsPrecisionSelect.value) as MeasurePrecision;
|
||
|
|
if (!this.isValidUnit(nextUnit) || !this.isValidPrecision(nextPrecision)) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
this.config = {
|
||
|
|
unit: nextUnit,
|
||
|
|
precision: nextPrecision
|
||
|
|
};
|
||
|
|
this.saveConfigToCache(this.config);
|
||
|
|
this.options.onConfigSave?.(this.getConfig());
|
||
|
|
this.view = 'main';
|
||
|
|
this.unlockPanelWidth();
|
||
|
|
this.applyViewState();
|
||
|
|
}
|
||
|
|
|
||
|
|
private lockPanelWidth(): void {
|
||
|
|
const width = this.element.getBoundingClientRect().width;
|
||
|
|
if (width <= 0) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.lockedWidthPx = Math.ceil(width);
|
||
|
|
this.element.style.width = `${this.lockedWidthPx}px`;
|
||
|
|
}
|
||
|
|
|
||
|
|
private unlockPanelWidth(): void {
|
||
|
|
this.lockedWidthPx = null;
|
||
|
|
this.element.style.removeProperty('width');
|
||
|
|
}
|
||
|
|
|
||
|
|
private syncSettingsFormFromConfig(): void {
|
||
|
|
this.settingsUnitSelect.value = this.config.unit;
|
||
|
|
this.settingsPrecisionSelect.value = String(this.config.precision);
|
||
|
|
}
|
||
|
|
|
||
|
|
private applyViewState(): void {
|
||
|
|
const showMain = this.view === 'main';
|
||
|
|
this.mainView.style.display = showMain ? 'block' : 'none';
|
||
|
|
this.settingsView.style.display = showMain ? 'none' : 'flex';
|
||
|
|
}
|
||
|
|
|
||
|
|
private applyClearHeightOptionsState(): void {
|
||
|
|
// this.clearHeightOptions.classList.toggle('is-visible', this.activeMode === 'clearHeight');
|
||
|
|
this.clearHeightOptions.classList.remove('is-visible');
|
||
|
|
|
||
|
|
for (const [direction, button] of this.directionButtons.entries()) {
|
||
|
|
button.classList.toggle('is-active', direction === this.clearHeightDirection);
|
||
|
|
}
|
||
|
|
for (const [selectType, button] of this.selectTypeButtons.entries()) {
|
||
|
|
button.classList.toggle('is-active', selectType === this.clearHeightSelectType);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private setClearHeightDirection(direction: ClearHeightDirection): void {
|
||
|
|
if (this.clearHeightDirection === direction) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.clearHeightDirection = direction;
|
||
|
|
this.applyClearHeightOptionsState();
|
||
|
|
this.options.onClearHeightDirectionChange?.(direction);
|
||
|
|
}
|
||
|
|
|
||
|
|
private setClearHeightSelectType(selectType: ClearHeightSelectType): void {
|
||
|
|
if (this.clearHeightSelectType === selectType) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
this.clearHeightSelectType = selectType;
|
||
|
|
this.applyClearHeightOptionsState();
|
||
|
|
this.options.onClearHeightSelectTypeChange?.(selectType);
|
||
|
|
}
|
||
|
|
|
||
|
|
private loadConfigFromCache(): MeasureConfig | null {
|
||
|
|
try {
|
||
|
|
const raw = localStorage.getItem(CONFIG_CACHE_KEY);
|
||
|
|
if (!raw) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
const parsed = JSON.parse(raw) as Partial<MeasureConfig>;
|
||
|
|
if (!parsed || typeof parsed !== 'object') {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
if (!this.isValidUnit(parsed.unit) || !this.isValidPrecision(parsed.precision)) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
unit: parsed.unit,
|
||
|
|
precision: parsed.precision
|
||
|
|
};
|
||
|
|
} catch {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private saveConfigToCache(config: MeasureConfig): void {
|
||
|
|
try {
|
||
|
|
localStorage.setItem(CONFIG_CACHE_KEY, JSON.stringify(config));
|
||
|
|
} catch {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private isValidUnit(unit: unknown): unit is MeasureUnit {
|
||
|
|
return unit === 'm' || unit === 'cm' || unit === 'mm' || unit === 'km';
|
||
|
|
}
|
||
|
|
|
||
|
|
private isValidPrecision(precision: unknown): precision is MeasurePrecision {
|
||
|
|
return precision === 0 || precision === 1 || precision === 2 || precision === 3;
|
||
|
|
}
|
||
|
|
|
||
|
|
private syncActiveMode(mode: MeasureMode): void {
|
||
|
|
for (const [key, button] of this.modeButtons.entries()) {
|
||
|
|
button.classList.toggle('is-active', key === mode);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|