Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ dist

# Documentation output created by jsdoc
docs/

# sometimes i copy files as refrence
ref-*
19 changes: 17 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,37 @@
<button id="uploadDxfBtn" class="btn btn-secondary">Upload DXF</button>
</div>
<div id="info2D" class="info-box">
<button
class="close-info"
onclick="this.parentElement.style.display='none'"
aria-label="Close"
>
×
</button>
<div id="shortcutsHeader2D" class="shortcuts-toggle">
<h3>Keyboard Shortcuts <span class="shortcutsArrow">▼</span></h3>
</div>
<div id="shortcutsContent2D" class="shortcuts-content">
<ul>
<li><strong>Ctrl + Z:</strong> Undo last action</li>
<li><strong>Alt:</strong> Move tool allow dragging vertex</li>
<li><strong>Alt + grab:</strong> navigate the canvas</li>
</ul>
</div>
</div>
<canvas id="canvas2D"></canvas>
<canvas id="canvas2D" resize hidpi="off"></canvas>
</div>

<!-- start with container3D hidden -->
<div id="container3D" style="display: none">
<!-- info, menu and canvas are fixed so Three.js correctly calculates canvas' dimensions -->
<div id="info3D" class="info-box">
<button
class="close-info"
onclick="this.parentElement.style.display='none'"
aria-label="Close"
>
×
</button>
<div id="shortcutsHeader3D" class="shortcuts-toggle">
<h3>Keyboard Shortcuts <span class="shortcutsArrow">▼</span></h3>
</div>
Expand Down
Empty file added src/config.js
Empty file.
98 changes: 98 additions & 0 deletions src/js/2d/contextMenu2d.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Context menu object
const ContextMenu = {
element: null,
options: [], // Store added options
lastClickClient: null, // store last right-click client coords

create(position) {
// Remove any existing context menu
this.remove()

// Create a new context menu
this.element = document.createElement('div')
this.element.className = 'context-menu'
this.element.style.top = `${position.y}px`
this.element.style.left = `${position.x}px`
// store last client coords for other modules to use
this.lastClickClient = { x: position.x, y: position.y }

// Add all stored options
this.options.forEach((option) => {
this.element.appendChild(option)
})

document.body.appendChild(this.element)
},

remove() {
if (this.element) {
this.element.remove()
this.element = null
}
},

addOption(text, callback) {
const optionElement = document.createElement('div')
optionElement.textContent = text
optionElement.className = 'context-menu-option'
optionElement.onclick = () => {
callback()
this.remove() // Close menu after clicking an option
}
this.options.push(optionElement)
},

clearOptions() {
this.options = []
},
}

// Default option
ContextMenu.addOption('Close Menu', () => ContextMenu.remove())

ContextMenu.addOption('Clear Canvas', () => {
const evt = new CustomEvent('datacenter:clearCanvas')
window.dispatchEvent(evt)
})

// Delete line option: will call into drawing logic using the last right-click position
// Note: drawing2d.deleteLineNear expects a Paper.js Point; the context that opens
// this menu (core2d) calls create with client coordinates. Consumers should convert
// client->paper coordinates before calling deleteLineNear. To keep this file decoupled
// we dispatch a custom event with the client coords and let drawing code listen for it.
ContextMenu.addOption('Delete Line', () => {
const detail = {
clientX: ContextMenu.lastClickClient?.x,
clientY: ContextMenu.lastClickClient?.y,
}
// dispatch a custom event that drawing2d can listen to
const evt = new CustomEvent('datacenter:deleteLineAt', { detail })
window.dispatchEvent(evt)
})

// ==========================================
// Safe initialization function
// ==========================================
export function initContextMenu(canvas2D) {
if (!canvas2D) {
console.warn('initContextMenu: canvas2D not found')
return
}

// Right-click opens custom menu
canvas2D.addEventListener('contextmenu', (e) => {
e.preventDefault()
ContextMenu.create({ x: e.clientX, y: e.clientY })
})

// Click anywhere else closes it
document.addEventListener('mousedown', (e) => {
if (ContextMenu.element && !ContextMenu.element.contains(e.target)) {
ContextMenu.remove()
}
})

console.log('Context menu initialized')
}

export { ContextMenu }
112 changes: 112 additions & 0 deletions src/js/2d/core2d.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import paper from 'paper'

import { setupMouseHandlers } from './drawing2d.js'
import { initContextMenu } from './contextMenu2d.js'

// ----------------------------------------------------
// 1. Canvas and Paper.js Setup
// ----------------------------------------------------
const canvas2D = document.getElementById('canvas2D')
paper.setup(canvas2D)

// ----------------------------------------------------
// 2. Constants
// ----------------------------------------------------
export const GRID_SPACING = 30
export const LINE_STROKE_WIDTH = 3
export const MIN_DRAG_DISTANCE = 5

// ----------------------------------------------------
// 3. Grid Management
// ----------------------------------------------------
let gridGroup = null
let currentGridSpacing = GRID_SPACING
let currentGridColor = '#d0d0d0'

/**
* Create the 2D grid based on view bounds
*/
export function createGrid(spacing = GRID_SPACING, color = '#d0d0d0') {
currentGridSpacing = spacing
currentGridColor = color

if (!gridGroup) {
gridGroup = new paper.Group()
gridGroup.name = 'grid'
} else {
gridGroup.removeChildren()
}

const viewBounds = paper.view.bounds
const left = Math.floor(viewBounds.left / spacing) * spacing
const right = Math.ceil(viewBounds.right / spacing) * spacing
const top = Math.floor(viewBounds.top / spacing) * spacing
const bottom = Math.ceil(viewBounds.bottom / spacing) * spacing

// Vertical lines
for (let x = left; x <= right; x += spacing) {
const line = new paper.Path.Line(
new paper.Point(x, top),
new paper.Point(x, bottom)
)
line.strokeColor = color
line.strokeWidth = 1
line.opacity = 0.7
gridGroup.addChild(line)
}

// Horizontal lines
for (let y = top; y <= bottom; y += spacing) {
const line = new paper.Path.Line(
new paper.Point(left, y),
new paper.Point(right, y)
)
line.strokeColor = color
line.strokeWidth = 1
line.opacity = 0.7
gridGroup.addChild(line)
}

gridGroup.sendToBack()
return gridGroup
}

/**
* Ensure grid is visible and created
*/
export function ensureGridVisible() {
if (!gridGroup) {
createGrid()
} else {
gridGroup.visible = true
}
return gridGroup
}

// Auto-refresh grid when view changes
paper.view.onFrame = function () {
if (gridGroup) {
createGrid(currentGridSpacing, currentGridColor)
}
}

// ----------------------------------------------------
// 4. Initialization (replaces floor2d.js)
// ----------------------------------------------------
export function setup2D() {
// Setup drawing handlers
setupMouseHandlers(canvas2D)

// Initialize context menu
initContextMenu(canvas2D)

// Create grid initially
createGrid()

console.log('2D environment initialized')
}

// ----------------------------------------------------
// 5. Exports
// ----------------------------------------------------
export { paper, canvas2D }
Loading