feat: 迁移BimEngine到radial+dock并完善测量/剖切/漫游面板
This commit is contained in:
@@ -144,13 +144,25 @@ export class BottomDockManager extends BaseManager {
|
||||
this.register({
|
||||
id: 'section',
|
||||
title: '剖切',
|
||||
createContent: () => this.stack.createPlaceholderContent('剖切面板占位')
|
||||
createContent: () => {
|
||||
const sectionPanel = this.registry.sectionDock?.getPanelElement();
|
||||
if (sectionPanel) {
|
||||
return sectionPanel;
|
||||
}
|
||||
return this.stack.createPlaceholderContent('剖切面板占位');
|
||||
}
|
||||
});
|
||||
|
||||
this.register({
|
||||
id: 'walk',
|
||||
title: '漫游',
|
||||
createContent: () => this.stack.createPlaceholderContent('漫游面板占位')
|
||||
createContent: () => {
|
||||
const walkPanel = this.registry.walkDock?.getPanelElement();
|
||||
if (walkPanel) {
|
||||
return walkPanel;
|
||||
}
|
||||
return this.stack.createPlaceholderContent('漫游面板占位');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
123
src/managers/section-dock-manager.ts
Normal file
123
src/managers/section-dock-manager.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { BaseManager } from '../core/base-manager';
|
||||
import { ManagerRegistry } from '../core/manager-registry';
|
||||
import { SectionDockPanel, type SectionDockAxis, type SectionDockType } from '../components/section-dock-panel';
|
||||
import { themeManager } from '../services/theme';
|
||||
import { localeManager } from '../services/locale';
|
||||
|
||||
export class SectionDockManager extends BaseManager {
|
||||
private panel: SectionDockPanel | null = null;
|
||||
private unsubscribeTheme: (() => void) | null = null;
|
||||
private unsubscribeLocale: (() => void) | null = null;
|
||||
private unsubscribeDockState: (() => void) | null = null;
|
||||
|
||||
constructor(registry: ManagerRegistry) {
|
||||
super(registry);
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
if (!this.unsubscribeTheme) {
|
||||
this.unsubscribeTheme = themeManager.subscribe(() => {
|
||||
this.applyPresentation();
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.unsubscribeLocale) {
|
||||
this.unsubscribeLocale = localeManager.subscribe(() => {
|
||||
this.applyPresentation();
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.unsubscribeDockState) {
|
||||
this.unsubscribeDockState = this.registry.bottomDock?.onStateChange((state) => {
|
||||
if (state.id !== 'section') {
|
||||
return;
|
||||
}
|
||||
if (!state.open) {
|
||||
console.log("deactivateSection")
|
||||
this.engineComponent?.deactivateSection();
|
||||
return;
|
||||
}
|
||||
this.panel?.resetForOpen();
|
||||
}) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
if (this.unsubscribeTheme) {
|
||||
this.unsubscribeTheme();
|
||||
this.unsubscribeTheme = null;
|
||||
}
|
||||
if (this.unsubscribeLocale) {
|
||||
this.unsubscribeLocale();
|
||||
this.unsubscribeLocale = null;
|
||||
}
|
||||
if (this.unsubscribeDockState) {
|
||||
this.unsubscribeDockState();
|
||||
this.unsubscribeDockState = null;
|
||||
}
|
||||
|
||||
this.panel?.destroy();
|
||||
this.panel = null;
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
public getPanelElement(): HTMLElement {
|
||||
if (!this.panel) {
|
||||
this.panel = new SectionDockPanel({
|
||||
defaultType: 'face',
|
||||
defaultAxis: 'x',
|
||||
defaultHidden: false,
|
||||
onTypeChange: (type, axis) => {
|
||||
this.activateByType(type, axis);
|
||||
},
|
||||
onAxisChange: (axis) => {
|
||||
if (this.panel?.getActiveType() === 'axis') {
|
||||
this.engineComponent?.activeSection(axis);
|
||||
}
|
||||
},
|
||||
onHideToggle: (hidden) => {
|
||||
if (hidden) {
|
||||
this.engineComponent?.hideSection();
|
||||
return;
|
||||
}
|
||||
this.engineComponent?.recoverSection();
|
||||
},
|
||||
onReverse: () => {
|
||||
this.engineComponent?.reverseSection();
|
||||
},
|
||||
onReset: (type, axis) => {
|
||||
this.engineComponent?.deactivateSection();
|
||||
this.activateByType(type, axis);
|
||||
},
|
||||
onFitToModel: () => {
|
||||
this.engineComponent?.scaleSectionBox();
|
||||
}
|
||||
});
|
||||
this.panel.init();
|
||||
}
|
||||
|
||||
this.panel.resetForOpen();
|
||||
this.applyPresentation();
|
||||
return this.panel.element;
|
||||
}
|
||||
|
||||
private activateByType(type: SectionDockType, axis: SectionDockAxis): void {
|
||||
if (type === 'face') {
|
||||
this.engineComponent?.activeSection('face');
|
||||
return;
|
||||
}
|
||||
if (type === 'box') {
|
||||
this.engineComponent?.activeSection('box');
|
||||
return;
|
||||
}
|
||||
this.engineComponent?.activeSection(axis);
|
||||
}
|
||||
|
||||
private applyPresentation(): void {
|
||||
if (!this.panel) {
|
||||
return;
|
||||
}
|
||||
this.panel.setTheme(themeManager.getTheme());
|
||||
this.panel.setLocales();
|
||||
}
|
||||
}
|
||||
@@ -100,8 +100,9 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
}
|
||||
|
||||
private ensurePresetListWithInternalDefault(): void {
|
||||
const builtin = this.createInternalDefaultPreset();
|
||||
const withoutInternal = this.presetList.filter((item) => item.id !== SettingDialogManager.INTERNAL_DEFAULT_PRESET_ID);
|
||||
const hasCustomDefault = withoutInternal.some((item) => item.isDefault);
|
||||
const builtin = this.createInternalDefaultPreset(!hasCustomDefault);
|
||||
this.presetList = [...withoutInternal, builtin];
|
||||
if (!this.selectedPresetId || !this.presetList.some((item) => item.id === this.selectedPresetId)) {
|
||||
this.selectedPresetId = builtin.id;
|
||||
@@ -118,21 +119,24 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
this.ensurePresetListWithInternalDefault();
|
||||
}
|
||||
|
||||
private createInternalDefaultPreset(): EngineSettingPreset {
|
||||
private createInternalDefaultPreset(isDefault: boolean = true): EngineSettingPreset {
|
||||
return {
|
||||
id: SettingDialogManager.INTERNAL_DEFAULT_PRESET_ID,
|
||||
presetName: t('setting.defaultPresetLabel'),
|
||||
isDefault: true,
|
||||
isDefault,
|
||||
settings: structuredClone(DEFAULT_SETTINGS),
|
||||
source: 'sdk-default',
|
||||
readonly: false,
|
||||
allowModify: false,
|
||||
};
|
||||
}
|
||||
|
||||
public setPresetList(presets: EngineSettingPreset[]): void {
|
||||
this.presetList = this.normalizePresetList(presets);
|
||||
this.ensurePresetListWithInternalDefault();
|
||||
|
||||
this.selectedPresetId = this.resolveDefaultPresetId(this.presetList);
|
||||
|
||||
if (!this.selectedPresetId && this.presetList.length > 0) {
|
||||
this.selectedPresetId = this.presetList[0].id;
|
||||
}
|
||||
@@ -169,11 +173,11 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
this.ensureSliderStyle();
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.style.cssText = 'max-height: 520px; display: flex; flex-direction: column;';
|
||||
content.style.cssText = 'height: 600px; display: flex; flex-direction: column;';
|
||||
|
||||
const scrollBody = document.createElement('div');
|
||||
scrollBody.className = 'setting-scroll-body';
|
||||
scrollBody.style.cssText = 'padding: 14px 16px 12px 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px;';
|
||||
scrollBody.style.cssText = 'padding: 14px 16px 12px 16px; overflow-y: auto; display: flex; flex-direction: column; gap: 6px; flex: 1; min-height: 0;';
|
||||
|
||||
scrollBody.appendChild(this.createRenderModeSection());
|
||||
scrollBody.appendChild(this.createSliderSection(
|
||||
@@ -228,7 +232,11 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
const engine = this.engineComponent;
|
||||
if (!engine) return;
|
||||
|
||||
const savedSelectedId = this.selectedPresetId;
|
||||
this.ensurePresetListWithInternalDefault();
|
||||
if (savedSelectedId && this.presetList.some((item) => item.id === savedSelectedId)) {
|
||||
this.selectedPresetId = savedSelectedId;
|
||||
}
|
||||
this.presetLists = engine.getPresetLists();
|
||||
this.settings = engine.getSettings();
|
||||
this.isDirty = this.getCurrentSettingsDirtyState(this.settings);
|
||||
@@ -314,23 +322,32 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
|
||||
const actions = document.createElement('div');
|
||||
const canDelete = this.isSelectedPresetDeletable();
|
||||
actions.style.cssText = canDelete
|
||||
? 'display: grid; grid-template-columns: minmax(0, 1.55fr) 40px minmax(0, 0.7fr) minmax(0, 0.95fr); gap: 8px; align-items: center;'
|
||||
: 'display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(0, 0.7fr) minmax(0, 0.95fr); gap: 8px; align-items: center;';
|
||||
const allowModify = this.isSelectedPresetAllowModify();
|
||||
|
||||
actions.style.cssText = 'display: grid; grid-template-columns: minmax(0, 1.55fr) 40px minmax(0, 0.7fr) minmax(0, 0.95fr); gap: 8px; align-items: center;';
|
||||
|
||||
actions.appendChild(this.createBottomPresetSelect());
|
||||
|
||||
if (canDelete) {
|
||||
if (allowModify && canDelete) {
|
||||
actions.appendChild(this.createDeleteIconButton());
|
||||
} else {
|
||||
const placeholder = document.createElement('div');
|
||||
placeholder.style.cssText = 'width: 40px; height: 36px;';
|
||||
actions.appendChild(placeholder);
|
||||
}
|
||||
|
||||
actions.appendChild(this.createFooterActionButton(t('setting.savePreset'), {
|
||||
background: PRIMARY,
|
||||
color: TEXT_INVERSE,
|
||||
border: 'none',
|
||||
onClick: () => this.handleSaveCurrentPreset(),
|
||||
compact: true,
|
||||
}));
|
||||
if (allowModify) {
|
||||
actions.appendChild(this.createFooterActionButton(t('setting.savePreset'), {
|
||||
background: PRIMARY,
|
||||
color: TEXT_INVERSE,
|
||||
border: 'none',
|
||||
onClick: () => this.handleSaveCurrentPreset(),
|
||||
compact: true,
|
||||
}));
|
||||
} else {
|
||||
const placeholder = document.createElement('div');
|
||||
actions.appendChild(placeholder);
|
||||
}
|
||||
|
||||
actions.appendChild(this.createFooterActionButton(t('setting.saveAsNewPreset'), {
|
||||
background: PRIMARY,
|
||||
@@ -371,9 +388,17 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
private isSelectedPresetDeletable(): boolean {
|
||||
if (!this.selectedPresetId) return false;
|
||||
if (this.selectedPresetId === SettingDialogManager.INTERNAL_DEFAULT_PRESET_ID) return false;
|
||||
const preset = this.presetList.find((item) => item.id === this.selectedPresetId);
|
||||
if (preset?.allowModify === false) return false;
|
||||
return this.presetList.some((item) => item.id === this.selectedPresetId);
|
||||
}
|
||||
|
||||
private isSelectedPresetAllowModify(): boolean {
|
||||
if (!this.selectedPresetId) return true;
|
||||
const preset = this.presetList.find((item) => item.id === this.selectedPresetId);
|
||||
return preset?.allowModify !== false;
|
||||
}
|
||||
|
||||
private createBottomPresetSelect(): HTMLElement {
|
||||
const options: SelectOption[] = [];
|
||||
if (this.presetList.length === 0) {
|
||||
@@ -875,6 +900,9 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
}
|
||||
|
||||
private handleSaveCurrentPreset(): void {
|
||||
if (!this.isSelectedPresetAllowModify()) {
|
||||
return;
|
||||
}
|
||||
if (!this.selectedPresetId) {
|
||||
window.alert(t('setting.selectPresetFirst'));
|
||||
return;
|
||||
@@ -886,14 +914,20 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const savedPresetId = this.selectedPresetId;
|
||||
const currentSettings = this.engineComponent?.getSettings() ?? structuredClone(this.settings);
|
||||
const updatedPreset: EngineSettingPreset = {
|
||||
...this.presetList[index],
|
||||
settings: structuredClone(currentSettings),
|
||||
isDefault: true,
|
||||
};
|
||||
|
||||
this.presetList = this.presetList.map((item, i) => (i === index ? updatedPreset : item));
|
||||
this.presetList = this.presetList.map((item, i) => ({
|
||||
...(i === index ? updatedPreset : item),
|
||||
isDefault: i === index,
|
||||
}));
|
||||
this.ensurePresetListWithInternalDefault();
|
||||
this.selectedPresetId = savedPresetId;
|
||||
this.settings = structuredClone(currentSettings);
|
||||
this.isDirty = false;
|
||||
this.updateUndoRestoreVisibility();
|
||||
@@ -922,14 +956,14 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
const preset: EngineSettingPreset = {
|
||||
id: `user-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
||||
presetName: trimmedName,
|
||||
isDefault: false,
|
||||
isDefault: true,
|
||||
settings: structuredClone(currentSettings),
|
||||
source: 'user',
|
||||
readonly: false,
|
||||
};
|
||||
|
||||
const withoutInternal = this.presetList.filter((item) => item.id !== SettingDialogManager.INTERNAL_DEFAULT_PRESET_ID);
|
||||
this.presetList = [...withoutInternal, preset];
|
||||
this.presetList = [...withoutInternal.map((item) => ({ ...item, isDefault: false })), preset];
|
||||
this.ensurePresetListWithInternalDefault();
|
||||
this.selectedPresetId = preset.id;
|
||||
this.settings = structuredClone(currentSettings);
|
||||
@@ -945,7 +979,7 @@ export class SettingDialogManager extends BaseDialogManager {
|
||||
}
|
||||
|
||||
private openDeletePresetConfirmModal(): void {
|
||||
if (!this.isSelectedPresetDeletable()) {
|
||||
if (!this.isSelectedPresetAllowModify() || !this.isSelectedPresetDeletable()) {
|
||||
return;
|
||||
}
|
||||
this.closeDeletePresetConfirmModal();
|
||||
|
||||
125
src/managers/walk-dock-manager.ts
Normal file
125
src/managers/walk-dock-manager.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { BaseManager } from '../core/base-manager';
|
||||
import { ManagerRegistry } from '../core/manager-registry';
|
||||
import { WalkDockPanel } from '../components/walk-dock-panel';
|
||||
import { WalkPathDialogManager } from './walk-path-dialog-manager';
|
||||
|
||||
export class WalkDockManager extends BaseManager {
|
||||
private panel: WalkDockPanel | null = null;
|
||||
private pathManager: WalkPathDialogManager | null = null;
|
||||
private unsubscribeDockState: (() => void) | null = null;
|
||||
|
||||
constructor(registry: ManagerRegistry) {
|
||||
super(registry);
|
||||
}
|
||||
|
||||
public init(): void {
|
||||
this.pathManager = new WalkPathDialogManager(this.registry);
|
||||
this.pathManager.init();
|
||||
|
||||
if (!this.unsubscribeDockState) {
|
||||
this.unsubscribeDockState = this.registry.bottomDock?.onStateChange((state) => {
|
||||
if (state.id !== 'walk') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.open) {
|
||||
this.onOpen();
|
||||
return;
|
||||
}
|
||||
|
||||
this.onClose();
|
||||
}) ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
if (this.unsubscribeDockState) {
|
||||
this.unsubscribeDockState();
|
||||
this.unsubscribeDockState = null;
|
||||
}
|
||||
|
||||
this.onClose();
|
||||
this.pathManager?.destroy();
|
||||
this.pathManager = null;
|
||||
super.destroy();
|
||||
}
|
||||
|
||||
public getPanelElement(): HTMLElement {
|
||||
if (!this.panel) {
|
||||
this.panel = new WalkDockPanel({
|
||||
onPlanViewToggle: (isActive) => {
|
||||
this.engineComponent?.toggleMiniMap();
|
||||
this.registry.toolbar?.setBtnActive('map', isActive);
|
||||
this.emit('walk:plan-view-toggle', { isActive });
|
||||
},
|
||||
onPathModeToggle: (isActive) => {
|
||||
if (isActive) {
|
||||
this.pathManager?.show();
|
||||
} else {
|
||||
this.pathManager?.hide();
|
||||
}
|
||||
this.emit('walk:path-mode-toggle', { isActive });
|
||||
},
|
||||
onWalkModeToggle: (isActive) => {
|
||||
if (isActive) {
|
||||
this.pathManager?.hide();
|
||||
}
|
||||
this.emit('walk:walk-mode-toggle', { isActive });
|
||||
},
|
||||
onSpeedChange: (speed) => {
|
||||
const engineSpeed = speed * 0.1;
|
||||
this.engineComponent?.setWalkSpeed(engineSpeed);
|
||||
this.emit('walk:speed-change', { speed });
|
||||
},
|
||||
onGravityToggle: (enabled) => {
|
||||
this.engineComponent?.setWalkGravity(enabled);
|
||||
this.emit('walk:gravity-toggle', { enabled });
|
||||
},
|
||||
onCollisionToggle: (enabled) => {
|
||||
this.engineComponent?.setWalkCollision(enabled);
|
||||
this.emit('walk:collision-toggle', { enabled });
|
||||
},
|
||||
onCharacterModelChange: (model) => {
|
||||
void model;
|
||||
},
|
||||
onWalkModeChange: (mode) => {
|
||||
void mode;
|
||||
},
|
||||
onExit: () => {
|
||||
this.registry.bottomDock?.close('walk');
|
||||
}
|
||||
});
|
||||
this.panel.init();
|
||||
}
|
||||
|
||||
this.syncMapState();
|
||||
return this.panel.element;
|
||||
}
|
||||
|
||||
private onOpen(): void {
|
||||
this.engineComponent?.activateFirstPersonMode();
|
||||
this.syncMapState();
|
||||
}
|
||||
|
||||
private onClose(): void {
|
||||
this.pathManager?.hide();
|
||||
|
||||
if (this.engineComponent?.getMiniMapState()) {
|
||||
this.engineComponent.toggleMiniMap();
|
||||
}
|
||||
|
||||
this.engineComponent?.deactivateFirstPersonMode();
|
||||
|
||||
if (this.panel) {
|
||||
this.panel.destroy();
|
||||
this.panel = null;
|
||||
}
|
||||
|
||||
this.registry.toolbar?.setBtnActive('map', false);
|
||||
}
|
||||
|
||||
private syncMapState(): void {
|
||||
const mapState = this.engineComponent?.getMiniMapState() ?? false;
|
||||
this.panel?.setPlanViewActive(mapState);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user