diff --git a/package-lock.json b/package-lock.json index f29588d..3a4bd7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "devDependencies": { "globals": "^15.9.0", "rete": "^2.0.1", - "rete-area-plugin": "^2.1.4", + "rete-area-plugin": "^2.1.5", "rete-cli": "~2.0.1", "rollup-plugin-sass": "1.12.17", "typescript": "4.8.4" @@ -6511,9 +6511,9 @@ } }, "node_modules/rete-area-plugin": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/rete-area-plugin/-/rete-area-plugin-2.1.4.tgz", - "integrity": "sha512-ZJ+5Kwqz0H50ymTtlnPGsqdbrvYbE6vhNjXAhbGOHrbp0v4jRsoQexkMA1ovt8+hJ9ttMxiH+ziSw156buAU/A==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/rete-area-plugin/-/rete-area-plugin-2.1.5.tgz", + "integrity": "sha512-iquEvwkQlcsO4cmgM3Z37TG0AWaE536dfA+lCJAze5YJzVx4RBaViUCqdB4dUA/utSytpBCkiDC4D3ztM9akGQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 1c9b7d4..05fd1ce 100644 --- a/package.json +++ b/package.json @@ -24,12 +24,12 @@ }, "peerDependencies": { "rete": "^2.0.1", - "rete-area-plugin": "^2.1.4" + "rete-area-plugin": "^2.1.5" }, "devDependencies": { "globals": "^15.9.0", "rete": "^2.0.1", - "rete-area-plugin": "^2.1.4", + "rete-area-plugin": "^2.1.5", "rete-cli": "~2.0.1", "rollup-plugin-sass": "1.12.17", "typescript": "4.8.4" diff --git a/src/comment.ts b/src/comment.ts index 6456b3a..39268a1 100644 --- a/src/comment.ts +++ b/src/comment.ts @@ -21,7 +21,7 @@ export class Comment { private events?: { contextMenu?: null | (() => void) pick?: null | (() => void) - translate?: null | ((dx: number, dy: number, sources?: NodeId[]) => void) + translate?: null | ((dx: number, dy: number, sources?: NodeId[]) => Promise) drag?: null | (() => void) } ) { @@ -53,7 +53,7 @@ export class Comment { const dx = pointer.x - this.prevPosition.x const dy = pointer.y - this.prevPosition.y - this.translate(dx, dy) + void this.translate(dx, dy) this.prevPosition = pointer } }, @@ -69,7 +69,7 @@ export class Comment { } linkTo(ids: NodeId[]) { - this.links = ids || [] + this.links = ids } linkedTo(nodeId: NodeId) { @@ -85,12 +85,12 @@ export class Comment { } } - translate(dx: number, dy: number, sources?: NodeId[]) { + async translate(dx: number, dy: number, sources?: NodeId[]) { this.x += dx this.y += dy if (this.events?.translate) { - this.events.translate(dx, dy, sources) + await this.events.translate(dx, dy, sources) } this.update() } diff --git a/src/extensions/selectable.ts b/src/extensions/selectable.ts index 01a90f1..af1e966 100644 --- a/src/extensions/selectable.ts +++ b/src/extensions/selectable.ts @@ -10,7 +10,11 @@ type Selector = ReturnType * @param selector Selector instance * @param accumulating Accumulating state */ -export function selectable(plugin: CommentPlugin, selector: Selector, accumulating: { active(): boolean }) { +export function selectable( + plugin: CommentPlugin, + selector: Selector, + accumulating: { active(): boolean } +) { // eslint-disable-next-line max-statements, complexity plugin.addPipe(async context => { if (!context || typeof context !== 'object' || !('type' in context)) return context @@ -28,8 +32,8 @@ export function selectable(plugin: CommentPlugin(plugin: CommentPlugin void pick?: (comment: FrameComment) => void - translate?: (comment: FrameComment, dx: number, dy: number, sources?: NodeId[]) => void + translate?: (comment: FrameComment, dx: number, dy: number, sources?: NodeId[]) => Promise } ) { super(text, area, { - contextMenu: () => events?.contextMenu && events.contextMenu(this), - pick: () => events?.pick && events.pick(this), - translate: (dx, dy, sources) => events?.translate && events.translate(this, dx, dy, sources), + contextMenu: () => { + events?.contextMenu?.(this) + }, + pick: () => { + events?.pick?.(this) + }, + translate: async (dx, dy, sources) => { + if (events?.translate) await events.translate(this, dx, dy, sources) + }, drag: () => 1 }) @@ -67,20 +73,20 @@ export class FrameComment extends Comment { public linkTo(ids: string[]): void { super.linkTo(ids) - this.resize() + void this.resize() } - public resize() { + public async resize() { const bbox = nodesBBox(this.editor, this.area, this.links, { top: 50, left: 20, right: 20, bottom: 20 }) if (bbox) { this.width = bbox.width this.height = bbox.height - this.translate(bbox.left - this.x, bbox.top - this.y, this.links) + await this.translate(bbox.left - this.x, bbox.top - this.y, this.links) } else { this.width = 100 this.height = 100 - this.translate(0, 0, this.links) + await this.translate(0, 0, this.links) } } diff --git a/src/index.ts b/src/index.ts index d9bf3b5..68b1856 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import { Comment } from './comment' import { FrameComment } from './frame-comment' import { InlineComment } from './inline-comment' import type { ExpectedSchemes } from './types' -import { trackedTranslate } from './utils' +import { trackedTranslate, trackedTranslateComment } from './utils' export { Comment, FrameComment, InlineComment } export type { ExpectedSchemes } @@ -38,7 +38,8 @@ export type Props = { * A plugin that provides comments for nodes * @priority 8 */ -export class CommentPlugin> extends Scope | K]> { +export class CommentPlugin> + extends Scope | K]> { public comments = new Map() private area!: BaseAreaPlugin private editor!: NodeEditor @@ -57,17 +58,13 @@ export class CommentPlugin>(BaseAreaPlugin) this.editor = this.area.parentScope>(NodeEditor) - let picked: string[] = [] - const { translate, isTranslating } = trackedTranslate({ area: this.area }) + const commentTracker = trackedTranslateComment(this.comments) // eslint-disable-next-line max-statements, complexity - this.addPipe(context => { + this.addPipe(async context => { if (!context || typeof context !== 'object' || !('type' in context)) return context - if (context.type === 'nodepicked') { - picked.push(context.data.id) - } if (context.type === 'reordered') { const views = Array.from(this.area.nodeViews.entries()) const matchedView = views.find(([, view]) => view.element === context.data.element) @@ -92,30 +89,33 @@ export class CommentPlugin comment.linkedTo(id)) - .map(comment => { - if (comment instanceof InlineComment) comment.translate(dx, dy, [id]) - if (comment instanceof FrameComment && !picked.includes(id)) comment.resize() - }) + .map(async comment => { + if (comment instanceof InlineComment && !commentTracker.isTranslating(comment.id)) { + await commentTracker.translate(comment.id, dx, dy, [id]) + } + if (comment instanceof FrameComment && !commentTracker.isResizing(comment.id)) { + await commentTracker.resize(comment.id) + } + })) } if (context.type === 'commenttranslated') { const { id, dx, dy, sources } = context.data const comment = this.comments.get(id) - if (!(comment instanceof FrameComment && comment)) return context + if (!(comment instanceof FrameComment)) return context - comment.links + await Promise.all(comment.links .filter(linkId => !sources?.includes(linkId)) .map(linkId => ({ linkId, view: this.area.nodeViews.get(linkId) })) - // eslint-disable-next-line @typescript-eslint/no-misused-promises - .forEach(async ({ linkId, view }) => { + .map(async ({ linkId, view }) => { if (!view) return // prevent an infinite loop if a node is selected and translated along with the selected comment if (!await this.emit({ type: 'commentlinktranslate', data: { id, link: linkId } })) return - if (!isTranslating(linkId)) void translate(linkId, view.position.x + dx, view.position.y + dy) - }) + if (!isTranslating(linkId)) await translate(linkId, view.position.x + dx, view.position.y + dy) + })) } if (context.type === 'nodedragged') { const { id } = context.data @@ -123,7 +123,7 @@ export class CommentPlugin comment instanceof FrameComment) - .filter(comment => { + .forEach(comment => { const contains = comment.intersects(id) const links = comment.links.filter(nodeId => nodeId !== id) @@ -131,8 +131,6 @@ export class CommentPlugin p !== id) } return context }) @@ -168,7 +166,7 @@ export class CommentPlugin void this.emit({ type: 'commenttranslated', data: { id, dx, dy, sources } }) + translate: async ({ id }, dx, dy, sources) => { + await this.emit({ type: 'commenttranslated', data: { id, dx, dy, sources } }) + } }) comment.x = x @@ -214,7 +214,9 @@ export class CommentPlugin void this.emit({ type: 'commenttranslated', data: { id, dx, dy, sources } }) + translate: async ({ id }, dx, dy, sources) => { + await this.emit({ type: 'commenttranslated', data: { id, dx, dy, sources } }) + } }) comment.linkTo(links) @@ -260,7 +262,7 @@ export class CommentPlugin void pick?: (comment: InlineComment) => void - translate?: (comment: InlineComment, dx: number, dy: number, sources?: NodeId[]) => void + translate?: (comment: InlineComment, dx: number, dy: number, sources?: NodeId[]) => Promise } ) { super(text, area, { - contextMenu: () => events?.contextMenu && events.contextMenu(this), - pick: () => events?.pick && events.pick(this), - translate: (dx, dy, sources) => events?.translate && events.translate(this, dx, dy, sources), + contextMenu: () => { + events?.contextMenu?.(this) + }, + pick: () => { + events?.pick?.(this) + }, + translate: async (dx, dy, sources) => { + if (events?.translate) await events.translate(this, dx, dy, sources) + }, drag: () => { this.link() } diff --git a/src/utils.ts b/src/utils.ts index 467018d..5fcdc82 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -23,7 +23,12 @@ export function containsRect(r1: Rect, r2: Rect) { ) } -export function nodesBBox(editor: NodeEditor, area: BaseAreaPlugin, ids: NodeId[], margin: number | Rect) { +export function nodesBBox( + editor: NodeEditor, + area: BaseAreaPlugin, + ids: NodeId[], + margin: number | Rect +) { const marginRect: Rect = typeof margin === 'number' ? { left: margin, top: margin, right: margin, bottom: margin } : margin @@ -87,3 +92,50 @@ export function trackedTranslate(props: { area: Ba } } } + +type TranslatableComment = { + id: string + translate: (dx: number, dy: number, sources?: NodeId[]) => Promise + resize?: () => Promise +} + +export function trackedTranslateComment(comments: Map): { + translate: (id: string, dx: number, dy: number, sources?: NodeId[]) => Promise + resize: (id: string) => Promise + isTranslating: (id: string) => boolean + isResizing: (id: string) => boolean +} { + const activeTranslations = new Map() + const increment = (id: string) => activeTranslations.set(id, (activeTranslations.get(id) ?? 0) + 1) + const decrement = (id: string) => activeTranslations.set(id, (activeTranslations.get(id) ?? 0) - 1) + + return { + async translate(id, dx, dy, sources) { + const comment = comments.get(id) + + if (!comment) throw new Error('cannot find comment') + + if (dx !== 0 || dy !== 0) { + increment(id) + await comment.translate(dx, dy, sources) + decrement(id) + } + }, + async resize(id) { + const comment = comments.get(id) + + if (!comment) throw new Error('cannot find comment') + if (!comment.resize) throw new Error('comment does not support resize') + + increment(id) + await comment.resize() + decrement(id) + }, + isTranslating(id) { + return (activeTranslations.get(id) ?? 0) > 0 + }, + isResizing(id) { + return (activeTranslations.get(id) ?? 0) > 0 + } + } +}