Skip to content

Commit

Permalink
feat: add spoolman support (#1542)
Browse files Browse the repository at this point in the history
  • Loading branch information
meteyou authored Oct 6, 2023
1 parent 5734f1c commit d8430f5
Show file tree
Hide file tree
Showing 20 changed files with 1,076 additions and 11 deletions.
7 changes: 7 additions & 0 deletions src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -277,13 +277,20 @@ export default class App extends Mixins(BaseMixin) {
@Watch('print_percent')
print_percentChanged(newVal: number): void {
this.drawFavicon(newVal)
this.refreshSpoolman()
}
@Watch('printerIsPrinting')
printerIsPrintingChanged(): void {
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
Expand Down
174 changes: 174 additions & 0 deletions src/components/dialogs/SpoolmanChangeSpoolDialog.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 104 additions & 0 deletions src/components/dialogs/SpoolmanChangeSpoolDialogRow.vue
Original file line number Diff line number Diff line change
@@ -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 changes: 55 additions & 0 deletions src/components/dialogs/SpoolmanEjectSpoolDialog.vue
Original file line number Diff line number Diff line change
@@ -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>
Loading

0 comments on commit d8430f5

Please sign in to comment.