import { EDITOR_CONFIG, MapAreaType, type MapPen, MapPointType } from '@api/map'; import sTheme from '@core/theme.service'; import { EditType, LockState, Meta2d } from '@meta2d/core'; import { useObservable } from '@vueuse/rxjs'; import THEME from 'asset/themes/editor.json'; import { cloneDeep, pick } from 'lodash-es'; import { debounceTime, filter, map, Subject, switchMap } from 'rxjs'; import { watch } from 'vue'; export type Point = Record<'x' | 'y', number>; export class EditorService extends Meta2d { public load(map?: string, readonly = false): void { const data = map ? JSON.parse(map) : undefined; this.open(data); this.lock(readonly ? LockState.Disable : LockState.None); } public save(): string { const data = this.data(); const map = JSON.stringify(data); return map; } public export(): string { const png = this.toPng(10); return png; } readonly #mouse$$ = new Subject<{ type: 'click' | 'mousedown' | 'mouseup'; value: Point }>(); public readonly mouseClick = useObservable( this.#mouse$$.pipe( filter(({ type }) => type === 'click'), debounceTime(100), map(({ value }) => value), ), ); public readonly mouseBrush = useObservable<[Point, Point]>( this.#mouse$$.pipe( filter(({ type }) => type === 'mousedown'), switchMap(({ value: s }) => this.#mouse$$.pipe( filter(({ type }) => type === 'mouseup'), map(({ value: e }) => <[Point, Point]>[s, e]), ), ), ), ); //#region 点位 public async addPoint(p: Point, type = MapPointType.普通点): Promise { const pen: MapPen = { name: 'point', x: p.x, y: p.y, width: 24, height: 24, point: { type }, }; await this.addPen(pen, false, true, true); this.pushHistory({ type: EditType.Add, pens: [cloneDeep(pen)] }); } //#endregion //#region 线路 //#endregion //#region 区域 public async addArea(p1: Point, p2: Point, type = MapAreaType.库区) { const scale = this.data().scale ?? 1; const w = Math.abs(p1.x - p2.x); const h = Math.abs(p1.y - p2.y); if (w * scale < 50 || h * scale < 60) return; const pen: MapPen = { name: 'area', x: Math.min(p1.x, p2.x), y: Math.min(p1.y, p2.y), width: w, height: h, area: { type }, locked: LockState.DisableMoveScale, }; const area = await this.addPen(pen, false, true, true); this.bottom(area); this.pushHistory({ type: EditType.Add, pens: [cloneDeep(pen)] }); } //#endregion constructor(container: HTMLDivElement) { super(container, EDITOR_CONFIG); (container.children.item(5)).ondrop = null; this.on('*', (e, v) => this.#listen(e, v)); this.#register(); watch( () => sTheme.theme, (v) => this.setTheme(v), { immediate: true }, ); } // eslint-disable-next-line @typescript-eslint/no-explicit-any #listen(e: unknown, v: any) { switch (e) { case 'click': case 'mousedown': case 'mouseup': this.#mouse$$.next({ type: e, value: pick(v, 'x', 'y') }); break; default: // console.log(e, v); break; } } #register() { this.store.theme = THEME; this.registerCanvasDraw({ point: drawPoint, line: drawLine, area: drawArea }); this.registerAnchors({ point: anchorPoint }); } } //#region 绘制函数 function drawPoint(ctx: CanvasRenderingContext2D, pen: MapPen): void { const { x = 0, y = 0, width = 0, height = 0 } = pen.calculative?.worldRect ?? {}; const { type } = pen.point ?? {}; ctx.save(); ctx.lineWidth = 2; ctx.roundRect(x, y, width, height, 4); ctx.stroke(); ctx.fillStyle = '#fff'; ctx.fillText(String(type), x + width / 2, y + height / 2); ctx.restore(); } function anchorPoint(pen: MapPen): void { pen.anchors = [{ x: 0.5, y: 0.5 }]; } function drawLine(ctx: CanvasRenderingContext2D, pen: MapPen): void { const [p1, p2] = pen.calculative?.worldAnchors ?? []; const { direction } = pen.route ?? {}; ctx.save(); ctx.lineWidth = 2; ctx.restore(); } function drawArea(ctx: CanvasRenderingContext2D, pen: MapPen): void { const { x = 0, y = 0, width = 0, height = 0 } = pen.calculative?.worldRect ?? {}; const { type } = pen.area ?? {}; ctx.save(); ctx.lineWidth = 1; ctx.strokeRect(x, y, width, height); ctx.fillStyle = '#fff'; ctx.fillText(String(type), x + width / 2, y); ctx.restore(); } //#endregion