Skip to content

Commit ee5e018

Browse files
authored
Merge pull request #47 from retejs/fix-freeze-translation
Fix comment translation freeze
2 parents 126e345 + ab20617 commit ee5e018

File tree

8 files changed

+124
-54
lines changed

8 files changed

+124
-54
lines changed

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,12 @@
2424
},
2525
"peerDependencies": {
2626
"rete": "^2.0.1",
27-
"rete-area-plugin": "^2.1.4"
27+
"rete-area-plugin": "^2.1.5"
2828
},
2929
"devDependencies": {
3030
"globals": "^15.9.0",
3131
"rete": "^2.0.1",
32-
"rete-area-plugin": "^2.1.4",
32+
"rete-area-plugin": "^2.1.5",
3333
"rete-cli": "~2.0.1",
3434
"rollup-plugin-sass": "1.12.17",
3535
"typescript": "4.8.4"

src/comment.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class Comment {
2121
private events?: {
2222
contextMenu?: null | (() => void)
2323
pick?: null | (() => void)
24-
translate?: null | ((dx: number, dy: number, sources?: NodeId[]) => void)
24+
translate?: null | ((dx: number, dy: number, sources?: NodeId[]) => Promise<void>)
2525
drag?: null | (() => void)
2626
}
2727
) {
@@ -53,7 +53,7 @@ export class Comment {
5353
const dx = pointer.x - this.prevPosition.x
5454
const dy = pointer.y - this.prevPosition.y
5555

56-
this.translate(dx, dy)
56+
void this.translate(dx, dy)
5757
this.prevPosition = pointer
5858
}
5959
},
@@ -69,7 +69,7 @@ export class Comment {
6969
}
7070

7171
linkTo(ids: NodeId[]) {
72-
this.links = ids || []
72+
this.links = ids
7373
}
7474

7575
linkedTo(nodeId: NodeId) {
@@ -85,12 +85,12 @@ export class Comment {
8585
}
8686
}
8787

88-
translate(dx: number, dy: number, sources?: NodeId[]) {
88+
async translate(dx: number, dy: number, sources?: NodeId[]) {
8989
this.x += dx
9090
this.y += dy
9191

9292
if (this.events?.translate) {
93-
this.events.translate(dx, dy, sources)
93+
await this.events.translate(dx, dy, sources)
9494
}
9595
this.update()
9696
}

src/extensions/selectable.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ type Selector = ReturnType<typeof AreaExtensions.selector>
1010
* @param selector Selector instance
1111
* @param accumulating Accumulating state
1212
*/
13-
export function selectable<S extends ExpectedSchemes, K>(plugin: CommentPlugin<S, K>, selector: Selector, accumulating: { active(): boolean }) {
13+
export function selectable<S extends ExpectedSchemes, K>(
14+
plugin: CommentPlugin<S, K>,
15+
selector: Selector,
16+
accumulating: { active(): boolean }
17+
) {
1418
// eslint-disable-next-line max-statements, complexity
1519
plugin.addPipe(async context => {
1620
if (!context || typeof context !== 'object' || !('type' in context)) return context
@@ -28,8 +32,8 @@ export function selectable<S extends ExpectedSchemes, K>(plugin: CommentPlugin<S
2832
await selector.add({
2933
id,
3034
label: 'comment',
31-
translate(dx, dy) {
32-
plugin.translate(id, dx, dy)
35+
async translate(dx, dy) {
36+
await plugin.translate(id, dx, dy)
3337
},
3438
unselect() {
3539
plugin.unselect(id)
@@ -46,7 +50,7 @@ export function selectable<S extends ExpectedSchemes, K>(plugin: CommentPlugin<S
4650
if (context.type === 'commenttranslated') {
4751
const { id, dx, dy } = context.data
4852

49-
if (selector.isPicked({ id, label: 'comment' })) selector.translate(dx, dy)
53+
if (selector.isPicked({ id, label: 'comment' })) await selector.translate(dx, dy)
5054
}
5155

5256
return context

src/frame-comment.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,19 @@ export class FrameComment extends Comment {
1717
events?: {
1818
contextMenu?: (comment: FrameComment) => void
1919
pick?: (comment: FrameComment) => void
20-
translate?: (comment: FrameComment, dx: number, dy: number, sources?: NodeId[]) => void
20+
translate?: (comment: FrameComment, dx: number, dy: number, sources?: NodeId[]) => Promise<void>
2121
}
2222
) {
2323
super(text, area, {
24-
contextMenu: () => events?.contextMenu && events.contextMenu(this),
25-
pick: () => events?.pick && events.pick(this),
26-
translate: (dx, dy, sources) => events?.translate && events.translate(this, dx, dy, sources),
24+
contextMenu: () => {
25+
events?.contextMenu?.(this)
26+
},
27+
pick: () => {
28+
events?.pick?.(this)
29+
},
30+
translate: async (dx, dy, sources) => {
31+
if (events?.translate) await events.translate(this, dx, dy, sources)
32+
},
2733
drag: () => 1
2834
})
2935

@@ -67,20 +73,20 @@ export class FrameComment extends Comment {
6773

6874
public linkTo(ids: string[]): void {
6975
super.linkTo(ids)
70-
this.resize()
76+
void this.resize()
7177
}
7278

73-
public resize() {
79+
public async resize() {
7480
const bbox = nodesBBox(this.editor, this.area, this.links, { top: 50, left: 20, right: 20, bottom: 20 })
7581

7682
if (bbox) {
7783
this.width = bbox.width
7884
this.height = bbox.height
79-
this.translate(bbox.left - this.x, bbox.top - this.y, this.links)
85+
await this.translate(bbox.left - this.x, bbox.top - this.y, this.links)
8086
} else {
8187
this.width = 100
8288
this.height = 100
83-
this.translate(0, 0, this.links)
89+
await this.translate(0, 0, this.links)
8490
}
8591
}
8692

src/index.ts

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Comment } from './comment'
77
import { FrameComment } from './frame-comment'
88
import { InlineComment } from './inline-comment'
99
import type { ExpectedSchemes } from './types'
10-
import { trackedTranslate } from './utils'
10+
import { trackedTranslate, trackedTranslateComment } from './utils'
1111

1212
export { Comment, FrameComment, InlineComment }
1313
export type { ExpectedSchemes }
@@ -38,7 +38,8 @@ export type Props = {
3838
* A plugin that provides comments for nodes
3939
* @priority 8
4040
*/
41-
export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes>> extends Scope<Produces, [BaseArea<Schemes> | K]> {
41+
export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes>>
42+
extends Scope<Produces, [BaseArea<Schemes> | K]> {
4243
public comments = new Map<Comment['id'], Comment>()
4344
private area!: BaseAreaPlugin<Schemes, K>
4445
private editor!: NodeEditor<Schemes>
@@ -57,17 +58,13 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
5758
this.area = this.parentScope<BaseAreaPlugin<Schemes, K>>(BaseAreaPlugin)
5859
this.editor = this.area.parentScope<NodeEditor<Schemes>>(NodeEditor)
5960

60-
let picked: string[] = []
61-
6261
const { translate, isTranslating } = trackedTranslate({ area: this.area })
62+
const commentTracker = trackedTranslateComment(this.comments)
6363

6464
// eslint-disable-next-line max-statements, complexity
65-
this.addPipe(context => {
65+
this.addPipe(async context => {
6666
if (!context || typeof context !== 'object' || !('type' in context)) return context
6767

68-
if (context.type === 'nodepicked') {
69-
picked.push(context.data.id)
70-
}
7168
if (context.type === 'reordered') {
7269
const views = Array.from(this.area.nodeViews.entries())
7370
const matchedView = views.find(([, view]) => view.element === context.data.element)
@@ -92,47 +89,48 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
9289
const dy = position.y - previous.y
9390
const comments = Array.from(this.comments.values())
9491

95-
comments
92+
await Promise.all(comments
9693
.filter(comment => comment.linkedTo(id))
97-
.map(comment => {
98-
if (comment instanceof InlineComment) comment.translate(dx, dy, [id])
99-
if (comment instanceof FrameComment && !picked.includes(id)) comment.resize()
100-
})
94+
.map(async comment => {
95+
if (comment instanceof InlineComment && !commentTracker.isTranslating(comment.id)) {
96+
await commentTracker.translate(comment.id, dx, dy, [id])
97+
}
98+
if (comment instanceof FrameComment && !commentTracker.isResizing(comment.id)) {
99+
await commentTracker.resize(comment.id)
100+
}
101+
}))
101102
}
102103
if (context.type === 'commenttranslated') {
103104
const { id, dx, dy, sources } = context.data
104105
const comment = this.comments.get(id)
105106

106-
if (!(comment instanceof FrameComment && comment)) return context
107+
if (!(comment instanceof FrameComment)) return context
107108

108-
comment.links
109+
await Promise.all(comment.links
109110
.filter(linkId => !sources?.includes(linkId))
110111
.map(linkId => ({ linkId, view: this.area.nodeViews.get(linkId) }))
111-
// eslint-disable-next-line @typescript-eslint/no-misused-promises
112-
.forEach(async ({ linkId, view }) => {
112+
.map(async ({ linkId, view }) => {
113113
if (!view) return
114114
// prevent an infinite loop if a node is selected and translated along with the selected comment
115115
if (!await this.emit({ type: 'commentlinktranslate', data: { id, link: linkId } })) return
116116

117-
if (!isTranslating(linkId)) void translate(linkId, view.position.x + dx, view.position.y + dy)
118-
})
117+
if (!isTranslating(linkId)) await translate(linkId, view.position.x + dx, view.position.y + dy)
118+
}))
119119
}
120120
if (context.type === 'nodedragged') {
121121
const { id } = context.data
122122
const comments = Array.from(this.comments.values())
123123

124124
comments
125125
.filter((comment): comment is FrameComment => comment instanceof FrameComment)
126-
.filter(comment => {
126+
.forEach(comment => {
127127
const contains = comment.intersects(id)
128128
const links = comment.links.filter(nodeId => nodeId !== id)
129129

130130
comment.linkTo(contains
131131
? [...links, id]
132132
: links)
133133
})
134-
135-
picked = picked.filter(p => p !== id)
136134
}
137135
return context
138136
})
@@ -168,7 +166,7 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
168166
if (newText !== null) {
169167
comment.text = newText
170168
comment.update()
171-
comment.translate(0, 0)
169+
await comment.translate(0, 0)
172170
}
173171
}
174172

@@ -188,7 +186,9 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
188186
this.area.area.content.reorder(comment.element, null)
189187
void this.emit({ type: 'commentselected', data })
190188
},
191-
translate: ({ id }, dx, dy, sources) => void this.emit({ type: 'commenttranslated', data: { id, dx, dy, sources } })
189+
translate: async ({ id }, dx, dy, sources) => {
190+
await this.emit({ type: 'commenttranslated', data: { id, dx, dy, sources } })
191+
}
192192
})
193193

194194
comment.x = x
@@ -214,7 +214,9 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
214214
this.area.area.content.reorder(comment.element, this.area.area.content.holder.firstChild)
215215
void this.emit({ type: 'commentselected', data })
216216
},
217-
translate: ({ id }, dx, dy, sources) => void this.emit({ type: 'commenttranslated', data: { id, dx, dy, sources } })
217+
translate: async ({ id }, dx, dy, sources) => {
218+
await this.emit({ type: 'commenttranslated', data: { id, dx, dy, sources } })
219+
}
218220
})
219221

220222
comment.linkTo(links)
@@ -260,7 +262,7 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
260262

261263
if (!comment) return
262264

263-
comment.translate(dx, dy)
265+
void comment.translate(dx, dy)
264266
}
265267

266268
/**

src/inline-comment.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,19 @@ export class InlineComment extends Comment {
1515
events?: {
1616
contextMenu?: (comment: InlineComment) => void
1717
pick?: (comment: InlineComment) => void
18-
translate?: (comment: InlineComment, dx: number, dy: number, sources?: NodeId[]) => void
18+
translate?: (comment: InlineComment, dx: number, dy: number, sources?: NodeId[]) => Promise<void>
1919
}
2020
) {
2121
super(text, area, {
22-
contextMenu: () => events?.contextMenu && events.contextMenu(this),
23-
pick: () => events?.pick && events.pick(this),
24-
translate: (dx, dy, sources) => events?.translate && events.translate(this, dx, dy, sources),
22+
contextMenu: () => {
23+
events?.contextMenu?.(this)
24+
},
25+
pick: () => {
26+
events?.pick?.(this)
27+
},
28+
translate: async (dx, dy, sources) => {
29+
if (events?.translate) await events.translate(this, dx, dy, sources)
30+
},
2531
drag: () => {
2632
this.link()
2733
}

src/utils.ts

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ export function containsRect(r1: Rect, r2: Rect) {
2323
)
2424
}
2525

26-
export function nodesBBox<S extends ExpectedSchemes>(editor: NodeEditor<S>, area: BaseAreaPlugin<S, any>, ids: NodeId[], margin: number | Rect) {
26+
export function nodesBBox<S extends ExpectedSchemes>(
27+
editor: NodeEditor<S>,
28+
area: BaseAreaPlugin<S, unknown>,
29+
ids: NodeId[],
30+
margin: number | Rect
31+
) {
2732
const marginRect: Rect = typeof margin === 'number'
2833
? { left: margin, top: margin, right: margin, bottom: margin }
2934
: margin
@@ -87,3 +92,50 @@ export function trackedTranslate<S extends ExpectedSchemes, T>(props: { area: Ba
8792
}
8893
}
8994
}
95+
96+
type TranslatableComment = {
97+
id: string
98+
translate: (dx: number, dy: number, sources?: NodeId[]) => Promise<void>
99+
resize?: () => Promise<void>
100+
}
101+
102+
export function trackedTranslateComment<CommentType extends TranslatableComment>(comments: Map<string, CommentType>): {
103+
translate: (id: string, dx: number, dy: number, sources?: NodeId[]) => Promise<void>
104+
resize: (id: string) => Promise<void>
105+
isTranslating: (id: string) => boolean
106+
isResizing: (id: string) => boolean
107+
} {
108+
const activeTranslations = new Map<string, number>()
109+
const increment = (id: string) => activeTranslations.set(id, (activeTranslations.get(id) ?? 0) + 1)
110+
const decrement = (id: string) => activeTranslations.set(id, (activeTranslations.get(id) ?? 0) - 1)
111+
112+
return {
113+
async translate(id, dx, dy, sources) {
114+
const comment = comments.get(id)
115+
116+
if (!comment) throw new Error('cannot find comment')
117+
118+
if (dx !== 0 || dy !== 0) {
119+
increment(id)
120+
await comment.translate(dx, dy, sources)
121+
decrement(id)
122+
}
123+
},
124+
async resize(id) {
125+
const comment = comments.get(id)
126+
127+
if (!comment) throw new Error('cannot find comment')
128+
if (!comment.resize) throw new Error('comment does not support resize')
129+
130+
increment(id)
131+
await comment.resize()
132+
decrement(id)
133+
},
134+
isTranslating(id) {
135+
return (activeTranslations.get(id) ?? 0) > 0
136+
},
137+
isResizing(id) {
138+
return (activeTranslations.get(id) ?? 0) > 0
139+
}
140+
}
141+
}

0 commit comments

Comments
 (0)