diff --git a/packages/core/src/app.ts b/packages/core/src/app.ts index 1de3eaf6..c15b8c49 100644 --- a/packages/core/src/app.ts +++ b/packages/core/src/app.ts @@ -11,6 +11,7 @@ import { DEFAULT_APP_CONFIG, guid, Point } from './utils'; export class App extends BaseService { public stage: Konva.Stage; public mainLayer: Konva.Layer; + public optionLayer: Konva.Layer; public config: AppConfig; private mouse: Mouse; @@ -28,9 +29,13 @@ export class App extends BaseService { height: 500, }); this.stage.container().style.backgroundColor = '#fff'; - this.mainLayer = new Konva.Layer(); - this.mainLayer.name('pictode:main:layer'); - this.stage.add(this.mainLayer); + this.mainLayer = new Konva.Layer({ + name: 'pictode:main:layer', + }); + this.optionLayer = new Konva.Layer({ + name: 'pictode:option:layer', + }); + this.stage.add(this.mainLayer, this.optionLayer); this.tooler = new Tooler(this); this.mouse = new Mouse(this); @@ -44,6 +49,7 @@ export class App extends BaseService { this.stage.width(width); this.stage.height(height); this.render(); + this.emit('canvas:resized', { width, height }); }; public get pointer(): Point { @@ -292,6 +298,7 @@ export class App extends BaseService { public render(): void { this.mainLayer.draw(); + this.optionLayer.draw(); } public scale(): number { diff --git a/packages/core/src/services/mouse.ts b/packages/core/src/services/mouse.ts index a05d0899..c657100d 100644 --- a/packages/core/src/services/mouse.ts +++ b/packages/core/src/services/mouse.ts @@ -8,6 +8,7 @@ export class Mouse extends Service { this.bindMouseEvent(); this.app.stage.on<'dragstart'>('dragstart', this.onDragStart); + this.app.stage.on<'dragmove'>('dragmove', this.onDragMove); this.app.stage.on<'dragend'>('dragend', this.onDragEnd); this.app.stage.on<'wheel'>('wheel', this.onWheel); } @@ -35,13 +36,19 @@ export class Mouse extends Service { this.app.stage.off('contextmenu', this.onMouseContextmenu); } - private onDragStart = (): void => { + private onDragStart = (event: KonvaMouseEvent): void => { this.unbindMouseEvent(); + this.app.emit('canvas:drag:start', { event }); + }; + + private onDragMove = (event: KonvaMouseEvent): void => { + this.app.emit('canvas:drag:move', { event }); }; private onDragEnd = (event: KonvaMouseEvent): void => { this.bindMouseEvent(); this.app.emit('mouse:up', { event }); + this.app.emit('canvas:drag:end', { event }); }; private onMouseDown = (event: KonvaMouseEvent): void => { @@ -119,6 +126,7 @@ export class Mouse extends Service { public destroy(): void { this.unbindMouseEvent(); this.app.stage.off('dragstart', this.onDragStart); + this.app.stage.off('dragmove', this.onDragMove); this.app.stage.off('dragend', this.onDragEnd); this.app.stage.off('wheel', this.onWheel); } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 3043b48a..8f068447 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -84,7 +84,20 @@ export interface EventArgs { 'canvas:rendered': { time: number; }; + 'canvas:resized': { + width: number; + height: number; + }; 'canvas:cleared': {}; + 'canvas:drag:start': { + event: KonvaMouseEvent; + }; + 'canvas:drag:move': { + event: KonvaMouseEvent; + }; + 'canvas:drag:end': { + event: KonvaMouseEvent; + }; 'canvas:zoom:start': { scale: number; }; diff --git a/packages/plugin-ruler/package.json b/packages/plugin-ruler/package.json new file mode 100644 index 00000000..0ac139f3 --- /dev/null +++ b/packages/plugin-ruler/package.json @@ -0,0 +1,54 @@ +{ + "name": "@pictode/plugin-ruler", + "private": false, + "version": "1.0.0", + "main": "dist/pictode-plugin-ruler.umd.js", + "module": "dist/pictode-plugin-ruler.mjs", + "types": "types/index.d.ts", + "exports": { + ".": { + "import": "./dist/pictode-plugin-ruler.mjs", + "require": "./dist/pictode-plugin-ruler.umd.js", + "types": "./types/index.d.ts" + } + }, + "files": [ + "dist/**/*", + "types/**/*" + ], + "scripts": { + "build": "npm run build:type && vite build", + "build:type": "npm run clear:type && tsc --declaration --emitDeclarationOnly --project tsconfig.build.json", + "clear:type": "rimraf ./types" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/JessYan0913/pictode.git", + "directory": "packages/plugin-ruler" + }, + "keywords": [ + "pictode", + "konva.js", + "canvas" + ], + "author": "JessYan0913", + "license": "MIT", + "bugs": { + "url": "https://github.com/JessYan0913/pictode/issues" + }, + "homepage": "https://github.com/JessYan0913/pictode#readme", + "dependencies": { + "@pictode/core": "workspace:^", + "@pictode/utils": "workspace:^", + "dot": "2.0.0-beta.1", + "rimraf": "^3.0.2", + "roughjs": "^4.5.2" + }, + "peerDependencies": { + "@pictode/core": "workspace:^" + }, + "devDependencies": { + "typescript": "^4.9.3", + "vite": "^4.1.0" + } +} diff --git a/packages/plugin-ruler/src/index.ts b/packages/plugin-ruler/src/index.ts new file mode 100644 index 00000000..bfdb229b --- /dev/null +++ b/packages/plugin-ruler/src/index.ts @@ -0,0 +1,70 @@ +import { App, Plugin } from '@pictode/core'; + +import './method'; + +import { Ruler } from './ruler'; +import { Options, RulerAxis } from './types'; + +const DEFAULT_OPTIONS: Options = { + enabled: true, + axis: 'x', + jump: 50, + thickness: 40, + fill: '#ffffff', +}; + +export class RulerPlugin implements Plugin { + public name: string = 'rulerPlugin'; + public app?: App; + public options: Options; + public rulers: Ruler[]; + private axis: RulerAxis[]; + + constructor(options?: Partial) { + this.options = { ...DEFAULT_OPTIONS, ...options }; + this.axis = []; + this.rulers = []; + if (typeof this.options.axis === 'string') { + this.axis = [this.options.axis]; + } else { + this.axis = this.options.axis; + } + } + + public install(app: App) { + this.app = app; + for (const axis of this.axis) { + this.rulers = this.rulers || []; + this.rulers.push(new Ruler(this.app, axis, { ...this.options })); + } + this.app.emit('ruler:installed', { ruler: this }); + } + + public destroy(): void { + this.rulers.forEach((ruler) => ruler.destroy()); + this.app?.emit('ruler:destroy', { ruler: this }); + } + + public enable(): void { + this.rulers.forEach((ruler) => ruler.triggerVisible(true)); + } + + public disable(): void { + this.rulers.forEach((ruler) => ruler.triggerVisible(false)); + } + + public isEnabled(): boolean { + return this.rulers.some((ruler) => ruler.enabled); + } + + public triggerVisible(visible?: boolean): void { + const rulerVisible = visible || !this.isEnabled(); + this.rulers.forEach((ruler) => ruler.triggerVisible(rulerVisible)); + } + + public update(): void { + this.rulers.forEach((ruler) => ruler.update()); + } +} + +export default RulerPlugin; diff --git a/packages/plugin-ruler/src/method.ts b/packages/plugin-ruler/src/method.ts new file mode 100644 index 00000000..c94d30a0 --- /dev/null +++ b/packages/plugin-ruler/src/method.ts @@ -0,0 +1,19 @@ +import { App } from '@pictode/core'; + +import { RulerPlugin } from './index'; + +App.prototype.triggerRulerVisible = function (enabled?: boolean) { + const rulerPlugin = this.getPlugin('rulerPlugin'); + if (rulerPlugin) { + rulerPlugin.triggerVisible(enabled); + } + return this; +}; + +App.prototype.rulerUpdate = function () { + const rulerPlugin = this.getPlugin('rulerPlugin'); + if (rulerPlugin) { + rulerPlugin.update(); + } + return this; +}; diff --git a/packages/plugin-ruler/src/ruler.ts b/packages/plugin-ruler/src/ruler.ts new file mode 100644 index 00000000..1ccbde13 --- /dev/null +++ b/packages/plugin-ruler/src/ruler.ts @@ -0,0 +1,201 @@ +import { App, Konva } from '@pictode/core'; + +import { Options, RulerAxis } from './types'; + +export class Ruler { + private ruler: Konva.Group; + private rulerFill: Konva.Rect; // Changed to a class property + private tickMarkGroup: Konva.Group; + private ticks: Konva.Path; + private tickText: Konva.Text; + private tickTexts: Konva.Text[] = []; + private width: number; // 声明 width 属性 + private height: number; // 声明 height 属性 + public axis: RulerAxis = 'x'; + public jump: number = 50; + public fill: string = '#ffffff'; + public thickness: number = 40; + public enabled: boolean = true; + + constructor( + private app: App, + axis: RulerAxis, + options: Omit + ) { + this.axis = axis; + this.enabled = options.enabled; + this.jump = options.jump; + this.fill = options.fill; + this.thickness = options.thickness; + + this.width = app.stage.width(); + this.height = app.stage.height(); + + // Initialize the ruler group + this.ruler = new Konva.Group({ + name: `pictode:ruler:${axis}`, + x: 0, + y: 0, + clip: { x: 0, y: 0, width: this.width, height: this.height }, + }); + + // Create and add the ruler fill + this.rulerFill = new Konva.Rect({ + x: 0, + y: 0, + width: this.axis === 'x' ? this.width : this.thickness, + height: this.axis === 'x' ? this.thickness : this.height, + fill: this.fill, + }); + this.ruler.add(this.rulerFill); + + // Initialize the tick mark group + this.tickMarkGroup = new Konva.Group(); + this.ruler.add(this.tickMarkGroup); + + this.ticks = new Konva.Path({ + data: '', + stroke: 'black', + }); + this.ruler.add(this.ticks); + + this.tickText = new Konva.Text({ + fill: 'black', + fontSize: 10, + fontFamily: 'Calibri', + text: '', + align: 'center', + }); + + // Add the ruler to the layer + this.app.optionLayer.add(this.ruler); + + this.app.on('canvas:resized', this.update); + this.app.on('canvas:drag:move', this.update); + this.app.on('canvas:zoom:end', this.update); + + // Initial update to draw ticks + this.update(); + } + + public destroy(): void { + this.app.off('canvas:resized', this.update); + this.app.off('canvas:drag:move', this.update); + this.app.off('canvas:zoom:end', this.update); + + this.ruler.remove(); + } + + public update = (): void => { + if (!this.enabled) { + return; + } + this.updateSize(); + this.updateScale(); + this.updatePosition(); + this.updateTicks(); + }; + + public triggerVisible(enabled: boolean): void { + this.enabled = enabled; + this.ruler.visible(enabled); + this.update(); + } + + private updateSize(): void { + this.width = this.app.stage.width(); + this.height = this.app.stage.height(); + this.ruler.clip({ x: 0, y: 0, width: this.width, height: this.height }); + + // Directly update rulerFill size + if (this.axis === 'x') { + this.rulerFill.width(this.width); + this.ruler.width(this.width); + } else { + this.rulerFill.height(this.height); + this.ruler.height(this.height); + } + } + + private updateScale(): void { + const scaleX = 1 / this.app.stage.scaleX(); + const scaleY = 1 / this.app.stage.scaleY(); + this.ruler.scale({ x: scaleX, y: scaleY }); + } + + private updatePosition(): void { + const stagePos = { + x: -this.app.stage.x() / this.app.stage.scaleX(), + y: -this.app.stage.y() / this.app.stage.scaleY(), + }; + this.ruler.position(stagePos); + } + + private updateTicks(): void { + const stage = this.app.stage; + // Calculate the zero point on the ruler + const rulerZero = this.axis === 'x' ? stage.x() : stage.y(); + + // get the ruler length + const rulerLength = this.axis === 'x' ? this.width : this.height; + + // Note the scale in force + const axisScale = this.axis === 'x' ? stage.scaleX() : stage.scaleY(); + + // displayStep is the jumps in the number displayed on the tick marks + let displayStep = this.jump / axisScale; + + // rulerStep is the gap between ruler tick marks we will draw, and + // the position of the text. + const rulerStep = displayStep * axisScale; + + // how many ticks from zero pt back to start and end of ruler? + const ticksBackward = -Math.ceil(rulerZero / rulerStep); + const ticksForward = Math.ceil((rulerLength - rulerZero) / rulerStep); + + // Which makes the positions in px + const tickPosStart = rulerZero + ticksBackward * rulerStep; + const tickPosEnd = rulerZero + ticksForward * rulerStep; + + // used to create path output + let dataSteps = []; + + // + let tickCnt = 0; // used to count ticks + + // Set up the text for the first ruler marker + let tickTag = ticksBackward * displayStep; + + const tickLength = 5; + + // Loop for each ruler mark + for (let i = tickPosStart; i < tickPosEnd; i = i + rulerStep) { + // Construct the path command for the tick mark + dataSteps.push(this.axis === 'x' ? `M${i},0 L${i},${tickLength}` : `M0,${i} L$${tickLength},${i}`); + + // Manage the tick mark text + if (this.tickTexts.length < tickCnt + 1) { + const tick = this.tickText.clone({ text: tickTag, width: 100 }); + tick.align(this.axis === 'x' ? 'center' : 'left'); + tick.name('tickText' + this.axis); + this.tickTexts[tickCnt] = tick; + this.tickMarkGroup.add(tick); + } + + // Update the tick number + this.tickTexts[tickCnt].setAttrs({ + x: this.axis === 'x' ? i - this.tickTexts[tickCnt].width() / 2 : 10, + y: this.axis === 'x' ? 10 : i - this.tickTexts[tickCnt].height() / 2, + text: Math.floor(tickTag), + visible: true, + }); + + tickCnt = tickCnt + 1; + + tickTag = tickTag + displayStep; + } + + // Apply that path data that we constructed. + this.ticks.data(dataSteps.join(' ')); + } +} diff --git a/packages/plugin-ruler/src/types.ts b/packages/plugin-ruler/src/types.ts new file mode 100644 index 00000000..d1c1c8e7 --- /dev/null +++ b/packages/plugin-ruler/src/types.ts @@ -0,0 +1,27 @@ +import { RulerPlugin } from './index'; + +declare module '@pictode/core' { + export interface App { + triggerRulerVisible(visible?: boolean): void; + rulerUpdate(): void; + } + + export interface EventArgs { + 'ruler:installed': { + ruler: RulerPlugin; + }; + 'ruler:destroy': { + ruler: RulerPlugin; + }; + } +} + +export type RulerAxis = 'x' | 'y'; + +export interface Options { + enabled: boolean; + axis: RulerAxis[] | RulerAxis; + jump: number; + fill: string; + thickness: number; +} diff --git a/packages/plugin-ruler/tsconfig.build.json b/packages/plugin-ruler/tsconfig.build.json new file mode 100644 index 00000000..7a7a7af5 --- /dev/null +++ b/packages/plugin-ruler/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "declaration": true, + "declarationDir": "types", + "forceConsistentCasingInFileNames": true, + "paths": {} + }, + "include": ["src"] +} diff --git a/packages/plugin-ruler/tsconfig.json b/packages/plugin-ruler/tsconfig.json new file mode 100644 index 00000000..925cb119 --- /dev/null +++ b/packages/plugin-ruler/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "baseUrl": "../.." + }, + "exclude": ["**/dist/**/*"] +} diff --git a/packages/plugin-ruler/vite.config.ts b/packages/plugin-ruler/vite.config.ts new file mode 100644 index 00000000..c6ea7311 --- /dev/null +++ b/packages/plugin-ruler/vite.config.ts @@ -0,0 +1,37 @@ +import { join } from 'path'; + +import { defineConfig } from 'vite'; + +import pkg from './package.json'; + +const deps = Object.keys(pkg.dependencies); + +export default defineConfig({ + resolve: { + alias: [ + { find: /^@pictode\/utils/, replacement: join(__dirname, '../packages/utils/src/index.ts') }, + { find: /^@pictode\/core/, replacement: join(__dirname, '../packages/core/src/index.ts') }, + ], + }, + build: { + cssCodeSplit: false, + sourcemap: true, + minify: false, + target: 'esnext', + lib: { + entry: 'src/index.ts', + name: 'PictodePluginRuler', + fileName: 'pictode-plugin-ruler', + }, + rollupOptions: { + external(id: string) { + return deps.some((k) => new RegExp(`^${k}`).test(id)); + }, + output: { + globals: { + axios: 'axios', + }, + }, + }, + }, +}); diff --git a/packages/plugin-selector/src/index.ts b/packages/plugin-selector/src/index.ts index 55e1a45c..a268831d 100644 --- a/packages/plugin-selector/src/index.ts +++ b/packages/plugin-selector/src/index.ts @@ -79,10 +79,6 @@ export class SelectorPlugin implements Plugin { private onCanvasCleared = (): void => { this.selector?.cancelSelect(); }; - - public resetLayer() { - this.selector?.resetLayer(); - } } export default SelectorPlugin; diff --git a/packages/plugin-selector/src/selector.ts b/packages/plugin-selector/src/selector.ts index 29b07e5f..2941ef2b 100644 --- a/packages/plugin-selector/src/selector.ts +++ b/packages/plugin-selector/src/selector.ts @@ -24,7 +24,6 @@ let TRANSFORM_CHANGE_STR = [ export class Selector { public app: App; public selected: Map; - public optionLayer: Konva.Layer; public enabled: boolean; public multipleSelect: boolean; public hightLightConfig: HightLightConfig; @@ -48,11 +47,8 @@ export class Selector { this.hightLightConfig = hightLight; this.rubberConfig = rubber; - this.optionLayer = new Konva.Layer(); - this.optionLayer.name('pictode:option:layer'); - this.app.stage.add(this.optionLayer); - this.transformer = new Konva.Transformer({ + name: 'pictode:transformer', ...this.transformerConfig, shouldOverdrawWholeArea: false, // 空白区域是否支持鼠标事件 flipEnabled: false, @@ -97,15 +93,16 @@ export class Selector { }); }); - this.optionLayer.add(this.transformer); + this.app.optionLayer.add(this.transformer); this.rubberRect = new Konva.Rect({ + name: 'pictode:rubber:rect', stroke: this.rubberConfig.stroke, fill: this.rubberConfig.fill, strokeWidth: this.rubberConfig.strokeWidth, strokeScaleEnabled: false, }); - this.optionLayer.add(this.rubberRect); + this.app.optionLayer.add(this.rubberRect); this.transformer.on<'transformstart'>('transformstart', this.onTransformStart); this.transformer.on<'transformend'>('transformend', this.onTransformEnd); @@ -193,7 +190,7 @@ export class Selector { private setHightRect(...nodes: KonvaNode[]) { this.hightLightRects = nodes.reduce((hightRects, node) => { const rect = new Konva.Rect({ - name: `${node._id}_height_rect`, + name: `pictode:${node._id}:height:rect`, stroke: this.hightLightConfig.stroke, strokeWidth: this.hightLightConfig.strokeWidth, dash: this.hightLightConfig.dash, @@ -201,7 +198,7 @@ export class Selector { strokeScaleEnabled: false, }); this.calculateNodeRect(node, rect, this.hightLightConfig.padding ?? 0); - this.optionLayer.add(rect); + this.app.optionLayer.add(rect); const transformHandler = () => requestAnimationFrame(() => this.calculateNodeRect(node, rect, this.hightLightConfig.padding ?? 0)); @@ -400,12 +397,6 @@ export class Selector { } }; - public resetLayer(): void { - this.optionLayer.remove(); - this.app.stage.add(this.optionLayer); - this.app.render(); - } - public destroy(): void { this.transformer.off('transformstart', this.onTransformStart); this.transformer.off('transformend', this.onTransformEnd); @@ -419,7 +410,7 @@ export class Selector { this.selected.clear(); this.enabled = false; this.transformer.remove(); - this.optionLayer.remove(); + this.rubberRect.remove(); } } diff --git a/pictode/index.html b/pictode/index.html index 31e75a4c..a78efb2a 100644 --- a/pictode/index.html +++ b/pictode/index.html @@ -19,9 +19,9 @@
+ src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_28449_52.501ba0a6902922a405bbf7ec71346672.js"> + src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_28449_52.501ba0a6902922a405bbf7ec71346672.es5.js"> \ No newline at end of file diff --git a/pictode/package.json b/pictode/package.json index 84041199..c71427ca 100644 --- a/pictode/package.json +++ b/pictode/package.json @@ -16,6 +16,7 @@ "@pictode/core": "workspace:^", "@pictode/plugin-alignment": "workspace:^", "@pictode/plugin-history": "workspace:^", + "@pictode/plugin-ruler": "workspace:^1.0.0", "@pictode/plugin-selector": "workspace:^", "@pictode/tools": "workspace:^", "@pictode/utils": "workspace:^", diff --git a/pictode/src/constants/inject-key.ts b/pictode/src/constants/inject-key.ts index bfc846a2..61443248 100644 --- a/pictode/src/constants/inject-key.ts +++ b/pictode/src/constants/inject-key.ts @@ -28,6 +28,7 @@ export const PictodeHotKeyActionsKey: InjectionKey<{ deleteNode: HotKeyFunction; selectAll: HotKeyFunction; resetStage: HotKeyFunction; + rulerVisible: HotKeyFunction; undo: HotKeyFunction; redo: HotKeyFunction; stageDrag: HotKeyFunction; diff --git a/pictode/src/hooks/useContextMenu.ts b/pictode/src/hooks/useContextMenu.ts index 2a3715a9..50de6a6d 100644 --- a/pictode/src/hooks/useContextMenu.ts +++ b/pictode/src/hooks/useContextMenu.ts @@ -16,6 +16,7 @@ export const useContextMenu = (app: App, selected: Ref>) => { deleteNode, selectAll, resetStage, + rulerVisible, undo, redo, makeGroup, @@ -123,6 +124,12 @@ export const useContextMenu = (app: App, selected: Ref>) => { hotKey: hotKey2String(resetStage.hotKey), action: resetStage, }, + { + icon: 'ruler', + label: rulerVisible.directions, + hotKey: hotKey2String(rulerVisible.hotKey), + action: rulerVisible, + }, ] : []; /** diff --git a/pictode/src/hooks/useHotKeyActions.ts b/pictode/src/hooks/useHotKeyActions.ts index a429571b..67a01712 100644 --- a/pictode/src/hooks/useHotKeyActions.ts +++ b/pictode/src/hooks/useHotKeyActions.ts @@ -82,6 +82,19 @@ export const useHotKeyActions = (target: Ref, app: App, sele }, { key: 'r', directions: '重置画布', exact: true, ctrlKey: true, target } ), + rulerVisible: useHotKey( + () => { + app.triggerRulerVisible(); + }, + { + key: 'r', + directions: '标尺', + exact: true, + ctrlKey: true, + shiftKey: true, + target, + } + ), undo: useHotKey( () => { app.undo(); diff --git a/pictode/src/hooks/usePictode.ts b/pictode/src/hooks/usePictode.ts index 0ce8bc5d..c41c3210 100644 --- a/pictode/src/hooks/usePictode.ts +++ b/pictode/src/hooks/usePictode.ts @@ -2,6 +2,7 @@ import { onMounted, onUnmounted, provide, Ref, ref } from 'vue'; import { App, EventArgs, KonvaNode } from '@pictode/core'; import AlignmentPlugin from '@pictode/plugin-alignment'; import HistoryPlugin from '@pictode/plugin-history'; +import RulerPlugin from '@pictode/plugin-ruler'; import SelectorPlugin from '@pictode/plugin-selector'; import { PictodeAppKey, PictodePanelFormKey, PictodePluginsKey, PictodeSelectedKey } from '@/constants/inject-key'; @@ -25,21 +26,16 @@ export const usePictode = () => { enabled: true, }); + const rulerPlugin = new RulerPlugin({ + enabled: false, + axis: ['x', 'y'], + }); + + app.use(rulerPlugin); app.use(historyPlugin); app.use(selectorPlugin); app.use(alignmentPlugin); - app.on('mouse:down', ({ event }) => { - if (event.evt.button === 1) { - app.triggerPanning(true); - } - }); - app.on('mouse:up', ({ event }) => { - if (event.evt.button === 1) { - app.triggerPanning(false); - } - }); - const selected: Ref> = ref([]); const panelFormConfig = ref([]); const panelFormModel = ref({}); diff --git a/pictode/src/locales/languages/en.ts b/pictode/src/locales/languages/en.ts index 81f799e6..957ffe3c 100644 --- a/pictode/src/locales/languages/en.ts +++ b/pictode/src/locales/languages/en.ts @@ -22,6 +22,7 @@ export default { 清除画布: 'Clear Canvas', '将会清空画布内容,是否继续?': 'This will clear the canvas content. Continue?', 重置画布: 'Reset Canvas', + 标尺: 'Ruler', 撤销: 'Undo', 重做: 'Redo', 移动画布: 'Pan Canvas', diff --git a/pictode/src/locales/languages/fr.ts b/pictode/src/locales/languages/fr.ts index 4bd6a98f..4ec82ddf 100644 --- a/pictode/src/locales/languages/fr.ts +++ b/pictode/src/locales/languages/fr.ts @@ -22,6 +22,7 @@ export default { 清除画布: 'Effacer le canevas', '将会清空画布内容,是否继续?': 'Le contenu du canevas sera effacé, continuer ?', 重置画布: 'Réinitialiser le canevas', + 标尺: 'Règle', 撤销: 'Annuler', 重做: 'Refaire', 移动画布: 'Déplacer le canevas', diff --git a/pictode/src/locales/languages/ja.ts b/pictode/src/locales/languages/ja.ts index 7a44b6bf..e8fc2ba2 100644 --- a/pictode/src/locales/languages/ja.ts +++ b/pictode/src/locales/languages/ja.ts @@ -22,6 +22,7 @@ export default { 清除画布: 'キャンバスをクリア', '将会清空画布内容,是否继续?': 'キャンバスの内容がクリアされます、続行しますか?', 重置画布: 'キャンバスをリセット', + 标尺: '目盛り', 撤销: '元に戻す', 重做: 'やり直し', 移动画布: 'キャンバスを移動', diff --git a/pictode/src/locales/languages/zh-CN.ts b/pictode/src/locales/languages/zh-CN.ts index c606efaa..cdf8f285 100644 --- a/pictode/src/locales/languages/zh-CN.ts +++ b/pictode/src/locales/languages/zh-CN.ts @@ -22,6 +22,7 @@ export default { 清除画布: '清除画布', '将会清空画布内容,是否继续?': '将会清空画布内容,是否继续?', 重置画布: '重置画布', + 标尺: '标尺', 撤销: '撤销', 重做: '重做', 移动画布: '移动画布', diff --git a/pictode/src/locales/languages/zh-TW.ts b/pictode/src/locales/languages/zh-TW.ts index 7326afac..7a1831ce 100644 --- a/pictode/src/locales/languages/zh-TW.ts +++ b/pictode/src/locales/languages/zh-TW.ts @@ -22,6 +22,7 @@ export default { 清除画布: '清除畫布', '将会清空画布内容,是否继续?': '將會清空畫布內容,是否繼續?', 重置画布: '重置畫布', + 标尺: '尺子', 撤销: '撤銷', 重做: '重做', 移动画布: '移動畫布', diff --git a/pictode/src/view/canvas/components/HotKeyList.vue b/pictode/src/view/canvas/components/HotKeyList.vue index 208919b9..7b1fbe11 100644 --- a/pictode/src/view/canvas/components/HotKeyList.vue +++ b/pictode/src/view/canvas/components/HotKeyList.vue @@ -37,6 +37,7 @@ const { alignTop, distributeX, distributeY, + rulerVisible, } = injectStrict(PictodeHotKeyActionsKey); const hotKeyListMack: Array<{ @@ -45,7 +46,7 @@ const hotKeyListMack: Array<{ }> = [ { title: '编辑', - hotKeyList: [undo, redo, selectAll, deleteNode, resetStage], + hotKeyList: [undo, redo, selectAll, deleteNode, resetStage, rulerVisible], }, { title: '视图', diff --git a/pictode/src/view/canvas/components/Menu.vue b/pictode/src/view/canvas/components/Menu.vue index 7da542d3..4a35079f 100644 --- a/pictode/src/view/canvas/components/Menu.vue +++ b/pictode/src/view/canvas/components/Menu.vue @@ -16,7 +16,7 @@ interface MenuConfig { } const app = injectStrict(PictodeAppKey); -const { open, resetStage, exportImg } = injectStrict(PictodeHotKeyActionsKey); +const { open, resetStage, exportImg, rulerVisible } = injectStrict(PictodeHotKeyActionsKey); const { execute: exportToFile } = useExport( () => app.toJSON(), @@ -52,6 +52,12 @@ const menuGroups: MenuConfig[][] = [ hotkey: resetStage.hotKey, handler: resetStage, }, + { + icon: 'ruler', + label: rulerVisible.directions ?? '', + hotkey: rulerVisible.hotKey, + handler: rulerVisible, + }, ], ]; diff --git a/pictode/vite.config.ts b/pictode/vite.config.ts index eff45fc4..76f261c6 100644 --- a/pictode/vite.config.ts +++ b/pictode/vite.config.ts @@ -45,6 +45,10 @@ export default defineConfig(({ mode }) => { find: /^@pictode\/plugin-selector/, replacement: join(__dirname, '../packages/plugin-selector/src/index.ts'), }, + { + find: /^@pictode\/plugin-ruler/, + replacement: join(__dirname, '../packages/plugin-ruler/src/index.ts'), + }, { find: /^@pictode\/tools/, replacement: join(__dirname, '../packages/tools/src/index.ts') }, { find: /^@pictode\/vue-aide/, replacement: join(__dirname, '../packages/vue-aide/src/index.ts') }, { find: 'vue', replacement: join(__dirname, './node_modules/vue/dist/vue.esm-bundler.js') }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e812eef4..c1c3499f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -189,6 +189,31 @@ importers: specifier: ^4.1.0 version: 4.4.8(@types/node@18.17.3)(sass@1.66.1) + packages/plugin-ruler: + dependencies: + '@pictode/core': + specifier: workspace:^ + version: link:../core + '@pictode/utils': + specifier: workspace:^ + version: link:../utils + dot: + specifier: 2.0.0-beta.1 + version: 2.0.0-beta.1 + rimraf: + specifier: ^3.0.2 + version: 3.0.2 + roughjs: + specifier: ^4.5.2 + version: 4.5.2 + devDependencies: + typescript: + specifier: ^4.9.3 + version: 4.9.5 + vite: + specifier: ^4.1.0 + version: 4.4.8(@types/node@18.17.3)(sass@1.66.1) + packages/plugin-selector: dependencies: '@pictode/core': @@ -321,6 +346,9 @@ importers: '@pictode/plugin-history': specifier: workspace:^ version: link:../packages/plugin-history + '@pictode/plugin-ruler': + specifier: workspace:^1.0.0 + version: link:../packages/plugin-ruler '@pictode/plugin-selector': specifier: workspace:^ version: link:../packages/plugin-selector @@ -799,7 +827,7 @@ packages: lodash.merge: 4.6.2 lodash.uniq: 4.5.0 resolve-from: 5.0.0 - ts-node: 10.9.1(@types/node@18.17.3)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@18.17.3)(typescript@5.1.6) typescript: 5.1.6 transitivePeerDependencies: - '@swc/core' @@ -2791,7 +2819,7 @@ packages: dependencies: '@types/node': 18.17.3 cosmiconfig: 8.2.0 - ts-node: 10.9.1(@types/node@18.17.3)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@18.17.3)(typescript@5.1.6) typescript: 5.1.6 dev: true @@ -6885,6 +6913,38 @@ packages: yn: 3.1.1 dev: true + /ts-node@10.9.1(@types/node@18.17.3)(typescript@5.1.6): + resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} + hasBin: true + requiresBuild: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.9 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 18.17.3 + acorn: 8.10.0 + acorn-walk: 8.2.0 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.1.6 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + dev: true + /tsconfig-paths@3.14.2: resolution: {integrity: sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==} dependencies: