HEX
Server: Apache
System: Linux p3plzcpnl506847.prod.phx3.secureserver.net 4.18.0-553.54.1.lve.el8.x86_64 #1 SMP Wed Jun 4 13:01:13 UTC 2025 x86_64
User: slfopp7cb1df (5698090)
PHP: 8.1.34
Disabled: NONE
Upload Files
File: /home/slfopp7cb1df/public_html/sitepacket.com/src/editor/src/components/canvas/selection.svelte
<script>
  import { onMount, getContext, onDestroy, afterUpdate } from 'svelte'
  import { store } from 'Editor/store'
  import { selected, tool, activeArtboard } from 'Editor/store/ui'
  import { getters, setters } from 'Editor/store'
  import * as consts from 'Editor/scripts/consts'
  import * as utilities from 'Editor/scripts/utilities'

  let subscribers = []
  let root
  let inner
  let context = getContext('canvas')
  let showTransformControls = true
  let editingPoly = false
  let pointsContainer
  let selectedObjects = []
  let selectionRect = {}

  onMount(() => {
    redraw()
    createEvents()
  })
  onDestroy(() => {
    subscribers.forEach((unsub) => unsub())
    subscribers = []
    removeEvents()
  })
  afterUpdate(() => {
    if (!moving && !scaling) redraw()
  })
  subscribers.push(
    store.subscribe(() => {
      redraw()
    })
  )
  subscribers.push(
    activeArtboard.subscribe(() => {
      redraw()
    })
  )

  function createEvents() {
    document.addEventListener('mousedown', handleMouseDown)
    document.addEventListener('mousemove', handleMouseMove)
    document.addEventListener('mouseup', handleMouseUp)
    document.addEventListener('keydown', handleKeyDown)
    document.addEventListener('keyup', handleKeyUp)
    document.addEventListener(consts.EVENT_FORM_CONTROL_UPDATE_START, handleFormControlUpdateStart)
    document.addEventListener(consts.EVENT_FORM_CONTROL_UPDATE_END, handleFormControlUpdateEnd)
    document.addEventListener(consts.EVENT_CANVAS_ZOOM_END, handleCanvasZoom)
    document.addEventListener(consts.EVENT_OBJECT_QUICK_DRAG, handleQuickDrag)
  }
  function removeEvents() {
    document.removeEventListener('mousedown', handleMouseDown)
    document.removeEventListener('mousemove', handleMouseMove)
    document.removeEventListener('mouseup', handleMouseUp)
    document.removeEventListener('keydown', handleKeyDown)
    document.removeEventListener('keyup', handleKeyUp)
    document.removeEventListener(consts.EVENT_FORM_CONTROL_UPDATE_START, handleFormControlUpdateStart)
    document.removeEventListener(consts.EVENT_FORM_CONTROL_UPDATE_END, handleFormControlUpdateEnd)
    document.removeEventListener(consts.EVENT_CANVAS_ZOOM_END, handleCanvasZoom)
    document.removeEventListener(consts.EVENT_OBJECT_QUICK_DRAG, handleQuickDrag)
  }

  // Zoom handling
  function handleCanvasZoom() {
    redraw()
  }

  // Quick move handling
  function handleQuickDrag() {
    startMove()
  }

  // Fast redraw handling
  let formControlUpdating = false
  function handleFormControlUpdateStart() {
    formControlUpdating = true
    redraw()
  }
  function handleFormControlUpdateEnd() {
    formControlUpdating = false
  }

  // Draw selection
  function redraw() {
    if (!document.querySelector('#selection')) return

    flattenSelectedObjectHierarchy()
    showTransformControls = shouldShowTransformControls()
    editingPoly = shouldPlacePolyPoints()

    if (selectedObjects.length > 0 && $tool === consts.TOOL_SELECT && !formControlUpdating) {
      drawSelection()
    } else {
      hideSelection()
    }
  }
  function drawSelection() {
    selectionRect = utilities.calcBoundingRectForObjects(selectedObjects, context.getCanvasRect())
    if (!selectionRect) return

    root.style.setProperty('left', selectionRect.x + '%', 'important')
    root.style.setProperty('top', selectionRect.y + '%', 'important')
    root.style.width = selectionRect.width + '%'
    root.style.height = selectionRect.height + '%'
    root.style.display = 'block'

    if (editingPoly) {
      inner.style.margin = '-15px 0 0 -15px'
      inner.style.padding = '15px'
      deletePoints()
      drawPoints()
    } else {
      inner.style.margin = '0'
      inner.style.padding = '0'
      deletePoints()
    }

    if (movingPoint) {
      inner.style.display = 'none'
    } else {
      inner.style.display = 'block'
    }
  }
  function hideSelection() {
    root.style.display = 'none'
  }
  function shouldShowTransformControls() {
    for (let obj of selectedObjects) {
      if (obj.type !== consts.OBJECT_SPOT && obj.type !== consts.OBJECT_TEXT) {
        return true
      }
    }
    return false
  }
  function shouldPlacePolyPoints() {
    if (selectedObjects.length === 1 && selectedObjects[0].type === 'poly') return true
    return false
  }
  function flattenSelectedObjectHierarchy() {
    // If we are moving a point, don't update the hierarchy
    // because it will overwrite the object with the temporary props
    if (movingPoint) return

    selectedObjects = []

    for (let id of $selected) {
      if (getters.isObjectChildOfActiveArtboard(id)) {
        let obj = structuredClone(getters.getObject(id))
        if (obj.type === consts.OBJECT_GROUP) {
          selectedObjects.push(...getChildren(obj))
        } else {
          selectedObjects.push(obj)
        }
      }
    }

    return selectedObjects
  }
  function drawPoints() {
    let obj = selectedObjects[0]
    pointsContainer = document.createElement('div')

    for (let i = 0; i < obj.points.length; i++) {
      let el = document.createElement('div')
      el.style.setProperty('left', obj.points[i].x + '%', 'important')
      el.style.setProperty('top', obj.points[i].y + '%', 'important')
      el.classList.add('point')
      el.dataset.selectionControlType = 'point'
      el.dataset.pointIndex = i
      pointsContainer.appendChild(el)
    }

    root.appendChild(pointsContainer)
  }
  function deletePoints() {
    if (pointsContainer) pointsContainer.remove()
  }

  // Editing functionality
  let shiftDown = false
  let initialCoords = { x: 0, y: 0 }
  let coords = { x: 0, y: 0 }
  let transformStartEvent = new Event(consts.EVENT_OBJECT_TRANSFORM_START)
  let transformEndEvent = new Event(consts.EVENT_OBJECT_TRANSFORM_END)

  // Moving
  let moving = false
  let moveOffset = { x: 0, y: 0 }
  let moveOffsetPerObject = []
  let moveMultiplierPerObject = []
  let canMoveX = false
  let canMoveY = false
  let canMoveSelectionRectX = false
  let canMoveSelectionRectY = false
  let moveEvent = new Event(consts.EVENT_OBJECT_MOVE)

  // Scaling
  let scaleEvent = new Event(consts.EVENT_OBJECT_SCALE)
  let scaling = false
  let anchorPoint = { x: 0, y: 0 }
  let scaleFactor = { x: 1, y: 1 }
  let canScaleX = false
  let canScaleY = false
  let scaleControl = 1

  // Poly editing
  let editPolyEvent = new Event(consts.EVENT_OBJECT_EDIT_POLY)
  let draggedPointIndex = 0
  let movingPoint = false
  let tempPoints = []
  let draggedPointEl = undefined
  let tempPointEl = undefined
  let tempPointIndex = undefined
  let tempPoint = { x: 0, y: 0 }

  // Event handlers
  function handleMouseDown(e) {
    initialCoords = { x: e.pageX, y: e.pageY }
    coords = { x: e.pageX, y: e.pageY }

    if (e.button !== 0) return

    if (e.target.dataset.selectionControlType === 'move') {
      startMove()
    }
    if (e.target.dataset.selectionControlType === 'scale') {
      startScaling(e)
    }
    if (e.target.dataset.selectionControlType === 'point') {
      startMovingPoint(parseInt(e.target.dataset.pointIndex))
    }
    if (e.target.dataset.selectionControlType === 'temp-point') {
      insertPoint()
      hideTempPoint()
      startMovingPoint(parseInt(e.target.dataset.pointIndex))
    }
  }
  function handleMouseMove(e) {
    coords = { x: e.pageX, y: e.pageY }
    hideTempPoint()

    if (moving) {
      move()
      return
    }
    if (scaling) {
      scale()
      return
    }
    if (movingPoint) {
      movePoint()
      return
    }

    if (editingPoly && e.target.dataset.selectionControlType !== 'point') {
      drawTempPoint()
    }
  }
  function handleMouseUp() {
    if (moving) {
      endMove()
    }
    if (scaling) {
      endScaling()
    }
    if (movingPoint) {
      endMovingPoint()
    }
  }
  function handleKeyDown(e) {
    shiftDown = e.shiftKey
  }
  function handleKeyUp(e) {
    shiftDown = false
  }

  // Move
  function startMove() {
    moving = true
    moveOffset = { x: 0, y: 0 }
    document.dispatchEvent(transformStartEvent)
  }
  function move() {
    moveOffset = absToRel({
      x: coords.x - initialCoords.x,
      y: coords.y - initialCoords.y,
    })

    // Prevent going out of canvas
    for (let obj of selectedObjects) {
      let objWidth = obj.width
      let objHeight = obj.height

      if (obj.type === consts.OBJECT_SPOT || obj.type === consts.OBJECT_TEXT) {
        objWidth = 0
        objHeight = 0
      }

      if (obj.x + objWidth + moveOffset.x > 100) {
        moveOffset.x = 100 - (obj.x + objWidth)
      }
      if (obj.y + objHeight + moveOffset.y > 100) {
        moveOffset.y = 100 - (obj.y + objHeight)
      }
      if (obj.x + moveOffset.x < 0) {
        moveOffset.x = -obj.x
      }
      if (obj.y + moveOffset.y < 0) {
        moveOffset.y = -obj.y
      }
    }

    // Update selection position
    root.style.setProperty('left', selectionRect.x + moveOffset.x + '%', 'important')
    root.style.setProperty('top', selectionRect.y + moveOffset.y + '%', 'important')

    // Dispatch event
    moveEvent.detail = {
      move: moveOffset,
      ids: selectedObjects.map((obj) => obj.id),
    }

    document.dispatchEvent(moveEvent)
  }
  function endMove() {
    moving = false
    document.dispatchEvent(transformEndEvent)

    // Apply transform
    let values = {}
    for (let obj of selectedObjects) {
      values[obj.id] = {
        x: obj.x + moveOffset.x,
        y: obj.y + moveOffset.y,
      }
    }

    setters.setObjectValues(values)

    // Reset translation of the selection
    redraw()
  }

  // Scale
  function startScaling(e) {
    document.dispatchEvent(transformStartEvent)
    scaling = true
    scaleControl = parseInt(e.target.dataset.selectionScaleControl)
    scaleFactor = { x: 1, y: 1 }

    canScaleX = scaleControl === 1 || scaleControl === 3 || scaleControl === 4 || scaleControl === 5 || scaleControl === 7 || scaleControl === 8 ? true : false
    canScaleY = scaleControl === 1 || scaleControl === 2 || scaleControl === 3 || scaleControl === 5 || scaleControl === 6 || scaleControl === 7 ? true : false

    canMoveX = scaleControl === 1 || scaleControl === 3 || scaleControl === 4 || scaleControl === 5 || scaleControl === 7 || scaleControl === 8 ? true : false
    canMoveY = scaleControl === 1 || scaleControl === 2 || scaleControl === 3 || scaleControl === 5 || scaleControl === 6 || scaleControl === 7 ? true : false

    canMoveSelectionRectX = scaleControl === 1 || scaleControl === 7 || scaleControl === 8 ? true : false
    canMoveSelectionRectY = scaleControl === 1 || scaleControl === 2 || scaleControl === 3 ? true : false

    // Calculate anchor point

    anchorPoint = { x: 0, y: 0 }

    if (scaleControl === 1) {
      anchorPoint = {
        x: selectionRect.x + selectionRect.width,
        y: selectionRect.y + selectionRect.height,
      }
    }
    if (scaleControl === 2) {
      anchorPoint = {
        x: selectionRect.x + selectionRect.width / 2,
        y: selectionRect.y + selectionRect.height,
      }
    }
    if (scaleControl === 3) {
      anchorPoint = {
        x: selectionRect.x,
        y: selectionRect.y + selectionRect.height,
      }
    }
    if (scaleControl === 4) {
      anchorPoint = {
        x: selectionRect.x,
        y: selectionRect.y + selectionRect.height / 2,
      }
    }
    if (scaleControl === 5) {
      anchorPoint = {
        x: selectionRect.x,
        y: selectionRect.y,
      }
    }
    if (scaleControl === 6) {
      anchorPoint = {
        x: selectionRect.x + selectionRect.width / 2,
        y: selectionRect.y,
      }
    }
    if (scaleControl === 7) {
      anchorPoint = {
        x: selectionRect.x + selectionRect.width,
        y: selectionRect.y,
      }
    }
    if (scaleControl === 8) {
      anchorPoint = {
        x: selectionRect.x + selectionRect.width,
        y: selectionRect.y + selectionRect.height / 2,
      }
    }

    for (let obj of selectedObjects) {
      moveOffsetPerObject[obj.id] = { x: 0, y: 0 }
      moveMultiplierPerObject[obj.id] = { x: 1, y: 1 }

      let distanceFromAnchorX = selectionRect.width
      let distanceFromAnchorY = selectionRect.height

      if (scaleControl === 3 || scaleControl === 4 || scaleControl === 5) {
        distanceFromAnchorX = obj.x - anchorPoint.x
      }
      if (scaleControl === 1 || scaleControl === 7 || scaleControl === 8) {
        distanceFromAnchorX = anchorPoint.x - obj.x
      }

      if (scaleControl === 1 || scaleControl === 2 || scaleControl === 3) {
        distanceFromAnchorY = anchorPoint.y - obj.y
      }
      if (scaleControl === 5 || scaleControl === 6 || scaleControl === 7) {
        distanceFromAnchorY = obj.y - anchorPoint.y
      }

      moveMultiplierPerObject[obj.id] = {
        x: distanceFromAnchorX / selectionRect.width,
        y: distanceFromAnchorY / selectionRect.height,
      }
    }
  }
  function scale() {
    let mouseOffsetRel = absToRel({
      x: coords.x - initialCoords.x,
      y: coords.y - initialCoords.y,
    })

    // Virtual canvas size
    // The selection can't go outside those bounds
    let canvasBounds = {
      top: 0,
      bottom: 100,
      left: 0,
      right: 100,
    }

    // If SHIFT is pressed, lock ratio and shrink canvas bounds
    if (shiftDown && [1, 3, 5, 7].includes(scaleControl)) {
      let ratio = selectionRect.width / selectionRect.height

      if (scaleControl === 3 || scaleControl === 7) {
        mouseOffsetRel.y = -mouseOffsetRel.x / ratio
      } else {
        mouseOffsetRel.y = mouseOffsetRel.x / ratio
      }

      let maxWidth, maxHeight
      if (scaleControl === 1) {
        maxWidth = selectionRect.x + selectionRect.width
        maxHeight = selectionRect.y + selectionRect.height
      }

      if (scaleControl === 3) {
        maxWidth = 100 - selectionRect.x
        maxHeight = selectionRect.y + selectionRect.height
      }

      if (scaleControl === 5) {
        maxWidth = 100 - selectionRect.x
        maxHeight = 100 - selectionRect.y
      }

      if (scaleControl === 7) {
        maxWidth = selectionRect.x + selectionRect.width
        maxHeight = 100 - selectionRect.y
      }

      let maxScaleX = maxWidth / selectionRect.width
      let maxScaleY = maxHeight / selectionRect.height
      let maxScale = maxScaleX < maxScaleY ? maxScaleX : maxScaleY
      canvasBounds.left = selectionRect.x - (selectionRect.width * maxScale - selectionRect.width)
      canvasBounds.top = selectionRect.y - (selectionRect.height * maxScale - selectionRect.height)
      canvasBounds.right = selectionRect.x + selectionRect.width * maxScale
      canvasBounds.bottom = selectionRect.y + selectionRect.height * maxScale
    }

    // Limit mouse offset to canvas bounds
    if (canMoveSelectionRectX) {
      if (selectionRect.x + mouseOffsetRel.x < canvasBounds.left) mouseOffsetRel.x = canvasBounds.left - selectionRect.x
      if (mouseOffsetRel.x > selectionRect.width) mouseOffsetRel.x = selectionRect.width
    } else {
      if (selectionRect.x + selectionRect.width + mouseOffsetRel.x > canvasBounds.right) mouseOffsetRel.x = canvasBounds.right - (selectionRect.x + selectionRect.width)
      if (-mouseOffsetRel.x > selectionRect.width) mouseOffsetRel.x = -selectionRect.width
    }

    if (canMoveSelectionRectY) {
      if (selectionRect.y + mouseOffsetRel.y < canvasBounds.top) mouseOffsetRel.y = canvasBounds.top - selectionRect.y
      if (mouseOffsetRel.y > selectionRect.height) mouseOffsetRel.y = selectionRect.height
    } else {
      if (selectionRect.y + selectionRect.height + mouseOffsetRel.y > canvasBounds.bottom) mouseOffsetRel.y = canvasBounds.bottom - (selectionRect.y + selectionRect.height)
      if (-mouseOffsetRel.y > selectionRect.height) mouseOffsetRel.y = -selectionRect.height
    }

    // Calculate mouse offset, relative to anchor
    let anchoredMouseOffsetRel = {
      x: mouseOffsetRel.x,
      y: mouseOffsetRel.y,
    }
    if (scaleControl === 1 || scaleControl === 7 || scaleControl === 8) anchoredMouseOffsetRel.x *= -1
    if (scaleControl === 1 || scaleControl === 2 || scaleControl === 3) anchoredMouseOffsetRel.y *= -1

    // Calculate scale
    scaleFactor.x = canScaleX ? (selectionRect.width + anchoredMouseOffsetRel.x) / selectionRect.width : 1
    scaleFactor.y = canScaleY ? (selectionRect.height + anchoredMouseOffsetRel.y) / selectionRect.height : 1

    // Calculate move
    moveOffset.x = canMoveSelectionRectX ? mouseOffsetRel.x : 0
    moveOffset.y = canMoveSelectionRectY ? mouseOffsetRel.y : 0

    // Apply move to each object
    for (let obj of selectedObjects) {
      moveOffsetPerObject[obj.id] = { x: 0, y: 0 }

      if (canMoveX) {
        moveOffsetPerObject[obj.id].x = mouseOffsetRel.x * moveMultiplierPerObject[obj.id].x
      }
      if (canMoveY) {
        moveOffsetPerObject[obj.id].y = mouseOffsetRel.y * moveMultiplierPerObject[obj.id].y
      }
    }

    // Calculate the move of the whole selection
    let totalMove = { x: 0, y: 0 }
    if (scaleControl === 1 || scaleControl === 7 || scaleControl === 8) totalMove.x = mouseOffsetRel.x
    if (scaleControl === 1 || scaleControl === 2 || scaleControl === 3) totalMove.y = mouseOffsetRel.y

    // Update selection position and size
    root.style.width = selectionRect.width * scaleFactor.x + '%'
    root.style.height = selectionRect.height * scaleFactor.y + '%'
    root.style.setProperty('left', selectionRect.x + moveOffset.x + '%', 'important')
    root.style.setProperty('top', selectionRect.y + moveOffset.y + '%', 'important')

    // Dispatch event
    scaleEvent.detail = {
      movePerObject: moveOffsetPerObject,
      move: totalMove,
      scale: scaleFactor,
      ids: selectedObjects.map((obj) => obj.id),
    }
    document.dispatchEvent(scaleEvent)
  }
  function endScaling() {
    document.dispatchEvent(transformEndEvent)
    scaling = false

    // Apply transform
    let values = {}
    for (let obj of selectedObjects) {
      values[obj.id] = {
        x: obj.x + moveOffsetPerObject[obj.id].x,
        y: obj.y + moveOffsetPerObject[obj.id].y,
        width: obj.width * scaleFactor.x,
        height: obj.height * scaleFactor.y,
      }
    }

    setters.setObjectValues(values)

    // Reset translation of the selection
    redraw()
  }

  // Poly editing
  function startMovingPoint(index = 0) {
    let obj = selectedObjects[0]
    document.dispatchEvent(transformStartEvent)
    movingPoint = true
    draggedPointIndex = index

    // Modify the object
    // ---- Convert points from object to canvas space
    let newPoints = []
    for (let p of obj.points) {
      newPoints.push({
        x: obj.width * (p.x / 100) + obj.x,
        y: obj.height * (p.y / 100) + obj.y,
      })
    }

    // ---- Fit object to canvas
    obj.x = 0
    obj.y = 0
    obj.width = 100
    obj.height = 100
    obj.points = newPoints
    tempPoints = structuredClone(obj.points)

    // ---- Redraw the object
    editPolyEvent.detail = {
      id: obj.id,
      x: obj.x,
      y: obj.y,
      width: obj.width,
      height: obj.height,
      points: obj.points,
    }
    document.dispatchEvent(editPolyEvent)

    redraw()
  }
  function movePoint() {
    let obj = selectedObjects[0]

    moveOffset = absToRel({
      x: coords.x - initialCoords.x,
      y: coords.y - initialCoords.y,
    })

    // Calculate new coords for the point
    let dx = (moveOffset.x / obj.width) * 100
    let dy = (moveOffset.y / obj.height) * 100
    let x = obj.points[draggedPointIndex].x + dx
    let y = obj.points[draggedPointIndex].y + dy

    // Limit point to canvas
    x = x < 0 ? 0 : x
    x = x > 100 ? 100 : x
    y = y < 0 ? 0 : y
    y = y > 100 ? 100 : y

    // Update the points array
    tempPoints = []
    for (let i = 0; i < obj.points.length; i++) {
      if (i !== draggedPointIndex) {
        tempPoints.push({
          x: obj.points[i].x,
          y: obj.points[i].y,
        })
      } else {
        tempPoints.push({
          x,
          y,
        })
      }
    }

    // Update the point element position
    draggedPointEl = document.querySelector(`[data-selection-control-type="point"][data-point-index="${draggedPointIndex}"]`)
    draggedPointEl.style.setProperty('left', x + '%', 'important')
    draggedPointEl.style.setProperty('top', y + '%', 'important')

    // Send redraw event to the object
    editPolyEvent.detail = {
      id: obj.id,
      x: obj.x,
      y: obj.y,
      width: obj.width,
      height: obj.height,
      points: tempPoints,
    }
    document.dispatchEvent(editPolyEvent)
  }
  function endMovingPoint() {
    let obj = selectedObjects[0]
    document.dispatchEvent(transformEndEvent)
    movingPoint = false

    // Recalc new width/height and point coords
    let minX = 9999
    let minY = 9999
    let maxX = 0
    let maxY = 0
    let x,
      y,
      width,
      height,
      points = []

    // ---- Find bounds of the object
    for (let p of tempPoints) {
      if (p.x < minX) minX = p.x
      if (p.y < minY) minY = p.y
      if (p.x > maxX) maxX = p.x
      if (p.y > maxY) maxY = p.y
    }

    // ---- Calc width/height/x/y
    x = minX
    y = minY
    width = maxX - minX
    height = maxY - minY

    // ---- Convert point coords from canvas to object space
    for (let p of tempPoints) {
      points.push({
        x: ((p.x - x) / width) * 100,
        y: ((p.y - y) / height) * 100,
      })
    }

    // Write to store
    let ids = [obj.id]
    let values = {}
    values[ids[0]] = {
      x,
      y,
      width,
      height,
      points,
    }

    setters.setObjectValues(values)
    redraw()
  }
  function drawTempPoint() {
    let obj = selectedObjects[0]
    tempPointIndex = undefined

    // Mouse coords in % relative to canvas
    let p = {
      x: ((coords.x - context.getCanvasRect().left) / context.getCanvasRect().width) * 100,
      y: ((coords.y - context.getCanvasRect().top) / context.getCanvasRect().height) * 100,
    }

    // Mouse coords in % relative to object
    p = {
      x: ((p.x - obj.x) / obj.width) * 100,
      y: ((p.y - obj.y) / obj.height) * 100,
    }

    let shortestDistance = 9999
    let minDistance = (20 / context.getCanvasRect().width) * 100

    // Find the closest point to each line
    // and take the shortest path
    for (let i = 0; i < obj.points.length; i++) {
      let a = { x: obj.points[i].x, y: obj.points[i].y }
      let b = undefined

      if (obj.points[i + 1]) {
        b = { x: obj.points[i + 1].x, y: obj.points[i + 1].y }
      } else {
        b = { x: obj.points[0].x, y: obj.points[0].y }
      }

      let closestPointToLine = new utilities.Vector2(p.x, p.y).closestPointOnLine(new utilities.Vector2(a.x, a.y), new utilities.Vector2(b.x, b.y))
      let d = Math.sqrt(Math.pow(p.x - closestPointToLine.x, 2) + Math.pow(p.y - closestPointToLine.y, 2))

      if (d < shortestDistance && d < minDistance) {
        self.tempControlPointIndex = i
        shortestDistance = d
        tempPointIndex = i
        tempPoint = { x: closestPointToLine.x, y: closestPointToLine.y }
      }
    }

    // Draw temp point
    if (tempPointIndex !== undefined) {
      tempPointEl.style.display = 'block'
      tempPointEl.style.setProperty('left', tempPoint.x + '%', 'important')
      tempPointEl.style.setProperty('top', tempPoint.y + '%', 'important')

      tempPointEl.dataset.pointIndex = tempPointIndex + 1
    } else {
      if (tempPointEl.style.display !== 'none') tempPointEl.style.display = 'none'
    }
  }
  function hideTempPoint() {
    if (tempPointEl.style.display !== 'none') tempPointEl.style.display = 'none'
  }
  function insertPoint() {
    if (tempPointIndex !== undefined) {
      let obj = selectedObjects[0]
      let ids = [obj.id]
      let values = {}
      let points = structuredClone(obj.points)
      points.splice(tempPointIndex + 1, 0, { ...tempPoint })

      values[obj.id] = { points }

      setters.setObjectValues(values)
      redraw()
    }
  }

  // Utility
  function absToRel(coord) {
    return {
      x: (coord.x / context.getCanvasRect().width) * 100,
      y: (coord.y / context.getCanvasRect().height) * 100,
    }
  }
  function getChildren(obj) {
    let result = []

    for (let child of obj.children) {
      if (child.type === consts.OBJECT_GROUP || child.type === consts.OBJECT_ARTBOARD) {
        result.push(...getChildren(child))
      } else {
        result.push(child)
      }
    }

    return result
  }
</script>

<div id="selection" bind:this={root} style="z-index: 9998;" class="absolute left-0 top-0 cursor-move">
  <div bind:this={inner} class="absolute w-full h-full box-content" style="padding: 20px; margin: -20px 0 0 -20px;">
    {#if showTransformControls}
      <div data-selection-control-type="scale" data-selection-scale-control="1" class="select-control select-control-top-left" />
      <div data-selection-control-type="scale" data-selection-scale-control="2" class="select-control select-control-top" />
      <div data-selection-control-type="scale" data-selection-scale-control="3" class="select-control select-control-top-right" />
      <div data-selection-control-type="scale" data-selection-scale-control="4" class="select-control select-control-right" />
      <div data-selection-control-type="scale" data-selection-scale-control="5" class="select-control select-control-bottom-right" />
      <div data-selection-control-type="scale" data-selection-scale-control="6" class="select-control select-control-bottom" />
      <div data-selection-control-type="scale" data-selection-scale-control="7" class="select-control select-control-bottom-left" />
      <div data-selection-control-type="scale" data-selection-scale-control="8" class="select-control select-control-left" />
    {/if}
    <div data-selection-control-type="move" class="z-0 absolute left-0 top-0 w-full h-full border border-black" />
    <div data-selection-control-type="move" class="z-0 absolute left-0 top-0 w-full h-full border border-dashed border-white" style="stroke-dasharray: 10, 10" />
  </div>
  <div bind:this={tempPointEl} data-selection-control-type="temp-point" class="point temporary-point" style="display: none;" />
</div>

<style>
  .select-control {
    position: absolute;
    z-index: 1;
    width: 10px;
    height: 10px;
    margin-left: -5px;
    margin-top: -5px;
    background: black;
    border: 1px solid white;
    border-radius: 2px;
  }
  .select-control-top-left {
    left: 0;
    top: 0;
    cursor: nwse-resize;
  }
  .select-control-top {
    left: 50%;
    top: 0;
    cursor: ns-resize;
  }
  .select-control-top-right {
    left: 100%;
    top: 0;
    cursor: nesw-resize;
  }
  .select-control-right {
    left: 100%;
    top: 50%;
    cursor: ew-resize;
  }
  .select-control-bottom-right {
    left: 100%;
    top: 100%;
    cursor: nwse-resize;
  }
  .select-control-bottom {
    left: 50%;
    top: 100%;
    cursor: ns-resize;
  }
  .select-control-bottom-left {
    left: 0;
    top: 100%;
    cursor: nesw-resize;
  }
  .select-control-left {
    left: 0;
    top: 50%;
    cursor: ew-resize;
  }
</style>