Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
10 changes: 5 additions & 5 deletions src/comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@

constructor(
public text: string,
public area: BaseAreaPlugin<ExpectedSchemes, any>,

Check warning on line 20 in src/comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

Unexpected any. Specify a different type

Check warning on line 20 in src/comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'

Check warning on line 20 in src/comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

Unexpected any. Specify a different type

Check warning on line 20 in src/comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'
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<void>)
drag?: null | (() => void)
}
) {
Expand All @@ -41,7 +41,7 @@
},
{
start: () => {
this.prevPosition = { ...area.area.pointer }

Check warning on line 44 in src/comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'

Check warning on line 44 in src/comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'

if (this.events?.pick) {
this.events.pick()
Expand All @@ -49,11 +49,11 @@
},
translate: () => {
if (this.prevPosition) {
const pointer = { ...area.area.pointer }

Check warning on line 52 in src/comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'

Check warning on line 52 in src/comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'
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
}
},
Expand All @@ -69,7 +69,7 @@
}

linkTo(ids: NodeId[]) {
this.links = ids || []
this.links = ids
}

linkedTo(nodeId: NodeId) {
Expand All @@ -85,12 +85,12 @@
}
}

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()
}
Expand Down
12 changes: 8 additions & 4 deletions src/extensions/selectable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@
* @param selector Selector instance
* @param accumulating Accumulating state
*/
export function selectable<S extends ExpectedSchemes, K>(plugin: CommentPlugin<S, K>, selector: Selector, accumulating: { active(): boolean }) {
export function selectable<S extends ExpectedSchemes, K>(
plugin: CommentPlugin<S, K>,
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
Expand All @@ -28,8 +32,8 @@
await selector.add({
id,
label: 'comment',
translate(dx, dy) {
plugin.translate(id, dx, dy)
async translate(dx, dy) {
await plugin.translate(id, dx, dy)

Check warning on line 36 in src/extensions/selectable.ts

View workflow job for this annotation

GitHub Actions / ci / ci

Placing a void expression inside another expression is forbidden. Move it to its own statement instead

Check failure on line 36 in src/extensions/selectable.ts

View workflow job for this annotation

GitHub Actions / ci / ci

Unexpected `await` of a non-Promise (non-"Thenable") value

Check warning on line 36 in src/extensions/selectable.ts

View workflow job for this annotation

GitHub Actions / ci / ci

Placing a void expression inside another expression is forbidden. Move it to its own statement instead

Check failure on line 36 in src/extensions/selectable.ts

View workflow job for this annotation

GitHub Actions / ci / ci

Unexpected `await` of a non-Promise (non-"Thenable") value
},
unselect() {
plugin.unselect(id)
Expand All @@ -46,7 +50,7 @@
if (context.type === 'commenttranslated') {
const { id, dx, dy } = context.data

if (selector.isPicked({ id, label: 'comment' })) selector.translate(dx, dy)
if (selector.isPicked({ id, label: 'comment' })) await selector.translate(dx, dy)
}

return context
Expand Down
22 changes: 14 additions & 8 deletions src/frame-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@

constructor(
text: string,
area: BaseAreaPlugin<ExpectedSchemes, any>,

Check warning on line 15 in src/frame-comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

Unexpected any. Specify a different type

Check warning on line 15 in src/frame-comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'

Check warning on line 15 in src/frame-comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

Unexpected any. Specify a different type

Check warning on line 15 in src/frame-comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'
private editor: NodeEditor<ExpectedSchemes>,
events?: {
contextMenu?: (comment: FrameComment) => 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<void>
}
) {
super(text, area, {

Check warning on line 23 in src/frame-comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'

Check warning on line 23 in src/frame-comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'
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
})

Expand All @@ -40,7 +46,7 @@
}

public contains(nodeId: NodeId) {
const view = this.area.nodeViews.get(nodeId)

Check warning on line 49 in src/frame-comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'

Check warning on line 49 in src/frame-comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'
const node = this.editor.getNode(nodeId)

if (!view) return false
Expand All @@ -53,7 +59,7 @@
}

public intersects(nodeId: NodeId) {
const view = this.area.nodeViews.get(nodeId)

Check warning on line 62 in src/frame-comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'

Check warning on line 62 in src/frame-comment.ts

View workflow job for this annotation

GitHub Actions / ci / ci

The type of 'area' contains 'any'
const node = this.editor.getNode(nodeId)

if (!view) return false
Expand All @@ -67,20 +73,20 @@

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)
}
}

Expand Down
54 changes: 28 additions & 26 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -38,7 +38,8 @@ export type Props = {
* A plugin that provides comments for nodes
* @priority 8
*/
export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes>> extends Scope<Produces, [BaseArea<Schemes> | K]> {
export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes>>
extends Scope<Produces, [BaseArea<Schemes> | K]> {
public comments = new Map<Comment['id'], Comment>()
private area!: BaseAreaPlugin<Schemes, K>
private editor!: NodeEditor<Schemes>
Expand All @@ -57,17 +58,13 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
this.area = this.parentScope<BaseAreaPlugin<Schemes, K>>(BaseAreaPlugin)
this.editor = this.area.parentScope<NodeEditor<Schemes>>(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)
Expand All @@ -92,47 +89,48 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
const dy = position.y - previous.y
const comments = Array.from(this.comments.values())

comments
await Promise.all(comments
.filter(comment => 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
const comments = Array.from(this.comments.values())

comments
.filter((comment): comment is FrameComment => comment instanceof FrameComment)
.filter(comment => {
.forEach(comment => {
const contains = comment.intersects(id)
const links = comment.links.filter(nodeId => nodeId !== id)

comment.linkTo(contains
? [...links, id]
: links)
})

picked = picked.filter(p => p !== id)
}
return context
})
Expand Down Expand Up @@ -168,7 +166,7 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
if (newText !== null) {
comment.text = newText
comment.update()
comment.translate(0, 0)
await comment.translate(0, 0)
}
}

Expand All @@ -188,7 +186,9 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
this.area.area.content.reorder(comment.element, null)
void this.emit({ type: 'commentselected', data })
},
translate: ({ id }, dx, dy, sources) => 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
Expand All @@ -214,7 +214,9 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes
this.area.area.content.reorder(comment.element, this.area.area.content.holder.firstChild)
void this.emit({ type: 'commentselected', data })
},
translate: ({ id }, dx, dy, sources) => 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)
Expand Down Expand Up @@ -260,7 +262,7 @@ export class CommentPlugin<Schemes extends ExpectedSchemes, K = BaseArea<Schemes

if (!comment) return

comment.translate(dx, dy)
void comment.translate(dx, dy)
}

/**
Expand Down
14 changes: 10 additions & 4 deletions src/inline-comment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,19 @@ export class InlineComment extends Comment {
events?: {
contextMenu?: (comment: InlineComment) => 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<void>
}
) {
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()
}
Expand Down
54 changes: 53 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ export function containsRect(r1: Rect, r2: Rect) {
)
}

export function nodesBBox<S extends ExpectedSchemes>(editor: NodeEditor<S>, area: BaseAreaPlugin<S, any>, ids: NodeId[], margin: number | Rect) {
export function nodesBBox<S extends ExpectedSchemes>(
editor: NodeEditor<S>,
area: BaseAreaPlugin<S, unknown>,
ids: NodeId[],
margin: number | Rect
) {
const marginRect: Rect = typeof margin === 'number'
? { left: margin, top: margin, right: margin, bottom: margin }
: margin
Expand Down Expand Up @@ -87,3 +92,50 @@ export function trackedTranslate<S extends ExpectedSchemes, T>(props: { area: Ba
}
}
}

type TranslatableComment = {
id: string
translate: (dx: number, dy: number, sources?: NodeId[]) => Promise<void>
resize?: () => Promise<void>
}

export function trackedTranslateComment<CommentType extends TranslatableComment>(comments: Map<string, CommentType>): {
translate: (id: string, dx: number, dy: number, sources?: NodeId[]) => Promise<void>
resize: (id: string) => Promise<void>
isTranslating: (id: string) => boolean
isResizing: (id: string) => boolean
} {
const activeTranslations = new Map<string, number>()
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
}
}
}
Loading