Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
f0a42d4
fix: Complete 8 bug fixes for control view, editor, macros, and file …
Q1WP Dec 3, 2025
0f9ec7d
Fix duplicate listeners
Q1WP Dec 3, 2025
555dca8
Add macro tabs system and service tracking features
Q1WP Dec 3, 2025
13fc92e
Improve service page UX with save indicators and layout refinements
Q1WP Dec 3, 2025
c2e7d16
Improve macros page layout and add delete confirmations
Q1WP Dec 3, 2025
fbff977
Refine service and macros page layouts
Q1WP Dec 3, 2025
228be40
fix(macros): Use GUID naming and auto-isolate on firmware update
Q1WP Dec 4, 2025
ef3d1af
Unify Add button placement and polish UI consistency
Q1WP Dec 4, 2025
6243458
Add system alerts for upgrades and service, hide camera when unavailable
Q1WP Dec 4, 2025
a38d06d
Rework camera view with consistent navbar and offline message
Q1WP Dec 4, 2025
7cf748e
Modernize files view with consistent navbar and improved styling
Q1WP Dec 4, 2025
7377775
Macros single row layout, remove spindle hours from service tracking
Q1WP Dec 4, 2025
5f23b5b
Modernize camera and files views with consistent UI
Q1WP Dec 4, 2025
ebd65d4
Macros single row layout, remove spindle hours from service tracking
Q1WP Dec 4, 2025
82f4d56
Modernize UI with consistent dark navbar across all views
Q1WP Dec 4, 2025
8cc0da2
Macros single row layout, remove spindle hours from service tracking
Q1WP Dec 4, 2025
98eee49
Modernize UI with consistent dark navbar, fix files breadcrumbs and d…
Q1WP Dec 4, 2025
6fb7b32
Fix files breadcrumbs and restore upload button in dialog
Q1WP Dec 4, 2025
8f09846
Fix motion timer tracking, improve macros input spacing
Q1WP Dec 4, 2025
c471a86
Fix macros hex input font size and name/file column spacing
Q1WP Dec 4, 2025
8c4915a
Remove duplicate Upload/New Folder buttons from Files view navbar
Q1WP Dec 4, 2025
f5f484b
Fix macros table column spacing and hex input font size
Q1WP Dec 4, 2025
369c8c7
Fine-tune macros table column spacing
Q1WP Dec 4, 2025
a370697
Adjust macros hex input font and name column spacing
Q1WP Dec 4, 2025
68cc571
Polish control page UI: axis table, macros, info tables, tabs
Q1WP Dec 4, 2025
d10e81e
Refine control page: MDI buttons, I/O Pins layout, Power styling
Q1WP Dec 4, 2025
258acd3
Fix axis buttons, power faults layout, I/O pins compact styling
Q1WP Dec 4, 2025
1c2e16e
Tweak macro row spacing
Q1WP Dec 4, 2025
858d4a9
Fix axis buttons, I/O pins layout, page padding, camera visibility
Q1WP Dec 4, 2025
e622ca9
Fix axis button icons, I/O pins layout, camera default
Q1WP Dec 4, 2025
a79469c
Fix I/O pins table divider centering
Q1WP Dec 4, 2025
43e69e2
Update axis button icons
Q1WP Dec 4, 2025
0aacf35
Fix I/O pins table layout and axis button icons
Q1WP Dec 4, 2025
1c5b0ea
Table style update
Q1WP Dec 4, 2025
e9e7647
Axis button improvements
Q1WP Dec 4, 2025
d2acade
Fix service alert not appearing in header
Q1WP Dec 5, 2025
ea6a4a4
Fix service alert not appearing in header
Q1WP Dec 5, 2025
888039b
Fix service alerts and remove dashboard view
Q1WP Dec 5, 2025
3b2c723
Add logout button to Service and Macros navbars
Q1WP Dec 5, 2025
5f6f3f0
Consistent navbar pattern across Settings, Service, and Macros
Q1WP Dec 5, 2025
a8d04c6
Consistent navbar pattern across Settings, Service, and Macros
Q1WP Dec 5, 2025
0e3d71c
Mobile responsive improvements
Q1WP Dec 5, 2025
da36530
Mobile responsive improvements
Q1WP Dec 5, 2025
f8cc3fd
Bug #4: File re-upload cache fix
Q1WP Dec 5, 2025
e692061
Fix spindle speed modal display and add ETA to control view
Q1WP Dec 5, 2025
d9a8807
Remove user-selectable colors from service items for cleaner UX
Q1WP Dec 5, 2025
17dadae
Power tab table improvements: transpose layout and streamline motor r…
Q1WP Dec 5, 2025
bdceb35
Responsive UI improvements: settings pages, console, WiFi dialogs, an…
Q1WP Dec 6, 2025
240cb40
editor freeze, cache invalidation, and spindle popup
Q1WP Dec 6, 2025
68fab7c
I/O settings table width and dropdown sizing
Q1WP Dec 6, 2025
18512e8
fix: spindle popup always shows RPM, disable jog during program
Q1WP Dec 6, 2025
920e7cc
feat(ui): console card layout, settings polish, editor height
Q1WP Dec 6, 2025
448163a
fix: spindle popup handles 'nan' string from backend
Q1WP Dec 6, 2025
dbd00c3
SAFETY: Display G90/G91 distance mode in info tables
Q1WP Dec 6, 2025
8ca9fdd
Fix remaining time display and editor height
Q1WP Dec 6, 2025
3a4b0cb
Fix ETA visibility
Q1WP Dec 6, 2025
c0bf6df
fix: Editor height and Messages tab card clipping
Q1WP Dec 7, 2025
0b9131c
refactor: Use CAMotics modal state for distance mode display fix: Res…
Q1WP Dec 7, 2025
e881d49
feat: broadcast distance mode from planner modal state
Q1WP Dec 7, 2025
66c0e34
fix: increase CodeMirror editor height
Q1WP Dec 7, 2025
33b8228
Fix CodeMirror height
Q1WP Dec 7, 2025
78fbe1c
Fix modal-state sequencing
Q1WP Dec 7, 2025
c203b5d
Fix CodeMirror editor height - use fixed pixel values
Q1WP Dec 7, 2025
25c7ac0
Fix file reload on re-upload and add position reference safety check
Q1WP Dec 7, 2025
0dad3d3
Fixed duplicate listeners
Q1WP Dec 7, 2025
b488e38
Fixed duplicate listeners
Q1WP Dec 7, 2025
bae1dee
Fix method name mismatch in upload handler
Q1WP Dec 7, 2025
d0f8aa3
Invalidate preplanner cache on file upload/delete
Q1WP Dec 7, 2025
84c9f54
Improve safety check error messages and fix unhome referenced flag
Q1WP Dec 7, 2025
d35b7a0
Improve safety check error messages
Q1WP Dec 7, 2025
9f28976
Improve safety check error messages and fix unhome referenced flag
Q1WP Dec 8, 2025
dba35a8
Fixed error handling.
Q1WP Dec 8, 2025
0c2a32d
Improved error handling.
Q1WP Dec 8, 2025
1506071
Improve safety check error messages and fix unhome referenced flag
Q1WP Dec 8, 2025
9e7befd
Add position reference safety check to MacroHandler
Q1WP Dec 8, 2025
693cf8b
Add position reference safety check to macros
Q1WP Dec 8, 2025
38d8769
fix: allow homing macros to bypass position reference check (#13)
Q1WP Dec 10, 2025
455e31c
feat: add dedicated Macro Safety Settings page
Q1WP Dec 10, 2025
beaf320
Move Safety settings to Macros page as dedicated tab
Q1WP Dec 14, 2025
fc151de
Remove Settings→Macros, enlarge checkboxes on Macros page
Q1WP Dec 14, 2025
9662115
fix(app.js): Vue 1.x reactivity for popupMessagesHeader
Q1WP Dec 15, 2025
86ecfa6
fix(app.js): Vue 1.x reactivity for popupMessagesHeader
Q1WP Dec 15, 2025
79074a4
fix(path-viewer.js): guard against undefined positions in draw_path
Q1WP Dec 15, 2025
888ff12
fix(view-control.pug): convert macro index fallback to String
Q1WP Dec 15, 2025
6a3f04b
fix(view-control.pug): use string coercion for macro name fallback
Q1WP Dec 15, 2025
9b7723c
fix(macros): remove duplicate confirm checkbox from macros tab
Q1WP Dec 18, 2025
ef7b471
fix(ui): restore USB drive access, fix layout on small screens, fix e…
Q1WP Dec 18, 2025
c764439
Fix dropdown hover and CodeMirror height
Q1WP Dec 18, 2025
6fd1202
Fix editor height, settings navbar, and files location UI
Q1WP Dec 18, 2025
5021d3b
Fix CodeMirror height and settings mobile issues
Q1WP Dec 18, 2025
90eb631
Fix CodeMirror height and settings mobile dots position
Q1WP Dec 18, 2025
d559a95
Fix settings mobile navbar - dots position and horizontal scroll
Q1WP Dec 18, 2025
18907c8
Fix macro isolation and modal state bugs
Q1WP Dec 23, 2025
e4e8cfd
Control page redesign: User panel, column selector, fullscreen
Q1WP Dec 23, 2025
8e48c4f
Refine control page UI: icon buttons, jog modal, layout fixes
Q1WP Dec 23, 2025
9cfc52f
UI polish: fullscreen icons, action order, jog layout, panel height
Q1WP Dec 23, 2025
c3db2d9
Fix header icon sizes, panel height, jog modal width
Q1WP Dec 23, 2025
b8b48d6
Fix error modal overflow and remove duplicate update() method
Q1WP Dec 24, 2025
b257406
UI fixes: MDI sizing, panel height, state column, messages clear
Q1WP Dec 24, 2025
0c5e49c
UI polish: sidebar cursor, MDI scroll, tab-level Clear
Q1WP Dec 24, 2025
dddf0bf
UI polish: MDI history scroll fix, Clear button styling
Q1WP Dec 24, 2025
ce3f55e
UI polish: MDI history scroll, Clear/Macros button styling
Q1WP Dec 24, 2025
c444e60
Mobile responsive fixes
Q1WP Dec 25, 2025
2919f01
Mobile responsive tweaks.
Q1WP Dec 25, 2025
3f65a92
Mobile responsive tweaks
Q1WP Dec 25, 2025
36b4f2a
fix: mobile responsive issues - overflow, jog modal, JS error
Q1WP Dec 25, 2025
df48698
Jog modal tweaks
Q1WP Dec 25, 2025
73484a1
fix: Vue 1.x reactivity bug in popupMessagesHeader
Q1WP Dec 29, 2025
1b8141b
Merge branch 'feature/macros-and-service-tracking' into testing-102
Q1WP Dec 29, 2025
9d79624
Merge branch 'feature/ui-tweaks' into testing-102
Q1WP Dec 29, 2025
0484df9
Fix, safety tweaks.
Q1WP Dec 29, 2025
9b8f832
Css tweaks
Q1WP Dec 30, 2025
124c611
Css tweaks for jog styles
Q1WP Dec 30, 2025
d223ce2
Fix, jogging bug
Q1WP Dec 30, 2025
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
16 changes: 15 additions & 1 deletion src/js/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,24 @@ class API {
error(path, xhr) {
let msg

// Try to get message from JSON response (responseType is 'json')
if (xhr.responseType == 'json' && xhr.response && xhr.response.message)
msg = xhr.response.message

else if (xhr.statusText) msg = xhr.statusText
// For empty or text responseType, try to parse responseText as JSON
else if (xhr.responseType == '' || xhr.responseType == 'text') {
if (xhr.responseText) {
try {
let response = JSON.parse(xhr.responseText)
if (response && response.message) msg = response.message
} catch (e) {
// Not valid JSON, fall through
}
}
}

// Fallback to status text
if (!msg && xhr.statusText) msg = xhr.statusText

if (!msg) msg = 'API Error: ' + path

Expand Down
186 changes: 172 additions & 14 deletions src/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,22 @@ module.exports = new Vue({
config: {
settings: {units: 'METRIC'},
motors: [{}, {}, {}, {}],
tool: {}, // FIX: Initialize tool config for reactivity
version: '<loading>'
},
state: {messages: []},
// FIX: Initialize state properties for Vue 1.x reactivity
// Properties must exist before Vue processes the component
state: {
messages: [],
service_due_count: 0,
s: 0, // Spindle speed - needed for popupMessagesHeader reactivity
xx: '', // Machine state
line: 0, // Current line
v: 0, // Velocity
feed: 0, // Feed rate
speed: 0, // Programmed speed
tool: 0 // Current tool
},
crosshair: cookie.get_bool('crosshair', false),
selected_program: new Program(this.$api, cookie.get('selected-path')),
active_program: undefined,
Expand All @@ -56,7 +69,14 @@ module.exports = new Vue({
errorMessage: '',
checkedUpgrade: false,
latestVersion: '',
webGLSupported: util.webgl_supported()
webGLSupported: util.webgl_supported(),
// Track if we've initialized after connect
initialized: false,
// Alert dismissal state (session-based, resets on browser close)
upgrade_dismissed: sessionStorage.getItem('upgrade_dismissed') === 'true',
service_dismissed: sessionStorage.getItem('service_dismissed') === 'true',
// Fullscreen state
is_fullscreen: false
}
},

Expand All @@ -69,6 +89,8 @@ module.exports = new Vue({
'view-editor': require('./view-editor'),
'view-settings': require('./view-settings'),
'view-files': require('./view-files'),
'view-macros': require('./view-macros'),
'view-service': require('./view-service'),
'view-camera': {template: '#view-camera-template'},
'view-docs': require('./view-docs')
},
Expand All @@ -78,15 +100,50 @@ module.exports = new Vue({
crosshair() {cookie.set_bool('crosshair', this.crosshair)},


'state.active_program'() {
let path = this.state.active_program
if (!path || path == '<mdi>') this.active_program = undefined
else new Program(this.$api, path)
// Watch for active_program changes from server
// Only update the active_program object - don't clear selected_program here
// (selected_program should persist through macro runs)
'state.active_program'(path) {
if (!path || path == '<mdi>') {
this.active_program = undefined
} else {
this.active_program = new Program(this.$api, path)
}
},


// Watch for e-stop state to clear programs
'state.xx'(state) {
if (state == 'ESTOPPED') {
// Clear programs on e-stop for safety
this.clear_selected_program()
}
},


'state.first_file'(value) {
if (!this.selected_program.path) this.select_path(value)
// Only auto-select first file if we have no selection
if (!this.selected_program.path && value) {
this.select_path(value)
}
},


// Reset upgrade dismissal when version changes
latestVersion(newVersion, oldVersion) {
if (oldVersion && newVersion !== oldVersion) {
this.upgrade_dismissed = false
sessionStorage.removeItem('upgrade_dismissed')
}
},


// Reset service dismissal when due count changes (new items become due)
'state.service_due_count'(newCount, oldCount) {
if (oldCount !== undefined && newCount > oldCount) {
this.service_dismissed = false
sessionStorage.removeItem('service_dismissed')
}
}
},

Expand Down Expand Up @@ -116,12 +173,16 @@ module.exports = new Vue({

async connected() {
await this.update()
this.parse_hash()
},


async update() {
await this.update()

// On initial connection, check if server has no active program
// and clear our cached selection to sync with server state
if (!this.initialized) {
this.initialized = true
if (!this.state.active_program) {
this.clear_selected_program()
}
}

this.parse_hash()
},

Expand Down Expand Up @@ -160,9 +221,46 @@ module.exports = new Vue({
},


// Dynamic header for GCode messages modal showing spindle speed
// Shows real-time spindle speed during M0 pause when tool is configured
popupMessagesHeader() {
let header = 'GCode Messages'

// FIX: Read state.s BEFORE conditional to ensure Vue 1.x dependency tracking
// If state.s is only read inside the conditional, Vue won't re-evaluate
// the computed property when state.s changes (if condition was initially false)
let speed = parseFloat(this.state.s)

// Show spindle speed when tool is configured (not Disabled)
let toolType = this.config.tool && this.config.tool['tool-type']
if (toolType && toolType !== 'Disabled' && !isNaN(speed)) {
header += ' - Spindle: ' + Math.round(speed) + ' RPM'
}

return header
},


show_upgrade() {
if (!this.latestVersion) return false
return util.compare_versions(this.config.version, this.latestVersion) < 0
},


show_upgrade_alert() {
return this.show_upgrade && !this.upgrade_dismissed
},


show_service_alert() {
let count = this.state.service_due_count || 0
return count > 0 && !this.service_dismissed
},


camera_available() {
// Only show camera if backend explicitly says it's available
return this.state.camera_available === true
}
},

Expand Down Expand Up @@ -193,10 +291,27 @@ module.exports = new Vue({
}

this.check_login()

// Listen for fullscreen changes (e.g., user presses Escape)
document.addEventListener('fullscreenchange', () => {
this.is_fullscreen = !!document.fullscreenElement
})
},


methods: {
dismiss_upgrade() {
this.upgrade_dismissed = true
sessionStorage.setItem('upgrade_dismissed', 'true')
},


dismiss_service() {
this.service_dismissed = true
sessionStorage.setItem('service_dismissed', 'true')
},


async check_login() {
this.authorized = await this.$api.get('auth/login')
},
Expand Down Expand Up @@ -370,14 +485,40 @@ module.exports = new Vue({
},


select_path(path) {
// Clear selected program and cookie
clear_selected_program() {
cookie.set('selected-path', '')
this.selected_program = new Program(this.$api, '')
// Broadcast that program was cleared so views can update
this.$broadcast('program-cleared')
},


// Handle re-selecting same path to ensure fresh file content
select_path(path, force) {
if (path && this.selected_program.path != path) {
cookie.set('selected-path', path)
this.selected_program = new Program(this.$api, path)
} else if (path && force) {
// Same path but force refresh - invalidate cached data
this.selected_program.invalidate()
}

return this.selected_program
},


// Reload current program - creates new instance to force fresh fetch
// Used after file upload to ensure new content is displayed
reload_selected_program() {
if (this.selected_program && this.selected_program.path) {
let path = this.selected_program.path
// Create new Program instance - guarantees fresh data fetch
this.selected_program = new Program(this.$api, path)
// Broadcast to trigger reload in view-control
this.$broadcast('program-reloaded')
}
},


edit(path) {
Expand All @@ -389,6 +530,23 @@ module.exports = new Vue({
view(path) {
this.select_path(path)
location.hash = 'viewer'
},


toggle_fullscreen() {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().then(() => {
this.is_fullscreen = true
}).catch(err => {
console.warn('Fullscreen request failed:', err)
})
} else {
document.exitFullscreen().then(() => {
this.is_fullscreen = false
}).catch(err => {
console.warn('Exit fullscreen failed:', err)
})
}
}
}
})
8 changes: 7 additions & 1 deletion src/js/axis-control.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,23 @@

module.exports = {
template: '#axis-control-template',
props: ['axes', 'colors', 'enabled', 'adjust', 'step'],
props: ['axes', 'colors', 'enabled', 'adjust', 'step', 'disabled'],


methods: {
jog(axis, ring, direction) {
// Prevent jogging when disabled (e.g., program is running)
if (this.disabled) return

let value = direction * this.value(ring)
this.$dispatch(this.step ? 'step' : 'jog', this.axes[axis], value)
},


release(axis) {
// NOTE: Do NOT check this.disabled here!
// When jog starts, machine state changes to JOGGING, which sets disabled=true.
// If we check disabled here, the stop command (jog 0) never gets sent.
if (!this.step) this.$dispatch('jog', this.axes[axis], 0)
},

Expand Down
2 changes: 1 addition & 1 deletion src/js/axis-row.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
module.exports = {
template: '#axis-row-template',
replace: true,
props: ['axis'],
props: ['axis', 'showOffset', 'showAbsolute', 'showState'],


data() {
Expand Down
19 changes: 17 additions & 2 deletions src/js/axis-vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,16 +111,22 @@ module.exports = {
let enabled = typeof motor.enabled != 'undefined' && motor.enabled
let homingMode = motor['homing-mode']
let homed = this.state[motor_id + 'homed']
let referenced = this.state[motor_id + 'referenced']
let min = this.state[motor_id + 'tn']
let max = this.state[motor_id + 'tm']
let dim = max - min
let bounds = this.toolpath.bounds
let pathMin = bounds ? bounds.min[axis] : 0
let pathMax = bounds ? bounds.max[axis] : 0
let pathDim = pathMax - pathMin
let klass = (homed ? 'homed' : 'unhomed') + ' axis-' + axis
let klass = 'axis-' + axis
let state = 'UNHOMED'
let icon = 'question-circle'

// Determine base state class
if (homed) klass += ' homed'
else if (referenced) klass += ' warn'
else klass += ' unhomed'
let fault = this.state[motor_id + 'df'] & 0x1f
let shutdown = this.state.power_shutdown
let title
Expand All @@ -138,11 +144,19 @@ module.exports = {
} else if (homed) {
state = 'HOMED'
icon = 'check-circle'

} else if (referenced) {
state = 'ZEROED'
icon = 'exclamation-triangle'
}

switch (state) {
case 'UNHOMED': title = 'Click the home button to home axis.'; break
case 'HOMED': title = 'Axis successfuly homed.'; break
case 'HOMED': title = 'Axis successfully homed.'; break
case 'ZEROED':
title = 'Position set manually. Soft limits not active - ' +
'verify program stays within machine bounds.'
break

case 'NO FIT':
title = 'Tool path dimensions exceed axis dimensions by ' +
Expand Down Expand Up @@ -178,6 +192,7 @@ module.exports = {
enabled: enabled,
homingMode: homingMode,
homed: homed,
referenced: referenced,
klass: klass,
state: state,
icon: icon,
Expand Down
Loading