diff --git a/src/examples/app/index.ts b/src/examples/app/index.ts index 56c46de..8f9926b 100644 --- a/src/examples/app/index.ts +++ b/src/examples/app/index.ts @@ -16,11 +16,12 @@ import { SignalDraw } from 'src/graphics/signal/SignalDrawAssistant'; import { TrainDraw } from 'src/graphics/train/TrainDrawAssistant'; import { CombinationKey, - GraphicApp, GraphicData, GraphicTransform, - JlDrawApp, + IDrawApp, + IGraphicStorage, KeyListener, + newDrawApp, } from 'src/jlgraphic'; import { ContextMenu } from 'src/jlgraphic/ui/ContextMenu'; import { MenuItemOptions } from 'src/jlgraphic/ui/Menu'; @@ -95,9 +96,9 @@ export const DefaultCanvasMenu = new ContextMenu({ ], }); -let drawApp: JlDrawApp | null = null; +let drawApp: IDrawApp | null = null; -export function getDrawApp(): JlDrawApp | null { +export function getDrawApp(): IDrawApp | null { return drawApp; } @@ -108,21 +109,9 @@ export function destroyDrawApp(): void { } } -export function initDrawApp(dom: HTMLElement): JlDrawApp { - drawApp = new JlDrawApp(dom); - const app = drawApp; - app.setOptions({ - drawAssistants: [ - new LinkDraw(app, new LinkTemplate(new LinkData())), - new IscsFanDraw( - app, - new IscsFanTemplate(new IscsFanData(), new IscsFanState()) - ), - new SignalDraw(app, new SignalTemplate(new SignalData())), - new TrainDraw(app, () => { - return new TrainData(); - }), - ], +export function initDrawApp(): IDrawApp { + drawApp = newDrawApp({ + dataLoader: loadDrawDatas, // isSupportDeletion: (g): boolean => { // if (g.type === Signal.Type) { // Notify.create({ @@ -135,11 +124,23 @@ export function initDrawApp(dom: HTMLElement): JlDrawApp { // return true; // }, }); + const app = drawApp; + app.initScene('test', {}); + + new LinkDraw(app, new LinkTemplate(new LinkData())); + new IscsFanDraw( + app, + new IscsFanTemplate(new IscsFanData(), new IscsFanState()) + ); + new SignalDraw(app, new SignalTemplate(new SignalData())); + new TrainDraw(app, () => { + return new TrainData(); + }); // 画布右键菜单 app.registerMenu(DefaultCanvasMenu); app.canvas.on('_rightclick', (e) => { - if (app._drawing) return; + if (app.drawing) return; UndoOptions.disabled = !app.opRecord.hasUndo; RedoOptions.disabled = !app.opRecord.hasRedo; UndoOptions.handler = () => { @@ -162,14 +163,16 @@ export function initDrawApp(dom: HTMLElement): JlDrawApp { }; jumpStaitonItems.push(item); }); - JumpStaitonOptions.subMenu = { - name: '车站列表', - groups: [ - { - items: jumpStaitonItems, - }, - ], - }; + if (jumpStaitonItems.length > 0) { + JumpStaitonOptions.subMenu = { + name: '车站列表', + groups: [ + { + items: jumpStaitonItems, + }, + ], + }; + } DefaultCanvasMenu.update(); DefaultCanvasMenu.open(e.global); }); @@ -256,7 +259,7 @@ export function initDrawApp(dom: HTMLElement): JlDrawApp { } const StorageKey = 'graphic-storage'; -export function saveDrawDatas(app: JlDrawApp) { +export function saveDrawDatas(app: IDrawApp) { const storage = new graphicData.RtssGraphicStorage(); const canvasData = app.canvas.saveData(); storage.canvas = new graphicData.Canvas({ @@ -293,7 +296,7 @@ export function saveDrawDatas(app: JlDrawApp) { localStorage.setItem(StorageKey, base64); } -export function loadDrawDatas(app: GraphicApp) { +export async function loadDrawDatas(): Promise { // localStorage.removeItem(StorageKey); const base64 = localStorage.getItem(StorageKey); // console.log('加载数据', base64); @@ -302,7 +305,6 @@ export function loadDrawDatas(app: GraphicApp) { toUint8Array(base64) ); console.log('加载数据', storage); - app.updateCanvas(storage.canvas); const datas: GraphicData[] = []; storage.links.forEach((link) => { datas.push(new LinkData(link)); @@ -328,8 +330,13 @@ export function loadDrawDatas(app: GraphicApp) { storage.stations.forEach((train) => { datas.push(new StationData(train)); }); - app.loadGraphic(datas); - } else { - app.loadGraphic([]); + return Promise.resolve({ + canvasProperty: storage.canvas, + datas: datas, + }); } + return Promise.resolve({ + // canvasProperty: {}, + datas: [], + }); } diff --git a/src/graphics/link/LinkDrawAssistant.ts b/src/graphics/link/LinkDrawAssistant.ts index 779c69c..63121b3 100644 --- a/src/graphics/link/LinkDrawAssistant.ts +++ b/src/graphics/link/LinkDrawAssistant.ts @@ -10,11 +10,11 @@ import { import { AbsorbablePosition, DraggablePoint, - GraphicApp, + IGraphicApp, GraphicDrawAssistant, GraphicInteractionPlugin, GraphicTransformEvent, - JlDrawApp, + IDrawApp, JlGraphic, KeyListener, calculateMirrorPoint, @@ -68,7 +68,7 @@ export class LinkDraw extends GraphicDrawAssistant { }, }); - constructor(app: JlDrawApp, template: LinkTemplate) { + constructor(app: IDrawApp, template: LinkTemplate) { super(app, template, Link.Type, '轨道Link'); this.container.addChild(this.graphic); this.graphicTemplate.curve = true; @@ -354,11 +354,11 @@ const LinkEditMenu: ContextMenu = ContextMenu.init({ */ export class LinkPointsEditPlugin extends GraphicInteractionPlugin { static Name = 'LinkPointsDrag'; - constructor(app: GraphicApp) { + constructor(app: IGraphicApp) { super(LinkPointsEditPlugin.Name, app); app.registerMenu(LinkEditMenu); } - static init(app: GraphicApp): LinkPointsEditPlugin { + static init(app: IGraphicApp): LinkPointsEditPlugin { return new LinkPointsEditPlugin(app); } filter(...grahpics: JlGraphic[]): Link[] | undefined { diff --git a/src/graphics/platform/Platform.ts b/src/graphics/platform/Platform.ts index ba46c2e..72266a6 100644 --- a/src/graphics/platform/Platform.ts +++ b/src/graphics/platform/Platform.ts @@ -188,7 +188,7 @@ export class PlatformTemplate extends JlGraphicTemplate { width: number; height: number; constructor() { - super(Platform.Type); + super(Platform.Type, {}); this.hasdoor = true; this.trainDirection = 'left'; this.lineWidth = platformConsts.lineWidth; diff --git a/src/graphics/platform/PlatformDrawAssistant.ts b/src/graphics/platform/PlatformDrawAssistant.ts index f7987a9..f186c10 100644 --- a/src/graphics/platform/PlatformDrawAssistant.ts +++ b/src/graphics/platform/PlatformDrawAssistant.ts @@ -8,7 +8,7 @@ import { import { GraphicDrawAssistant, GraphicInteractionPlugin, - JlDrawApp, + IDrawApp, JlGraphic, getRectangleCenter, } from 'src/jlgraphic'; @@ -27,14 +27,8 @@ export class PlatformDraw extends GraphicDrawAssistant< platformGraphic: Graphics = new Graphics(); doorGraphic: Graphics = new Graphics(); - constructor(app: JlDrawApp, createData: () => IPlatformData) { - super( - app, - new PlatformTemplate(), - createData, - Platform.Type, - '站台Platform' - ); + constructor(app: IDrawApp, createData: () => IPlatformData) { + super(app, new PlatformTemplate(), '', '站台Platform'); this.container.addChild(this.platformGraphic); this.container.addChild(this.doorGraphic); this.graphicTemplate.hasdoor = true; @@ -104,10 +98,10 @@ export class PlatformDraw extends GraphicDrawAssistant< export class platformInteraction extends GraphicInteractionPlugin { static Name = 'platform_transform'; - constructor(app: JlDrawApp) { + constructor(app: IDrawApp) { super(platformInteraction.Name, app); } - static init(app: JlDrawApp) { + static init(app: IDrawApp) { return new platformInteraction(app); } filter(...grahpics: JlGraphic[]): Platform[] | undefined { diff --git a/src/jlgraphic/app/BasicOperation.ts b/src/jlgraphic/app/BasicOperation.ts new file mode 100644 index 0000000..e91cd84 --- /dev/null +++ b/src/jlgraphic/app/BasicOperation.ts @@ -0,0 +1,110 @@ +import { GraphicData, JlGraphic } from '../core'; +import { JlOperation } from '../operation'; +import { ICanvasProperties, IGraphicApp, JlCanvas } from './JlGraphicApp'; + +/** + * 更新画布操作 + */ +export class UpdateCanvasOperation extends JlOperation { + obj: JlCanvas; + old: ICanvasProperties; + data: ICanvasProperties; + description = ''; + + constructor( + app: IGraphicApp, + obj: JlCanvas, + old: ICanvasProperties, + data: ICanvasProperties + ) { + super(app, 'update-canvas'); + this.app = app; + this.obj = obj; + this.old = old; + this.data = data; + } + + undo(): JlGraphic[] { + this.obj.update(this.old); + return []; + } + redo(): JlGraphic[] { + this.obj.update(this.data); + return []; + } +} +/** + * 创建图形操作 + */ +export class GraphicCreateOperation extends JlOperation { + obj: JlGraphic[]; + description = ''; + + constructor(app: IGraphicApp, obj: JlGraphic[]) { + super(app, 'graphic-create'); + this.app = app; + this.obj = obj; + } + + undo(): JlGraphic[] | void { + this.app.deleteGraphics(...this.obj); + } + redo(): JlGraphic[] { + this.app.addGraphics(...this.obj); + return this.obj; + } +} +/** + * 删除图形操作 + */ +export class GraphicDeleteOperation extends JlOperation { + obj: JlGraphic[]; + + constructor(app: IGraphicApp, obj: JlGraphic[]) { + super(app, 'graphic-delete'); + this.app = app; + this.obj = obj; + } + + undo(): JlGraphic[] { + this.app.addGraphics(...this.obj); + return this.obj; + } + redo(): void { + this.app.deleteGraphics(...this.obj); + } +} + +export class GraphicDataUpdateOperation extends JlOperation { + obj: JlGraphic[]; + oldData: GraphicData[]; + newData: GraphicData[]; + constructor( + app: IGraphicApp, + obj: JlGraphic[], + oldData: GraphicData[], + newData: GraphicData[] + ) { + super(app, 'graphic-drag'); + this.obj = [...obj]; + this.oldData = oldData; + this.newData = newData; + } + + undo(): void | JlGraphic[] { + for (let i = 0; i < this.obj.length; i++) { + const g = this.obj[i]; + // g.exitChildEdit(); + g.updateData(this.oldData[i]); + } + return this.obj; + } + redo(): void | JlGraphic[] { + for (let i = 0; i < this.obj.length; i++) { + const g = this.obj[i]; + // g.exitChildEdit(); + g.updateData(this.newData[i]); + } + return this.obj; + } +} diff --git a/src/jlgraphic/app/JlDrawApp.ts b/src/jlgraphic/app/JlDrawApp.ts index 79f1071..ba33fc3 100644 --- a/src/jlgraphic/app/JlDrawApp.ts +++ b/src/jlgraphic/app/JlDrawApp.ts @@ -11,7 +11,6 @@ import { } from 'pixi.js'; import { GraphicIdGenerator } from '../core/IdGenerator'; import { GraphicData, GraphicTemplate, JlGraphic } from '../core/JlGraphic'; -import { JlOperation } from '../operation/JlOperation'; import { AppDragEvent, AppInteractionPlugin, @@ -24,10 +23,15 @@ import { import { CommonMouseTool } from '../plugins/CommonMousePlugin'; import { MenuItemOptions } from '../ui/Menu'; import { DOWN, LEFT, RIGHT, UP, recursiveChildren } from '../utils'; +import { + GraphicDataUpdateOperation, + UpdateCanvasOperation, +} from './BasicOperation'; import { GraphicApp, GraphicAppOptions, ICanvasProperties, + IGraphicApp, JlCanvas, } from './JlGraphicApp'; @@ -39,7 +43,7 @@ export abstract class GraphicDrawAssistant< GD extends GraphicData > extends AppInteractionPlugin { readonly __GraphicDrawAssistant = true; - app: JlDrawApp; + app: IDrawApp; type: string; // 图形对象类型 description: string; // 描述 icon: string; // 界面显示的图标 @@ -58,7 +62,7 @@ export abstract class GraphicDrawAssistant< } constructor( - graphicApp: JlDrawApp, + graphicApp: IDrawApp, graphicTemplate: GT, icon: string, description: string @@ -77,7 +81,7 @@ export abstract class GraphicDrawAssistant< } bind(): void { - this.app._drawing = true; + this.app.drawing = true; const canvas = this.canvas; canvas.addChild(this.container); canvas.on('mousedown', this.onLeftDown, this); @@ -113,7 +117,7 @@ export abstract class GraphicDrawAssistant< this.app.removeKeyboardListener(this.escListener); this.app.viewport.plugins.remove('drag'); - this.app._drawing = false; + this.app.drawing = false; } onLeftDown(e: FederatedMouseEvent) {} @@ -152,9 +156,7 @@ export abstract class GraphicDrawAssistant< * 保存创建的图形对象 */ storeGraphic(...graphics: JlGraphic[]): void { - this.app.addGraphics(...graphics); - // 创建图形对象操作记录 - this.app.opRecord.record(new GraphicCreateOperation(this.app, graphics)); + this.app.addGraphicAndRecord(...graphics); } /** * 创建并添加到图形App @@ -189,21 +191,54 @@ export abstract class GraphicDrawAssistant< } } +/** + * 绘制助手类型 + */ export type DrawAssistant = GraphicDrawAssistant; -export interface IDrawAppOptions { - /** - * 绘制辅助交互插件 - */ - drawAssistants: DrawAssistant[]; -} +/** + * 绘制配置选项 + */ +export type DrawAppOptions = GraphicAppOptions; -export type DrawAppOptions = GraphicAppOptions & IDrawAppOptions; +/** + * 绘制应用接口 + */ +export interface IDrawApp extends IGraphicApp { + /** + * 是否正在绘制图形 + */ + get drawing(): boolean; + /** + * 更新绘制中状态 + */ + set drawing(value: boolean); + /** + * 设置配置选项 + * @param options + */ + setOptions(options: DrawAppOptions): void; + /** + * 获取绘制助手 + */ + getDrawAssistant(graphicType: string): DA; + /** + * 更新画布并记录 + * @param data + */ + updateCanvasAndRecord(data: ICanvasProperties): void; + /** + * 更新图形并记录 + * @param g + * @param data + */ + updateGraphicAndRecord(g: JlGraphic, data: GraphicData): void; +} /** * 绘制应用 */ -export class JlDrawApp extends GraphicApp { +export class JlDrawApp extends GraphicApp implements IDrawApp { font: BitmapFont = BitmapFont.from( 'coordinates', { @@ -223,8 +258,15 @@ export class JlDrawApp extends GraphicApp { drawAssistants: DrawAssistant[] = []; _drawing = false; - constructor(dom: HTMLElement) { - super(dom); + get drawing(): boolean { + return this._drawing; + } + set drawing(value: boolean) { + this._drawing = value; + } + + constructor(options: DrawAppOptions) { + super(options); this.appendDrawStatesDisplay(); @@ -236,7 +278,6 @@ export class JlDrawApp extends GraphicApp { setOptions(options: DrawAppOptions): void { super.setOptions(options); - // this.registerInteractionPlugin(...options.drawAssistants); } registerInteractionPlugin(...plugins: InteractionPlugin[]): void { @@ -310,8 +351,8 @@ export class JlDrawApp extends GraphicApp { * 绘制状态信息显示 */ private appendDrawStatesDisplay(): void { - this.app.stage.addChild(this.coordinates); - this.app.stage.addChild(this.scaleText); + this.pixi.stage.addChild(this.coordinates); + this.pixi.stage.addChild(this.scaleText); const bound = this.coordinates.getLocalBounds(); this.scaleText.position.set(bound.width + 10, 0); this.canvas.on('mousemove', (e) => { @@ -336,7 +377,7 @@ export class JlDrawApp extends GraphicApp { new KeyListener({ value: 'KeyA', combinations: [CombinationKey.Ctrl], - onPress: (e: KeyboardEvent, app: GraphicApp) => { + onPress: (e: KeyboardEvent, app: IGraphicApp) => { if (e.ctrlKey) { (app as JlDrawApp).selectAllGraphics(); } @@ -349,8 +390,8 @@ export class JlDrawApp extends GraphicApp { new KeyListener({ value: 'KeyD', combinations: [CombinationKey.Shift], - onPress: (e: KeyboardEvent, app: GraphicApp) => { - app.graphicCopyPlugin.init(); + onPress: (e: KeyboardEvent, app: IGraphicApp) => { + this.graphicCopyPlugin.init(); }, }) ); @@ -360,7 +401,7 @@ export class JlDrawApp extends GraphicApp { value: 'KeyZ', global: true, combinations: [CombinationKey.Ctrl], - onPress: (e: KeyboardEvent, app: GraphicApp) => { + onPress: (e: KeyboardEvent, app: IGraphicApp) => { app.opRecord.undo(); }, }) @@ -371,7 +412,7 @@ export class JlDrawApp extends GraphicApp { value: 'KeyZ', global: true, combinations: [CombinationKey.Ctrl, CombinationKey.Shift], - onPress: (e: KeyboardEvent, app: GraphicApp) => { + onPress: (e: KeyboardEvent, app: IGraphicApp) => { app.opRecord.redo(); }, }) @@ -380,8 +421,8 @@ export class JlDrawApp extends GraphicApp { this.addKeyboardListener( new KeyListener({ value: 'Delete', - onPress: (e: KeyboardEvent, app: GraphicApp) => { - (app as JlDrawApp).deleteSelectedGraphics(); + onPress: (e: KeyboardEvent, app: IGraphicApp) => { + app.deleteGraphicAndRecord(...app.selectedGraphics); }, }) ); @@ -389,10 +430,10 @@ export class JlDrawApp extends GraphicApp { new KeyListener({ value: 'ArrowUp', pressTriggerAsOriginalEvent: true, - onPress: (e: KeyboardEvent, app: GraphicApp) => { + onPress: (e: KeyboardEvent, app: IGraphicApp) => { updateGraphicPositionOnKeyboardEvent(app, UP); }, - onRelease: (e: KeyboardEvent, app: GraphicApp) => { + onRelease: (e: KeyboardEvent, app: IGraphicApp) => { recordGraphicMoveOperation(app); }, }) @@ -401,10 +442,10 @@ export class JlDrawApp extends GraphicApp { new KeyListener({ value: 'ArrowDown', pressTriggerAsOriginalEvent: true, - onPress: (e: KeyboardEvent, app: GraphicApp) => { + onPress: (e: KeyboardEvent, app: IGraphicApp) => { updateGraphicPositionOnKeyboardEvent(app, DOWN); }, - onRelease: (e: KeyboardEvent, app: GraphicApp) => { + onRelease: (e: KeyboardEvent, app: IGraphicApp) => { recordGraphicMoveOperation(app); }, }) @@ -413,10 +454,10 @@ export class JlDrawApp extends GraphicApp { new KeyListener({ value: 'ArrowLeft', pressTriggerAsOriginalEvent: true, - onPress: (e: KeyboardEvent, app: GraphicApp) => { + onPress: (e: KeyboardEvent, app: IGraphicApp) => { updateGraphicPositionOnKeyboardEvent(app, LEFT); }, - onRelease: (e: KeyboardEvent, app: GraphicApp) => { + onRelease: (e: KeyboardEvent, app: IGraphicApp) => { recordGraphicMoveOperation(app); }, }) @@ -425,23 +466,16 @@ export class JlDrawApp extends GraphicApp { new KeyListener({ value: 'ArrowRight', pressTriggerAsOriginalEvent: true, - onPress: (e: KeyboardEvent, app: GraphicApp) => { + onPress: (e: KeyboardEvent, app: IGraphicApp) => { updateGraphicPositionOnKeyboardEvent(app, RIGHT); }, - onRelease: (e: KeyboardEvent, app: GraphicApp) => { + onRelease: (e: KeyboardEvent, app: IGraphicApp) => { recordGraphicMoveOperation(app); }, }) ); } - /** - * 全选 - */ - selectAllGraphics() { - this.updateSelected(...this.queryStore.getAllGraphics()); - } - /** * 图形对象存储处理,默认添加图形交互 * @param graphic @@ -452,19 +486,6 @@ export class JlDrawApp extends GraphicApp { graphic.draggable = true; } - /** - * 删除选中图形对象 - */ - deleteSelectedGraphics() { - const deletes = this.deleteGraphics(...this.selectedGraphics); - if (deletes.length > 0) { - // 删除图形对象操作记录 - this.opRecord.record(new GraphicDeleteOperation(this, deletes)); - } else { - console.debug('没有删除元素,不记录'); - } - } - updateCanvasAndRecord(data: ICanvasProperties) { const old = this.canvas.properties.clone(); this.canvas.update(data); @@ -487,7 +508,7 @@ export class JlDrawApp extends GraphicApp { let dragStartDatas: GraphicData[] = []; function handleArrowKeyMoveGraphics( - app: GraphicApp, + app: IGraphicApp, handler: (obj: DisplayObject) => void ) { if ( @@ -506,7 +527,10 @@ function handleArrowKeyMoveGraphics( } } -function updateGraphicPositionOnKeyboardEvent(app: GraphicApp, dp: IPointData) { +function updateGraphicPositionOnKeyboardEvent( + app: IGraphicApp, + dp: IPointData +) { let dragStart = false; if (dragStartDatas.length === 0) { dragStartDatas = app.selectedGraphics.map((g) => g.saveData()); @@ -541,7 +565,7 @@ function updateGraphicPositionOnKeyboardEvent(app: GraphicApp, dp: IPointData) { } }); } -function recordGraphicMoveOperation(app: GraphicApp) { +function recordGraphicMoveOperation(app: IGraphicApp) { if ( dragStartDatas.length > 0 && dragStartDatas.length === app.selectedGraphics.length @@ -570,110 +594,3 @@ function recordGraphicMoveOperation(app: GraphicApp) { } dragStartDatas = []; } - -/** - * 更新画布操作 - */ -export class UpdateCanvasOperation extends JlOperation { - obj: JlCanvas; - old: ICanvasProperties; - data: ICanvasProperties; - description = ''; - - constructor( - app: GraphicApp, - obj: JlCanvas, - old: ICanvasProperties, - data: ICanvasProperties - ) { - super(app, 'update-canvas'); - this.app = app; - this.obj = obj; - this.old = old; - this.data = data; - } - - undo(): JlGraphic[] { - this.obj.update(this.old); - return []; - } - redo(): JlGraphic[] { - this.obj.update(this.data); - return []; - } -} -/** - * 创建图形操作 - */ -export class GraphicCreateOperation extends JlOperation { - obj: JlGraphic[]; - description = ''; - - constructor(app: GraphicApp, obj: JlGraphic[]) { - super(app, 'graphic-create'); - this.app = app; - this.obj = obj; - } - - undo(): JlGraphic[] | void { - this.app.deleteGraphics(...this.obj); - } - redo(): JlGraphic[] { - this.app.addGraphics(...this.obj); - return this.obj; - } -} -/** - * 删除图形操作 - */ -export class GraphicDeleteOperation extends JlOperation { - obj: JlGraphic[]; - - constructor(app: GraphicApp, obj: JlGraphic[]) { - super(app, 'graphic-delete'); - this.app = app; - this.obj = obj; - } - - undo(): JlGraphic[] { - this.app.addGraphics(...this.obj); - return this.obj; - } - redo(): void { - this.app.deleteGraphics(...this.obj); - } -} - -export class GraphicDataUpdateOperation extends JlOperation { - obj: JlGraphic[]; - oldData: GraphicData[]; - newData: GraphicData[]; - constructor( - app: GraphicApp, - obj: JlGraphic[], - oldData: GraphicData[], - newData: GraphicData[] - ) { - super(app, 'graphic-drag'); - this.obj = [...obj]; - this.oldData = oldData; - this.newData = newData; - } - - undo(): void | JlGraphic[] { - for (let i = 0; i < this.obj.length; i++) { - const g = this.obj[i]; - // g.exitChildEdit(); - g.updateData(this.oldData[i]); - } - return this.obj; - } - redo(): void | JlGraphic[] { - for (let i = 0; i < this.obj.length; i++) { - const g = this.obj[i]; - // g.exitChildEdit(); - g.updateData(this.newData[i]); - } - return this.obj; - } -} diff --git a/src/jlgraphic/app/JlGraphicApp.ts b/src/jlgraphic/app/JlGraphicApp.ts index 0c770a3..7332016 100644 --- a/src/jlgraphic/app/JlGraphicApp.ts +++ b/src/jlgraphic/app/JlGraphicApp.ts @@ -47,6 +47,10 @@ import { import { ContextMenu, ContextMenuPlugin } from '../ui/ContextMenu'; import { MenuItemOptions } from '../ui/Menu'; import { getRectangleCenter, recursiveChildren } from '../utils/GraphicUtils'; +import { + GraphicCreateOperation, + GraphicDeleteOperation, +} from './BasicOperation'; export const AppConsts = { viewportname: '__viewport', @@ -113,15 +117,15 @@ export class CanvasData implements ICanvasProperties { export class JlCanvas extends Container { __JlCanvas = true; type = 'Canvas'; - app: GraphicApp; + scene: IGraphicScene; _properties: CanvasData; bg: Graphics = new Graphics(); // 背景 nonInteractiveContainer: Container; // 无交互对象容器 assistantAppendContainer: Container; // 辅助附加容器 - constructor(app: GraphicApp, properties: CanvasData = new CanvasData()) { + constructor(scene: IGraphicScene, properties: CanvasData = new CanvasData()) { super(); - this.app = app; + this.scene = scene; this._properties = properties; this.eventMode = 'static'; this.nonInteractiveContainer = new Container(); @@ -181,11 +185,8 @@ export class JlCanvas extends Container { update(properties: ICanvasProperties) { // 更新画布 const old = this.properties.clone(); - const sizeChange = this._properties.copyFrom(properties); + this._properties.copyFrom(properties); this.repaint(); - if (sizeChange) { - this.app.updateViewport(); - } const vp = this.getViewport(); vp.loadTransform(properties.viewportTransform); this.emit('dataupdate', this.properties, old); @@ -272,7 +273,9 @@ export class SelectedChangeEvent { this.select = select; } } - +/** + * 应用事件 + */ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents { graphicstored: [graphic: JlGraphic]; graphicdeleted: [graphic: JlGraphic]; @@ -291,7 +294,21 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents { 'pre-menu-handle': [menu: MenuItemOptions]; 'post-menu-handle': [menu: MenuItemOptions]; 'websocket-connect-state-change': [connected: boolean]; - destroy: [app: GraphicApp]; + destroy: [app: IGraphicApp]; +} + +/** + * 图形数据存储 + */ +export interface IGraphicStorage { + /** + * 画布属性 + */ + canvasProperty?: ICanvasProperties; + /** + * 图形数据 + */ + datas: GraphicData[]; } /** @@ -299,9 +316,10 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents { */ export interface IGraphicAppConfig { /** - * 交互类型配置 + * 数据加载 + * @returns */ - interactiveTypeOptions?: IInteractiveGraphicOptions; + dataLoader?: () => Promise; /** * 最大保存的操作记录数,默认100,越大越占用内存资源 */ @@ -343,57 +361,174 @@ export interface IInteractiveGraphicOptions { interactiveGraphicTypeExcludes?: string[]; } -export type GraphicAppOptions = IGraphicAppConfig; +export type GraphicAppOptions = IGraphicAppConfig & IInteractiveGraphicOptions; /** - * 图形app基类 + * 图形场景接口 */ -export class GraphicApp extends EventEmitter { +export interface IGraphicScene extends EventEmitter { + /** + * 获取图形应用对象 + */ + get app(): GraphicApp; + /** + * 获取pixijs应用对象 + */ + get pixi(): Application; + /** + * 获取视口对象 + */ + get viewport(): Viewport; + /** + * 获取画布对象 + */ + get canvas(): JlCanvas; + /** + * 获取dom + */ + get dom(): HTMLElement | undefined; + /** + * 获取图形查询仓库 + */ + get queryStore(): GraphicQueryStore; + /** + * 获取选中的图形对象 + */ + get selectedGraphics(): JlGraphic[]; + /** + * 获取动画管理器 + */ + get animationManager(): AnimationManager; + /** + * 设置配置选项 + * @param options + */ + setOptions(options: GraphicAppOptions): void; + /** + * 注册菜单 + * @param menu + */ + registerMenu(menu: ContextMenu): void; + /** + * 将屏幕点转换为画布坐标 + * @param p 屏幕坐标 + */ + toCanvasCoordinates(p: Point): Point; + /** + * 加载/重新加载数据 + */ + reload(): Promise; + /** + * 绑定到dom + * @param dom + */ + bindDom(dom: HTMLElement): void; + /** + * 从dom节点移除 + */ + unbindDom(): void; + /** + * 注册图形模板 + * @param graphicTemplates + */ + registerGraphicTemplates(...graphicTemplates: GraphicTemplate[]): void; + /** + * 处理图形状态 + * @param graphicStates + */ + handleGraphicStates(graphicStates: GraphicState[]): void; + /** + * 根据类型获取图形模板 + * @param type + */ + getGraphicTemplatesByType(type: string): GT; + /** + * 添加图形 + * @param graphics + */ + addGraphics(...graphics: JlGraphic[]): void; + /** + * 删除图形 + * @param graphics + */ + deleteGraphics(...graphics: JlGraphic[]): JlGraphic[]; + /** + * 检测并构建关系 + */ + detectRelations(): void; + /** + * 注册交互插件 + * @param plugins + */ + registerInteractionPlugin(...plugins: InteractionPlugin[]): void; + /** + * 暂停交互插件 + */ + pauseAppInteractionPlugins(): void; + /** + * 根据name获取交互插件 + * @param name + */ + interactionPlugin

(name: string): P; + /** + * 更新选中图形对象 + * @param graphics + */ + updateSelected(...graphics: JlGraphic[]): void; + /** + * 发布选中对象改变事件 + * @param graphic + */ + fireSelectedChange(graphic: JlGraphic): void; + /** + * 选中所有图形 + */ + selectAllGraphics(): void; + /** + * 使所选图形居中 + * @param group + */ + makeGraphicCenterShow(...group: JlGraphic[]): void; + /** + * 销毁 + */ + destroy(): void; +} + +abstract class GraphicSceneBase + extends EventEmitter + implements IGraphicScene +{ private graphicStore: GraphicStore; - _options?: GraphicAppOptions; - dom: HTMLElement; - app: Application; // Pixi 渲染器 + _options: GraphicAppOptions; + pixi: Application; // Pixi 渲染器 viewport: Viewport; // 视口 canvas: JlCanvas; // 画布 - interactiveTypeOptions: IInteractiveGraphicOptions; // 图形交互配置 - + _dom?: HTMLElement; // 场景绑定到的dom节点 + _viewportResizer?: NodeJS.Timeout; // 自动根据dom大小变化调整视口尺寸 graphicTemplateMap: Map = new Map< string, GraphicTemplate >(); // 图形对象模板 - - opRecord: OperationRecord; // 操作记录 - - keyboardPlugin: JlGraphicAppKeyboardPlugin; // 键盘操作处理插件 - graphicCopyPlugin: GraphicCopyPlugin; // 图形复制操作插件 - menuPlugin: ContextMenuPlugin; // 菜单插件 - animationManager: AnimationManager; // 动画管理组件 - interactionPluginMap: Map = new Map< string, InteractionPlugin >(); // 交互插件 + graphicCopyPlugin: GraphicCopyPlugin; // 图形复制操作插件 - wsMsgBroker?: AppWsMsgBroker; // websocket消息代理 + animationManager: AnimationManager; // 动画管理组件 - constructor(dom: HTMLElement) { + menuPlugin: ContextMenuPlugin; // 菜单插件 + + wsMsgBroker: AppWsMsgBroker; // websocket消息代理 + constructor(options: GraphicAppOptions) { super(); - document.body.style.overflow = 'hidden'; - // console.log('创建图形App') - this.dom = dom; - this.graphicStore = new GraphicStore(this); - /** - * 默认都添加为非交互 - */ - this.interactiveTypeOptions = { interactiveGraphicTypeIncludes: [] }; + this.graphicStore = new GraphicStore(); + this._options = options; // 创建pixi渲染app - this.app = new Application({ - width: dom.clientWidth, - height: dom.clientHeight, + this.pixi = new Application({ antialias: true, - resizeTo: dom, }); - dom.appendChild(this.app.view as unknown as Node); // 创建画布 this.canvas = new JlCanvas(this); @@ -404,9 +539,8 @@ export class GraphicApp extends EventEmitter { screenHeight: window.innerHeight, worldWidth: this.canvas._properties.width, worldHeight: this.canvas._properties.height, - // divWheel: dom, passiveWheel: true, - events: this.app.renderer.events, + events: this.pixi.renderer.events, disableOnContextMenu: true, }); // 设置视口操作方式 @@ -425,7 +559,7 @@ export class GraphicApp extends EventEmitter { this.viewport.name = AppConsts.viewportname; this.viewport.interactiveChildren = true; // 添加视口到渲染器舞台 - this.app.stage.addChild(this.viewport); + this.pixi.stage.addChild(this.viewport); // 将画布置于视口 this.viewport.addChild(this.canvas); @@ -433,13 +567,7 @@ export class GraphicApp extends EventEmitter { this.viewport.on('zoomed-end', () => { this.emit('viewport-scaled', this.viewport); }); - - this.opRecord = new OperationRecord(); - - // 绑定键盘监听 - this.keyboardPlugin = new JlGraphicAppKeyboardPlugin(this); this.graphicCopyPlugin = new GraphicCopyPlugin(this); - this.menuPlugin = new ContextMenuPlugin(this); // 添加通用交互插件 CommonMouseTool.new(this).resume(); @@ -454,8 +582,44 @@ export class GraphicApp extends EventEmitter { // 动画管理 this.animationManager = new AnimationManager(this); + this.menuPlugin = new ContextMenuPlugin(this); + + this.wsMsgBroker = new AppWsMsgBroker(this); + } + abstract get app(): GraphicApp; + + get dom(): HTMLElement | undefined { + return this._dom; } + public get queryStore(): GraphicQueryStore { + return this.graphicStore; + } + + public get selectedGraphics(): JlGraphic[] { + return this.queryStore.getAllGraphics().filter((g) => g.selected); + } + + /** + * 重新加载数据 + */ + async reload(): Promise { + this.graphicStore.clear(); + if (this._options.dataLoader) { + const storage = await this._options.dataLoader(); + if (storage.canvasProperty) { + this.canvas.update(storage.canvasProperty); + } + if (storage.datas) { + await this.loadGraphic(storage.datas); + } + } + } + + /** + * 更新选项 + * @param options + */ setOptions(options: GraphicAppOptions) { // console.log('更新选项', options); if (this._options) { @@ -463,16 +627,19 @@ export class GraphicApp extends EventEmitter { } else { this._options = options; } - if (options.interactiveTypeOptions) { - // 更新交互类型配置 - this.interactiveTypeOptions = options.interactiveTypeOptions; - } - if (options.maxOperationRecords && options.maxOperationRecords > 0) { - this.opRecord.setMaxLen(options.maxOperationRecords); - } this.emit('options-update', options); } + toCanvasCoordinates(p: Point): Point { + return this.viewport.toWorld(p); + } + /** + * 注册菜单 + * @param menu + */ + registerMenu(menu: ContextMenu) { + this.menuPlugin.registerMenu(menu); + } /** * 注册图形对象模板 * @param graphicTemplates @@ -491,141 +658,57 @@ export class GraphicApp extends EventEmitter { return template as GT; } - /** - * 注册菜单 - * @param menu - */ - registerMenu(menu: ContextMenu) { - this.menuPlugin.registerMenu(menu); - } - - /** - * 启动websocket消息客户端 - */ - enableWsMassaging(options: MessageCliOption) { - WsMsgCli.new(options); - this.wsMsgBroker = new AppWsMsgBroker(this); - } - - /** - * 订阅websocket消息 - */ - subscribe(sub: AppStateSubscription) { - if (this.wsMsgBroker) { - // console.log('APP订阅', sub) - this.wsMsgBroker.subscribe(sub); - } else { - throw new Error('订阅消息需先启动消息代理, 执行app.enableWebsocket()'); + private updateViewport(domWidth?: number, domHeight?: number): void { + let screenWidth = this.viewport.screenWidth; + let screenHeight = this.viewport.screenHeight; + if (domWidth) { + screenWidth = domWidth; } - } - /** - * 取消websocket订阅 - */ - unsubscribe(destination: string) { - if (this.wsMsgBroker) { - this.wsMsgBroker.unsbuscribe(destination); - } else { - throw new Error('请先执行enableWebsocket'); + if (domHeight) { + screenHeight = domHeight; + } + const worldWidth = this.canvas._properties.width; + const worldHeight = this.canvas._properties.height; + this.pixi.resize(); + this.viewport.resize(screenWidth, screenHeight, worldWidth, worldHeight); + if (this.viewport.OOB().right) { + this.viewport.right = this.viewport.right + 1; + } else if (this.viewport.OOB().left) { + this.viewport.left = this.viewport.left - 1; + } else if (this.viewport.OOB().top) { + this.viewport.top = this.viewport.top - 1; + } else if (this.viewport.OOB().bottom) { + this.viewport.bottom = this.viewport.bottom + 1; } } - /** - * 处理websocket状态 - * @param graphicStates - */ - handleGraphicStates(graphicStates: GraphicState[]) { - graphicStates.forEach((state) => { - const g = this.queryStore.queryByCodeAndType( - state.code, - state.graphicType - ); - try { - if (!g) { - const template = this.getGraphicTemplatesByType(state.graphicType); - const g = template.new(); - g.loadState(state); - this.addGraphics(g); - } else if (g.updateStates(state)) { - g.repaint(); - } - } catch (err) { - console.error('图形状态处理异常', g, state, err); - // throw err; - } - }); + bindDom(dom: HTMLElement): void { + this._dom = dom; + this.pixi.resizeTo = dom; + dom.appendChild(this.pixi.view as unknown as Node); + this._viewportResizer = setInterval(() => { + // console.log( + // 'dom resize ', + // dom.style.width, + // dom.style.height, + // dom.clientWidth, + // dom.clientHeight + // ); + this.updateViewport(dom.clientWidth, dom.clientHeight); + }, 1000); + // 恢复动画 + this.animationManager.resume(); } - /** - * 添加键盘监听器,如果是相同的按键,新注册的会覆盖旧的,当移除新的时,旧的自动生效 - * @param keyListeners - */ - addKeyboardListener(...keyListeners: KeyListener[]) { - keyListeners.forEach((keyListener) => - this.keyboardPlugin.addKeyListener(keyListener) - ); + unbindDom(): void { + if (this._dom) { + clearInterval(this._viewportResizer); + this._dom.removeChild(this.pixi.view as unknown as Node); + this._dom = undefined; + // 暂停动画 + this.animationManager.pause(); + } } - - /** - * 移除键盘监听器 - * @param keyListeners - */ - removeKeyboardListener(...keyListeners: KeyListener[]) { - keyListeners.forEach((keyListener) => - this.keyboardPlugin.removeKeyListener(keyListener) - ); - } - - /** - * dom尺寸变更处理 - * @param domWidth canvas容器的宽 - * @param domHeight canvas容器的高 - */ - onDomResize(domWidth: number, domHeight: number) { - this.updateViewport(domWidth, domHeight); - } - - public get queryStore(): GraphicQueryStore { - return this.graphicStore; - } - - public get selectedGraphics(): JlGraphic[] { - return this.queryStore.getAllGraphics().filter((g) => g.selected); - } - - fireSelectedChange(graphic: JlGraphic) { - // console.log('通知选中变化', this.selecteds) - const select = graphic.selected; - this.emit('graphicselectedchange', graphic, select); - } - - /** - * 更新选中 - */ - updateSelected(...graphics: JlGraphic[]) { - this.selectedGraphics.forEach((graphic) => { - if (graphics.findIndex((g) => g.id === graphic.id) >= 0) { - return; - } - if (graphic.selected) { - graphic.updateSelected(false); - this.fireSelectedChange(graphic); - } - }); - graphics.forEach((graphic) => { - if (graphic.updateSelected(true)) { - this.fireSelectedChange(graphic); - } - }); - } - - /** - * 更新画布 - * @param param - */ - updateCanvas(param: ICanvasProperties) { - this.canvas.update(param); - } - /** * 加载图形,GraphicApp默认添加到无交互容器,DrawApp默认添加到交互容器,如需定制,提供选项配置 * @param protos @@ -644,10 +727,10 @@ export class GraphicApp extends EventEmitter { this.addGraphics(g); }); // 加载数据关系 - this.graphicStore.getAllGraphics().forEach((g) => g.loadRelations()); + this.queryStore.getAllGraphics().forEach((g) => g.loadRelations()); // 更新id生成器 const max = - this.graphicStore + this.queryStore .getAllGraphics() .filter((g) => !isNaN(parseInt(g.id))) .map((g) => parseInt(g.id)) @@ -660,33 +743,35 @@ export class GraphicApp extends EventEmitter { // 加载完成通知 this.emit('loadfinish'); } - /** * 添加图形前处理 * @param graphic */ beforeGraphicStore(graphic: JlGraphic): void { - const options = this.interactiveTypeOptions; + const interactiveGraphicTypeIncludes = + this._options.interactiveGraphicTypeIncludes || []; + const interactiveGraphicTypeExcludes = + this._options.interactiveGraphicTypeExcludes || []; // 默认无交互 graphic.eventMode = 'auto'; - if (options) { - if ( - options.interactiveGraphicTypeIncludes && - options.interactiveGraphicTypeIncludes.findIndex( - (type) => type === graphic.type - ) >= 0 - ) { - graphic.eventMode = 'static'; - } else if ( - options.interactiveGraphicTypeExcludes && - options.interactiveGraphicTypeExcludes.findIndex( - (type) => type === graphic.type - ) < 0 - ) { - graphic.eventMode = 'static'; - } + if ( + interactiveGraphicTypeIncludes.findIndex( + (type) => type === graphic.type + ) >= 0 + ) { + graphic.eventMode = 'static'; + } else if ( + interactiveGraphicTypeExcludes.findIndex( + (type) => type === graphic.type + ) < 0 + ) { + graphic.eventMode = 'static'; } } + /** + * 执行添加图形对象 + * @param graphic + */ private doAddGraphics(graphic: JlGraphic): void { this.beforeGraphicStore(graphic); if (this.graphicStore.storeGraphics(graphic)) { @@ -737,6 +822,7 @@ export class GraphicApp extends EventEmitter { addGraphics(...graphics: JlGraphic[]) { graphics.forEach((g) => this.doAddGraphics(g)); } + /** * 删除图形 * @param graphics 图形对象 @@ -760,37 +846,89 @@ export class GraphicApp extends EventEmitter { * 检测并构建关系 */ detectRelations(): void { - this.graphicStore.getAllGraphics().forEach((g) => g.buildRelation()); + this.queryStore.getAllGraphics().forEach((g) => g.buildRelation()); } /** - * 转换操作坐标点为画布坐标点 - * @param e 事件 - * @returns 画布坐标点 + * 全选 */ - toCanvasCoordinates(p: Point): Point { - return this.viewport.toWorld(p); + selectAllGraphics() { + this.updateSelected(...this.queryStore.getAllGraphics()); } /** - * 获取当前缩放倍率 + * 发送选中变化事件 + * @param graphic */ - getViewportScaled(): number { - return this.viewport.scaled; + fireSelectedChange(graphic: JlGraphic) { + // console.log('通知选中变化', this.selecteds) + const select = graphic.selected; + this.emit('graphicselectedchange', graphic, select); } /** - * 视口中心坐标 - * @returns + * 更新选中 */ - getViewportCenter(): Point { - return this.viewport.center; + updateSelected(...graphics: JlGraphic[]) { + this.selectedGraphics.forEach((graphic) => { + if (graphics.findIndex((g) => g.id === graphic.id) >= 0) { + return; + } + if (graphic.selected) { + graphic.updateSelected(false); + this.fireSelectedChange(graphic); + } + }); + graphics.forEach((graphic) => { + if (graphic.updateSelected(true)) { + this.fireSelectedChange(graphic); + } + }); } + /** - * 获取视口角落坐标 - * @returns + * 更新画布 + * @param param */ - getViewportCorner(): Point { - return this.viewport.corner; + updateCanvas(param: ICanvasProperties) { + this.canvas.update(param); + } + + /** + * 使图形居中显示(所有图形的外包围盒) + */ + makeGraphicCenterShow(...group: JlGraphic[]): void { + if (group.length > 0) { + const bounds0 = group[0].getBounds(); + let lx = bounds0.x; + let ly = bounds0.y; + let rx = bounds0.x + bounds0.width; + let ry = bounds0.y + bounds0.height; + if (group.length > 1) { + for (let i = 1; i < group.length; i++) { + const g = group[i]; + const bound = g.getBounds(); + if (bound.x < lx) { + lx = bound.x; + } + if (bound.y < ly) { + ly = bound.y; + } + const brx = bound.x + bound.width; + if (brx > rx) { + rx = brx; + } + const bry = bound.y + bound.height; + if (bry > ry) { + ry = bry; + } + } + } + const { x, y } = getRectangleCenter( + new Rectangle(lx, ly, rx - lx, ry - ly) + ); + const p = this.viewport.toWorld(x, y); + this.viewport.moveCenter(p.x, p.y); + } } /** @@ -845,87 +983,246 @@ export class GraphicApp extends EventEmitter { this.interactionPluginMap.delete(plugin.name); } - updateViewport(domWidth?: number, domHeight?: number): void { - let screenWidth = this.viewport.screenWidth; - let screenHeight = this.viewport.screenHeight; - if (domWidth) { - screenWidth = domWidth; - } - if (domHeight) { - screenHeight = domHeight; - } - const worldWidth = this.canvas._properties.width; - const worldHeight = this.canvas._properties.height; - this.app.resize(); - this.viewport.resize(screenWidth, screenHeight, worldWidth, worldHeight); - if (this.viewport.OOB().right) { - this.viewport.right = this.viewport.right + 1; - } else if (this.viewport.OOB().left) { - this.viewport.left = this.viewport.left - 1; - } else if (this.viewport.OOB().top) { - this.viewport.top = this.viewport.top - 1; - } else if (this.viewport.OOB().bottom) { - this.viewport.bottom = this.viewport.bottom + 1; + private checkWsMsgCli() { + if (!WsMsgCli.isInitiated()) { + throw new Error('订阅消息需先启动消息代理, 执行app.enableWebsocket()'); } } /** - * 使图形居中显示(所有图形的外包围盒) + * 订阅websocket消息 */ - makeGraphicCenterShow(...group: JlGraphic[]): void { - if (group.length > 0) { - const bounds0 = group[0].getBounds(); - let lx = bounds0.x; - let ly = bounds0.y; - let rx = bounds0.x + bounds0.width; - let ry = bounds0.y + bounds0.height; - if (group.length > 1) { - for (let i = 1; i < group.length; i++) { - const g = group[i]; - const bound = g.getBounds(); - if (bound.x < lx) { - lx = bound.x; - } - if (bound.y < ly) { - ly = bound.y; - } - const brx = bound.x + bound.width; - if (brx > rx) { - rx = brx; - } - const bry = bound.y + bound.height; - if (bry > ry) { - ry = bry; - } - } - } - const { x, y } = getRectangleCenter( - new Rectangle(lx, ly, rx - lx, ry - ly) + subscribe(sub: AppStateSubscription) { + this.checkWsMsgCli(); + this.wsMsgBroker.subscribe(sub); + } + /** + * 取消websocket订阅 + */ + unsubscribe(destination: string) { + this.checkWsMsgCli(); + this.wsMsgBroker.unsbuscribe(destination); + } + /** + * 处理图形状态 + * @param graphicStates + */ + handleGraphicStates(graphicStates: GraphicState[]): void { + graphicStates.forEach((state) => { + const g = this.queryStore.queryByCodeAndType( + state.code, + state.graphicType ); - const p = this.viewport.toWorld(x, y); - this.viewport.moveCenter(p.x, p.y); - } + try { + if (!g) { + const template = this.getGraphicTemplatesByType(state.graphicType); + const g = template.new(); + g.loadState(state); + this.addGraphics(g); + } else if (g.updateStates(state)) { + g.repaint(); + } + } catch (err) { + console.error('图形状态处理异常', g, state, err); + // throw err; + } + }); } /** * 销毁 */ destroy(): void { - console.log('销毁图形 APP'); - this.emit('destroy', this); + console.debug('场景销毁', this); if (this.wsMsgBroker) { this.wsMsgBroker.close(); - // if (!StompCli.hasAppMsgBroker()) { - // // 如果没有其他消息代理,关闭websocket Stomp客户端 - // StompCli.close(); - // } } this.interactionPluginMap.forEach((plugin) => { plugin.pause(); }); + this.animationManager.destroy(); this.canvas.destroy(true); this.viewport.destroy(); - this.app.destroy(true, true); - document.body.style.overflow = 'auto'; + this.pixi.destroy(true, true); + this.unbindDom(); + } +} + +/** + * 图形应用接口 + */ +export interface IGraphicApp extends IGraphicScene { + get opRecord(): OperationRecord; + /** + * 实例化一个场景 + * @param code 场景标识 + * @returns + */ + initScene(code: string, options: GraphicAppOptions): IGraphicScene; + /** + * 获取场景 + * @param code + * @returns + */ + getScene(code: string): IGraphicScene; + /** + * 移除指定code场景 + * @param code + */ + removeScene(code: string): void; + /** + * 添加图形并记录 + * @param graphics + */ + addGraphicAndRecord(...graphics: JlGraphic[]): void; + /** + * 删除图形并记录 + * @param graphics + */ + deleteGraphicAndRecord(...graphics: JlGraphic[]): void; + + /** + * 启动websocket消息客户端 + */ + enableWsMassaging(options: MessageCliOption): void; + /** + * 添加键盘监听器,如果是相同的按键,新注册的会覆盖旧的,当移除新的时,旧的自动生效 + * @param keyListeners + */ + addKeyboardListener(...keyListeners: KeyListener[]): void; + /** + * 移除键盘监听器 + * @param keyListeners + */ + removeKeyboardListener(...keyListeners: KeyListener[]): void; +} + +/** + * 图形app基类 + */ +export class GraphicApp extends GraphicSceneBase implements IGraphicApp { + /** + * 场景列表 + */ + scenes: Map = new Map(); + + opRecord: OperationRecord; // 操作记录 + keyboardPlugin: JlGraphicAppKeyboardPlugin; // 键盘操作处理插件 + + constructor(options: GraphicAppOptions) { + super(options); + // console.log('创建图形App') + this.opRecord = new OperationRecord(); + // 绑定键盘监听 + this.keyboardPlugin = new JlGraphicAppKeyboardPlugin(this); + } + + get app(): GraphicApp { + return this; + } + + setOptions(options: GraphicAppOptions) { + // console.log('更新选项', options); + if (options.maxOperationRecords && options.maxOperationRecords > 0) { + this.opRecord.setMaxLen(options.maxOperationRecords); + } + super.setOptions(options); + } + + addGraphicAndRecord(...graphics: JlGraphic[]): void { + this.addGraphics(...graphics); + this.opRecord.record(new GraphicCreateOperation(this, graphics)); + } + + deleteGraphicAndRecord(...graphics: JlGraphic[]): void { + this.deleteGraphics(...graphics); + this.opRecord.record(new GraphicDeleteOperation(this, graphics)); + } + + /** + * 实例化一个场景 + * @param code 场景标识 + * @returns + */ + initScene(code: string, options: GraphicAppOptions): IGraphicScene { + let scene = this.scenes.get(code); + if (!scene) { + scene = new JlScene(this, code, options); + this.scenes.set(code, scene); + } + return scene; + } + + /** + * 获取场景 + * @param code + * @returns + */ + getScene(code: string): IGraphicScene { + const scene = this.scenes.get(code); + if (!scene) { + throw new Error(`不存在code=${code}的场景`); + } + return scene; + } + + removeScene(code: string): void { + const scene = this.scenes.get(code); + if (scene) { + this.scenes.delete(code); + scene.destroy(); + } + } + + /** + * 启动websocket消息客户端 + */ + enableWsMassaging(options: MessageCliOption) { + WsMsgCli.new(options); + this.wsMsgBroker = new AppWsMsgBroker(this); + } + + /** + * 添加键盘监听器,如果是相同的按键,新注册的会覆盖旧的,当移除新的时,旧的自动生效 + * @param keyListeners + */ + addKeyboardListener(...keyListeners: KeyListener[]) { + keyListeners.forEach((keyListener) => + this.keyboardPlugin.addKeyListener(keyListener) + ); + } + + /** + * 移除键盘监听器 + * @param keyListeners + */ + removeKeyboardListener(...keyListeners: KeyListener[]) { + keyListeners.forEach((keyListener) => + this.keyboardPlugin.removeKeyListener(keyListener) + ); + } + + /** + * 销毁 + */ + destroy(): void { + console.debug('图形应用销毁', this); + this.emit('destroy', this); + super.destroy(); + this.scenes.forEach((scene) => scene.destroy()); + } +} + +/** + * 场景 + */ +export default class JlScene extends GraphicSceneBase { + code: string; + app: GraphicApp; + + constructor(app: GraphicApp, code: string, options: GraphicAppOptions) { + super(options); + this.code = code; + this.app = app; } } diff --git a/src/jlgraphic/app/index.ts b/src/jlgraphic/app/index.ts index 9e8a44a..96ad03f 100644 --- a/src/jlgraphic/app/index.ts +++ b/src/jlgraphic/app/index.ts @@ -1,2 +1,45 @@ -export * from './JlGraphicApp'; -export * from './JlDrawApp'; +import { + DrawAppOptions, + DrawAssistant, + GraphicDrawAssistant, + IDrawApp, + JlDrawApp, +} from './JlDrawApp'; +import { + AppConsts, + ICanvasProperties, + JlCanvas, + IGraphicScene, + GraphicApp, + GraphicAppOptions, + IGraphicApp, + IGraphicStorage, +} from './JlGraphicApp'; + +/** + * 实例化图形app + * @param options + * @returns + */ +export function newGraphicApp(options: GraphicAppOptions): IGraphicApp { + return new GraphicApp(options); +} + +/** + * 实例化绘图app + * @param options + * @returns + */ +export function newDrawApp(options: DrawAppOptions): IDrawApp { + return new JlDrawApp(options); +} + +export { AppConsts, JlCanvas, GraphicDrawAssistant }; +export type { + ICanvasProperties, + IGraphicScene, + IGraphicApp, + IGraphicStorage, + IDrawApp, + DrawAssistant, +}; diff --git a/src/jlgraphic/core/GraphicRelation.ts b/src/jlgraphic/core/GraphicRelation.ts index 88375d5..5cdb72e 100644 --- a/src/jlgraphic/core/GraphicRelation.ts +++ b/src/jlgraphic/core/GraphicRelation.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { GraphicApp } from '../app/JlGraphicApp'; import { JlGraphic } from './JlGraphic'; /** @@ -101,11 +100,7 @@ export class GraphicRelation { * 图形关系管理 */ export class RelationManage { - app: GraphicApp; relations: GraphicRelation[] = []; - constructor(app: GraphicApp) { - this.app = app; - } isContainsRelation( rp1: GraphicRelationParam, @@ -182,4 +177,11 @@ export class RelationManage { const relations = this.getRelationsOfGraphicAndOtherType(g, type); relations.forEach((rl) => this.deleteRelation(rl)); } + + /** + * 清空 + */ + clear() { + this.relations.splice(0, this.relations.length); + } } diff --git a/src/jlgraphic/core/GraphicStore.ts b/src/jlgraphic/core/GraphicStore.ts index 23e8889..300fc85 100644 --- a/src/jlgraphic/core/GraphicStore.ts +++ b/src/jlgraphic/core/GraphicStore.ts @@ -1,4 +1,3 @@ -import { GraphicApp } from '../app/JlGraphicApp'; import { RelationManage } from './GraphicRelation'; import { JlGraphic } from './JlGraphic'; @@ -67,13 +66,11 @@ export interface GraphicQueryStore { * 图形存储 */ export class GraphicStore implements GraphicQueryStore { - app: GraphicApp; store: Map; relationManage: RelationManage; - constructor(app: GraphicApp) { - this.app = app; + constructor() { this.store = new Map(); - this.relationManage = new RelationManage(app); + this.relationManage = new RelationManage(); } /** @@ -198,4 +195,12 @@ export class GraphicStore implements GraphicQueryStore { } return remove; } + + /** + * 清空 + */ + clear() { + this.relationManage.clear(); + this.store.clear(); + } } diff --git a/src/jlgraphic/core/JlGraphic.ts b/src/jlgraphic/core/JlGraphic.ts index a350a8c..d3ae38b 100644 --- a/src/jlgraphic/core/JlGraphic.ts +++ b/src/jlgraphic/core/JlGraphic.ts @@ -290,7 +290,7 @@ DisplayObject.prototype.getViewport = function getViewport() { }; DisplayObject.prototype.getGraphicApp = function getGraphicApp() { const canvas = this.getCanvas(); - return canvas.app; + return canvas.scene.app; }; DisplayObject.prototype.localToCanvasPoint = function localToCanvasPoint( p: IPointData diff --git a/src/jlgraphic/global.d.ts b/src/jlgraphic/global.d.ts index a70a5f9..87b301a 100644 --- a/src/jlgraphic/global.d.ts +++ b/src/jlgraphic/global.d.ts @@ -2,7 +2,7 @@ declare namespace GlobalMixins { type JlCanvasType = import('./app').JlCanvas; type CanvasProperties = import('./app').ICanvasProperties; - type GraphicApp = import('./app').GraphicApp; + type GraphicApp = import('./app').IGraphicApp; type JlGraphicType = import('./core').JlGraphic; type GraphicData = import('./core').GraphicData; type GraphicState = import('./core').GraphicState; diff --git a/src/jlgraphic/message/BasicMessageClient.ts b/src/jlgraphic/message/BasicMessageClient.ts index 557c61b..3fd71b7 100644 --- a/src/jlgraphic/message/BasicMessageClient.ts +++ b/src/jlgraphic/message/BasicMessageClient.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import EventEmitter from 'eventemitter3'; -import { GraphicApp } from '../app'; +import { IGraphicScene } from '../app'; import { CompleteMessageCliOption, IMessageClient } from './MessageBroker'; export interface MessageClientEvents { @@ -14,7 +14,7 @@ export interface IMessageHandler { /** * id */ - get App(): GraphicApp; + get App(): IGraphicScene; /** * 处理消息数据 * @param data diff --git a/src/jlgraphic/message/MessageBroker.ts b/src/jlgraphic/message/MessageBroker.ts index 6aed080..99c9d1f 100644 --- a/src/jlgraphic/message/MessageBroker.ts +++ b/src/jlgraphic/message/MessageBroker.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import EventEmitter from 'eventemitter3'; -import { GraphicApp } from '../app'; +import { IGraphicScene } from '../app'; import { GraphicState } from '../core'; import { IMessageHandler, @@ -94,6 +94,10 @@ export class WsMsgCli { }); } + static isInitiated(): boolean { + return !!WsMsgCli.client; + } + static emitConnectStateChangeEvent(connected: boolean) { WsMsgCli.appMsgBroker.forEach((broker) => { broker.app.emit('websocket-connect-state-change', connected); @@ -167,16 +171,16 @@ export interface AppStateSubscription { } class AppMessageHandler implements IMessageHandler { - app: GraphicApp; + app: IGraphicScene; sub: AppStateSubscription; - constructor(app: GraphicApp, subOptions: AppStateSubscription) { + constructor(app: IGraphicScene, subOptions: AppStateSubscription) { this.app = app; if (!subOptions.messageConverter && !subOptions.messageHandle) { throw new Error(`没有消息处理器或图形状态消息转换器: ${subOptions}`); } this.sub = subOptions; } - get App(): GraphicApp { + get App(): IGraphicScene { return this.app; } handle(data: any): void { @@ -194,13 +198,13 @@ class AppMessageHandler implements IMessageHandler { * 图形APP的websocket消息代理 */ export class AppWsMsgBroker { - app: GraphicApp; + app: IGraphicScene; subscriptions: Map = new Map< string, AppMessageHandler >(); - constructor(app: GraphicApp) { + constructor(app: IGraphicScene) { this.app = app; WsMsgCli.registerAppMsgBroker(this); } diff --git a/src/jlgraphic/operation/JlOperation.ts b/src/jlgraphic/operation/JlOperation.ts index 5673930..6b7f3a6 100644 --- a/src/jlgraphic/operation/JlOperation.ts +++ b/src/jlgraphic/operation/JlOperation.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { GraphicApp } from '../app/JlGraphicApp'; +import { IGraphicApp } from '../app/JlGraphicApp'; import { JlGraphic } from '../core/JlGraphic'; /** @@ -7,12 +7,12 @@ import { JlGraphic } from '../core/JlGraphic'; */ export abstract class JlOperation { type: string; // 操作类型/名称 - app: GraphicApp; + app: IGraphicApp; obj?: any; // 操作对象 data?: any; // 操作数据 description?: string = ''; // 操作描述 - constructor(app: GraphicApp, type: string) { + constructor(app: IGraphicApp, type: string) { this.app = app; this.type = type; } diff --git a/src/jlgraphic/plugins/AnimationManager.ts b/src/jlgraphic/plugins/AnimationManager.ts index 492a7fd..d783098 100644 --- a/src/jlgraphic/plugins/AnimationManager.ts +++ b/src/jlgraphic/plugins/AnimationManager.ts @@ -1,31 +1,49 @@ -import { GraphicApp } from '../app'; +import { IGraphicScene } from '../app'; import { GraphicAnimation, JlGraphic } from '../core'; /** * 图形动画管理 */ export class AnimationManager { - app: GraphicApp; + app: IGraphicScene; + _pause: boolean; + /** + * key - graphic.id + */ graphicAnimationMap: Map>; - constructor(app: GraphicApp) { + constructor(app: IGraphicScene) { this.app = app; + this._pause = false; this.graphicAnimationMap = new Map>(); // 动画控制 - app.app.ticker.add((dt: number) => { - this.graphicAnimationMap.forEach((map) => { - map.forEach((animation) => { - if (animation.running) { - animation.run(dt); - } - }); + app.pixi.ticker.add(this.run, this); + } + + private run(dt: number) { + if (this._pause) { + // 暂停 + return; + } + this.graphicAnimationMap.forEach((map) => { + map.forEach((animation) => { + if (animation.running) { + animation.run(dt); + } }); }); } - static new(app: GraphicApp): AnimationManager { - return new AnimationManager(app); + pause() { + this._pause = true; } + resume() { + this._pause = false; + } + + destroy() { + this.app.pixi.ticker.remove(this.run, this); + } /** * 图形对象的所有动画map * @param graphic diff --git a/src/jlgraphic/plugins/CommonMousePlugin.ts b/src/jlgraphic/plugins/CommonMousePlugin.ts index 70027b5..35f0654 100644 --- a/src/jlgraphic/plugins/CommonMousePlugin.ts +++ b/src/jlgraphic/plugins/CommonMousePlugin.ts @@ -1,5 +1,5 @@ import { DisplayObject, FederatedMouseEvent, Graphics, Point } from 'pixi.js'; -import { GraphicApp, JlCanvas } from '../app'; +import { IGraphicScene, JlCanvas } from '../app'; import { JlGraphic } from '../core'; import { AppDragEvent, @@ -73,7 +73,7 @@ export class CommonMouseTool extends AppInteractionPlugin { rightTarget: DisplayObject | null = null; - constructor(graphicApp: GraphicApp) { + constructor(graphicApp: IGraphicScene) { super(CommonMouseTool.Name, graphicApp); this.options = new CompleteMouseToolOptions(); @@ -93,7 +93,7 @@ export class CommonMouseTool extends AppInteractionPlugin { }); } - static new(app: GraphicApp) { + static new(app: IGraphicScene) { return new CommonMouseTool(app); } @@ -174,8 +174,8 @@ export class CommonMouseTool extends AppInteractionPlugin { setCursor(e: FederatedMouseEvent) { this.rightTarget = e.target as DisplayObject; - if (e.target instanceof JlCanvas && this.app.app.view.style) { - this.app.app.view.style.cursor = 'grab'; + if (e.target instanceof JlCanvas && this.app.pixi.view.style) { + this.app.pixi.view.style.cursor = 'grab'; } } @@ -183,9 +183,9 @@ export class CommonMouseTool extends AppInteractionPlugin { if ( this.rightTarget && this.rightTarget instanceof JlCanvas && - this.app.app.view.style + this.app.pixi.view.style ) { - this.app.app.view.style.cursor = 'inherit'; + this.app.pixi.view.style.cursor = 'inherit'; } this.rightTarget = null; } diff --git a/src/jlgraphic/plugins/CopyPlugin.ts b/src/jlgraphic/plugins/CopyPlugin.ts index 2cbc71f..0821cae 100644 --- a/src/jlgraphic/plugins/CopyPlugin.ts +++ b/src/jlgraphic/plugins/CopyPlugin.ts @@ -1,18 +1,18 @@ import { Container, FederatedPointerEvent, Point } from 'pixi.js'; -import { GraphicApp, GraphicCreateOperation } from '../app'; +import { IGraphicScene } from '../app'; import { JlGraphic } from '../core'; import { KeyListener } from './KeyboardPlugin'; export class GraphicCopyPlugin { container: Container; - app: GraphicApp; + scene: IGraphicScene; keyListeners: KeyListener[]; copys: JlGraphic[]; start?: Point; running = false; moveLimit?: 'x' | 'y'; - constructor(app: GraphicApp) { - this.app = app; + constructor(scene: IGraphicScene) { + this.scene = scene; this.container = new Container(); this.copys = []; this.keyListeners = []; @@ -57,15 +57,15 @@ export class GraphicCopyPlugin { init(): void { if (this.running) return; - if (this.app.selectedGraphics.length === 0) { + if (this.scene.selectedGraphics.length === 0) { throw new Error('没有选中图形,复制取消'); } this.running = true; this.copys = []; this.container.alpha = 0.5; - this.app.canvas.addChild(this.container); - const app = this.app; - this.app.selectedGraphics.forEach((g) => { + this.scene.canvas.addChild(this.container); + const app = this.scene; + this.scene.selectedGraphics.forEach((g) => { const template = app.getGraphicTemplatesByType(g.type); const clone = template.clone(g); this.copys.push(clone); @@ -73,11 +73,11 @@ export class GraphicCopyPlugin { this.container.addChild(clone); clone.repaint(); }); - this.app.canvas.on('mousemove', this.onPointerMove, this); - this.app.canvas.on('mouseup', this.onFinish, this); - this.app.canvas.on('rightup', this.cancle, this); + this.scene.canvas.on('mousemove', this.onPointerMove, this); + this.scene.canvas.on('mouseup', this.onFinish, this); + this.scene.canvas.on('rightup', this.cancle, this); this.keyListeners.forEach((kl) => { - this.app.addKeyboardListener(kl); + this.scene.app.addKeyboardListener(kl); }); } @@ -87,17 +87,17 @@ export class GraphicCopyPlugin { this.moveLimit = undefined; this.copys = []; this.container.removeChildren(); - this.app.canvas.removeChild(this.container); - this.app.canvas.off('mousemove', this.onPointerMove, this); - this.app.canvas.off('mouseup', this.onFinish, this); - this.app.canvas.off('rightup', this.cancle, this); + this.scene.canvas.removeChild(this.container); + this.scene.canvas.off('mousemove', this.onPointerMove, this); + this.scene.canvas.off('mouseup', this.onFinish, this); + this.scene.canvas.off('rightup', this.cancle, this); this.keyListeners.forEach((kl) => { - this.app.removeKeyboardListener(kl); + this.scene.app.removeKeyboardListener(kl); }); } onPointerMove(e: FederatedPointerEvent): void { - const cp = this.app.toCanvasCoordinates(e.global); + const cp = this.scene.toCanvasCoordinates(e.global); if (!this.start) { this.start = cp; } else { @@ -125,17 +125,15 @@ export class GraphicCopyPlugin { g.position.x += this.container.position.x; g.position.y += this.container.position.y; }); - this.app.addGraphics(...this.copys); - // 创建图形对象操作记录 - this.app.opRecord.record(new GraphicCreateOperation(this.app, this.copys)); - this.app.detectRelations(); - this.app.updateSelected(...this.copys); + this.scene.app.addGraphicAndRecord(...this.copys); + this.scene.detectRelations(); + this.scene.updateSelected(...this.copys); this.clear(); } cancle(): void { console.log('复制操作取消'); - this.app.canvas.removeChild(this.container); + this.scene.canvas.removeChild(this.container); this.clear(); } } diff --git a/src/jlgraphic/plugins/GraphicTransformPlugin.ts b/src/jlgraphic/plugins/GraphicTransformPlugin.ts index e80025d..fcd259e 100644 --- a/src/jlgraphic/plugins/GraphicTransformPlugin.ts +++ b/src/jlgraphic/plugins/GraphicTransformPlugin.ts @@ -12,7 +12,7 @@ import { InteractionPluginType, KeyListener, } from '.'; -import { GraphicApp } from '../app'; +import { IGraphicScene } from '../app'; import { JlGraphic } from '../core'; import { AbsorbablePosition, VectorText } from '../graphic'; import { DraggablePoint } from '../graphic/DraggablePoint'; @@ -164,7 +164,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase { apContainer: Container; static AbsorbablePosisiontsName = '__AbsorbablePosisionts'; - constructor(app: GraphicApp) { + constructor(app: IGraphicScene) { super(app, GraphicTransformPlugin.Name, InteractionPluginType.Other); this.apContainer = new Container(); this.apContainer.name = GraphicTransformPlugin.AbsorbablePosisiontsName; @@ -205,7 +205,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase { return aps; } - static new(app: GraphicApp) { + static new(app: IGraphicScene) { return new GraphicTransformPlugin(app); } diff --git a/src/jlgraphic/plugins/InteractionPlugin.ts b/src/jlgraphic/plugins/InteractionPlugin.ts index 70226d6..080f879 100644 --- a/src/jlgraphic/plugins/InteractionPlugin.ts +++ b/src/jlgraphic/plugins/InteractionPlugin.ts @@ -4,7 +4,11 @@ import { FederatedPointerEvent, Point, } from 'pixi.js'; -import { GraphicApp, IGraphicAppConfig } from '../app/JlGraphicApp'; +import { + IGraphicApp, + IGraphicAppConfig, + IGraphicScene, +} from '../app/JlGraphicApp'; import { JlGraphic } from '../core/JlGraphic'; export enum InteractionPluginType { @@ -19,7 +23,7 @@ export enum InteractionPluginType { export interface InteractionPlugin { readonly _type: string; name: string; // 唯一标识 - app: GraphicApp; + app: IGraphicScene; /** * 恢复 @@ -41,10 +45,10 @@ export interface InteractionPlugin { export abstract class InteractionPluginBase implements InteractionPlugin { readonly _type: string; name: string; // 唯一标识 - app: GraphicApp; + app: IGraphicScene; _pause: boolean; - constructor(app: GraphicApp, name: string, type: string) { + constructor(app: IGraphicScene, name: string, type: string) { this._type = type; this.app = app; this.name = name; @@ -89,19 +93,19 @@ export abstract class InteractionPluginBase implements InteractionPlugin { } export abstract class OtherInteractionPlugin extends InteractionPluginBase { - constructor(app: GraphicApp, name: string) { + constructor(app: IGraphicScene, name: string) { super(app, name, InteractionPluginType.Other); } } export class AppDragEvent { - app: GraphicApp; + app: IGraphicScene; type: 'start' | 'move' | 'end'; target: DisplayObject; original: FederatedPointerEvent; start: Point; // 画布坐标 constructor( - app: GraphicApp, + app: IGraphicScene, type: 'start' | 'move' | 'end', target: DisplayObject, original: FederatedPointerEvent, @@ -191,7 +195,7 @@ export class DragPlugin extends OtherInteractionPlugin { start: Point | null = null; startClientPoint: Point | null = null; drag = false; - constructor(app: GraphicApp) { + constructor(app: IGraphicScene) { super(app, DragPlugin.Name); app.on('options-update', (options: IGraphicAppConfig) => { if (options.threshold !== undefined) { @@ -199,7 +203,7 @@ export class DragPlugin extends OtherInteractionPlugin { } }); } - static new(app: GraphicApp) { + static new(app: IGraphicScene) { return new DragPlugin(app); } bind(): void { @@ -301,11 +305,11 @@ export class ViewportMovePlugin extends OtherInteractionPlugin { moveSpeedx = 0; moveSpeedy = 0; - constructor(app: GraphicApp) { + constructor(app: IGraphicScene) { super(app, ViewportMovePlugin.Name); } - static new(app: GraphicApp): ViewportMovePlugin { + static new(app: IGraphicScene): ViewportMovePlugin { return new ViewportMovePlugin(app); } pause(): void { @@ -393,7 +397,7 @@ export class ViewportMovePlugin extends OtherInteractionPlugin { * 应用交互插件,同时只能生效一个 */ export abstract class AppInteractionPlugin extends InteractionPluginBase { - constructor(name: string, app: GraphicApp) { + constructor(name: string, app: IGraphicScene) { super(app, name, InteractionPluginType.App); } @@ -413,10 +417,10 @@ export abstract class GraphicInteractionPlugin implements InteractionPlugin { readonly _type = InteractionPluginType.Graphic; - app: GraphicApp; + app: IGraphicApp; name: string; // 唯一标识 _pause: boolean; - constructor(name: string, app: GraphicApp) { + constructor(name: string, app: IGraphicApp) { this.app = app; this.name = name; this._pause = true; diff --git a/src/jlgraphic/plugins/KeyboardPlugin.ts b/src/jlgraphic/plugins/KeyboardPlugin.ts index da562c1..a0aa8ca 100644 --- a/src/jlgraphic/plugins/KeyboardPlugin.ts +++ b/src/jlgraphic/plugins/KeyboardPlugin.ts @@ -1,4 +1,4 @@ -import { GraphicApp } from '../app/JlGraphicApp'; +import { IGraphicApp } from '../app/JlGraphicApp'; let target: Node | undefined; @@ -62,7 +62,7 @@ export class GlobalKeyboardHelper { const GlobalKeyboardPlugin = new GlobalKeyboardHelper(); export class JlGraphicAppKeyboardPlugin { - app: GraphicApp; + app: IGraphicApp; /** * 结构为Map> */ @@ -75,7 +75,7 @@ export class JlGraphicAppKeyboardPlugin { KeyListener[] >(); // 键值监听栈(多次注册相同的监听会把之前注册的监听器入栈,移除最新的监听会从栈中弹出一个作为指定事件监听处理器) - constructor(app: GraphicApp) { + constructor(app: IGraphicApp) { this.app = app; GlobalKeyboardPlugin.registerGAKPlugin(this); const onMouseUpdateTarget = (e: MouseEvent) => { @@ -85,7 +85,7 @@ export class JlGraphicAppKeyboardPlugin { }; const keydownHandle = (e: KeyboardEvent) => { // console.debug(e.key, e.code, e.keyCode); - if (target && target == this.app.dom.getElementsByTagName('canvas')[0]) { + if (target && target == this.app.dom?.getElementsByTagName('canvas')[0]) { const listenerMap = this.getKeyListener(e); listenerMap?.forEach((listener) => { if (!listener.global) { @@ -95,7 +95,7 @@ export class JlGraphicAppKeyboardPlugin { } }; const keyupHandle = (e: KeyboardEvent) => { - if (target && target == this.app.dom.getElementsByTagName('canvas')[0]) { + if (target && target == this.app.dom?.getElementsByTagName('canvas')[0]) { const listenerMap = this.getKeyListener(e); listenerMap?.forEach((listener) => { if (!listener.global) { @@ -202,7 +202,7 @@ export class JlGraphicAppKeyboardPlugin { } } -type KeyboardKeyHandler = (e: KeyboardEvent, app: GraphicApp) => void; +type KeyboardKeyHandler = (e: KeyboardEvent, app: IGraphicApp) => void; export enum CombinationKey { Ctrl = 'Ctrl', @@ -300,7 +300,7 @@ export class KeyListener { this.options.pressTriggerAsOriginalEvent = v; } - press(e: KeyboardEvent, app: GraphicApp): void { + press(e: KeyboardEvent, app: IGraphicApp): void { if (!this.checkCombinations(e)) { console.debug('组合键不匹配, 不执行press', e, this); return; @@ -335,7 +335,7 @@ export class KeyListener { return false; } - release(e: KeyboardEvent, app: GraphicApp): void { + release(e: KeyboardEvent, app: IGraphicApp): void { if (this.isPress) { // console.log('Keyup : ', e.key, e); this.isPress = false; diff --git a/src/jlgraphic/ui/ContextMenu.ts b/src/jlgraphic/ui/ContextMenu.ts index 3a0a3c7..065a268 100644 --- a/src/jlgraphic/ui/ContextMenu.ts +++ b/src/jlgraphic/ui/ContextMenu.ts @@ -1,5 +1,5 @@ import { Color, Container, Graphics, Point, Rectangle, Text } from 'pixi.js'; -import { GraphicApp } from '../app'; +import { IGraphicScene } from '../app'; import { OutOfBound } from '../utils'; import { DefaultWhiteMenuOptions, @@ -12,10 +12,10 @@ import { } from './Menu'; export class ContextMenuPlugin { - app: GraphicApp; + app: IGraphicScene; contextMenuMap: Map = new Map(); - constructor(app: GraphicApp) { + constructor(app: IGraphicScene) { this.app = app; const canvas = this.app.canvas; canvas.on('pointerdown', () => { @@ -52,7 +52,7 @@ export class ContextMenuPlugin { open(menu: ContextMenu, global: Point) { if (!menu.opened) { menu.opened = true; - this.app.app.stage.addChild(menu); + this.app.pixi.stage.addChild(menu); } // 处理超出显示范围 const screenHeight = this.screenHeight; @@ -88,7 +88,7 @@ export class ContextMenuPlugin { close(menu: ContextMenu) { if (menu.opened) { menu.opened = false; - this.app.app.stage.removeChild(menu); + this.app.pixi.stage.removeChild(menu); } } /** @@ -453,6 +453,12 @@ class MenuGroup extends Container { this.config.items.forEach((item) => { this.items.push(new ContextMenuItem(this.menu, item)); }); + if (this.items.length === 0) { + console.error('菜单group为空', this.config, this.menu); + throw new Error( + `{name=${this.menu.name}}的菜单的group为{name=${this.config.name}}的条目为空!` + ); + } this.addChild(...this.items); } diff --git a/src/layouts/DrawLayout.vue b/src/layouts/DrawLayout.vue index 4f30581..d594a26 100644 --- a/src/layouts/DrawLayout.vue +++ b/src/layouts/DrawLayout.vue @@ -124,9 +124,9 @@ import { useQuasar } from 'quasar'; import DrawProperties from 'src/components/draw-app/DrawProperties.vue'; import { getDrawApp, loadDrawDatas } from 'src/examples/app'; import { getWebsocketUrl } from 'src/examples/app/configs/UrlManage'; -import { ClientEngine } from 'src/jlgraphic'; +import { ClientEngine } from 'src/jlgraphic'; import { useDrawStore } from 'src/stores/draw-store'; -import { onMounted, onUnmounted, ref } from 'vue'; +import { onMounted, onUnmounted, ref } from 'vue'; import { useRouter } from 'vue-router'; const $q = useQuasar(); @@ -159,28 +159,29 @@ onMounted(() => { const dom = document.getElementById('draw-app-container'); if (dom) { - const drawApp = drawStore.initDrawApp(dom); + const drawApp = drawStore.initDrawApp(); drawApp.on('websocket-connect-state-change', (connected) => { console.log('应用websocket状态变更', connected); }); - loadDrawDatas(drawApp); + drawApp.bindDom(dom); + drawApp.reload(); onResize(); - drawApp.enableWsMassaging({ - engine: ClientEngine.Centrifugo, - // protocol: 'json', - wsUrl: getWebsocketUrl(), - token: - // 'Bearer eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzZWxmIiwic3ViIjoiNiIsImV4cCI6MTY5MDc5MDczMSwiaWF0IjoxNjkwNTMxNTMxfQ.caXc0jfI766p57Qx6hnuMp6XPpnBvfQuBeFI5-mlOHLBEK6Js13jy0cfGX_FxICWUGEEV0lYI8U1EfCDYPyQ38i4BcHe88mq4jK6byztuyWtkWjx4nOMcCwO-haOfJ2E0pIJOOm5aFBWT2YRdlb63gjvDbyLwdsvO1wr4-QSkB2uyyKpTaFnYP4OiF264UdN-XYuME5k_RkjcinvcYjPR7WI2YCu_Sg0exzqQW03pspTyx5ozbuc_3JSkOA7g4aqnizMJgVAsCbTwYBnfvLClDbcztkyaqbhN5pA4Eme_yPos4ISfufjR7TyUROVp7yEV6gSLHRtPulageQxNhltXA', - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTA5NjU5MDYsImlkIjo2LCJvcmlnX2lhdCI6MTY5MDk2MjMwNiwic3ViIjoiNiJ9.AeSBdG20_3qP0QeeOcMbcKUqjHkr5BIZvPCs4YvGmFA', - }); - const simulationId = 57; - drawApp.subscribe({ - destination: `simulation-${simulationId}-devices-status`, - messageHandle: (msg) => { - // const storage = graphicData.RtssGraphicStorage.deserialize(msg); - console.log('设备消息处理', msg); - }, - }); + // drawApp.enableWsMassaging({ + // engine: ClientEngine.Centrifugo, + // // protocol: 'json', + // wsUrl: getWebsocketUrl(), + // token: + // // 'Bearer eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJzZWxmIiwic3ViIjoiNiIsImV4cCI6MTY5MDc5MDczMSwiaWF0IjoxNjkwNTMxNTMxfQ.caXc0jfI766p57Qx6hnuMp6XPpnBvfQuBeFI5-mlOHLBEK6Js13jy0cfGX_FxICWUGEEV0lYI8U1EfCDYPyQ38i4BcHe88mq4jK6byztuyWtkWjx4nOMcCwO-haOfJ2E0pIJOOm5aFBWT2YRdlb63gjvDbyLwdsvO1wr4-QSkB2uyyKpTaFnYP4OiF264UdN-XYuME5k_RkjcinvcYjPR7WI2YCu_Sg0exzqQW03pspTyx5ozbuc_3JSkOA7g4aqnizMJgVAsCbTwYBnfvLClDbcztkyaqbhN5pA4Eme_yPos4ISfufjR7TyUROVp7yEV6gSLHRtPulageQxNhltXA', + // 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTA5NjU5MDYsImlkIjo2LCJvcmlnX2lhdCI6MTY5MDk2MjMwNiwic3ViIjoiNiJ9.AeSBdG20_3qP0QeeOcMbcKUqjHkr5BIZvPCs4YvGmFA', + // }); + // const simulationId = 57; + // drawApp.subscribe({ + // destination: `simulation-${simulationId}-devices-status`, + // messageHandle: (msg) => { + // // const storage = graphicData.RtssGraphicStorage.deserialize(msg); + // console.log('设备消息处理', msg); + // }, + // }); } }); @@ -204,8 +205,9 @@ function onRightResize(size: { height: number; width: number }) { } function onResize() { - const clientWidth = document.body.clientWidth; - const clientHeight = document.body.clientHeight; + const clientWidth = window.innerWidth; + + const clientHeight = window.innerHeight; canvasWidth.value = clientWidth - (leftDrawerOpen.value ? leftWidth.value : 0) - @@ -216,10 +218,19 @@ function onResize() { dom.style.width = canvasWidth.value + 'px'; dom.style.height = canvasHeight.value + 'px'; } - const drawApp = getDrawApp(); - if (drawApp) { - drawApp.onDomResize(canvasWidth.value, canvasHeight.value); - } + // console.log( + // clientWidth, + // clientHeight, + // headerHeight.value, + // leftWidth.value, + // rightWidth.value, + // canvasWidth.value, + // canvasHeight.value + // ); + // const drawApp = getDrawApp(); + // if (drawApp) { + // drawApp.updateViewport(canvasWidth.value, canvasHeight.value); + // } } onUnmounted(() => { diff --git a/src/stores/draw-store.ts b/src/stores/draw-store.ts index 9102554..c2635ab 100644 --- a/src/stores/draw-store.ts +++ b/src/stores/draw-store.ts @@ -1,6 +1,6 @@ import { defineStore } from 'pinia'; import { destroyDrawApp, getDrawApp, initDrawApp } from 'src/examples/app'; -import { DrawAssistant, JlCanvas, JlDrawApp, JlGraphic } from 'src/jlgraphic'; +import { DrawAssistant, JlCanvas, IDrawApp, JlGraphic } from 'src/jlgraphic'; export const useDrawStore = defineStore('draw', { state: () => ({ @@ -41,7 +41,7 @@ export const useDrawStore = defineStore('draw', { }, }, actions: { - getDrawApp(): JlDrawApp { + getDrawApp(): IDrawApp { const app = getDrawApp(); if (app == null) { throw new Error('未初始化app'); @@ -51,8 +51,8 @@ export const useDrawStore = defineStore('draw', { getJlCanvas(): JlCanvas { return this.getDrawApp().canvas; }, - initDrawApp(dom: HTMLElement) { - const app = initDrawApp(dom); + initDrawApp() { + const app = initDrawApp(); app.on('interaction-plugin-resume', (plugin) => { if (plugin.isAppPlugin()) { if (Object.hasOwn(plugin, '__GraphicDrawAssistant')) {