/* * Tencent is pleased to support the open source community by making TMagicEditor available. * * Copyright (C) 2025 Tencent. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { shallowReactive } from 'vue'; import type { Writable } from 'type-fest'; import { convertToNumber } from '@tmagic/utils'; import editorService from '@editor/services/editor'; import type { AsyncHookPlugin, StageRect, UiState } from '@editor/type'; import { DEFAULT_LEFT_COLUMN_WIDTH, DEFAULT_RIGHT_COLUMN_WIDTH, LEFT_COLUMN_WIDTH_STORAGE_KEY, RIGHT_COLUMN_WIDTH_STORAGE_KEY, } from '@editor/utils/const'; import BaseService from './BaseService'; import storageService, { Protocol } from './storage'; const state = shallowReactive({ uiSelectMode: false, showSrc: false, showStylePanel: true, zoom: 1, stageContainerRect: { width: 0, height: 0, }, stageRect: { width: 375, height: 817, }, columnWidth: { left: storageService.getItem(LEFT_COLUMN_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) || DEFAULT_LEFT_COLUMN_WIDTH, center: 0, right: storageService.getItem(RIGHT_COLUMN_WIDTH_STORAGE_KEY, { protocol: Protocol.NUMBER }) || DEFAULT_RIGHT_COLUMN_WIDTH, }, showGuides: true, showRule: true, propsPanelSize: 'small', showAddPageButton: true, showPageListButton: true, hideSlideBar: false, sideBarItems: [], navMenuRect: { left: 0, top: 0, width: 0, height: 0, }, frameworkRect: { width: 0, height: 0, left: 0, top: 0, }, }); const canUsePluginMethods = { async: ['zoom', 'calcZoom'] as const, sync: [] as const, }; type AsyncMethodName = Writable<(typeof canUsePluginMethods)['async']>; class Ui extends BaseService { constructor() { super(canUsePluginMethods.async.map((methodName) => ({ name: methodName, isAsync: true }))); } public set(name: K, value: T) { const mask = editorService.get('stage')?.mask; if (name === 'stageRect') { this.setStageRect(value as unknown as StageRect); return; } if (name === 'showGuides') { mask?.showGuides(value as unknown as boolean); } if (name === 'showRule') { mask?.showRule(value as unknown as boolean); } state[name] = value; } public get(name: K) { return state[name]; } public async zoom(zoom: number) { this.set('zoom', (this.get('zoom') * 100 + zoom * 100) / 100); if (this.get('zoom') < 0.1) this.set('zoom', 0.1); } public async calcZoom() { const { stageRect, stageContainerRect } = state; const { height, width } = stageContainerRect; if (!width || !height) return 1; let stageWidth: number = convertToNumber(stageRect.width, width); let stageHeight: number = convertToNumber(stageRect.height, height); // 30为标尺的大小 stageWidth = stageWidth + 30; stageHeight = stageHeight + 30; if (width > stageWidth && height > stageHeight) { return 1; } // 60/80是为了不要让画布太过去贴住四周(这样好看些) return Math.min((width - 60) / stageWidth || 1, (height - 80) / stageHeight || 1); } public resetState() { this.set('showSrc', false); this.set('uiSelectMode', false); this.set('zoom', 1); this.set('stageContainerRect', { width: 0, height: 0, }); } public destroy() { this.resetState(); this.removeAllListeners(); this.removeAllPlugins(); } public usePlugin(options: AsyncHookPlugin): void { super.usePlugin(options); } private async setStageRect(value: StageRect) { state.stageRect = { ...state.stageRect, ...value, }; state.zoom = await this.calcZoom(); } } export type UiService = Ui; export default new Ui();