diff --git a/biome.json b/biome.json index e1ffa5e..eecb262 100644 --- a/biome.json +++ b/biome.json @@ -13,7 +13,8 @@ "rules": { "recommended": true, "complexity": { - "noForEach": "off" + "noForEach": "off", + "noBannedTypes": "off" }, "correctness": { "useExhaustiveDependencies": "off" diff --git a/package.json b/package.json index 08f82ce..4de741f 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "1.3.0", "partykit": "0.0.27", + "rbush": "^4.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "tldraw": "2.3.0", @@ -24,6 +25,7 @@ }, "devDependencies": { "@biomejs/biome": "1.4.1", + "@types/rbush": "^3.0.3", "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", "@vitejs/plugin-react": "^4.0.3", @@ -34,4 +36,4 @@ "vite-plugin-wasm": "^3.2.2" }, "packageManager": "yarn@4.0.2" -} \ No newline at end of file +} diff --git a/src/App.tsx b/src/App.tsx index 3401bad..bd79a72 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -6,6 +6,7 @@ import { SocialShapeUtil } from "./SocialShapeUtil"; import { SocialShapeTool } from "./SocialShapeTool"; import { CustomToolbar, overrides } from "./ui"; import { getDocumentMeta, getUserId, getUsersInRoom, setDocumentMeta } from "./storeUtils"; +import { registerDefaultPropagators } from "./propagators/ScopedPropagators"; const shapeUtils = [SocialShapeUtil]; const tools = [SocialShapeTool]; @@ -35,17 +36,19 @@ export default function Canvas() { onMount={(editor) => { //@ts-ignore editor.getStateDescendant('select.idle').handleDoubleClickOnCanvas = () => void null; - console.log(editor.store.allRecords()) - const userId = getUserId(editor) - setDocumentMeta(editor, { - [userId]: 123 - }) - // console.log(getDocumentMeta(editor)) - // removeDocumentMeta(editor, 'test') - setTimeout(() => { - console.log(getDocumentMeta(editor)) - console.log(getUsersInRoom(editor)) - }, 2000); + + registerDefaultPropagators(editor) + + // const userId = getUserId(editor) + // setDocumentMeta(editor, { + // [userId]: 123 + // }) + // // console.log(getDocumentMeta(editor)) + // // removeDocumentMeta(editor, 'test') + // setTimeout(() => { + // console.log(getDocumentMeta(editor)) + // console.log(getUsersInRoom(editor)) + // }, 2000); }} components={{ diff --git a/src/propagators/DeltaTime.ts b/src/propagators/DeltaTime.ts new file mode 100644 index 0000000..5f58c4f --- /dev/null +++ b/src/propagators/DeltaTime.ts @@ -0,0 +1,24 @@ +//@ts-nocheck +export class DeltaTime { + private static lastTime = Date.now() + private static initialized = false + private static _dt = 0 + + static get dt(): number { + if (!DeltaTime.initialized) { + DeltaTime.lastTime = Date.now() + DeltaTime.initialized = true + window.requestAnimationFrame(DeltaTime.tick) + return 0 + } + const clamp = (min: number, max: number, value: number) => Math.min(max, Math.max(min, value)) + return clamp(0, 100, DeltaTime._dt) + } + + static tick(nowish) { + DeltaTime._dt = nowish - DeltaTime.lastTime + DeltaTime.lastTime = nowish + + window.requestAnimationFrame(DeltaTime.tick) + } +} \ No newline at end of file diff --git a/src/propagators/Geo.ts b/src/propagators/Geo.ts new file mode 100644 index 0000000..e85eed3 --- /dev/null +++ b/src/propagators/Geo.ts @@ -0,0 +1,119 @@ +import { SpatialIndex } from "./SpatialIndex" +import { Editor, TLShape, TLShapeId, VecLike, polygonsIntersect } from "tldraw" + +export class Geo { + editor: Editor + spatialIndex: SpatialIndex + constructor(editor: Editor) { + this.editor = editor + this.spatialIndex = new SpatialIndex(editor) + } + intersects(shape: TLShape | TLShapeId): boolean { + const id = typeof shape === 'string' ? shape : shape?.id ?? null + if (!id) return false + const sourceTransform = this.editor.getShapePageTransform(id) + const sourceGeo = this.editor.getShapeGeometry(id) + const sourcePagespace = sourceTransform.applyToPoints(sourceGeo.vertices) + const sourceBounds = this.editor.getShapePageBounds(id) + + const shapesInBounds = this.spatialIndex.getShapeIdsInsideBounds(sourceBounds) + for (const boundsShapeId of shapesInBounds) { + if (boundsShapeId === id) continue + const pageShape = this.editor.getShape(boundsShapeId) + if (!pageShape) continue + if (pageShape.type === 'arrow') continue + const pageShapeGeo = this.editor.getShapeGeometry(pageShape) + const pageShapeTransform = this.editor.getShapePageTransform(pageShape) + const pageShapePagespace = pageShapeTransform.applyToPoints(pageShapeGeo.vertices) + const pageShapeBounds = this.editor.getShapePageBounds(boundsShapeId) + if (polygonsIntersect(sourcePagespace, pageShapePagespace) || sourceBounds.contains(pageShapeBounds) || pageShapeBounds.contains(sourceBounds)) { + return true + } + } + return false + } + distance(a: TLShape | TLShapeId, b: TLShape | TLShapeId): VecLike { + const idA = typeof a === 'string' ? a : a?.id ?? null + const idB = typeof b === 'string' ? b : b?.id ?? null + if (!idA || !idB) return { x: 0, y: 0 } + const shapeA = this.editor.getShape(idA) + const shapeB = this.editor.getShape(idB) + if (!shapeA || !shapeB) return { x: 0, y: 0 } + return { x: shapeA.x - shapeB.x, y: shapeA.y - shapeB.y } + } + distanceCenter(a: TLShape | TLShapeId, b: TLShape | TLShapeId): VecLike { + const idA = typeof a === 'string' ? a : a?.id ?? null + const idB = typeof b === 'string' ? b : b?.id ?? null + if (!idA || !idB) return { x: 0, y: 0 } + const aBounds = this.editor.getShapePageBounds(idA) + const bBounds = this.editor.getShapePageBounds(idB) + if (!aBounds || !bBounds) return { x: 0, y: 0 } + const aCenter = aBounds.center + const bCenter = bBounds.center + return { x: aCenter.x - bCenter.x, y: aCenter.y - bCenter.y } + } + getIntersects(shape: TLShape | TLShapeId): TLShape[] { + const id = typeof shape === 'string' ? shape : shape?.id ?? null + if (!id) return [] + const sourceTransform = this.editor.getShapePageTransform(id) + const sourceGeo = this.editor.getShapeGeometry(id) + const sourcePagespace = sourceTransform.applyToPoints(sourceGeo.vertices) + const sourceBounds = this.editor.getShapePageBounds(id) + + const boundsShapes = this.spatialIndex.getShapeIdsInsideBounds(sourceBounds) + const overlaps: TLShape[] = [] + for (const boundsShapeId of boundsShapes) { + if (boundsShapeId === id) continue + const pageShape = this.editor.getShape(boundsShapeId) + if (!pageShape) continue + if (pageShape.type === 'arrow') continue + const pageShapeGeo = this.editor.getShapeGeometry(pageShape) + const pageShapeTransform = this.editor.getShapePageTransform(pageShape) + const pageShapePagespace = pageShapeTransform.applyToPoints(pageShapeGeo.vertices) + const pageShapeBounds = this.editor.getShapePageBounds(boundsShapeId) + if (polygonsIntersect(sourcePagespace, pageShapePagespace) || sourceBounds.contains(pageShapeBounds) || pageShapeBounds.contains(sourceBounds)) { + overlaps.push(pageShape) + } + } + return overlaps + } + + contains(shape: TLShape | TLShapeId): boolean { + const id = typeof shape === 'string' ? shape : shape?.id ?? null + if (!id) return false + const sourceBounds = this.editor.getShapePageBounds(id) + + const boundsShapes = this.spatialIndex.getShapeIdsInsideBounds(sourceBounds) + for (const boundsShapeId of boundsShapes) { + if (boundsShapeId === id) continue + const pageShape = this.editor.getShape(boundsShapeId) + if (!pageShape) continue + if (pageShape.type !== 'geo') continue + const pageShapeBounds = this.editor.getShapePageBounds(boundsShapeId) + if (sourceBounds.contains(pageShapeBounds)) { + return true + } + } + return false + } + + getContains(shape: TLShape | TLShapeId): TLShape[] { + const id = typeof shape === 'string' ? shape : shape?.id ?? null + if (!id) return [] + const sourceBounds = this.editor.getShapePageBounds(id) + + const boundsShapes = this.spatialIndex.getShapeIdsInsideBounds(sourceBounds) + const contains: TLShape[] = [] + for (const boundsShapeId of boundsShapes) { + if (boundsShapeId === id) continue + const pageShape = this.editor.getShape(boundsShapeId) + if (!pageShape) continue + if (pageShape.type !== 'geo') continue + const pageShapeBounds = this.editor.getShapePageBounds(boundsShapeId) + if (sourceBounds.contains(pageShapeBounds)) { + contains.push(pageShape) + } + } + return contains + } +} \ No newline at end of file diff --git a/src/propagators/ScopedPropagators.ts b/src/propagators/ScopedPropagators.ts new file mode 100644 index 0000000..e7a9a8d --- /dev/null +++ b/src/propagators/ScopedPropagators.ts @@ -0,0 +1,303 @@ +import { DeltaTime } from "./DeltaTime" +import { Geo } from "./Geo" +import { Edge, getArrowsFromShape, getEdge } from "./tlgraph" +import { isShapeOfType } from "./utils" +import { Editor, TLArrowShape, TLBinding, TLGroupShape, TLShape, TLShapeId } from "tldraw" + +type Prefix = 'click' | 'tick' | 'geo' | '' + +export function registerDefaultPropagators(editor: Editor) { + registerPropagators(editor, [ + ChangePropagator, + ClickPropagator, + TickPropagator, + SpatialPropagator, + ]) +} + +function isPropagatorOfType(arrow: TLShape, prefix: Prefix) { + if (!isShapeOfType(arrow, 'arrow')) return false + const regex = new RegExp(`^\\s*${prefix}\\s*\\{`) + return regex.test(arrow.props.text) +} +function isExpandedPropagatorOfType(arrow: TLShape, prefix: Prefix) { + if (!isShapeOfType(arrow, 'arrow')) return false + const regex = new RegExp(`^\\s*${prefix}\\s*\\(\\)\\s*\\{`) + return regex.test(arrow.props.text) +} + +class ArrowFunctionCache { + private cache: Map = new Map() + + /** returns undefined if the function could not be found or created */ + get(editor: Editor, edge: Edge, prefix: Prefix): Function | undefined { + if (this.cache.has(edge.arrowId)) { + return this.cache.get(edge.arrowId) + } + console.log('creating func because it didnt exist') + return this.set(editor, edge, prefix) + } + /** returns undefined if the function could not be created */ + set(editor: Editor, edge: Edge, prefix: Prefix): Function | undefined { + try { + const arrowShape = editor.getShape(edge.arrowId) + if (!arrowShape) throw new Error('Arrow shape not found') + const textWithoutPrefix = edge.text.replace(prefix, '') + const isExpanded = isExpandedPropagatorOfType(arrowShape, prefix) + const body = isExpanded ? textWithoutPrefix.trim().replace(/^\s*\(\)\s*{|}$/g, '') : ` + const mapping = ${textWithoutPrefix} + editor.updateShape(_unpack({...to, ...mapping})) + ` + const func = new Function('editor', 'from', 'to', 'G', 'bounds', 'dt', '_unpack', body); + this.cache.set(edge.arrowId, func) + return func + } catch (error) { + this.cache.set(edge.arrowId, null) + return undefined + } + } + delete(edge: Edge): void { + this.cache.delete(edge.arrowId) + } +} + +const packShape = (shape: TLShape) => { + return { + id: shape.id, + type: shape.type, + x: shape.x, + y: shape.y, + rotation: shape.rotation, + ...shape.props, + m: shape.meta, + } +} + +const unpackShape = (shape: any) => { + const { id, type, x, y, rotation, m, ...props } = shape + const cast = (prop: any, constructor: (value: any) => any) => { + return prop !== undefined ? constructor(prop) : undefined; + }; + return { + id, + type, + x: Number(x), + y: Number(y), + rotation: Number(rotation), + props: { + ...props, + text: cast(props.text, String), + }, + meta: m, + } +} + +function setArrowColor(editor: Editor, arrow: TLArrowShape, color: TLArrowShape['props']['color']): void { + editor.updateShape({ + ...arrow, + props: { + ...arrow.props, + color: color, + } + }) +} + +export function registerPropagators(editor: Editor, propagators: (new (editor: Editor) => Propagator)[]) { + const _propagators = propagators.map((PropagatorClass) => new PropagatorClass(editor)) + + for (const prop of _propagators) { + for (const shape of editor.getCurrentPageShapes()) { + if (isShapeOfType(shape, 'arrow')) { + prop.onArrowChange(editor, shape) + } + } + editor.sideEffects.registerAfterChangeHandler<"shape">("shape", (_, next) => { + if (isShapeOfType(next, 'group')) { + const childIds = editor.getSortedChildIdsForParent(next.id) + for (const childId of childIds) { + const child = editor.getShape(childId) + prop.afterChangeHandler?.(editor, child) + } + return + } + prop.afterChangeHandler?.(editor, next) + if (isShapeOfType(next, 'arrow')) { + prop.onArrowChange(editor, next) + } + }) + + function updateOnBindingChange(editor: Editor, binding: TLBinding) { + if (binding.type !== 'arrow') return + const arrow = editor.getShape(binding.fromId) + if (!arrow) return + if (!isShapeOfType(arrow, 'arrow')) return + prop.onArrowChange(editor, arrow) + } + + // TODO: remove this when binding creation + editor.sideEffects.registerAfterCreateHandler<"binding">("binding", (binding) => { + updateOnBindingChange(editor, binding) + }) + // TODO: remove this when binding creation + editor.sideEffects.registerAfterDeleteHandler<"binding">("binding", (binding) => { + updateOnBindingChange(editor, binding) + }) + + editor.on('event', (event) => { + prop.eventHandler?.(event) + }) + editor.on('tick', () => { + prop.tickHandler?.() + }) + } +} + +// TODO: separate generic propagator setup from scope registration +// TODO: handle cycles +export abstract class Propagator { + abstract prefix: Prefix + protected listenerArrows: Set = new Set() + protected listenerShapes: Set = new Set() + protected arrowFunctionCache: ArrowFunctionCache = new ArrowFunctionCache() + protected editor: Editor + protected geo: Geo + protected validateOnArrowChange: boolean = false + + constructor(editor: Editor) { + this.editor = editor + this.geo = new Geo(editor) + } + + /** function to check if any listeners need to be added/removed + * called on mount and when an arrow changes + */ + onArrowChange(editor: Editor, arrow: TLArrowShape): void { + const edge = getEdge(arrow, editor) + if (!edge) return + + const isPropagator = isPropagatorOfType(arrow, this.prefix) || isExpandedPropagatorOfType(arrow, this.prefix) + + if (isPropagator) { + if (this.validateOnArrowChange && !this.propagate(editor, arrow.id)) { + this.removeListener(arrow.id, edge) + return + } + this.addListener(arrow.id, edge) + + // TODO: find a way to do this properly so we can run arrow funcs on change without chaos... + // this.arrowFunc(editor, arrow.id) + } else { + this.removeListener(arrow.id, edge) + } + } + + private addListener(arrowId: TLShapeId, edge: Edge): void { + this.listenerArrows.add(arrowId) + this.listenerShapes.add(edge.from) + this.listenerShapes.add(edge.to) + this.arrowFunctionCache.set(this.editor, edge, this.prefix) + } + + private removeListener(arrowId: TLShapeId, edge: Edge): void { + this.listenerArrows.delete(arrowId) + this.arrowFunctionCache.delete(edge) + } + + /** the function to be called when side effect / event is triggered */ + propagate(editor: Editor, arrow: TLShapeId): boolean { + const edge = getEdge(editor.getShape(arrow), editor) + if (!edge) return + + const arrowShape = editor.getShape(arrow) as TLArrowShape + const fromShape = editor.getShape(edge.from) + const toShape = editor.getShape(edge.to) + const fromShapePacked = packShape(fromShape) + const toShapePacked = packShape(toShape) + const bounds = (shape: TLShape) => editor.getShapePageBounds(shape.id) + + try { + const func = this.arrowFunctionCache.get(editor, edge, this.prefix) + const result = func(editor, fromShapePacked, toShapePacked, this.geo, bounds, DeltaTime.dt, unpackShape); + if (result) { + editor.updateShape(unpackShape({ ...toShapePacked, ...result })) + } + + setArrowColor(editor, arrowShape, 'black') + return true + } catch (error) { + console.error(error) + setArrowColor(editor, arrowShape, 'orange') + return false + } + } + + /** called after every shape change */ + afterChangeHandler?(editor: Editor, next: TLShape): void + /** called on every editor event */ + eventHandler?(event: any): void + /** called every tick */ + tickHandler?(): void +} + +export class ClickPropagator extends Propagator { + prefix: Prefix = 'click' + + eventHandler(event: any): void { + if (event.type !== 'pointer' || event.name !== 'pointer_down') return; + const shapeAtPoint = this.editor.getShapeAtPoint(this.editor.inputs.currentPagePoint, { filter: (shape) => shape.type === 'geo' }); + if (!shapeAtPoint) return + if (!this.listenerShapes.has(shapeAtPoint.id)) return + const edgesFromHovered = getArrowsFromShape(this.editor, shapeAtPoint.id) + + const visited = new Set() + for (const edge of edgesFromHovered) { + if (this.listenerArrows.has(edge) && !visited.has(edge)) { + this.propagate(this.editor, edge) + visited.add(edge) + } + } + } +} + +export class ChangePropagator extends Propagator { + prefix: Prefix = '' + + afterChangeHandler(editor: Editor, next: TLShape): void { + if (this.listenerShapes.has(next.id)) { + const arrowsFromShape = getArrowsFromShape(editor, next.id) + for (const arrow of arrowsFromShape) { + if (this.listenerArrows.has(arrow)) { + const bindings = editor.getBindingsInvolvingShape(arrow) + if (bindings.length !== 2) continue + // don't run func if its pointing to itself to avoid change-induced recursion error + if (bindings[0].toId === bindings[1].toId) continue + this.propagate(editor, arrow) + } + } + } + } +} + +export class TickPropagator extends Propagator { + prefix: Prefix = 'tick' + validateOnArrowChange = true + + tickHandler(): void { + for (const arrow of this.listenerArrows) { + this.propagate(this.editor, arrow) + } + } +} + +export class SpatialPropagator extends Propagator { + prefix: Prefix = 'geo' + + // TODO: make this smarter, and scale sublinearly + afterChangeHandler(editor: Editor, next: TLShape): void { + if (next.type === 'arrow') return + for (const arrowId of this.listenerArrows) { + this.propagate(editor, arrowId) + } + } +} + diff --git a/src/propagators/SpatialIndex.ts b/src/propagators/SpatialIndex.ts new file mode 100644 index 0000000..8576892 --- /dev/null +++ b/src/propagators/SpatialIndex.ts @@ -0,0 +1,165 @@ +import { RESET_VALUE, computed, isUninitialized } from '@tldraw/state' +import { TLPageId, TLShapeId, isShape, isShapeId } from '@tldraw/tlschema' +import RBush from 'rbush' +import { Box, Editor } from 'tldraw' + +type Element = { + minX: number + minY: number + maxX: number + maxY: number + id: TLShapeId +} + +export class SpatialIndex { + private readonly spatialIndex: ReturnType + private lastPageId: TLPageId | null = null + private shapesInTree: Map + private rBush: RBush + + constructor(private editor: Editor) { + this.spatialIndex = this.createSpatialIndex() + this.shapesInTree = new Map() + this.rBush = new RBush() + } + + private addElement(id: TLShapeId, a: Element[], existingBounds?: Box) { + const e = this.getElement(id, existingBounds) + if (!e) return + a.push(e) + this.shapesInTree.set(id, e) + } + + private getElement(id: TLShapeId, existingBounds?: Box): Element | null { + const bounds = existingBounds ?? this.editor.getShapeMaskedPageBounds(id) + if (!bounds) return null + return { + minX: bounds.minX, + minY: bounds.minY, + maxX: bounds.maxX, + maxY: bounds.maxY, + id, + } + } + + private fromScratch(lastComputedEpoch: number) { + this.lastPageId = this.editor.getCurrentPageId() + this.shapesInTree = new Map() + const elementsToAdd: Element[] = [] + + this.editor.getCurrentPageShapeIds().forEach((id) => { + this.addElement(id, elementsToAdd) + }) + + this.rBush = new RBush().load(elementsToAdd) + + return lastComputedEpoch + } + + private createSpatialIndex() { + const shapeHistory = this.editor.store.query.filterHistory('shape') + + return computed('spatialIndex', (prevValue, lastComputedEpoch) => { + if (isUninitialized(prevValue)) { + return this.fromScratch(lastComputedEpoch) + } + + const diff = shapeHistory.getDiffSince(lastComputedEpoch) + if (diff === RESET_VALUE) { + return this.fromScratch(lastComputedEpoch) + } + + const currentPageId = this.editor.getCurrentPageId() + if (!this.lastPageId || this.lastPageId !== currentPageId) { + return this.fromScratch(lastComputedEpoch) + } + + let isDirty = false + for (const changes of diff) { + const elementsToAdd: Element[] = [] + for (const record of Object.values(changes.added)) { + if (isShape(record)) { + this.addElement(record.id, elementsToAdd) + } + } + + for (const [_from, to] of Object.values(changes.updated)) { + if (isShape(to)) { + const currentElement = this.shapesInTree.get(to.id) + const newBounds = this.editor.getShapeMaskedPageBounds(to.id) + if (currentElement) { + if ( + newBounds?.minX === currentElement.minX && + newBounds.minY === currentElement.minY && + newBounds.maxX === currentElement.maxX && + newBounds.maxY === currentElement.maxY + ) { + continue + } + this.shapesInTree.delete(to.id) + this.rBush.remove(currentElement) + isDirty = true + } + this.addElement(to.id, elementsToAdd, newBounds) + } + } + if (elementsToAdd.length) { + this.rBush.load(elementsToAdd) + isDirty = true + } + for (const id of Object.keys(changes.removed)) { + if (isShapeId(id)) { + const currentElement = this.shapesInTree.get(id) + if (currentElement) { + this.shapesInTree.delete(id) + this.rBush.remove(currentElement) + isDirty = true + } + } + } + } + + return isDirty ? lastComputedEpoch : prevValue + }) + } + + private _getVisibleShapes() { + return computed>('visible shapes', (prevValue) => { + // Make sure the spatial index is up to date + const _index = this.spatialIndex.get() + const newValue = this.rBush.search(this.editor.getViewportPageBounds()).map((s) => s.id) + if (isUninitialized(prevValue)) { + return new Set(newValue) + } + const isSame = prevValue.size === newValue.length && newValue.every((id) => prevValue.has(id)) + return isSame ? prevValue : new Set(newValue) + }) + } + + getVisibleShapes() { + return this._getVisibleShapes().get() + } + + _getNotVisibleShapes() { + return computed>('not visible shapes', (prevValue) => { + const visibleShapes = this._getVisibleShapes().get() + const pageShapes = this.editor.getCurrentPageShapeIds() + const nonVisibleShapes = [...pageShapes].filter((id) => !visibleShapes.has(id)) + if (isUninitialized(prevValue)) return new Set(nonVisibleShapes) + const isSame = + prevValue.size === nonVisibleShapes.length && + nonVisibleShapes.every((id) => prevValue.has(id)) + return isSame ? prevValue : new Set(nonVisibleShapes) + }) + } + + getNotVisibleShapes() { + return this._getNotVisibleShapes().get() + } + + getShapeIdsInsideBounds(bounds: Box) { + // Make sure the spatial index is up to date + const _index = this.spatialIndex.get() + return this.rBush.search(bounds).map((s) => s.id) + } +} \ No newline at end of file diff --git a/src/propagators/tlgraph.ts b/src/propagators/tlgraph.ts new file mode 100644 index 0000000..767afa1 --- /dev/null +++ b/src/propagators/tlgraph.ts @@ -0,0 +1,115 @@ +import { isShapeOfType } from "./utils"; +import { Editor, TLArrowBinding, TLArrowShape, TLShape, TLShapeId } from "tldraw"; + +export interface Edge { + arrowId: TLShapeId + from: TLShapeId + to: TLShapeId + text?: string +} + +export interface Graph { + nodes: TLShapeId[] + edges: Edge[] +} + +export function getEdge(shape: TLShape | undefined, editor: Editor): Edge | undefined { + if (!shape || !isShapeOfType(shape, 'arrow')) return undefined + const bindings = editor.getBindingsInvolvingShape(shape.id) + if (!bindings || bindings.length !== 2) return undefined + if (bindings[0].props.terminal === "end") { + return { + arrowId: shape.id, + from: bindings[1].toId, + to: bindings[0].toId, + text: shape.props.text + } + } + return { + arrowId: shape.id, + from: bindings[0].toId, + to: bindings[1].toId, + text: shape.props.text + } +} + +/** + * Returns the graph(s) of edges and nodes from a list of shapes + */ +export function getGraph(shapes: TLShape[], editor: Editor): Graph { + const nodes: Set = new Set() + const edges: Edge[] = [] + + for (const shape of shapes) { + const edge = getEdge(shape, editor) + if (edge) { + edges.push({ + arrowId: edge.arrowId, + from: edge.from, + to: edge.to, + text: edge.text + }) + nodes.add(edge.from) + nodes.add(edge.to) + } + } + + return { nodes: Array.from(nodes), edges } +} + +/** + * Returns the start and end nodes of a topologically sorted graph + */ +export function sortGraph(graph: Graph): { startNodes: TLShapeId[], endNodes: TLShapeId[] } { + const targetNodes = new Set(graph.edges.map(e => e.to)); + const sourceNodes = new Set(graph.edges.map(e => e.from)); + + const startNodes = []; + const endNodes = []; + + for (const node of graph.nodes) { + if (sourceNodes.has(node) && !targetNodes.has(node)) { + startNodes.push(node); + } else if (targetNodes.has(node) && !sourceNodes.has(node)) { + endNodes.push(node); + } + } + + return { startNodes, endNodes }; +} + +/** + * Returns the arrows starting from the given shape + */ +export function getArrowsFromShape(editor: Editor, shapeId: TLShapeId): TLShapeId[] { + const bindings = editor.getBindingsToShape(shapeId, 'arrow') + return bindings.filter(edge => edge.props.terminal === 'start').map(edge => edge.fromId) +} + +/** + * Returns the arrows ending at the given shape + */ +export function getArrowsToShape(editor: Editor, shapeId: TLShapeId): TLShapeId[] { + const bindings = editor.getBindingsToShape(shapeId, 'arrow') + return bindings.filter(edge => edge.props.terminal === 'end').map(edge => edge.fromId) +} + +/** + * Returns the arrows which share the same start shape as the given arrow + */ +export function getSiblingArrowIds(editor: Editor, arrow: TLShape): TLShapeId[] { + if (arrow.type !== 'arrow') return []; + + const bindings = editor.getBindingsInvolvingShape(arrow.id); + if (!bindings || bindings.length !== 2) return []; + + const startShapeId = bindings.find(binding => binding.props.terminal === 'start')?.toId; + if (!startShapeId) return []; + + const siblingBindings = editor.getBindingsToShape(startShapeId, 'arrow'); + const siblingArrows = siblingBindings + .filter(binding => binding.props.terminal === 'start' && binding.fromId !== arrow.id) + .map(binding => binding.fromId); + + return siblingArrows; +} \ No newline at end of file diff --git a/src/propagators/utils.ts b/src/propagators/utils.ts new file mode 100644 index 0000000..13846f8 --- /dev/null +++ b/src/propagators/utils.ts @@ -0,0 +1,22 @@ +import { Editor, TLShape, TLShapePartial } from "tldraw"; + +/** + * @returns true if the shape is of the given type + * @example + * ```ts + * isShapeOfType(shape, 'arrow') + * ``` + */ +export function isShapeOfType(shape: TLShape, type: T['type']): shape is T { + return shape.type === type; +} + +export function updateProps(editor: Editor, shape: T, props: Partial) { + editor.updateShape({ + ...shape, + props: { + ...shape.props, + ...props + }, + } as TLShapePartial) +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 319eb78..ed73e2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,13 +5,6 @@ __metadata: version: 8 cacheKey: 10c0 -"@alloc/quick-lru@npm:^5.2.0": - version: 5.2.0 - resolution: "@alloc/quick-lru@npm:5.2.0" - checksum: 7b878c48b9d25277d0e1a9b8b2f2312a314af806b4129dc902f2bc29ab09b58236e53964689feec187b28c80d2203aff03829754773a707a8a5987f1b7682d92 - languageName: node - linkType: hard - "@ampproject/remapping@npm:^2.2.0": version: 2.2.1 resolution: "@ampproject/remapping@npm:2.2.1" @@ -840,33 +833,6 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.scandir@npm:2.1.5": - version: 2.1.5 - resolution: "@nodelib/fs.scandir@npm:2.1.5" - dependencies: - "@nodelib/fs.stat": "npm:2.0.5" - run-parallel: "npm:^1.1.9" - checksum: 732c3b6d1b1e967440e65f284bd06e5821fedf10a1bea9ed2bb75956ea1f30e08c44d3def9d6a230666574edbaf136f8cfd319c14fd1f87c66e6a44449afb2eb - languageName: node - linkType: hard - -"@nodelib/fs.stat@npm:2.0.5, @nodelib/fs.stat@npm:^2.0.2": - version: 2.0.5 - resolution: "@nodelib/fs.stat@npm:2.0.5" - checksum: 88dafe5e3e29a388b07264680dc996c17f4bda48d163a9d4f5c1112979f0ce8ec72aa7116122c350b4e7976bc5566dc3ddb579be1ceaacc727872eb4ed93926d - languageName: node - linkType: hard - -"@nodelib/fs.walk@npm:^1.2.3": - version: 1.2.8 - resolution: "@nodelib/fs.walk@npm:1.2.8" - dependencies: - "@nodelib/fs.scandir": "npm:2.1.5" - fastq: "npm:^1.6.0" - checksum: db9de047c3bb9b51f9335a7bb46f4fcfb6829fb628318c12115fbaf7d369bfce71c15b103d1fc3b464812d936220ee9bc1c8f762d032c9f6be9acc99249095b1 - languageName: node - linkType: hard - "@npmcli/agent@npm:^2.0.0": version: 2.2.0 resolution: "@npmcli/agent@npm:2.2.0" @@ -1963,6 +1929,13 @@ __metadata: languageName: node linkType: hard +"@types/rbush@npm:^3.0.3": + version: 3.0.3 + resolution: "@types/rbush@npm:3.0.3" + checksum: a0c2425eb9cb43f442ef48d9d0221c006e0f7303c35f97d443c9832e7c824572b7206a68f9ffe486ba0789cc19600732c3412729211f0c1a5058e7b3f0fd937d + languageName: node + linkType: hard + "@types/react-dom@npm:^18.2.7": version: 18.2.7 resolution: "@types/react-dom@npm:18.2.7" @@ -2129,23 +2102,6 @@ __metadata: languageName: node linkType: hard -"any-promise@npm:^1.0.0": - version: 1.3.0 - resolution: "any-promise@npm:1.3.0" - checksum: 60f0298ed34c74fef50daab88e8dab786036ed5a7fad02e012ab57e376e0a0b4b29e83b95ea9b5e7d89df762f5f25119b83e00706ecaccb22cfbacee98d74889 - languageName: node - linkType: hard - -"anymatch@npm:~3.1.2": - version: 3.1.3 - resolution: "anymatch@npm:3.1.3" - dependencies: - normalize-path: "npm:^3.0.0" - picomatch: "npm:^2.0.4" - checksum: 57b06ae984bc32a0d22592c87384cd88fe4511b1dd7581497831c56d41939c8a001b28e7b853e1450f2bf61992dfcaa8ae2d0d161a0a90c4fb631ef07098fbac - languageName: node - linkType: hard - "arch@npm:^2.2.0": version: 2.2.0 resolution: "arch@npm:2.2.0" @@ -2153,13 +2109,6 @@ __metadata: languageName: node linkType: hard -"arg@npm:^5.0.2": - version: 5.0.2 - resolution: "arg@npm:5.0.2" - checksum: ccaf86f4e05d342af6666c569f844bec426595c567d32a8289715087825c2ca7edd8a3d204e4d2fb2aa4602e09a57d0c13ea8c9eea75aac3dbb4af5514e6800e - languageName: node - linkType: hard - "aria-hidden@npm:^1.1.1": version: 1.2.3 resolution: "aria-hidden@npm:1.2.3" @@ -2199,13 +2148,6 @@ __metadata: languageName: node linkType: hard -"binary-extensions@npm:^2.0.0": - version: 2.3.0 - resolution: "binary-extensions@npm:2.3.0" - checksum: 75a59cafc10fb12a11d510e77110c6c7ae3f4ca22463d52487709ca7f18f69d886aa387557cc9864fbdb10153d0bdb4caacabf11541f55e89ed6e18d12ece2b5 - languageName: node - linkType: hard - "brace-expansion@npm:^2.0.1": version: 2.0.1 resolution: "brace-expansion@npm:2.0.1" @@ -2215,15 +2157,6 @@ __metadata: languageName: node linkType: hard -"braces@npm:^3.0.3, braces@npm:~3.0.2": - version: 3.0.3 - resolution: "braces@npm:3.0.3" - dependencies: - fill-range: "npm:^7.1.1" - checksum: 7c6dfd30c338d2997ba77500539227b9d1f85e388a5f43220865201e407e076783d0881f2d297b9f80951b4c957fcf0b51c1d2d24227631643c3f7c284b0aa04 - languageName: node - linkType: hard - "browserslist@npm:^4.21.9": version: 4.21.10 resolution: "browserslist@npm:4.21.10" @@ -2275,13 +2208,6 @@ __metadata: languageName: node linkType: hard -"camelcase-css@npm:^2.0.1": - version: 2.0.1 - resolution: "camelcase-css@npm:2.0.1" - checksum: 1a1a3137e8a781e6cbeaeab75634c60ffd8e27850de410c162cce222ea331cd1ba5364e8fb21c95e5ca76f52ac34b81a090925ca00a87221355746d049c6e273 - languageName: node - linkType: hard - "caniuse-lite@npm:^1.0.30001517": version: 1.0.30001519 resolution: "caniuse-lite@npm:1.0.30001519" @@ -2327,25 +2253,6 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.5.3": - version: 3.6.0 - resolution: "chokidar@npm:3.6.0" - dependencies: - anymatch: "npm:~3.1.2" - braces: "npm:~3.0.2" - fsevents: "npm:~2.3.2" - glob-parent: "npm:~5.1.2" - is-binary-path: "npm:~2.1.0" - is-glob: "npm:~4.0.1" - normalize-path: "npm:~3.0.0" - readdirp: "npm:~3.6.0" - dependenciesMeta: - fsevents: - optional: true - checksum: 8361dcd013f2ddbe260eacb1f3cb2f2c6f2b0ad118708a343a5ed8158941a39cb8fb1d272e0f389712e74ee90ce8ba864eece9e0e62b9705cb468a2f6d917462 - languageName: node - linkType: hard - "chownr@npm:^2.0.0": version: 2.0.0 resolution: "chownr@npm:2.0.0" @@ -2421,13 +2328,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^4.0.0": - version: 4.1.1 - resolution: "commander@npm:4.1.1" - checksum: 84a76c08fe6cc08c9c93f62ac573d2907d8e79138999312c92d4155bc2325d487d64d13f669b2000c9f8caf70493c1be2dac74fec3c51d5a04f8bc3ae1830bab - languageName: node - linkType: hard - "concurrently@npm:^8.2.0": version: 8.2.0 resolution: "concurrently@npm:8.2.0" @@ -2480,15 +2380,6 @@ __metadata: languageName: node linkType: hard -"cssesc@npm:^3.0.0": - version: 3.0.0 - resolution: "cssesc@npm:3.0.0" - bin: - cssesc: bin/cssesc - checksum: 6bcfd898662671be15ae7827120472c5667afb3d7429f1f917737f3bf84c4176003228131b643ae74543f17a394446247df090c597bb9a728cce298606ed0aa7 - languageName: node - linkType: hard - "csstype@npm:^3.0.2": version: 3.1.2 resolution: "csstype@npm:3.1.2" @@ -2541,20 +2432,6 @@ __metadata: languageName: node linkType: hard -"didyoumean@npm:^1.2.2": - version: 1.2.2 - resolution: "didyoumean@npm:1.2.2" - checksum: 95d0b53d23b851aacff56dfadb7ecfedce49da4232233baecfeecb7710248c4aa03f0aa8995062f0acafaf925adf8536bd7044a2e68316fd7d411477599bc27b - languageName: node - linkType: hard - -"dlv@npm:^1.1.3": - version: 1.1.3 - resolution: "dlv@npm:1.1.3" - checksum: 03eb4e769f19a027fd5b43b59e8a05e3fd2100ac239ebb0bf9a745de35d449e2f25cfaf3aa3934664551d72856f4ae8b7822016ce5c42c2d27c18ae79429ec42 - languageName: node - linkType: hard - "eastasianwidth@npm:^0.2.0": version: 0.2.0 resolution: "eastasianwidth@npm:0.2.0" @@ -2835,37 +2712,6 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.3.0": - version: 3.3.2 - resolution: "fast-glob@npm:3.3.2" - dependencies: - "@nodelib/fs.stat": "npm:^2.0.2" - "@nodelib/fs.walk": "npm:^1.2.3" - glob-parent: "npm:^5.1.2" - merge2: "npm:^1.3.0" - micromatch: "npm:^4.0.4" - checksum: 42baad7b9cd40b63e42039132bde27ca2cb3a4950d0a0f9abe4639ea1aa9d3e3b40f98b1fe31cbc0cc17b664c9ea7447d911a152fa34ec5b72977b125a6fc845 - languageName: node - linkType: hard - -"fastq@npm:^1.6.0": - version: 1.17.1 - resolution: "fastq@npm:1.17.1" - dependencies: - reusify: "npm:^1.0.4" - checksum: 1095f16cea45fb3beff558bb3afa74ca7a9250f5a670b65db7ed585f92b4b48381445cd328b3d87323da81e43232b5d5978a8201bde84e0cd514310f1ea6da34 - languageName: node - linkType: hard - -"fill-range@npm:^7.1.1": - version: 7.1.1 - resolution: "fill-range@npm:7.1.1" - dependencies: - to-regex-range: "npm:^5.0.1" - checksum: b75b691bbe065472f38824f694c2f7449d7f5004aa950426a2c28f0306c60db9b880c0b0e4ed819997ffb882d1da02cfcfc819bddc94d71627f5269682edf018 - languageName: node - linkType: hard - "foreground-child@npm:^3.1.0": version: 3.1.1 resolution: "foreground-child@npm:3.1.1" @@ -2932,13 +2778,6 @@ __metadata: languageName: node linkType: hard -"function-bind@npm:^1.1.2": - version: 1.1.2 - resolution: "function-bind@npm:1.1.2" - checksum: d8680ee1e5fcd4c197e4ac33b2b4dce03c71f4d91717292785703db200f5c21f977c568d28061226f9b5900cbcd2c84463646134fd5337e7925e0942bc3f46d5 - languageName: node - linkType: hard - "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -2984,14 +2823,15 @@ __metadata: "@biomejs/biome": "npm:1.4.1" "@radix-ui/react-dropdown-menu": "npm:^2.0.6" "@radix-ui/react-icons": "npm:1.3.0" + "@types/rbush": "npm:^3.0.3" "@types/react": "npm:^18.2.15" "@types/react-dom": "npm:^18.2.7" "@vitejs/plugin-react": "npm:^4.0.3" concurrently: "npm:^8.2.0" partykit: "npm:0.0.27" + rbush: "npm:^4.0.0" react: "npm:^18.2.0" react-dom: "npm:^18.2.0" - tailwindcss: "npm:^3.4.6" tldraw: "npm:2.3.0" typescript: "npm:^5.0.2" vite: "npm:^4.4.5" @@ -3004,24 +2844,6 @@ __metadata: languageName: unknown linkType: soft -"glob-parent@npm:^5.1.2, glob-parent@npm:~5.1.2": - version: 5.1.2 - resolution: "glob-parent@npm:5.1.2" - dependencies: - is-glob: "npm:^4.0.1" - checksum: cab87638e2112bee3f839ef5f6e0765057163d39c66be8ec1602f3823da4692297ad4e972de876ea17c44d652978638d2fd583c6713d0eb6591706825020c9ee - languageName: node - linkType: hard - -"glob-parent@npm:^6.0.2": - version: 6.0.2 - resolution: "glob-parent@npm:6.0.2" - dependencies: - is-glob: "npm:^4.0.3" - checksum: 317034d88654730230b3f43bb7ad4f7c90257a426e872ea0bf157473ac61c99bf5d205fad8f0185f989be8d2fa6d3c7dce1645d99d545b6ea9089c39f838e7f8 - languageName: node - linkType: hard - "glob-to-regexp@npm:^0.4.1": version: 0.4.1 resolution: "glob-to-regexp@npm:0.4.1" @@ -3072,15 +2894,6 @@ __metadata: languageName: node linkType: hard -"hasown@npm:^2.0.2": - version: 2.0.2 - resolution: "hasown@npm:2.0.2" - dependencies: - function-bind: "npm:^1.1.2" - checksum: 3769d434703b8ac66b209a4cca0737519925bbdb61dd887f93a16372b14694c63ff4e797686d87c90f08168e81082248b9b028bad60d4da9e0d1148766f56eb9 - languageName: node - linkType: hard - "hotkeys-js@npm:^3.11.2": version: 3.12.0 resolution: "hotkeys-js@npm:3.12.0" @@ -3189,24 +3002,6 @@ __metadata: languageName: node linkType: hard -"is-binary-path@npm:~2.1.0": - version: 2.1.0 - resolution: "is-binary-path@npm:2.1.0" - dependencies: - binary-extensions: "npm:^2.0.0" - checksum: a16eaee59ae2b315ba36fad5c5dcaf8e49c3e27318f8ab8fa3cdb8772bf559c8d1ba750a589c2ccb096113bb64497084361a25960899cb6172a6925ab6123d38 - languageName: node - linkType: hard - -"is-core-module@npm:^2.13.0": - version: 2.15.0 - resolution: "is-core-module@npm:2.15.0" - dependencies: - hasown: "npm:^2.0.2" - checksum: da161f3d9906f459486da65609b2f1a2dfdc60887c689c234d04e88a062cb7920fa5be5fb7ab08dc43b732929653c4135ef05bf77888ae2a9040ce76815eb7b1 - languageName: node - linkType: hard - "is-docker@npm:^2.0.0": version: 2.2.1 resolution: "is-docker@npm:2.2.1" @@ -3216,13 +3011,6 @@ __metadata: languageName: node linkType: hard -"is-extglob@npm:^2.1.1": - version: 2.1.1 - resolution: "is-extglob@npm:2.1.1" - checksum: 5487da35691fbc339700bbb2730430b07777a3c21b9ebaecb3072512dfd7b4ba78ac2381a87e8d78d20ea08affb3f1971b4af629173a6bf435ff8a4c47747912 - languageName: node - linkType: hard - "is-fullwidth-code-point@npm:^3.0.0": version: 3.0.0 resolution: "is-fullwidth-code-point@npm:3.0.0" @@ -3230,15 +3018,6 @@ __metadata: languageName: node linkType: hard -"is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": - version: 4.0.3 - resolution: "is-glob@npm:4.0.3" - dependencies: - is-extglob: "npm:^2.1.1" - checksum: 17fb4014e22be3bbecea9b2e3a76e9e34ff645466be702f1693e8f1ee1adac84710d0be0bd9f967d6354036fd51ab7c2741d954d6e91dae6bb69714de92c197a - languageName: node - linkType: hard - "is-lambda@npm:^1.0.1": version: 1.0.1 resolution: "is-lambda@npm:1.0.1" @@ -3246,13 +3025,6 @@ __metadata: languageName: node linkType: hard -"is-number@npm:^7.0.0": - version: 7.0.0 - resolution: "is-number@npm:7.0.0" - checksum: b4686d0d3053146095ccd45346461bc8e53b80aeb7671cc52a4de02dbbf7dc0d1d2a986e2fe4ae206984b4d34ef37e8b795ebc4f4295c978373e6575e295d811 - languageName: node - linkType: hard - "is-plain-object@npm:^5.0.0": version: 5.0.0 resolution: "is-plain-object@npm:5.0.0" @@ -3310,15 +3082,6 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.21.0": - version: 1.21.6 - resolution: "jiti@npm:1.21.6" - bin: - jiti: bin/jiti.js - checksum: 05b9ed58cd30d0c3ccd3c98209339e74f50abd9a17e716f65db46b6a35812103f6bde6e134be7124d01745586bca8cc5dae1d0d952267c3ebe55171949c32e56 - languageName: node - linkType: hard - "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -3484,27 +3247,6 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:^2.1.0": - version: 2.1.0 - resolution: "lilconfig@npm:2.1.0" - checksum: 64645641aa8d274c99338e130554abd6a0190533c0d9eb2ce7ebfaf2e05c7d9961f3ffe2bfa39efd3b60c521ba3dd24fa236fe2775fc38501bf82bf49d4678b8 - languageName: node - linkType: hard - -"lilconfig@npm:^3.0.0": - version: 3.1.2 - resolution: "lilconfig@npm:3.1.2" - checksum: f059630b1a9bddaeba83059db00c672b64dc14074e9f232adce32b38ca1b5686ab737eb665c5ba3c32f147f0002b4bee7311ad0386a9b98547b5623e87071fbe - languageName: node - linkType: hard - -"lines-and-columns@npm:^1.1.6": - version: 1.2.4 - resolution: "lines-and-columns@npm:1.2.4" - checksum: 3da6ee62d4cd9f03f5dc90b4df2540fb85b352081bee77fe4bbcd12c9000ead7f35e0a38b8d09a9bb99b13223446dd8689ff3c4959807620726d788701a83d2d - languageName: node - linkType: hard - "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -3618,23 +3360,6 @@ __metadata: languageName: node linkType: hard -"merge2@npm:^1.3.0": - version: 1.4.1 - resolution: "merge2@npm:1.4.1" - checksum: 254a8a4605b58f450308fc474c82ac9a094848081bf4c06778200207820e5193726dc563a0d2c16468810516a5c97d9d3ea0ca6585d23c58ccfff2403e8dbbeb - languageName: node - linkType: hard - -"micromatch@npm:^4.0.4, micromatch@npm:^4.0.5": - version: 4.0.7 - resolution: "micromatch@npm:4.0.7" - dependencies: - braces: "npm:^3.0.3" - picomatch: "npm:^2.3.1" - checksum: 58fa99bc5265edec206e9163a1d2cec5fabc46a5b473c45f4a700adce88c2520456ae35f2b301e4410fb3afb27e9521fb2813f6fc96be0a48a89430e0916a772 - languageName: node - linkType: hard - "mimic-fn@npm:^2.1.0": version: 2.1.0 resolution: "mimic-fn@npm:2.1.0" @@ -3780,17 +3505,6 @@ __metadata: languageName: node linkType: hard -"mz@npm:^2.7.0": - version: 2.7.0 - resolution: "mz@npm:2.7.0" - dependencies: - any-promise: "npm:^1.0.0" - object-assign: "npm:^4.0.1" - thenify-all: "npm:^1.0.0" - checksum: 103114e93f87362f0b56ab5b2e7245051ad0276b646e3902c98397d18bb8f4a77f2ea4a2c9d3ad516034ea3a56553b60d3f5f78220001ca4c404bd711bd0af39 - languageName: node - linkType: hard - "nanoid@npm:4.0.2": version: 4.0.2 resolution: "nanoid@npm:4.0.2" @@ -3809,15 +3523,6 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.7": - version: 3.3.7 - resolution: "nanoid@npm:3.3.7" - bin: - nanoid: bin/nanoid.cjs - checksum: e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3 - languageName: node - linkType: hard - "napi-macros@npm:~2.0.0": version: 2.0.0 resolution: "napi-macros@npm:2.0.0" @@ -3881,13 +3586,6 @@ __metadata: languageName: node linkType: hard -"normalize-path@npm:^3.0.0, normalize-path@npm:~3.0.0": - version: 3.0.0 - resolution: "normalize-path@npm:3.0.0" - checksum: e008c8142bcc335b5e38cf0d63cfd39d6cf2d97480af9abdbe9a439221fd4d749763bab492a8ee708ce7a194bb00c9da6d0a115018672310850489137b3da046 - languageName: node - linkType: hard - "npm-run-path@npm:^4.0.1": version: 4.0.1 resolution: "npm-run-path@npm:4.0.1" @@ -3897,20 +3595,6 @@ __metadata: languageName: node linkType: hard -"object-assign@npm:^4.0.1": - version: 4.1.1 - resolution: "object-assign@npm:4.1.1" - checksum: 1f4df9945120325d041ccf7b86f31e8bcc14e73d29171e37a7903050e96b81323784ec59f93f102ec635bcf6fa8034ba3ea0a8c7e69fa202b87ae3b6cec5a414 - languageName: node - linkType: hard - -"object-hash@npm:^3.0.0": - version: 3.0.0 - resolution: "object-hash@npm:3.0.0" - checksum: a06844537107b960c1c8b96cd2ac8592a265186bfa0f6ccafe0d34eabdb526f6fa81da1f37c43df7ed13b12a4ae3457a16071603bcd39d8beddb5f08c37b0f47 - languageName: node - linkType: hard - "onetime@npm:^5.1.2": version: 5.1.2 resolution: "onetime@npm:5.1.2" @@ -3955,13 +3639,6 @@ __metadata: languageName: node linkType: hard -"path-parse@npm:^1.0.7": - version: 1.0.7 - resolution: "path-parse@npm:1.0.7" - checksum: 11ce261f9d294cc7a58d6a574b7f1b935842355ec66fba3c3fd79e0f036462eaf07d0aa95bb74ff432f9afef97ce1926c720988c6a7451d8a584930ae7de86e1 - languageName: node - linkType: hard - "path-scurry@npm:^1.10.1": version: 1.10.1 resolution: "path-scurry@npm:1.10.1" @@ -3979,115 +3656,6 @@ __metadata: languageName: node linkType: hard -"picocolors@npm:^1.0.1": - version: 1.0.1 - resolution: "picocolors@npm:1.0.1" - checksum: c63cdad2bf812ef0d66c8db29583802355d4ca67b9285d846f390cc15c2f6ccb94e8cb7eb6a6e97fc5990a6d3ad4ae42d86c84d3146e667c739a4234ed50d400 - languageName: node - linkType: hard - -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": - version: 2.3.1 - resolution: "picomatch@npm:2.3.1" - checksum: 26c02b8d06f03206fc2ab8d16f19960f2ff9e81a658f831ecb656d8f17d9edc799e8364b1f4a7873e89d9702dff96204be0fa26fe4181f6843f040f819dac4be - languageName: node - linkType: hard - -"pify@npm:^2.3.0": - version: 2.3.0 - resolution: "pify@npm:2.3.0" - checksum: 551ff8ab830b1052633f59cb8adc9ae8407a436e06b4a9718bcb27dc5844b83d535c3a8512b388b6062af65a98c49bdc0dd523d8b2617b188f7c8fee457158dc - languageName: node - linkType: hard - -"pirates@npm:^4.0.1": - version: 4.0.6 - resolution: "pirates@npm:4.0.6" - checksum: 00d5fa51f8dded94d7429700fb91a0c1ead00ae2c7fd27089f0c5b63e6eca36197fe46384631872690a66f390c5e27198e99006ab77ae472692ab9c2ca903f36 - languageName: node - linkType: hard - -"postcss-import@npm:^15.1.0": - version: 15.1.0 - resolution: "postcss-import@npm:15.1.0" - dependencies: - postcss-value-parser: "npm:^4.0.0" - read-cache: "npm:^1.0.0" - resolve: "npm:^1.1.7" - peerDependencies: - postcss: ^8.0.0 - checksum: 518aee5c83ea6940e890b0be675a2588db68b2582319f48c3b4e06535a50ea6ee45f7e63e4309f8754473245c47a0372632378d1d73d901310f295a92f26f17b - languageName: node - linkType: hard - -"postcss-js@npm:^4.0.1": - version: 4.0.1 - resolution: "postcss-js@npm:4.0.1" - dependencies: - camelcase-css: "npm:^2.0.1" - peerDependencies: - postcss: ^8.4.21 - checksum: af35d55cb873b0797d3b42529514f5318f447b134541844285c9ac31a17497297eb72296902967911bb737a75163441695737300ce2794e3bd8c70c13a3b106e - languageName: node - linkType: hard - -"postcss-load-config@npm:^4.0.1": - version: 4.0.2 - resolution: "postcss-load-config@npm:4.0.2" - dependencies: - lilconfig: "npm:^3.0.0" - yaml: "npm:^2.3.4" - peerDependencies: - postcss: ">=8.0.9" - ts-node: ">=9.0.0" - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - checksum: 3d7939acb3570b0e4b4740e483d6e555a3e2de815219cb8a3c8fc03f575a6bde667443aa93369c0be390af845cb84471bf623e24af833260de3a105b78d42519 - languageName: node - linkType: hard - -"postcss-nested@npm:^6.0.1": - version: 6.0.1 - resolution: "postcss-nested@npm:6.0.1" - dependencies: - postcss-selector-parser: "npm:^6.0.11" - peerDependencies: - postcss: ^8.2.14 - checksum: 2a50aa36d5d103c2e471954830489f4c024deed94fa066169101db55171368d5f80b32446b584029e0471feee409293d0b6b1d8ede361f6675ba097e477b3cbd - languageName: node - linkType: hard - -"postcss-selector-parser@npm:^6.0.11": - version: 6.1.1 - resolution: "postcss-selector-parser@npm:6.1.1" - dependencies: - cssesc: "npm:^3.0.0" - util-deprecate: "npm:^1.0.2" - checksum: 5608765e033fee35d448e1f607ffbaa750eb86901824a8bc4a911ea8bc137cb82f29239330787427c5d3695afd90d8721e190f211dbbf733e25033d8b3100763 - languageName: node - linkType: hard - -"postcss-value-parser@npm:^4.0.0": - version: 4.2.0 - resolution: "postcss-value-parser@npm:4.2.0" - checksum: f4142a4f56565f77c1831168e04e3effd9ffcc5aebaf0f538eee4b2d465adfd4b85a44257bb48418202a63806a7da7fe9f56c330aebb3cac898e46b4cbf49161 - languageName: node - linkType: hard - -"postcss@npm:^8.4.23": - version: 8.4.39 - resolution: "postcss@npm:8.4.39" - dependencies: - nanoid: "npm:^3.3.7" - picocolors: "npm:^1.0.1" - source-map-js: "npm:^1.2.0" - checksum: 16f5ac3c4e32ee76d1582b3c0dcf1a1fdb91334a45ad755eeb881ccc50318fb8d64047de4f1601ac96e30061df203f0f2e2edbdc0bfc49b9c57bc9fb9bedaea3 - languageName: node - linkType: hard - "postcss@npm:^8.4.26": version: 8.4.27 resolution: "postcss@npm:8.4.27" @@ -4130,10 +3698,19 @@ __metadata: languageName: node linkType: hard -"queue-microtask@npm:^1.2.2": - version: 1.2.3 - resolution: "queue-microtask@npm:1.2.3" - checksum: 900a93d3cdae3acd7d16f642c29a642aea32c2026446151f0778c62ac089d4b8e6c986811076e1ae180a694cedf077d453a11b58ff0a865629a4f82ab558e102 +"quickselect@npm:^2.0.0": + version: 2.0.0 + resolution: "quickselect@npm:2.0.0" + checksum: 6c8d591bc73beae4c1996b7b7138233a7dbbbdde29b7b6d822a02d08cd220fd27613f47d6e9635989b12e250d42ef9da3448de1ed12ad962974e207ab3c3562c + languageName: node + linkType: hard + +"rbush@npm:^4.0.0": + version: 4.0.0 + resolution: "rbush@npm:4.0.0" + dependencies: + quickselect: "npm:^2.0.0" + checksum: a2d407ea87e11eb0ae7b924acb2094994a60c503a892421313a48d2073aa9f1201fe0413b576e81d2c41954d97e78ee507a9ddbf361cc613f33efcce83a85b0a languageName: node linkType: hard @@ -4217,15 +3794,6 @@ __metadata: languageName: node linkType: hard -"read-cache@npm:^1.0.0": - version: 1.0.0 - resolution: "read-cache@npm:1.0.0" - dependencies: - pify: "npm:^2.3.0" - checksum: 90cb2750213c7dd7c80cb420654344a311fdec12944e81eb912cd82f1bc92aea21885fa6ce442e3336d9fccd663b8a7a19c46d9698e6ca55620848ab932da814 - languageName: node - linkType: hard - "readable-stream@npm:^3.4.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" @@ -4237,15 +3805,6 @@ __metadata: languageName: node linkType: hard -"readdirp@npm:~3.6.0": - version: 3.6.0 - resolution: "readdirp@npm:3.6.0" - dependencies: - picomatch: "npm:^2.2.1" - checksum: 6fa848cf63d1b82ab4e985f4cf72bd55b7dcfd8e0a376905804e48c3634b7e749170940ba77b32804d5fe93b3cc521aa95a8d7e7d725f830da6d93f3669ce66b - languageName: node - linkType: hard - "regenerator-runtime@npm:^0.13.11": version: 0.13.11 resolution: "regenerator-runtime@npm:0.13.11" @@ -4260,32 +3819,6 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.7, resolve@npm:^1.22.2": - version: 1.22.8 - resolution: "resolve@npm:1.22.8" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 07e179f4375e1fd072cfb72ad66d78547f86e6196c4014b31cb0b8bb1db5f7ca871f922d08da0fbc05b94e9fd42206f819648fa3b5b873ebbc8e1dc68fec433a - languageName: node - linkType: hard - -"resolve@patch:resolve@npm%3A^1.1.7#optional!builtin, resolve@patch:resolve@npm%3A^1.22.2#optional!builtin": - version: 1.22.8 - resolution: "resolve@patch:resolve@npm%3A1.22.8#optional!builtin::version=1.22.8&hash=c3c19d" - dependencies: - is-core-module: "npm:^2.13.0" - path-parse: "npm:^1.0.7" - supports-preserve-symlinks-flag: "npm:^1.0.0" - bin: - resolve: bin/resolve - checksum: 0446f024439cd2e50c6c8fa8ba77eaa8370b4180f401a96abf3d1ebc770ac51c1955e12764cde449fde3fff480a61f84388e3505ecdbab778f4bef5f8212c729 - languageName: node - linkType: hard - "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -4293,13 +3826,6 @@ __metadata: languageName: node linkType: hard -"reusify@npm:^1.0.4": - version: 1.0.4 - resolution: "reusify@npm:1.0.4" - checksum: c19ef26e4e188f408922c46f7ff480d38e8dfc55d448310dfb518736b23ed2c4f547fb64a6ed5bdba92cd7e7ddc889d36ff78f794816d5e71498d645ef476107 - languageName: node - linkType: hard - "rollup@npm:^3.25.2": version: 3.27.2 resolution: "rollup@npm:3.27.2" @@ -4314,15 +3840,6 @@ __metadata: languageName: node linkType: hard -"run-parallel@npm:^1.1.9": - version: 1.2.0 - resolution: "run-parallel@npm:1.2.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 200b5ab25b5b8b7113f9901bfe3afc347e19bb7475b267d55ad0eb86a62a46d77510cb0f232507c9e5d497ebda569a08a9867d0d14f57a82ad5564d991588b39 - languageName: node - linkType: hard - "rxjs@npm:^7.8.1": version: 7.8.1 resolution: "rxjs@npm:7.8.1" @@ -4447,13 +3964,6 @@ __metadata: languageName: node linkType: hard -"source-map-js@npm:^1.2.0": - version: 1.2.0 - resolution: "source-map-js@npm:1.2.0" - checksum: 7e5f896ac10a3a50fe2898e5009c58ff0dc102dcb056ed27a354623a0ece8954d4b2649e1a1b2b52ef2e161d26f8859c7710350930751640e71e374fe2d321a4 - languageName: node - linkType: hard - "source-map-support@npm:0.5.21": version: 0.5.21 resolution: "source-map-support@npm:0.5.21" @@ -4560,24 +4070,6 @@ __metadata: languageName: node linkType: hard -"sucrase@npm:^3.32.0": - version: 3.35.0 - resolution: "sucrase@npm:3.35.0" - dependencies: - "@jridgewell/gen-mapping": "npm:^0.3.2" - commander: "npm:^4.0.0" - glob: "npm:^10.3.10" - lines-and-columns: "npm:^1.1.6" - mz: "npm:^2.7.0" - pirates: "npm:^4.0.1" - ts-interface-checker: "npm:^0.1.9" - bin: - sucrase: bin/sucrase - sucrase-node: bin/sucrase-node - checksum: ac85f3359d2c2ecbf5febca6a24ae9bf96c931f05fde533c22a94f59c6a74895e5d5f0e871878dfd59c2697a75ebb04e4b2224ef0bfc24ca1210735c2ec191ef - languageName: node - linkType: hard - "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -4605,46 +4097,6 @@ __metadata: languageName: node linkType: hard -"supports-preserve-symlinks-flag@npm:^1.0.0": - version: 1.0.0 - resolution: "supports-preserve-symlinks-flag@npm:1.0.0" - checksum: 6c4032340701a9950865f7ae8ef38578d8d7053f5e10518076e6554a9381fa91bd9c6850193695c141f32b21f979c985db07265a758867bac95de05f7d8aeb39 - languageName: node - linkType: hard - -"tailwindcss@npm:^3.4.6": - version: 3.4.6 - resolution: "tailwindcss@npm:3.4.6" - dependencies: - "@alloc/quick-lru": "npm:^5.2.0" - arg: "npm:^5.0.2" - chokidar: "npm:^3.5.3" - didyoumean: "npm:^1.2.2" - dlv: "npm:^1.1.3" - fast-glob: "npm:^3.3.0" - glob-parent: "npm:^6.0.2" - is-glob: "npm:^4.0.3" - jiti: "npm:^1.21.0" - lilconfig: "npm:^2.1.0" - micromatch: "npm:^4.0.5" - normalize-path: "npm:^3.0.0" - object-hash: "npm:^3.0.0" - picocolors: "npm:^1.0.0" - postcss: "npm:^8.4.23" - postcss-import: "npm:^15.1.0" - postcss-js: "npm:^4.0.1" - postcss-load-config: "npm:^4.0.1" - postcss-nested: "npm:^6.0.1" - postcss-selector-parser: "npm:^6.0.11" - resolve: "npm:^1.22.2" - sucrase: "npm:^3.32.0" - bin: - tailwind: lib/cli.js - tailwindcss: lib/cli.js - checksum: 8d82b697ecf41d8cd100ab3369e3cbcb30e350afc5b595a000b201ba666628a68543154297f96c5b2a3f2614f37bb981af4766556ebaae832079fa9a436e8563 - languageName: node - linkType: hard - "tar@npm:^6.1.11, tar@npm:^6.1.2": version: 6.2.0 resolution: "tar@npm:6.2.0" @@ -4659,24 +4111,6 @@ __metadata: languageName: node linkType: hard -"thenify-all@npm:^1.0.0": - version: 1.6.0 - resolution: "thenify-all@npm:1.6.0" - dependencies: - thenify: "npm:>= 3.1.0 < 4" - checksum: 9b896a22735e8122754fe70f1d65f7ee691c1d70b1f116fda04fea103d0f9b356e3676cb789506e3909ae0486a79a476e4914b0f92472c2e093d206aed4b7d6b - languageName: node - linkType: hard - -"thenify@npm:>= 3.1.0 < 4": - version: 3.3.1 - resolution: "thenify@npm:3.3.1" - dependencies: - any-promise: "npm:^1.0.0" - checksum: f375aeb2b05c100a456a30bc3ed07ef03a39cbdefe02e0403fb714b8c7e57eeaad1a2f5c4ecfb9ce554ce3db9c2b024eba144843cd9e344566d9fcee73b04767 - languageName: node - linkType: hard - "tldraw@npm:2.3.0": version: 2.3.0 resolution: "tldraw@npm:2.3.0" @@ -4710,15 +4144,6 @@ __metadata: languageName: node linkType: hard -"to-regex-range@npm:^5.0.1": - version: 5.0.1 - resolution: "to-regex-range@npm:5.0.1" - dependencies: - is-number: "npm:^7.0.0" - checksum: 487988b0a19c654ff3e1961b87f471702e708fa8a8dd02a298ef16da7206692e8552a0250e8b3e8759270f62e9d8314616f6da274734d3b558b1fc7b7724e892 - languageName: node - linkType: hard - "tree-kill@npm:^1.2.2": version: 1.2.2 resolution: "tree-kill@npm:1.2.2" @@ -4728,13 +4153,6 @@ __metadata: languageName: node linkType: hard -"ts-interface-checker@npm:^0.1.9": - version: 0.1.13 - resolution: "ts-interface-checker@npm:0.1.13" - checksum: 232509f1b84192d07b81d1e9b9677088e590ac1303436da1e92b296e9be8e31ea042e3e1fd3d29b1742ad2c959e95afe30f63117b8f1bc3a3850070a5142fea7 - languageName: node - linkType: hard - "tslib@npm:^2.0.0, tslib@npm:^2.1.0": version: 2.6.1 resolution: "tslib@npm:2.6.1" @@ -4841,7 +4259,7 @@ __metadata: languageName: node linkType: hard -"util-deprecate@npm:^1.0.1, util-deprecate@npm:^1.0.2": +"util-deprecate@npm:^1.0.1": version: 1.0.2 resolution: "util-deprecate@npm:1.0.2" checksum: 41a5bdd214df2f6c3ecf8622745e4a366c4adced864bc3c833739791aeeeb1838119af7daed4ba36428114b5c67dcda034a79c882e97e43c03e66a4dd7389942 @@ -5120,15 +4538,6 @@ __metadata: languageName: node linkType: hard -"yaml@npm:^2.3.4": - version: 2.4.5 - resolution: "yaml@npm:2.4.5" - bin: - yaml: bin.mjs - checksum: e1ee78b381e5c710f715cc4082fd10fc82f7f5c92bd6f075771d20559e175616f56abf1c411f545ea0e9e16e4f84a83a50b42764af5f16ec006328ba9476bb31 - languageName: node - linkType: hard - "yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1"