|
| 1 | +import { NodeId } from 'rete' |
| 2 | +import { BaseAreaPlugin } from 'rete-area-plugin' |
| 3 | + |
| 4 | +import { Comment } from './comment' |
| 5 | +import { FrameComment } from './frame-comment' |
| 6 | +import { detachNodeFromFrame, FrameMembership, syncFrameOnNodeDrop } from './frame-membership' |
| 7 | +import type { Produces } from './index' |
| 8 | +import { InlineComment } from './inline-comment' |
| 9 | +import type { ExpectedSchemes, MembershipState, Position } from './types' |
| 10 | +import { trackDragging, trackedTranslate, trackedTranslateComment } from './utils' |
| 11 | + |
| 12 | +export type CommentNodeSyncDeps<Schemes extends ExpectedSchemes, K> = { |
| 13 | + area: BaseAreaPlugin<Schemes, K> |
| 14 | + comments: Map<Comment['id'], Comment> |
| 15 | + membershipState: MembershipState |
| 16 | + emit: (signal: Produces) => Promise<unknown> |
| 17 | +} |
| 18 | + |
| 19 | +export function createCommentNodeSync<Schemes extends ExpectedSchemes, K>(deps: CommentNodeSyncDeps<Schemes, K>) { |
| 20 | + const { area, comments, membershipState, emit } = deps |
| 21 | + const { translate, isTranslating } = trackedTranslate({ area }) |
| 22 | + const commentTracker = trackedTranslateComment(comments) |
| 23 | + const dragging = trackDragging() |
| 24 | + const nodeDragPrevious = new Map<NodeId, Position>() |
| 25 | + |
| 26 | + const collectFrameMembership = async (nodeId: NodeId) => { |
| 27 | + const frames: FrameMembership[] = [] |
| 28 | + |
| 29 | + for (const comment of Array.from(comments.values())) { |
| 30 | + if (comment instanceof FrameComment) { |
| 31 | + const entry = await syncFrameOnNodeDrop(comment, nodeId) |
| 32 | + |
| 33 | + if (entry) frames.push(entry) |
| 34 | + } |
| 35 | + } |
| 36 | + |
| 37 | + return frames |
| 38 | + } |
| 39 | + |
| 40 | + const emitMembership = async ( |
| 41 | + frames: FrameMembership[], |
| 42 | + node?: { id: NodeId, previous: Position, current: Position } |
| 43 | + ) => { |
| 44 | + if (!frames.length) return |
| 45 | + |
| 46 | + await emit({ |
| 47 | + type: 'commentmembershipchanged', |
| 48 | + data: node |
| 49 | + ? { node, frames } |
| 50 | + : { frames } |
| 51 | + }) |
| 52 | + } |
| 53 | + |
| 54 | + return { |
| 55 | + beginNodeDrag(nodeId: NodeId) { |
| 56 | + dragging.start(nodeId) |
| 57 | + const view = area.nodeViews.get(nodeId) |
| 58 | + |
| 59 | + if (view) nodeDragPrevious.set(nodeId, { ...view.position }) |
| 60 | + }, |
| 61 | + |
| 62 | + reorderLinkedComments(element: HTMLElement) { |
| 63 | + const views = Array.from(area.nodeViews.entries()) |
| 64 | + const matchedView = views.find(([, view]) => view.element === element) |
| 65 | + |
| 66 | + if (!matchedView) return |
| 67 | + |
| 68 | + const linkedComments = Array.from(comments.entries()) |
| 69 | + .filter(([, comment]) => comment.linkedTo(matchedView[0])) |
| 70 | + |
| 71 | + for (const [, comment] of linkedComments) { |
| 72 | + if (comment instanceof InlineComment) { |
| 73 | + area.area.content.reorder(comment.element, matchedView[1].element.nextElementSibling) |
| 74 | + } |
| 75 | + if (comment instanceof FrameComment) { |
| 76 | + area.area.content.reorder(comment.element, area.area.content.holder.firstChild) |
| 77 | + } |
| 78 | + } |
| 79 | + }, |
| 80 | + |
| 81 | + async onNodeTranslated(data: { id: NodeId, position: Position, previous: Position }) { |
| 82 | + if (membershipState.restoring) return |
| 83 | + |
| 84 | + const { id, position, previous } = data |
| 85 | + const dx = position.x - previous.x |
| 86 | + const dy = position.y - previous.y |
| 87 | + |
| 88 | + await Promise.all(Array.from(comments.values()) |
| 89 | + .filter(comment => comment.linkedTo(id)) |
| 90 | + .map(async comment => { |
| 91 | + if (comment instanceof InlineComment && !commentTracker.isTranslating(comment.id)) { |
| 92 | + await commentTracker.translate(comment.id, dx, dy, [id]) |
| 93 | + } |
| 94 | + if (comment instanceof FrameComment |
| 95 | + && !dragging.isDragging(id) |
| 96 | + && !commentTracker.isResizing(comment.id)) { |
| 97 | + await commentTracker.resize(comment.id) |
| 98 | + } |
| 99 | + })) |
| 100 | + }, |
| 101 | + |
| 102 | + async onCommentTranslated(data: { id: Comment['id'], dx: number, dy: number, sources?: NodeId[] }) { |
| 103 | + const { id, dx, dy, sources } = data |
| 104 | + const comment = comments.get(id) |
| 105 | + |
| 106 | + if (!(comment instanceof FrameComment)) return |
| 107 | + |
| 108 | + await Promise.all(comment.links |
| 109 | + .filter(linkId => !sources?.includes(linkId)) |
| 110 | + .map(linkId => ({ linkId, view: area.nodeViews.get(linkId) })) |
| 111 | + .map(async ({ linkId, view }) => { |
| 112 | + if (!view) return |
| 113 | + // prevent an infinite loop if a node is selected and translated along with the selected comment |
| 114 | + if (!await emit({ type: 'commentlinktranslate', data: { id, link: linkId } })) return |
| 115 | + |
| 116 | + if (!isTranslating(linkId)) await translate(linkId, view.position.x + dx, view.position.y + dy) |
| 117 | + })) |
| 118 | + }, |
| 119 | + |
| 120 | + async finalizeNodeDrag(nodeId: NodeId) { |
| 121 | + if (membershipState.restoring) return |
| 122 | + |
| 123 | + const nodePrevious = nodeDragPrevious.get(nodeId) |
| 124 | + const nodeView = area.nodeViews.get(nodeId) |
| 125 | + const frames = await collectFrameMembership(nodeId) |
| 126 | + |
| 127 | + if (frames.length && nodePrevious && nodeView) { |
| 128 | + await emitMembership(frames, { |
| 129 | + id: nodeId, |
| 130 | + previous: nodePrevious, |
| 131 | + current: { ...nodeView.position } |
| 132 | + }) |
| 133 | + } |
| 134 | + |
| 135 | + nodeDragPrevious.delete(nodeId) |
| 136 | + dragging.stop(nodeId) |
| 137 | + }, |
| 138 | + |
| 139 | + async cleanupOnNodeRemoved(nodeId: NodeId) { |
| 140 | + dragging.stop(nodeId) |
| 141 | + nodeDragPrevious.delete(nodeId) |
| 142 | + |
| 143 | + const frames: FrameMembership[] = [] |
| 144 | + |
| 145 | + for (const comment of Array.from(comments.values())) { |
| 146 | + if (comment instanceof InlineComment && comment.linkedTo(nodeId)) { |
| 147 | + comment.linkTo([]) |
| 148 | + } |
| 149 | + if (comment instanceof FrameComment) { |
| 150 | + const entry = await detachNodeFromFrame(comment, nodeId) |
| 151 | + |
| 152 | + if (entry) frames.push(entry) |
| 153 | + } |
| 154 | + } |
| 155 | + |
| 156 | + await emitMembership(frames) |
| 157 | + } |
| 158 | + } |
| 159 | +} |
0 commit comments