feat: add spoolman support (#1542)
This commit is contained in:
parent
5734f1cd1d
commit
d8430f54ed
@ -277,6 +277,7 @@ export default class App extends Mixins(BaseMixin) {
|
||||
@Watch('print_percent')
|
||||
print_percentChanged(newVal: number): void {
|
||||
this.drawFavicon(newVal)
|
||||
this.refreshSpoolman()
|
||||
}
|
||||
|
||||
@Watch('printerIsPrinting')
|
||||
@ -284,6 +285,12 @@ export default class App extends Mixins(BaseMixin) {
|
||||
this.drawFavicon(this.print_percent)
|
||||
}
|
||||
|
||||
refreshSpoolman(): void {
|
||||
if (this.moonrakerComponents.includes('spoolman')) {
|
||||
this.$store.dispatch('server/spoolman/refreshActiveSpool', null, { root: true })
|
||||
}
|
||||
}
|
||||
|
||||
appHeight() {
|
||||
this.$nextTick(() => {
|
||||
const doc = document.documentElement
|
||||
|
174
src/components/dialogs/SpoolmanChangeSpoolDialog.vue
Normal file
174
src/components/dialogs/SpoolmanChangeSpoolDialog.vue
Normal file
@ -0,0 +1,174 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-dialog v-model="showDialog" width="800" persistent :fullscreen="isMobile">
|
||||
<panel
|
||||
:title="$t('Panels.SpoolmanPanel.ChangeSpool')"
|
||||
:icon="mdiAdjust"
|
||||
card-class="spoolman-change-spool-dialog"
|
||||
:margin-bottom="false">
|
||||
<template #buttons>
|
||||
<v-btn icon tile @click="close">
|
||||
<v-icon>{{ mdiCloseThick }}</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card-title>
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
:append-icon="mdiMagnify"
|
||||
:label="$t('Panels.SpoolmanPanel.Search')"
|
||||
outlined
|
||||
dense
|
||||
hide-details
|
||||
style="max-width: 300px" />
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
:title="$t('Panels.SpoolmanPanel.Refresh')"
|
||||
class="px-2 minwidth-0 ml-3"
|
||||
:loading="loadings.includes('refreshSpools')"
|
||||
@click="refreshSpools">
|
||||
<v-icon>{{ mdiRefresh }}</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:title="$t('Panels.SpoolmanPanel.OpenSpoolManager')"
|
||||
class="px-2 minwidth-0 ml-3"
|
||||
@click="openSpoolManager">
|
||||
<v-icon>{{ mdiDatabase }}</v-icon>
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text class="px-0 pb-0">
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="spools"
|
||||
item-key="id"
|
||||
:search="search"
|
||||
sort-by="last_used"
|
||||
:sort-desc="true"
|
||||
:custom-filter="customFilter">
|
||||
<template #no-data>
|
||||
<div class="text-center">{{ $t('Panels.SpoolmanPanel.NoSpools') }}</div>
|
||||
</template>
|
||||
<template #no-results>
|
||||
<div class="text-center">{{ $t('Panels.SpoolmanPanel.NoResults') }}</div>
|
||||
</template>
|
||||
|
||||
<template #item="{ item }">
|
||||
<SpoolmanChangeSpoolDialogRow :key="item.id" :spool="item" @set-spool="setSpool" />
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
</panel>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Component from 'vue-class-component'
|
||||
import { Mixins, Prop, Watch } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import Panel from '@/components/ui/Panel.vue'
|
||||
import { mdiCloseThick, mdiAdjust, mdiDatabase, mdiMagnify, mdiRefresh } from '@mdi/js'
|
||||
import { ServerSpoolmanStateSpool } from '@/store/server/spoolman/types'
|
||||
import SpoolmanChangeSpoolDialogRow from '@/components/dialogs/SpoolmanChangeSpoolDialogRow.vue'
|
||||
@Component({
|
||||
components: { SpoolmanChangeSpoolDialogRow, Panel },
|
||||
})
|
||||
export default class SpoolmanChangeSpoolDialog extends Mixins(BaseMixin) {
|
||||
mdiAdjust = mdiAdjust
|
||||
mdiCloseThick = mdiCloseThick
|
||||
mdiDatabase = mdiDatabase
|
||||
mdiMagnify = mdiMagnify
|
||||
mdiRefresh = mdiRefresh
|
||||
|
||||
@Prop({ required: true }) declare readonly showDialog: boolean
|
||||
|
||||
search = ''
|
||||
|
||||
get spools(): ServerSpoolmanStateSpool[] {
|
||||
return this.$store.state.server.spoolman.spools ?? []
|
||||
}
|
||||
|
||||
get headers() {
|
||||
return [
|
||||
{
|
||||
text: ' ',
|
||||
align: 'start',
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
text: this.$t('Panels.SpoolmanPanel.Filament'),
|
||||
align: 'start',
|
||||
value: 'filament.name',
|
||||
sortable: false,
|
||||
},
|
||||
{
|
||||
text: this.$t('Panels.SpoolmanPanel.Material'),
|
||||
align: 'center',
|
||||
value: 'filament.material',
|
||||
},
|
||||
{
|
||||
text: this.$t('Panels.SpoolmanPanel.LastUsed'),
|
||||
align: 'end',
|
||||
value: 'last_used',
|
||||
},
|
||||
{
|
||||
text: this.$t('Panels.SpoolmanPanel.Weight'),
|
||||
align: 'end',
|
||||
value: 'remaining_weight',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
get spoolManagerUrl() {
|
||||
return this.$store.state.server.config.config?.spoolman?.server ?? null
|
||||
}
|
||||
|
||||
openSpoolManager() {
|
||||
window.open(this.spoolManagerUrl, '_blank')
|
||||
}
|
||||
|
||||
mounted() {
|
||||
this.refresh()
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.$store.dispatch('server/spoolman/refreshSpools')
|
||||
}
|
||||
|
||||
close() {
|
||||
this.$emit('close')
|
||||
}
|
||||
|
||||
refreshSpools() {
|
||||
this.$store.dispatch('server/spoolman/refreshSpools')
|
||||
}
|
||||
|
||||
customFilter(value: any, search: string, item: ServerSpoolmanStateSpool): boolean {
|
||||
const querySplits = search.toLowerCase().split(' ')
|
||||
const searchArray = [
|
||||
item.comment,
|
||||
item.filament.name,
|
||||
item.filament.vendor.name,
|
||||
item.filament.material,
|
||||
item.location,
|
||||
]
|
||||
|
||||
for (const query of querySplits) {
|
||||
const result = searchArray.some((q) => q?.toLowerCase().includes(query))
|
||||
|
||||
if (!result) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
setSpool(spool: ServerSpoolmanStateSpool) {
|
||||
this.$store.dispatch('server/spoolman/setActiveSpool', spool.id)
|
||||
this.close()
|
||||
}
|
||||
|
||||
@Watch('showDialog')
|
||||
onShowDialogChanged(newVal: boolean) {
|
||||
if (newVal) this.search = ''
|
||||
}
|
||||
}
|
||||
</script>
|
104
src/components/dialogs/SpoolmanChangeSpoolDialogRow.vue
Normal file
104
src/components/dialogs/SpoolmanChangeSpoolDialogRow.vue
Normal file
@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<tr class="cursor-pointer" @click="setSpoolRow">
|
||||
<td style="width: 50px" class="pr-0 py-2">
|
||||
<spool-icon :color="color" style="width: 50px; float: left" class="mr-3" />
|
||||
</td>
|
||||
<td class="py-2" style="min-width: 300px">
|
||||
<strong class="text-no-wrap">{{ vendor }} - {{ name }}</strong>
|
||||
<template v-if="location">
|
||||
<br />
|
||||
{{ $t('Panels.SpoolmanPanel.Location') }}: {{ location }}
|
||||
</template>
|
||||
<template v-if="spool.comment">
|
||||
<br />
|
||||
{{ spool.comment }}
|
||||
</template>
|
||||
</td>
|
||||
<td class="text-center text-no-wrap">{{ material }}</td>
|
||||
<td class="text-right text-no-wrap">{{ last_used }}</td>
|
||||
<td class="text-right text-no-wrap">
|
||||
<strong>{{ remaining_weight_format }}</strong>
|
||||
<small class="ml-1">/ {{ total_weight_format }}</small>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Component from 'vue-class-component'
|
||||
import { Mixins, Prop } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import { ServerSpoolmanStateSpool } from '@/store/server/spoolman/types'
|
||||
@Component({})
|
||||
export default class SpoolmanChangeSpoolDialogRow extends Mixins(BaseMixin) {
|
||||
@Prop({ required: true }) declare readonly spool: ServerSpoolmanStateSpool
|
||||
get color() {
|
||||
const color = this.spool.filament?.color_hex ?? '000'
|
||||
|
||||
return `#${color}`
|
||||
}
|
||||
|
||||
get vendor() {
|
||||
return this.spool.filament?.vendor?.name ?? 'Unknown'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.spool.filament?.name ?? 'Unknown'
|
||||
}
|
||||
|
||||
get location() {
|
||||
return this.spool.location
|
||||
}
|
||||
|
||||
get material() {
|
||||
return this.spool.filament?.material ?? '--'
|
||||
}
|
||||
|
||||
get remaining_weight() {
|
||||
return this.spool.remaining_weight ?? 0
|
||||
}
|
||||
|
||||
get total_weight() {
|
||||
return this.spool.filament?.weight ?? 0
|
||||
}
|
||||
|
||||
get remaining_weight_format() {
|
||||
return `${this.remaining_weight.toFixed(0)}g`
|
||||
}
|
||||
|
||||
get total_weight_format() {
|
||||
if (this.total_weight < 1000) {
|
||||
return `${this.total_weight.toFixed(0)}g`
|
||||
}
|
||||
|
||||
let totalRound = Math.round(this.total_weight / 1000)
|
||||
if (totalRound !== this.total_weight / 1000) {
|
||||
totalRound = Math.round(this.total_weight / 100) / 10
|
||||
}
|
||||
|
||||
return `${totalRound}kg`
|
||||
}
|
||||
|
||||
get last_used() {
|
||||
const last_used = this.spool.last_used ?? null
|
||||
if (!last_used) return this.$t('Panels.SpoolmanPanel.Never')
|
||||
|
||||
const date = new Date(this.spool.last_used)
|
||||
const now = new Date()
|
||||
const diff = now.getTime() - date.getTime()
|
||||
|
||||
if (diff <= 1000 * 60 * 60 * 24) return this.$t('Panels.SpoolmanPanel.Today')
|
||||
if (diff <= 1000 * 60 * 60 * 24 * 2) return this.$t('Panels.SpoolmanPanel.Yesterday')
|
||||
if (diff <= 1000 * 60 * 60 * 24 * 14) {
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||
|
||||
return this.$t('Panels.SpoolmanPanel.DaysAgo', { days })
|
||||
}
|
||||
|
||||
return date.toLocaleDateString()
|
||||
}
|
||||
|
||||
setSpoolRow() {
|
||||
this.$emit('set-spool', this.spool)
|
||||
}
|
||||
}
|
||||
</script>
|
55
src/components/dialogs/SpoolmanEjectSpoolDialog.vue
Normal file
55
src/components/dialogs/SpoolmanEjectSpoolDialog.vue
Normal file
@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<v-dialog v-model="showDialog" width="400" persistent :fullscreen="isMobile">
|
||||
<panel
|
||||
:title="$t('Panels.SpoolmanPanel.EjectSpool')"
|
||||
:icon="mdiEject"
|
||||
card-class="spoolman-eject-spool-dialog"
|
||||
:margin-bottom="false">
|
||||
<template #buttons>
|
||||
<v-btn icon tile @click="close">
|
||||
<v-icon>{{ mdiCloseThick }}</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<p class="body-2">{{ $t('Panels.SpoolmanPanel.EjectSpoolQuestion') }}</p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn text @click="close">{{ $t('Panels.SpoolmanPanel.Cancel') }}</v-btn>
|
||||
<v-btn color="primary" text @click="removeSpool">
|
||||
{{ $t('Panels.SpoolmanPanel.EjectSpool') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</panel>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Component from 'vue-class-component'
|
||||
import { Mixins, Prop } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import Panel from '@/components/ui/Panel.vue'
|
||||
import { mdiCloseThick, mdiEject } from '@mdi/js'
|
||||
@Component({
|
||||
components: { Panel },
|
||||
})
|
||||
export default class SpoolmanEjectSpoolDialog extends Mixins(BaseMixin) {
|
||||
mdiEject = mdiEject
|
||||
mdiCloseThick = mdiCloseThick
|
||||
|
||||
@Prop({ required: true }) declare readonly showDialog: boolean
|
||||
|
||||
close() {
|
||||
this.$emit('close')
|
||||
}
|
||||
|
||||
removeSpool() {
|
||||
this.$store.dispatch('server/spoolman/setActiveSpool', 0)
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,23 +1,28 @@
|
||||
<template>
|
||||
<v-dialog v-model="bool" :max-width="dialogWidth">
|
||||
<v-dialog v-model="bool" :max-width="dialogWidth" @click:outside="closeDialog" @keydown.esc="closeDialog">
|
||||
<v-card>
|
||||
<v-img v-if="file.big_thumbnail" contain :src="file.big_thumbnail"></v-img>
|
||||
<v-card-title class="headline">{{ $t('Dialogs.StartPrint.Headline') }}</v-card-title>
|
||||
<v-img v-if="file.big_thumbnail" contain :src="file.big_thumbnail" />
|
||||
<v-card-title class="text-h5">
|
||||
{{ $t('Dialogs.StartPrint.Headline') }}
|
||||
</v-card-title>
|
||||
<v-card-text class="pb-0">
|
||||
{{ $t('Dialogs.StartPrint.DoYouWantToStartFilename', { filename: file.filename }) }}
|
||||
<p class="body-2">
|
||||
{{ question }}
|
||||
</p>
|
||||
</v-card-text>
|
||||
<start-print-dialog-spoolman v-if="moonrakerComponents.includes('spoolman')" :file="file" />
|
||||
<template v-if="moonrakerComponents.includes('timelapse')">
|
||||
<v-divider class="mt-3 mb-2"></v-divider>
|
||||
<v-card-text class="pb-0">
|
||||
<v-divider v-if="!moonrakerComponents.includes('spoolman')" class="mt-3 mb-2" />
|
||||
<v-card-text class="py-0">
|
||||
<settings-row :title="$t('Dialogs.StartPrint.Timelapse')">
|
||||
<v-switch v-model="timelapseEnabled" hide-details class="mt-0"></v-switch>
|
||||
<v-switch v-model="timelapseEnabled" hide-details class="mt-0" />
|
||||
</settings-row>
|
||||
</v-card-text>
|
||||
<v-divider class="mt-2 mb-0"></v-divider>
|
||||
<v-divider class="mt-2 mb-0" />
|
||||
</template>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="" text @click="closeDialog">{{ $t('Dialogs.StartPrint.Cancel') }}</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn text @click="closeDialog">{{ $t('Dialogs.StartPrint.Cancel') }}</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
text
|
||||
@ -35,6 +40,8 @@ import { Component, Mixins, Prop } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import { FileStateGcodefile } from '@/store/files/types'
|
||||
import SettingsRow from '@/components/settings/SettingsRow.vue'
|
||||
import { mdiPrinter3d } from '@mdi/js'
|
||||
import { ServerSpoolmanStateSpool } from '@/store/server/spoolman/types'
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
@ -42,6 +49,8 @@ import SettingsRow from '@/components/settings/SettingsRow.vue'
|
||||
},
|
||||
})
|
||||
export default class StartPrintDialog extends Mixins(BaseMixin) {
|
||||
mdiPrinter3d = mdiPrinter3d
|
||||
|
||||
@Prop({ required: true, default: false })
|
||||
declare readonly bool: boolean
|
||||
|
||||
@ -67,6 +76,31 @@ export default class StartPrintDialog extends Mixins(BaseMixin) {
|
||||
return this.file.big_thumbnail_width ?? 400
|
||||
}
|
||||
|
||||
get active_spool(): ServerSpoolmanStateSpool | null {
|
||||
return this.$store.state.server.spoolman.active_spool ?? null
|
||||
}
|
||||
|
||||
get filamentVendor() {
|
||||
return this.active_spool?.filament?.vendor?.name ?? 'Unknown'
|
||||
}
|
||||
|
||||
get filamentName() {
|
||||
return this.active_spool?.filament.name ?? 'Unknown'
|
||||
}
|
||||
|
||||
get filament() {
|
||||
return `${this.filamentVendor} - ${this.filamentName}`
|
||||
}
|
||||
|
||||
get question() {
|
||||
if (this.active_spool)
|
||||
return this.$t('Dialogs.StartPrint.DoYouWantToStartFilenameFilament', {
|
||||
filename: this.file?.filename ?? 'unknown',
|
||||
})
|
||||
|
||||
return this.$t('Dialogs.StartPrint.DoYouWantToStartFilename', { filename: this.file?.filename ?? 'unknown' })
|
||||
}
|
||||
|
||||
startPrint(filename = '') {
|
||||
filename = (this.currentPath + '/' + filename).substring(1)
|
||||
this.closeDialog()
|
||||
|
105
src/components/dialogs/StartPrintDialogSpoolman.vue
Normal file
105
src/components/dialogs/StartPrintDialogSpoolman.vue
Normal file
@ -0,0 +1,105 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-divider class="mt-3 mb-0" />
|
||||
<v-card-text class="py-0 px-2">
|
||||
<spoolman-panel-active-spool
|
||||
v-if="activeSpoolId !== null"
|
||||
:small="true"
|
||||
class="my-0"
|
||||
@change-spool="showChangeSpoolDialog = true" />
|
||||
<v-alert v-for="alert in alerts" :key="alert.text" text :color="alert.color" class="mt-4 mx-3">
|
||||
{{ alert.text }}
|
||||
</v-alert>
|
||||
<div class="text-center">
|
||||
<v-btn color="primary" small class="mx-auto" @click="showChangeSpoolDialog = true">
|
||||
{{ buttonText }}
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-divider :class="classSecondDivider" />
|
||||
<spoolman-change-spool-dialog :show-dialog="showChangeSpoolDialog" @close="showChangeSpoolDialog = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Mixins, Prop } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import SpoolmanPanelActiveSpool from '@/components/panels/Spoolman/SpoolmanPanelActiveSpool.vue'
|
||||
import { FileStateGcodefile } from '@/store/files/types'
|
||||
|
||||
@Component({
|
||||
components: { SpoolmanPanelActiveSpool },
|
||||
})
|
||||
export default class StartPrintDialogSpoolman extends Mixins(BaseMixin) {
|
||||
@Prop({ required: true }) readonly file!: FileStateGcodefile
|
||||
|
||||
showChangeSpoolDialog = false
|
||||
|
||||
get activeSpoolId() {
|
||||
let spoolId = this.$store.state.server.spoolman?.active_spool_id ?? null
|
||||
if (spoolId === 0) spoolId = null
|
||||
|
||||
return spoolId
|
||||
}
|
||||
|
||||
get activeSpool() {
|
||||
return this.$store.state.server.spoolman?.active_spool ?? null
|
||||
}
|
||||
|
||||
get classSecondDivider() {
|
||||
const classes = ['mt-4']
|
||||
|
||||
classes.push(this.moonrakerComponents.includes('timelapse') ? 'mb-2' : 'mb-0')
|
||||
|
||||
return classes
|
||||
}
|
||||
|
||||
get buttonText() {
|
||||
if (this.activeSpoolId === null) return this.$t('Panels.SpoolmanPanel.SelectSpool') as string
|
||||
|
||||
return this.$t('Panels.SpoolmanPanel.ChangeSpool') as string
|
||||
}
|
||||
|
||||
get alerts() {
|
||||
let alerts = []
|
||||
|
||||
if (this.activeSpoolId === null) {
|
||||
alerts.push({
|
||||
text: this.$t('Panels.SpoolmanPanel.NoSpoolSelected'),
|
||||
color: 'orange',
|
||||
})
|
||||
|
||||
// No need to check for filament type mismatch if no spool is selected
|
||||
return alerts
|
||||
}
|
||||
|
||||
const gcodeFilamentType = this.file.filament_type ?? ''
|
||||
if (
|
||||
gcodeFilamentType !== '' &&
|
||||
this.activeSpool?.filament?.material?.toLowerCase() !== gcodeFilamentType.toLowerCase()
|
||||
) {
|
||||
alerts.push({
|
||||
text: this.$t('Panels.SpoolmanPanel.FilamentTypeMismatch', {
|
||||
fileType: gcodeFilamentType,
|
||||
spoolType: this.activeSpool?.filament?.material,
|
||||
}),
|
||||
color: 'warning',
|
||||
})
|
||||
}
|
||||
|
||||
const fileWeight = Math.round(this.file.filament_weight_total ?? 0)
|
||||
const spoolWeight = Math.round(this.activeSpool?.remaining_weight ?? 0)
|
||||
if (spoolWeight < fileWeight) {
|
||||
alerts.push({
|
||||
text: this.$t('Panels.SpoolmanPanel.TooLessFilament', {
|
||||
fileWeight,
|
||||
spoolWeight,
|
||||
}),
|
||||
color: 'warning',
|
||||
})
|
||||
}
|
||||
|
||||
return alerts
|
||||
}
|
||||
}
|
||||
</script>
|
129
src/components/panels/Spoolman/SpoolmanPanelActiveSpool.vue
Normal file
129
src/components/panels/Spoolman/SpoolmanPanelActiveSpool.vue
Normal file
@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<v-list-item three-line>
|
||||
<v-list-item-content :class="listItemContentClass">
|
||||
<div :class="overlineClass">#{{ id }} | {{ vendor }}</div>
|
||||
<v-list-item-title :class="listItemTitleClass">
|
||||
<span class="cursor-pointer" @click="clickSpool">{{ name }}</span>
|
||||
</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ subtitle }}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
|
||||
<v-list-item-avatar tile :size="avatarSize">
|
||||
<spool-icon :color="color" @click-spool="clickSpool" />
|
||||
</v-list-item-avatar>
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Mixins, Prop } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import Panel from '@/components/ui/Panel.vue'
|
||||
import SpoolmanChangeSpoolDialog from '@/components/dialogs/SpoolmanChangeSpoolDialog.vue'
|
||||
import SpoolmanEjectSpoolDialog from '@/components/dialogs/SpoolmanEjectSpoolDialog.vue'
|
||||
import { ServerSpoolmanStateSpool } from '@/store/server/spoolman/types'
|
||||
|
||||
@Component({
|
||||
components: { Panel, SpoolmanChangeSpoolDialog, SpoolmanEjectSpoolDialog },
|
||||
})
|
||||
export default class SpoolmanPanelActiveSpool extends Mixins(BaseMixin) {
|
||||
@Prop({ required: false, default: false }) readonly small!: boolean
|
||||
|
||||
get listItemContentClass() {
|
||||
if (this.small) return 'my-0'
|
||||
|
||||
return ''
|
||||
}
|
||||
|
||||
get overlineClass() {
|
||||
const classes = ['text-overline', 'mb-1']
|
||||
if (this.small) classes.push('line-height-auto')
|
||||
|
||||
return classes
|
||||
}
|
||||
|
||||
get listItemTitleClass() {
|
||||
if (this.small) return ['text-h6', 'mb-1']
|
||||
|
||||
return ['text-h5', 'mb-1']
|
||||
}
|
||||
|
||||
get avatarSize() {
|
||||
if (this.small) return 60
|
||||
|
||||
return 80
|
||||
}
|
||||
|
||||
get active_spool(): ServerSpoolmanStateSpool | null {
|
||||
return this.$store.state.server.spoolman.active_spool ?? null
|
||||
}
|
||||
|
||||
get color() {
|
||||
const color = this.active_spool?.filament.color_hex ?? null
|
||||
if (color === null) return '#000'
|
||||
|
||||
return `#${color}`
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.active_spool?.id ?? 'XX'
|
||||
}
|
||||
|
||||
get vendor() {
|
||||
return this.active_spool?.filament?.vendor?.name ?? 'Unknown'
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.active_spool?.filament.name ?? 'Unknown'
|
||||
}
|
||||
|
||||
get materialOutput() {
|
||||
const material = this.active_spool?.filament.material ?? null
|
||||
if (material === null) return null
|
||||
|
||||
return material
|
||||
}
|
||||
|
||||
get weightOutput() {
|
||||
let remaining = this.active_spool?.remaining_weight ?? null
|
||||
let total = this.active_spool?.filament.weight ?? null
|
||||
let unit = 'g'
|
||||
|
||||
if (remaining === null || total === null) return null
|
||||
remaining = Math.round(remaining)
|
||||
let totalRound = Math.floor(total / 1000)
|
||||
|
||||
if (total >= 1000) {
|
||||
if (totalRound !== total / 1000) {
|
||||
totalRound = Math.round(total / 100) / 10
|
||||
}
|
||||
|
||||
return `${remaining}g / ${totalRound}kg`
|
||||
}
|
||||
|
||||
return `${remaining} / ${total}${unit}`
|
||||
}
|
||||
|
||||
get lengthOutput() {
|
||||
let remaining = this.active_spool?.remaining_length ?? null
|
||||
|
||||
if (remaining === null) return null
|
||||
remaining = Math.round(remaining / 1000)
|
||||
|
||||
return `${remaining}m`
|
||||
}
|
||||
|
||||
get subtitle() {
|
||||
return [this.materialOutput, this.weightOutput, this.lengthOutput].filter((v) => v !== null).join(' | ')
|
||||
}
|
||||
|
||||
clickSpool() {
|
||||
this.$emit('change-spool')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.line-height-auto {
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
102
src/components/panels/SpoolmanPanel.vue
Normal file
102
src/components/panels/SpoolmanPanel.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<template>
|
||||
<div>
|
||||
<panel :icon="mdiAdjust" :title="title" card-class="spoolman-panel" :collapsible="true">
|
||||
<template #buttons>
|
||||
<v-btn icon tile :title="changeSpoolTooltip" @click="showChangeSpoolDialog = true">
|
||||
<v-icon>{{ mdiSwapVertical }}</v-icon>
|
||||
</v-btn>
|
||||
<v-menu :offset-y="true" :close-on-content-click="false" left>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn icon tile v-bind="attrs" v-on="on">
|
||||
<v-icon>{{ mdiDotsVertical }}</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item>
|
||||
<v-btn small style="width: 100%" @click="showEjectSpoolDialog = true">
|
||||
<v-icon left>{{ mdiEject }}</v-icon>
|
||||
{{ $t('Panels.SpoolmanPanel.EjectSpool') }}
|
||||
</v-btn>
|
||||
</v-list-item>
|
||||
<v-list-item>
|
||||
<v-btn small style="width: 100%" @click="openSpoolManager">
|
||||
<v-icon left>{{ mdiOpenInNew }}</v-icon>
|
||||
{{ $t('Panels.SpoolmanPanel.OpenSpoolManager') }}
|
||||
</v-btn>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</template>
|
||||
<v-card-text v-if="active_spool === null">
|
||||
<v-row>
|
||||
<v-col class="text-center">
|
||||
<p class="text--disabled">{{ $t('Panels.SpoolmanPanel.NoActiveSpool') }}</p>
|
||||
<v-btn small color="primary" @click="showChangeSpoolDialog = true">
|
||||
{{ $t('Panels.SpoolmanPanel.SelectSpool') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<spoolman-panel-active-spool v-else @change-spool="showChangeSpoolDialog = true" />
|
||||
</panel>
|
||||
<spoolman-change-spool-dialog :show-dialog="showChangeSpoolDialog" @close="showChangeSpoolDialog = false" />
|
||||
<spoolman-eject-spool-dialog :show-dialog="showEjectSpoolDialog" @close="showEjectSpoolDialog = false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Mixins } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
import Panel from '@/components/ui/Panel.vue'
|
||||
import { mdiAdjust, mdiDotsVertical, mdiEject, mdiOpenInNew, mdiSwapVertical } from '@mdi/js'
|
||||
import SpoolmanChangeSpoolDialog from '@/components/dialogs/SpoolmanChangeSpoolDialog.vue'
|
||||
import SpoolmanEjectSpoolDialog from '@/components/dialogs/SpoolmanEjectSpoolDialog.vue'
|
||||
import { ServerSpoolmanStateSpool } from '@/store/server/spoolman/types'
|
||||
import SpoolmanPanelActiveSpool from '@/components/panels/Spoolman/SpoolmanPanelActiveSpool.vue'
|
||||
|
||||
@Component({
|
||||
components: { SpoolmanPanelActiveSpool, Panel, SpoolmanChangeSpoolDialog, SpoolmanEjectSpoolDialog },
|
||||
})
|
||||
export default class SpoolmanPanel extends Mixins(BaseMixin) {
|
||||
mdiAdjust = mdiAdjust
|
||||
mdiDotsVertical = mdiDotsVertical
|
||||
mdiEject = mdiEject
|
||||
mdiOpenInNew = mdiOpenInNew
|
||||
mdiSwapVertical = mdiSwapVertical
|
||||
|
||||
showChangeSpoolDialog = false
|
||||
showEjectSpoolDialog = false
|
||||
|
||||
get health() {
|
||||
return this.$store.state.server.spoolman.health ?? ''
|
||||
}
|
||||
|
||||
get title() {
|
||||
const headline = this.$t('Panels.SpoolmanPanel.Headline') as string
|
||||
|
||||
if (this.health === '' || this.health === 'healthy') return headline
|
||||
|
||||
return `${headline} (${this.health})`
|
||||
}
|
||||
|
||||
get changeSpoolTooltip(): string {
|
||||
if (this.active_spool === null) return this.$t('Panels.SpoolmanPanel.SelectSpool') as string
|
||||
|
||||
return this.$t('Panels.SpoolmanPanel.ChangeSpool') as string
|
||||
}
|
||||
|
||||
get active_spool(): ServerSpoolmanStateSpool | null {
|
||||
return this.$store.state.server.spoolman.active_spool ?? null
|
||||
}
|
||||
|
||||
get spoolManagerUrl() {
|
||||
return this.$store.state.server.config.config?.spoolman?.server ?? null
|
||||
}
|
||||
|
||||
openSpoolManager() {
|
||||
window.open(this.spoolManagerUrl, '_blank')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
48
src/components/ui/SpoolIcon.vue
Normal file
48
src/components/ui/SpoolIcon.vue
Normal file
@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<svg
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 487.04 487.04"
|
||||
xml:space="preserve"
|
||||
class="cursor-pointer"
|
||||
@click="clickSpool">
|
||||
<g>
|
||||
<circle :style="styleCircle1" cx="243.52" cy="243.52" r="232.97" />
|
||||
<circle :style="styleCircle2" cx="243.52" cy="243.52" r="112.5" />
|
||||
<path
|
||||
:style="styleCircle3"
|
||||
d="M0,243.52c0,134.42,109.1,243.52,243.52,243.52,134.42,0,243.52-109.1,243.52-243.52S377.95,0,243.52,0C109.1,0,0,109.1,0,243.52Zm115.73,181.78c-52.4-39.5-86.52-98.59-94.52-163.72v-.09c-.68-5.43,1-10.89,4.6-15,3.6-4.12,8.79-6.51,14.26-6.57l118.36-1.33c18.99-.21,36.63,9.83,46.12,26.29,9.5,16.45,9.38,36.74-.3,53.09l-60.29,101.76c-2.8,4.73-7.48,8.03-12.87,9.1-5.39,1.06-10.98-.22-15.36-3.52ZM450.22,238.8c5.49,.06,10.7,2.46,14.31,6.59,3.62,4.13,5.3,9.61,4.63,15.06-8.01,65.13-42.12,124.22-94.52,163.72l-.07,.05c-4.37,3.29-9.93,4.57-15.3,3.51-5.37-1.06-10.03-4.36-12.82-9.06l-60.33-101.84c-9.68-16.34-9.8-36.64-.3-53.09,9.5-16.45,27.13-26.5,46.12-26.29l118.27,1.33ZM338.12,40.02c5.04,2.14,8.92,6.32,10.69,11.49,1.77,5.18,1.24,10.86-1.44,15.63l-58.03,103.17c-9.31,16.56-26.83,26.8-45.83,26.8-19,0-36.51-10.25-45.83-26.8l-57.99-103.09c-2.69-4.79-3.22-10.49-1.45-15.69,1.77-5.2,5.68-9.4,10.73-11.54,60.41-25.63,128.64-25.63,189.05,0l.08,.04Z" />
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Component from 'vue-class-component'
|
||||
import { Mixins, Prop } from 'vue-property-decorator'
|
||||
import BaseMixin from '@/components/mixins/base'
|
||||
|
||||
@Component({})
|
||||
export default class SpoolIcon extends Mixins(BaseMixin) {
|
||||
@Prop({ required: false, default: '#ff0' })
|
||||
declare readonly color: string
|
||||
|
||||
get styleCircle1() {
|
||||
return { fill: this.color }
|
||||
}
|
||||
|
||||
get styleCircle2() {
|
||||
return { fill: '#bebebe' }
|
||||
}
|
||||
|
||||
get styleCircle3() {
|
||||
return { fill: '#343434' }
|
||||
}
|
||||
|
||||
clickSpool() {
|
||||
this.$emit('click-spool')
|
||||
}
|
||||
}
|
||||
</script>
|
@ -149,6 +149,7 @@
|
||||
"StartPrint": {
|
||||
"Cancel": "Cancel",
|
||||
"DoYouWantToStartFilename": "Do you want to start {filename}?",
|
||||
"DoYouWantToStartFilenameFilament": "Do you want to start {filename} with the following filament?",
|
||||
"Headline": "Start Job",
|
||||
"Print": "print",
|
||||
"Timelapse": "Timelapse"
|
||||
@ -622,6 +623,33 @@
|
||||
"On": "On",
|
||||
"PowerControl": "Power Control"
|
||||
},
|
||||
"SpoolmanPanel": {
|
||||
"Cancel": "Cancel",
|
||||
"ChangeSpool": "Change Spool",
|
||||
"DaysAgo": "{days} days ago",
|
||||
"EjectSpool": "Eject spool",
|
||||
"EjectSpoolQuestion": "Are you sure to eject the filament spool?",
|
||||
"Filament": "Filament",
|
||||
"FilamentTypeMismatch": "The material of the active spool ({spoolType}) does not match the material of the G-Code ({fileType}).",
|
||||
"FirstUsedOutput": "First used: {firstUsed}",
|
||||
"Headline": "Spoolman",
|
||||
"LastUsed": "Last Used",
|
||||
"Location": "Location",
|
||||
"Material": "Material",
|
||||
"Never": "Never",
|
||||
"NoActiveSpool": "Filament tracking is inactive. To get started, please select a spool.",
|
||||
"NoResults": "No spool found with the current search criteria.",
|
||||
"NoSpools": "No spools available",
|
||||
"NoSpoolSelected": "No spool selected. Please select a spool or this print will not be tracked.",
|
||||
"OpenSpoolManager": "open Spool Manager",
|
||||
"Refresh": "refresh",
|
||||
"Search": "Search",
|
||||
"SelectSpool": "Select Spool",
|
||||
"Today": "Today",
|
||||
"TooLessFilament": "The current spool may not have enough filament for this print. ({spoolWeight}g of {fileWeight}g)",
|
||||
"Weight": "Weight",
|
||||
"Yesterday": "Yesterday"
|
||||
},
|
||||
"StatusPanel": {
|
||||
"CancelPrint": "Cancel print",
|
||||
"ClearPrintStats": "Clear print stats",
|
||||
|
@ -91,6 +91,7 @@ import MacrosPanel from '@/components/panels/MacrosPanel.vue'
|
||||
import MiniconsolePanel from '@/components/panels/MiniconsolePanel.vue'
|
||||
import MinSettingsPanel from '@/components/panels/MinSettingsPanel.vue'
|
||||
import MiscellaneousPanel from '@/components/panels/MiscellaneousPanel.vue'
|
||||
import SpoolmanPanel from '@/components/panels/SpoolmanPanel.vue'
|
||||
import StatusPanel from '@/components/panels/StatusPanel.vue'
|
||||
import ToolheadControlPanel from '@/components/panels/ToolheadControlPanel.vue'
|
||||
import TemperaturePanel from '@/components/panels/TemperaturePanel.vue'
|
||||
@ -106,6 +107,7 @@ import WebcamPanel from '@/components/panels/WebcamPanel.vue'
|
||||
MiniconsolePanel,
|
||||
MinSettingsPanel,
|
||||
MiscellaneousPanel,
|
||||
SpoolmanPanel,
|
||||
StatusPanel,
|
||||
ToolheadControlPanel,
|
||||
TemperaturePanel,
|
||||
|
@ -72,6 +72,11 @@ export const getters: GetterTree<GuiState, any> = {
|
||||
allPanels = allPanels.filter((name) => name !== 'webcam')
|
||||
}
|
||||
|
||||
// remove spoolman panel, if no spoolman component exists in moonraker
|
||||
if (!rootState.server.components.includes('spoolman')) {
|
||||
allPanels = allPanels.filter((name) => name !== 'spoolman')
|
||||
}
|
||||
|
||||
return allPanels
|
||||
},
|
||||
|
||||
|
@ -11,6 +11,7 @@ import { history } from '@/store/server/history'
|
||||
import { timelapse } from '@/store/server/timelapse'
|
||||
import { jobQueue } from '@/store/server/jobQueue'
|
||||
import { announcements } from '@/store/server/announcements'
|
||||
import { spoolman } from '@/store/server/spoolman'
|
||||
|
||||
// create getDefaultState
|
||||
export const getDefaultState = (): ServerState => {
|
||||
@ -59,5 +60,6 @@ export const server: Module<ServerState, any> = {
|
||||
timelapse,
|
||||
jobQueue,
|
||||
announcements,
|
||||
spoolman,
|
||||
},
|
||||
}
|
||||
|
132
src/store/server/spoolman/actions.ts
Normal file
132
src/store/server/spoolman/actions.ts
Normal file
@ -0,0 +1,132 @@
|
||||
import Vue from 'vue'
|
||||
import { ActionTree } from 'vuex'
|
||||
import { RootState } from '@/store/types'
|
||||
import { ServerSpoolmanState } from '@/store/server/spoolman/types'
|
||||
|
||||
export const actions: ActionTree<ServerSpoolmanState, RootState> = {
|
||||
reset({ commit }) {
|
||||
commit('reset')
|
||||
},
|
||||
|
||||
init({ dispatch }) {
|
||||
Vue.$socket.emit('server.spoolman.get_spool_id', {}, { action: 'server/spoolman/getActiveSpoolId' })
|
||||
Vue.$socket.emit(
|
||||
'server.spoolman.proxy',
|
||||
{
|
||||
request_method: 'GET',
|
||||
path: '/v1/info',
|
||||
},
|
||||
{ action: 'server/spoolman/getInfo' }
|
||||
)
|
||||
Vue.$socket.emit(
|
||||
'server.spoolman.proxy',
|
||||
{
|
||||
request_method: 'GET',
|
||||
path: '/v1/health',
|
||||
},
|
||||
{ action: 'server/spoolman/getHealth' }
|
||||
)
|
||||
Vue.$socket.emit(
|
||||
'server.spoolman.proxy',
|
||||
{
|
||||
request_method: 'GET',
|
||||
path: '/v1/vendor',
|
||||
},
|
||||
{ action: 'server/spoolman/getVendors' }
|
||||
)
|
||||
|
||||
dispatch('socket/addInitModule', 'server/spoolman/getActiveSpoolId', { root: true })
|
||||
dispatch('socket/addInitModule', 'server/spoolman/getHealth', { root: true })
|
||||
dispatch('socket/addInitModule', 'server/spoolman/getInfo', { root: true })
|
||||
dispatch('socket/addInitModule', 'server/spoolman/getVendors', { root: true })
|
||||
|
||||
dispatch('socket/removeInitModule', 'server/spoolman/init', { root: true })
|
||||
},
|
||||
|
||||
getActiveSpoolId({ commit, dispatch }, payload) {
|
||||
commit('setActiveSpoolId', payload.spool_id)
|
||||
dispatch('socket/removeInitModule', 'server/spoolman/getActiveSpoolId', { root: true })
|
||||
|
||||
// also set active spool to null, if spool_id is 0
|
||||
if (payload.spool_id === 0) {
|
||||
commit('setActiveSpool', null)
|
||||
return
|
||||
}
|
||||
|
||||
Vue.$socket.emit(
|
||||
'server.spoolman.proxy',
|
||||
{
|
||||
request_method: 'GET',
|
||||
path: `/v1/spool/${payload.spool_id}`,
|
||||
},
|
||||
{ action: 'server/spoolman/getActiveSpool' }
|
||||
)
|
||||
},
|
||||
|
||||
getActiveSpool({ commit }, payload) {
|
||||
if ('requestParams' in payload) delete payload.requestParams
|
||||
|
||||
commit('setActiveSpool', payload)
|
||||
},
|
||||
|
||||
getHealth({ commit, dispatch }, payload) {
|
||||
delete payload.requestParams
|
||||
commit('setHealth', payload.status)
|
||||
dispatch('socket/removeInitModule', 'server/spoolman/getHealth', { root: true })
|
||||
},
|
||||
|
||||
getInfo({ commit, dispatch }, payload) {
|
||||
delete payload.requestParams
|
||||
commit('setInfo', payload)
|
||||
dispatch('socket/removeInitModule', 'server/spoolman/getInfo', { root: true })
|
||||
},
|
||||
|
||||
getVendors({ commit, dispatch }, payload) {
|
||||
delete payload.requestParams
|
||||
commit(
|
||||
'setVendors',
|
||||
Object.entries(payload).map((value) => value)
|
||||
)
|
||||
dispatch('socket/removeInitModule', 'server/spoolman/getVendors', { root: true })
|
||||
},
|
||||
|
||||
refreshSpools({ dispatch }) {
|
||||
Vue.$socket.emit(
|
||||
'server.spoolman.proxy',
|
||||
{
|
||||
request_method: 'GET',
|
||||
path: '/v1/spool',
|
||||
},
|
||||
{ action: 'server/spoolman/getSpools' }
|
||||
)
|
||||
|
||||
dispatch('socket/addLoading', 'refreshSpools', { root: true })
|
||||
},
|
||||
|
||||
getSpools({ commit, dispatch }, payload) {
|
||||
if ('requestParams' in payload) delete payload.requestParams
|
||||
const spools = Object.entries(payload).map((value) => value[1])
|
||||
commit('setSpools', spools)
|
||||
|
||||
dispatch('socket/removeLoading', 'refreshSpools', { root: true })
|
||||
},
|
||||
|
||||
setActiveSpool(_, id: number | null) {
|
||||
Vue.$socket.emit('server.spoolman.post_spool_id', {
|
||||
spool_id: id,
|
||||
})
|
||||
},
|
||||
|
||||
refreshActiveSpool({ state }) {
|
||||
if (state.active_spool_id === null) return
|
||||
|
||||
Vue.$socket.emit(
|
||||
'server.spoolman.proxy',
|
||||
{
|
||||
request_method: 'GET',
|
||||
path: `/v1/spool/${state.active_spool_id}`,
|
||||
},
|
||||
{ action: 'server/spoolman/getActiveSpool' }
|
||||
)
|
||||
},
|
||||
}
|
5
src/store/server/spoolman/getters.ts
Normal file
5
src/store/server/spoolman/getters.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { GetterTree } from 'vuex'
|
||||
import { ServerSpoolmanState } from './types'
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const getters: GetterTree<ServerSpoolmanState, any> = {}
|
34
src/store/server/spoolman/index.ts
Normal file
34
src/store/server/spoolman/index.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { Module } from 'vuex'
|
||||
import { ServerSpoolmanState } from '@/store/server/spoolman/types'
|
||||
import { actions } from '@/store/server/spoolman/actions'
|
||||
import { mutations } from '@/store/server/spoolman/mutations'
|
||||
import { getters } from '@/store/server/spoolman/getters'
|
||||
|
||||
export const getDefaultState = (): ServerSpoolmanState => {
|
||||
return {
|
||||
health: '',
|
||||
info: {
|
||||
automatic_backups: false,
|
||||
backups_dir: '',
|
||||
data_dir: '',
|
||||
debug_mode: false,
|
||||
version: '',
|
||||
},
|
||||
active_spool_id: null,
|
||||
active_spool: null,
|
||||
vendors: [],
|
||||
feeds: [],
|
||||
}
|
||||
}
|
||||
|
||||
// initial state
|
||||
const state = getDefaultState()
|
||||
|
||||
// eslint-disable-next-line
|
||||
export const spoolman: Module<ServerSpoolmanState, any> = {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations,
|
||||
}
|
34
src/store/server/spoolman/mutations.ts
Normal file
34
src/store/server/spoolman/mutations.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { getDefaultState } from './index'
|
||||
import { MutationTree } from 'vuex'
|
||||
import { ServerSpoolmanState } from './types'
|
||||
import Vue from 'vue'
|
||||
|
||||
export const mutations: MutationTree<ServerSpoolmanState> = {
|
||||
reset(state) {
|
||||
Object.assign(state, getDefaultState())
|
||||
},
|
||||
|
||||
setActiveSpoolId(state, payload) {
|
||||
Vue.set(state, 'active_spool_id', payload)
|
||||
},
|
||||
|
||||
setActiveSpool(state, payload) {
|
||||
Vue.set(state, 'active_spool', payload)
|
||||
},
|
||||
|
||||
setHealth(state, payload) {
|
||||
Vue.set(state, 'health', payload)
|
||||
},
|
||||
|
||||
setInfo(state, payload) {
|
||||
Vue.set(state, 'info', payload)
|
||||
},
|
||||
|
||||
setVendors(state, payload) {
|
||||
Vue.set(state, 'vendors', payload)
|
||||
},
|
||||
|
||||
setSpools(state, payload) {
|
||||
Vue.set(state, 'spools', payload)
|
||||
},
|
||||
}
|
52
src/store/server/spoolman/types.ts
Normal file
52
src/store/server/spoolman/types.ts
Normal file
@ -0,0 +1,52 @@
|
||||
export interface ServerSpoolmanState {
|
||||
health: string
|
||||
info: {
|
||||
automatic_backups: boolean
|
||||
backups_dir: string
|
||||
data_dir: string
|
||||
debug_mode: boolean
|
||||
version: string
|
||||
}
|
||||
active_spool_id: number | null
|
||||
active_spool: ServerSpoolmanStateSpool | null
|
||||
vendors: ServerSpoolmanStateVendor[]
|
||||
feeds: string[]
|
||||
}
|
||||
|
||||
export interface ServerSpoolmanStateVendor {
|
||||
id: number
|
||||
registered: string
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface ServerSpoolmanStateFilament {
|
||||
id: number
|
||||
registered: string
|
||||
name: string
|
||||
comment?: string
|
||||
color_hex: string
|
||||
density: number
|
||||
diameter: number
|
||||
material: string
|
||||
price: number
|
||||
settings_bed_temp: number
|
||||
settings_extruder_temp: number
|
||||
spool_weight: number
|
||||
weight: number
|
||||
vendor: ServerSpoolmanStateVendor
|
||||
}
|
||||
|
||||
export interface ServerSpoolmanStateSpool {
|
||||
id: number
|
||||
registered: string
|
||||
archived: boolean
|
||||
filament: ServerSpoolmanStateFilament
|
||||
first_used: string
|
||||
last_used: string
|
||||
remaining_length: number
|
||||
remaining_weight: number
|
||||
used_length: number
|
||||
used_weight: number
|
||||
location?: string
|
||||
comment?: string
|
||||
}
|
@ -128,6 +128,10 @@ export const actions: ActionTree<SocketState, RootState> = {
|
||||
dispatch('gui/webcams/initStore', payload.params[0], { root: true })
|
||||
break
|
||||
|
||||
case 'notify_active_spool_set':
|
||||
dispatch('server/spoolman/getActiveSpoolId', payload.params[0], { root: true })
|
||||
break
|
||||
|
||||
default:
|
||||
window.console.debug(payload)
|
||||
}
|
||||
|
@ -25,7 +25,15 @@ export const validGcodeExtensions = ['.gcode', '.g', '.gco', '.ufp', '.nc']
|
||||
/*
|
||||
* List of initable server components
|
||||
*/
|
||||
export const initableServerComponents = ['history', 'power', 'updateManager', 'timelapse', 'jobQueue', 'announcements']
|
||||
export const initableServerComponents = [
|
||||
'history',
|
||||
'power',
|
||||
'updateManager',
|
||||
'timelapse',
|
||||
'jobQueue',
|
||||
'announcements',
|
||||
'spoolman',
|
||||
]
|
||||
|
||||
/*
|
||||
* List of required klipper config modules
|
||||
@ -78,6 +86,7 @@ export const allDashboardPanels = [
|
||||
'machine-settings',
|
||||
'miniconsole',
|
||||
'miscellaneous',
|
||||
'spoolman',
|
||||
'temperature',
|
||||
'webcam',
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user