Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
5c5580d
Added to gate context menu for complete change-tool operation based o…
moggieuk Feb 7, 2026
c26c248
Better icon selection for toolchange
moggieuk Feb 8, 2026
dce738d
Added initial filament editor to context menu
moggieuk Feb 11, 2026
82c6fee
Implementation of regular click context menu and change to default se…
moggieuk Feb 11, 2026
90bac33
ease merge problems
moggieuk Feb 11, 2026
0e3a581
changed default for mmu-unit to show context menu
moggieuk Feb 11, 2026
9d1f425
changed default for mmu-unit to show context menu
moggieuk Feb 11, 2026
c15a85c
fixed erroneous (legal) parameter to selectGate(); improve context se…
moggieuk Feb 12, 2026
46ccfec
make cursor reflect the action on a spool - selection or menu
moggieuk Feb 13, 2026
f994b8c
Merge branch 'develop' into fork/moggieuk/changetool_menu2
meteyou Jun 18, 2026
e4a72bb
fix(mmu): add can_crossload property to MmuMachineUnit interface
meteyou Jun 18, 2026
844f429
fix(mmu): convert labels to strings in MmuUnitGate context menu
meteyou Jun 18, 2026
cfd780f
fix(mmu): remove unused select-spool event handler in MmuUnitGate com…
meteyou Jun 18, 2026
b874ca0
fix(mmu): fix condition getter name
meteyou Jun 18, 2026
78a6b54
fix(mmu): remove isHover class from svg element in MmuUnitGateSpool c…
meteyou Jun 18, 2026
1c419ce
refactor(mmu): add shared MmuUnitGateContextMenuItem type to mmu mixin
meteyou Jun 20, 2026
2a6b3ea
refactor(mmu): extract gate context menu into MmuUnitGateMenu and Mmu…
meteyou Jun 20, 2026
7f41be2
refactor(mmu): use mdi icon imports directly in MmuUnitGateMenu
meteyou Jun 20, 2026
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
18 changes: 17 additions & 1 deletion src/components/dialogs/MmuEditGateMapDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
:selected-gate="selectedGate"
:unit-index="i - 1"
:hide-bypass="true"
:show-context-menu="false"
:unhighlight-spools="true"
@select-gate="selectGate" />
</v-col>
Expand Down Expand Up @@ -56,7 +57,7 @@

<script lang="ts">
import Component from 'vue-class-component'
import { Mixins, VModel } from 'vue-property-decorator'
import { Mixins, VModel, Prop, Watch } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base'
import MmuMixin, { TOOL_GATE_UNKNOWN } from '@/components/mixins/mmu'
import ConfirmationDialog from '@/components/dialogs/ConfirmationDialog.vue'
Expand All @@ -73,9 +74,23 @@ export default class MmuEditGateMapDialog extends Mixins(BaseMixin, MmuMixin) {

@VModel({ type: Boolean }) showDialog!: boolean

@Prop({ required: false, default: null })
readonly initialGate!: number | null

showResetConfirmationDialog = false
selectedGate = TOOL_GATE_UNKNOWN

@Watch('showDialog')
onShowDialogChanged(val: boolean) {
if (!val) return

if (this.initialGate !== null && this.initialGate !== undefined) {
this.selectedGate = this.initialGate
} else {
this.selectedGate = TOOL_GATE_UNKNOWN
}
}

selectGate(gate: number) {
this.selectedGate = gate
}
Expand All @@ -99,6 +114,7 @@ export default class MmuEditGateMapDialog extends Mixins(BaseMixin, MmuMixin) {
}

close() {
this.$emit('close')
this.showDialog = false
}
}
Expand Down
9 changes: 7 additions & 2 deletions src/components/panels/Mmu/MmuUnit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
:unhighlight-spools="unhighlightSpools"
:selected-gate="selectedGate"
:has-bypass="hasBypass"
@edit-filament="editFilament"
@select-gate="selectGate" />
<mmu-unit-gate
v-if="hasBypass"
:gate-index="TOOL_GATE_BYPASS"
:mmu-machine-unit="mmuMachineUnit"
:show-context-menu="false"
:show-context-menu="showContextMenu"
:selected-gate="selectedGate"
@select-gate="selectGate" />
</div>
Expand All @@ -35,7 +36,7 @@ export default class MmuUnit extends Mixins(BaseMixin, MmuMixin) {
@Prop({ required: true }) readonly selectedGate!: number
@Prop({ required: true }) readonly unitIndex!: number
@Prop({ default: false }) readonly showDetails!: boolean
@Prop({ default: false }) readonly showContextMenu!: boolean
@Prop({ default: true }) readonly showContextMenu!: boolean
@Prop({ default: false }) readonly hideBypass!: boolean
@Prop({ default: false }) readonly unhighlightSpools!: boolean

Expand All @@ -57,6 +58,10 @@ export default class MmuUnit extends Mixins(BaseMixin, MmuMixin) {
return this.mmuMachineUnit?.has_bypass ?? true
}

editFilament(gateIndex: number) {
this.$emit('edit-filament', gateIndex)
}

selectGate(gateIndex: number) {
this.$emit('select-gate', gateIndex)
}
Expand Down
163 changes: 132 additions & 31 deletions src/components/panels/Mmu/MmuUnitGate.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
<template>
<div class="d-flex flex-column align-center">
<div
v-longpress:500="openContextMenu"
class="d-flex flex-wrap mb-n2 pt-1 position-relative"
@contextmenu.prevent="openContextMenu($event)">
<div class="d-flex flex-column align-center" :class="cursorType" @click="handleClickGate" @contextmenu.prevent>
<div class="d-flex flex-wrap mb-n2 pt-1 position-relative">
<mmu-unit-gate-spool
class="position-relative zindex-1"
:gate-index="gateIndex"
:show-details="showDetails"
:is-selected="isSelected"
:unhighlight-spools="unhighlightSpools"
@select-gate="selectGate" />
@select-spool="() => {}" />
</div>

<div class="mmu-unit-box d-flex zindex-3 pb-1 pt-2 position-relative" :class="gateClass">
<div class="d-flex w-100 gate-contents">
<span class="gate-number rounded cursor-pointer" :class="gateNumberClass" @click="selectGate">
<span class="gate-number rounded" :class="gateNumberClass">
{{ gateName }}
</span>
</div>
</div>

<v-menu
v-model="contextMenu"
transition="slide-y-transition"
Expand All @@ -29,18 +28,18 @@
offset-y>
<v-list dense @mouseleave="closeContextMenu">
<v-subheader class="d-block text-subtitle-2 text-center mb-0 h-auto pb-2">
{{ $t('Panels.MmuPanel.Gate') }} {{ gateIndex }}
{{ contextMenuHeader }}
</v-subheader>
<v-divider class="mb-2" />
<v-list-item v-for="(button, index) in contextMenuButtons" :key="index">
<v-list-item v-for="(item, index) in contextMenuItems" :key="index">
<v-btn
small
class="w-100"
:disabled="!canSend"
:loading="loadings.includes(button.command.toLowerCase())"
@click="gateCommand(button.command)">
<v-icon left>{{ button.icon }}</v-icon>
{{ button.label }}
:disabled="isItemDisabled(item)"
:loading="loadings.includes(item.loading)"
@click="runMenuItem(item)">
<v-icon left>{{ item.icon }}</v-icon>
{{ item.label }}
</v-btn>
</v-list-item>
</v-list>
Expand All @@ -52,14 +51,28 @@
import { Component, Mixins, Prop } from 'vue-property-decorator'
import type { LongpressEvent } from '@/directives/longpress'
import BaseMixin from '@/components/mixins/base'
import MmuMixin, { MmuMachineUnit, TOOL_GATE_BYPASS } from '@/components/mixins/mmu'
import { mdiSwapHorizontal, mdiDownloadOutline, mdiEject } from '@mdi/js'
import MmuMixin, { MmuMachineUnit, TOOL_GATE_BYPASS, FILAMENT_POS_LOADED } from '@/components/mixins/mmu'
import { mdiSwapHorizontal, mdiDownloadOutline, mdiEject, mdiAxisArrow, mdiDatabaseEdit } from '@mdi/js'

type MenuDisabled = boolean | ((gate: number) => boolean)

type MenuAction = { kind: 'gcode'; command: string } | { kind: 'call'; fn: (gate: number) => void }

type ContextMenuItem = {
icon: string
label: string
loading: string
disabled?: MenuDisabled
action: MenuAction
}

@Component
export default class MmuUnitGate extends Mixins(BaseMixin, MmuMixin) {
mdiSwapHorizontal = mdiSwapHorizontal
mdiDownloadOutline = mdiDownloadOutline
mdiEject = mdiEject
mdiAxisArrow = mdiAxisArrow
mdiDatabaseEdit = mdiDatabaseEdit

@Prop({ required: true }) readonly gateIndex!: number
@Prop({ required: true }) readonly mmuMachineUnit!: MmuMachineUnit
Expand All @@ -74,6 +87,11 @@ export default class MmuUnitGate extends Mixins(BaseMixin, MmuMixin) {
menuX = 0
menuY = 0

get cursorType() {
if (this.showContextMenu) return 'gate-menu'
return 'gate-selection'
}

get gateName() {
if (this.gateIndex === TOOL_GATE_BYPASS) return 'Bypass'
return this.gateIndex
Expand All @@ -96,17 +114,98 @@ export default class MmuUnitGate extends Mixins(BaseMixin, MmuMixin) {
return this.selectedGate === this.gateIndex
}

get contextMenuButtons() {
return [
{ icon: this.mdiSwapHorizontal, command: 'MMU_SELECT', label: this.$t('Panels.MmuPanel.ButtonSelect') },
{ icon: this.mdiDownloadOutline, command: 'MMU_PRELOAD', label: this.$t('Panels.MmuPanel.ButtonPreload') },
{ icon: this.mdiEject, command: 'MMU_EJECT', label: this.$t('Panels.MmuPanel.ButtonEject') },
private isItemDisabled(item: ContextMenuItem): boolean {
if (!item.disabled) return false
return typeof item.disabled === 'function' ? item.disabled(this.gateIndex) : item.disabled
}

get contextMenuHeader() {
if (this.gateIndex >= 0) return this.$t('Panels.MmuPanel.Gate') + ' ' + this.gateIndex
return this.gateName
}

private runMenuItem(item: ContextMenuItem) {
if (this.isItemDisabled(item)) return

this.closeContextMenu()

if (item.action.kind === 'gcode') {
if (!this.canSend) return
this.doSend(`${item.action.command} GATE=${this.gateIndex}`, item.loading)
} else {
item.action.fn(this.gateIndex)
}
}

get canCrossload() {
return this.mmuMachineUnit?.can_crossload ?? false
}

get isLoaded() {
return this.mmuFilamentPos === FILAMENT_POS_LOADED
}

get isSelectedGate() {
return this.gateIndex === this.selectedGate
}

get contextMenuItems(): ContextMenuItem[] {
const items: ContextMenuItem[] = [
{
icon: this.mdiSwapHorizontal,
label: this.$t('Panels.MmuPanel.ButtonSelect'),
loading: '',
action: { kind: 'call', fn: () => this.selectGate() },
disabled: () => !this.canSend || this.isSelectedGate || this.isPrinting || this.isLoaded,
},
{
icon: this.mdiDatabaseEdit,
label: this.$t('Panels.MmuPanel.EditGateMap'),
loading: '',
action: { kind: 'call', fn: () => this.editFilament() },
disabled: () => false,
},
{
icon: this.mdiDownloadOutline,
label: this.$t('Panels.MmuPanel.ButtonPreload'),
loading: 'mmu_preload',
action: { kind: 'gcode', command: 'MMU_PRELOAD' },
disabled: () =>
!this.canSend ||
(!this.isSelectedGate && !this.canCrossload) ||
(this.isSelectedGate && this.isLoaded),
},
{
icon: this.mdiEject,
label: this.$t('Panels.MmuPanel.ButtonEject'),
loading: 'mmu_eject',
action: { kind: 'gcode', command: 'MMU_EJECT' },
disabled: () => !this.canSend || (this.gateIndex !== this.selectedGate && !this.canCrossload),
},
{
icon: this.mdiAxisArrow,
label: this.$t('Panels.MmuPanel.ButtonChangeTool'),
loading: 'mmu_change_tool',
action: { kind: 'gcode', command: 'MMU_CHANGE_TOOL' },
disabled: () => !this.canSend || this.isSelectedGate || this.isPrinting,
},
]

if (this.gateIndex < 0) return items.slice(0, 1)

return items
}

private editFilament() {
this.$emit('edit-filament', this.gateIndex)
}

private selectGate() {
this.$emit('select-gate', this.gateIndex)
}

get gatePosition() {
const firstGateNumber = this.mmuMachineUnit?.first_gate ?? 0

return this.gateIndex + 1 - firstGateNumber
}

Expand All @@ -127,15 +226,14 @@ export default class MmuUnitGate extends Mixins(BaseMixin, MmuMixin) {
}
}

selectGate() {
this.$emit('select-gate', this.gateIndex)
handleClickGate(e: MouseEvent) {
if (this.showContextMenu) return this.openContextMenu(e)
this.selectGate()
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

openContextMenu(e: MouseEvent | LongpressEvent) {
e.preventDefault()

if (this.gateIndex < 0 || this.gateIndex === this.selectedGate || !this.showContextMenu) return

this.menuX = e.clientX - 20
this.menuY = e.clientY - 20

Expand All @@ -154,7 +252,6 @@ export default class MmuUnitGate extends Mixins(BaseMixin, MmuMixin) {

clearCloseTimeout() {
if (this.closeTimeout === null) return

clearTimeout(this.closeTimeout)
this.closeTimeout = null
}
Expand All @@ -167,10 +264,6 @@ export default class MmuUnitGate extends Mixins(BaseMixin, MmuMixin) {
removeEventListener('mmu-close-gate-context-menus', this.closeContextMenu)
this.clearCloseTimeout()
}

gateCommand(command: string) {
this.doSend(`${command} GATE=${this.gateIndex}`, command.toLowerCase())
}
}
</script>

Expand Down Expand Up @@ -258,4 +351,12 @@ html.theme--light .mmu-unit-box {
width: calc(100% + 32px);
margin-right: -16px;
}

.gate-selection {
cursor: pointer;
}

.gate-menu {
cursor: context-menu;
}
</style>
16 changes: 3 additions & 13 deletions src/components/panels/Mmu/MmuUnitGateSpool.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
:width="spoolWidth"
v-bind="attrs"
:class="svgClasses"
v-on="on"
@click="selectGate">
v-on="on">
<defs>
<path
id="oval"
Expand Down Expand Up @@ -271,27 +270,18 @@ export default class MmuUnitGateSpool extends Mixins(BaseMixin, MmuMixin) {

get svgClasses() {
const classes = [this.svgClass]
if (this.hasSelectGateListener) classes.push('hasSelectGate')
classes.push('isHover')
if (this.isSelected) classes.push('isSelected')
if (!this.isSelected && this.unhighlightSpools) classes.push('unhighlighted')

return classes
}

get hasSelectGateListener() {
return !!this.$listeners['select-gate']
}

selectGate() {
this.$emit('select-gate')
}
}
</script>

<style scoped>
svg {
outline: none;
cursor: pointer;
transition:
transform 0.2s,
opacity 0.2s;
Expand All @@ -306,7 +296,7 @@ svg.isSelected {
opacity: 1 !important;
}

svg.hasSelectGate:hover {
svg.isHover:hover {
transform: translateY(-4px);
}

Expand Down
Loading
Loading