feat: add jobs to queue in batches (#1253)
fixes https://github.com/mainsail-crew/mainsail/issues/920
This commit is contained in:
parent
c904b7ba71
commit
b3ce868dec
@ -324,6 +324,13 @@
|
|||||||
<v-icon class="mr-1">{{ mdiPlaylistPlus }}</v-icon>
|
<v-icon class="mr-1">{{ mdiPlaylistPlus }}</v-icon>
|
||||||
{{ $t('Files.AddToQueue') }}
|
{{ $t('Files.AddToQueue') }}
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
v-if="!contextMenu.item.isDirectory && moonrakerComponents.includes('job_queue')"
|
||||||
|
:disabled="!isGcodeFile(contextMenu.item)"
|
||||||
|
@click="openAddBatchToQueueDialog(contextMenu.item)">
|
||||||
|
<v-icon class="mr-1">{{ mdiPlaylistPlus }}</v-icon>
|
||||||
|
{{ $t('Files.AddBatchToQueue') }}
|
||||||
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="contextMenu.item.preheat_gcode !== null"
|
v-if="contextMenu.item.preheat_gcode !== null"
|
||||||
:disabled="['error', 'printing', 'paused'].includes(printer_state)"
|
:disabled="['error', 'printing', 'paused'].includes(printer_state)"
|
||||||
@ -496,6 +503,53 @@
|
|||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</panel>
|
</panel>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
<v-dialog v-model="dialogAddBatchToQueue.show" max-width="400">
|
||||||
|
<panel
|
||||||
|
:title="$t('Files.AddToQueue').toString()"
|
||||||
|
card-class="gcode-files-add-to-queue-dialog"
|
||||||
|
:icon="mdiPlaylistPlus"
|
||||||
|
:margin-bottom="false">
|
||||||
|
<template #buttons>
|
||||||
|
<v-btn icon tile @click="dialogAddBatchToQueue.show = false">
|
||||||
|
<v-icon>{{ mdiCloseThick }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field
|
||||||
|
ref="inputFieldAddToQueueCount"
|
||||||
|
v-model="dialogAddBatchToQueue.count"
|
||||||
|
:label="$t('Files.Count')"
|
||||||
|
required
|
||||||
|
hide-spin-buttons
|
||||||
|
type="number"
|
||||||
|
:rules="countInputRules"
|
||||||
|
@keyup.enter="addBatchToQueueAction">
|
||||||
|
<template #append-outer>
|
||||||
|
<div class="_spin_button_group">
|
||||||
|
<v-btn class="mt-n3" icon plain small @click="dialogAddBatchToQueue.count++">
|
||||||
|
<v-icon>{{ mdiChevronUp }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
:disabled="dialogAddBatchToQueue.count <= 1"
|
||||||
|
class="mb-n3"
|
||||||
|
icon
|
||||||
|
plain
|
||||||
|
small
|
||||||
|
@click="dialogAddBatchToQueue.count--">
|
||||||
|
<v-icon>{{ mdiChevronDown }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="" text @click="dialogAddBatchToQueue.show = false">{{ $t('Files.Cancel') }}</v-btn>
|
||||||
|
<v-btn color="primary" text @click="addBatchToQueueAction">{{ $t('Files.AddToQueue') }}</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</panel>
|
||||||
|
</v-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -509,6 +563,8 @@ import Panel from '@/components/ui/Panel.vue'
|
|||||||
import SettingsRow from '@/components/settings/SettingsRow.vue'
|
import SettingsRow from '@/components/settings/SettingsRow.vue'
|
||||||
import draggable from 'vuedraggable'
|
import draggable from 'vuedraggable'
|
||||||
import {
|
import {
|
||||||
|
mdiChevronDown,
|
||||||
|
mdiChevronUp,
|
||||||
mdiDragVertical,
|
mdiDragVertical,
|
||||||
mdiCheckboxBlankOutline,
|
mdiCheckboxBlankOutline,
|
||||||
mdiCheckboxMarked,
|
mdiCheckboxMarked,
|
||||||
@ -552,6 +608,12 @@ interface dialogPrintFile {
|
|||||||
item: FileStateGcodefile
|
item: FileStateGcodefile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface dialogAddBatchToQueue {
|
||||||
|
show: boolean
|
||||||
|
count: number
|
||||||
|
item: FileStateGcodefile
|
||||||
|
}
|
||||||
|
|
||||||
interface dialogRenameObject {
|
interface dialogRenameObject {
|
||||||
show: boolean
|
show: boolean
|
||||||
newName: string
|
newName: string
|
||||||
@ -572,6 +634,8 @@ interface tableColumnSetting {
|
|||||||
components: { StartPrintDialog, Panel, SettingsRow, draggable },
|
components: { StartPrintDialog, Panel, SettingsRow, draggable },
|
||||||
})
|
})
|
||||||
export default class GcodefilesPanel extends Mixins(BaseMixin, ControlMixin) {
|
export default class GcodefilesPanel extends Mixins(BaseMixin, ControlMixin) {
|
||||||
|
mdiChevronDown = mdiChevronDown
|
||||||
|
mdiChevronUp = mdiChevronUp
|
||||||
mdiFile = mdiFile
|
mdiFile = mdiFile
|
||||||
mdiFileDocumentMultipleOutline = mdiFileDocumentMultipleOutline
|
mdiFileDocumentMultipleOutline = mdiFileDocumentMultipleOutline
|
||||||
mdiMagnify = mdiMagnify
|
mdiMagnify = mdiMagnify
|
||||||
@ -646,6 +710,12 @@ export default class GcodefilesPanel extends Mixins(BaseMixin, ControlMixin) {
|
|||||||
item: { ...this.contextMenu.item },
|
item: { ...this.contextMenu.item },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private dialogAddBatchToQueue: dialogAddBatchToQueue = {
|
||||||
|
show: false,
|
||||||
|
count: 1,
|
||||||
|
item: { ...this.contextMenu.item },
|
||||||
|
}
|
||||||
|
|
||||||
private dialogRenameFile: dialogRenameObject = {
|
private dialogRenameFile: dialogRenameObject = {
|
||||||
show: false,
|
show: false,
|
||||||
newName: '',
|
newName: '',
|
||||||
@ -671,6 +741,10 @@ export default class GcodefilesPanel extends Mixins(BaseMixin, ControlMixin) {
|
|||||||
(value: string) => !!value || this.$t('Files.InvalidNameEmpty'),
|
(value: string) => !!value || this.$t('Files.InvalidNameEmpty'),
|
||||||
(value: string) => !this.existsFilename(value) || this.$t('Files.InvalidNameAlreadyExists'),
|
(value: string) => !this.existsFilename(value) || this.$t('Files.InvalidNameAlreadyExists'),
|
||||||
]
|
]
|
||||||
|
private countInputRules = [
|
||||||
|
(value: string) => !!value || this.$t('JobQueue.InvalidCountEmpty'),
|
||||||
|
(value: string) => parseInt(value) > 0 || this.$t('JobQueue.InvalidCountGreaterZero'),
|
||||||
|
]
|
||||||
|
|
||||||
existsFilename(name: string) {
|
existsFilename(name: string) {
|
||||||
return this.files.findIndex((file: FileStateFile) => file.filename === name) >= 0
|
return this.files.findIndex((file: FileStateFile) => file.filename === name) >= 0
|
||||||
@ -1097,11 +1171,31 @@ export default class GcodefilesPanel extends Mixins(BaseMixin, ControlMixin) {
|
|||||||
this.currentPath = this.currentPath.slice(0, this.currentPath.lastIndexOf('/'))
|
this.currentPath = this.currentPath.slice(0, this.currentPath.lastIndexOf('/'))
|
||||||
}
|
}
|
||||||
|
|
||||||
addToQueue(item: FileStateGcodefile | FileStateFile) {
|
async addToQueue(item: FileStateGcodefile) {
|
||||||
let filename = [this.currentPath, item.filename].join('/')
|
let filename = [this.currentPath, item.filename].join('/')
|
||||||
if (filename.startsWith('/')) filename = filename.slice(1)
|
if (filename.startsWith('/')) filename = filename.slice(1)
|
||||||
|
|
||||||
this.$store.dispatch('server/jobQueue/addToQueue', [filename])
|
await this.$store.dispatch('server/jobQueue/addToQueue', [filename])
|
||||||
|
}
|
||||||
|
|
||||||
|
openAddBatchToQueueDialog(item: FileStateGcodefile) {
|
||||||
|
this.dialogAddBatchToQueue.show = true
|
||||||
|
this.dialogAddBatchToQueue.count = 1
|
||||||
|
this.dialogAddBatchToQueue.item = item
|
||||||
|
}
|
||||||
|
|
||||||
|
async addBatchToQueueAction() {
|
||||||
|
let filename = [this.currentPath, this.dialogAddBatchToQueue.item.filename].join('/')
|
||||||
|
if (filename.startsWith('/')) filename = filename.slice(1)
|
||||||
|
|
||||||
|
const array: string[] = []
|
||||||
|
for (let i = 0; i < this.dialogAddBatchToQueue.count; i++) {
|
||||||
|
array.push(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.$store.dispatch('server/jobQueue/addToQueue', array)
|
||||||
|
|
||||||
|
this.dialogAddBatchToQueue.show = false
|
||||||
}
|
}
|
||||||
|
|
||||||
changeMetadataVisible(name: string, value: boolean) {
|
changeMetadataVisible(name: string, value: boolean) {
|
||||||
@ -1336,6 +1430,15 @@ export default class GcodefilesPanel extends Mixins(BaseMixin, ControlMixin) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
._spin_button_group {
|
||||||
|
width: 24px;
|
||||||
|
margin-top: -6px;
|
||||||
|
margin-left: -6px;
|
||||||
|
margin-bottom: -6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/*noinspection CssUnusedSymbol*/
|
/*noinspection CssUnusedSymbol*/
|
||||||
.files-table .v-data-table-header__icon {
|
.files-table .v-data-table-header__icon {
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<panel ref="jobqueuePanel" :icon="mdiTrayFull" :title="$t('JobQueue.JobQueue')" card-class="jobqueue-panel">
|
<panel
|
||||||
|
ref="jobqueuePanel"
|
||||||
|
:icon="mdiTrayFull"
|
||||||
|
:title="$t('JobQueue.JobQueue').toString()"
|
||||||
|
card-class="jobqueue-panel">
|
||||||
<template #buttons>
|
<template #buttons>
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="queueState === 'paused'"
|
v-if="queueState === 'paused'"
|
||||||
@ -48,98 +52,29 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item="{ item }">
|
<template #item="{ item }">
|
||||||
<tr
|
<jobqueue-entry :key="item.job_id" :item="item" :content-td-width="contentTdWidth" />
|
||||||
:key="item.job_id"
|
|
||||||
v-longpress:600="(e) => showContextMenu(e, item)"
|
|
||||||
class="file-list-cursor user-select-none"
|
|
||||||
@contextmenu="showContextMenu($event, item)">
|
|
||||||
<td class="pr-0 text-center" style="width: 32px">
|
|
||||||
<template v-if="getSmallThumbnail(item) && getBigThumbnail(item)">
|
|
||||||
<v-tooltip
|
|
||||||
v-if="!item.isDirectory && getSmallThumbnail(item) && getBigThumbnail(item)"
|
|
||||||
top
|
|
||||||
content-class="tooltip__content-opacity1">
|
|
||||||
<template #activator="{ on, attrs }">
|
|
||||||
<vue-load-image>
|
|
||||||
<img
|
|
||||||
slot="image"
|
|
||||||
:src="getSmallThumbnail(item)"
|
|
||||||
width="32"
|
|
||||||
height="32"
|
|
||||||
v-bind="attrs"
|
|
||||||
v-on="on" />
|
|
||||||
<v-progress-circular
|
|
||||||
slot="preloader"
|
|
||||||
indeterminate
|
|
||||||
color="primary"></v-progress-circular>
|
|
||||||
<v-icon slot="error">{{ mdiFile }}</v-icon>
|
|
||||||
</vue-load-image>
|
|
||||||
</template>
|
|
||||||
<span><img :src="getBigThumbnail(item)" width="250" /></span>
|
|
||||||
</v-tooltip>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="getSmallThumbnail(item)">
|
|
||||||
<vue-load-image>
|
|
||||||
<img slot="image" :src="getSmallThumbnail(item)" width="32" height="32" />
|
|
||||||
<v-progress-circular
|
|
||||||
slot="preloader"
|
|
||||||
indeterminate
|
|
||||||
color="primary"></v-progress-circular>
|
|
||||||
<v-icon slot="error">{{ mdiFile }}</v-icon>
|
|
||||||
</vue-load-image>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<v-icon>{{ mdiFile }}</v-icon>
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
<td class=" ">
|
|
||||||
<div class="d-block text-truncate" :style="styleContentTdWidth">{{ item.filename }}</div>
|
|
||||||
<small v-if="existMetadata(item)">{{ getDescription(item) }}</small>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
<resize-observer @notify="handleResize" />
|
<resize-observer @notify="handleResize" />
|
||||||
</panel>
|
</panel>
|
||||||
<v-menu v-model="contextMenu.shown" :position-x="contextMenu.x" :position-y="contextMenu.y" absolute offset-y>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item @click="deleteJob(contextMenu.item)">
|
|
||||||
<v-icon class="mr-1">{{ mdiPlaylistRemove }}</v-icon>
|
|
||||||
{{ $t('JobQueue.RemoveFromQueue') }}
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Mixins } from 'vue-property-decorator'
|
import { Component, Mixins } from 'vue-property-decorator'
|
||||||
import BaseMixin from '@/components/mixins/base'
|
import BaseMixin from '@/components/mixins/base'
|
||||||
import { ServerHistoryStateJob } from '@/store/server/history/types'
|
|
||||||
import { formatFilesize, formatPrintTime } from '@/plugins/helpers'
|
|
||||||
import Panel from '@/components/ui/Panel.vue'
|
import Panel from '@/components/ui/Panel.vue'
|
||||||
import { ServerJobQueueStateJob } from '@/store/server/jobQueue/types'
|
import { mdiPlay, mdiPause, mdiTrayFull } from '@mdi/js'
|
||||||
import { mdiPlay, mdiPause, mdiFile, mdiPlaylistRemove, mdiTrayFull } from '@mdi/js'
|
import JobqueueEntry from '@/components/panels/Status/JobqueueEntry.vue'
|
||||||
@Component({
|
@Component({
|
||||||
components: { Panel },
|
components: { JobqueueEntry, Panel },
|
||||||
})
|
})
|
||||||
export default class JobqueuePanel extends Mixins(BaseMixin) {
|
export default class JobqueuePanel extends Mixins(BaseMixin) {
|
||||||
mdiPlay = mdiPlay
|
mdiPlay = mdiPlay
|
||||||
mdiPause = mdiPause
|
mdiPause = mdiPause
|
||||||
mdiFile = mdiFile
|
|
||||||
mdiPlaylistRemove = mdiPlaylistRemove
|
|
||||||
mdiTrayFull = mdiTrayFull
|
mdiTrayFull = mdiTrayFull
|
||||||
|
|
||||||
formatFilesize = formatFilesize
|
|
||||||
|
|
||||||
private contentTdWidth = 100
|
private contentTdWidth = 100
|
||||||
private contextMenu = {
|
|
||||||
shown: false,
|
|
||||||
touchTimer: undefined,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
item: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
declare $refs: {
|
declare $refs: {
|
||||||
jobqueuePanel: any
|
jobqueuePanel: any
|
||||||
@ -161,27 +96,6 @@ export default class JobqueuePanel extends Mixins(BaseMixin) {
|
|||||||
this.$store.dispatch('gui/saveSetting', { name: 'view.jobqueue.countPerPage', value: newVal })
|
this.$store.dispatch('gui/saveSetting', { name: 'view.jobqueue.countPerPage', value: newVal })
|
||||||
}
|
}
|
||||||
|
|
||||||
get styleContentTdWidth() {
|
|
||||||
return `width: ${this.contentTdWidth}px;`
|
|
||||||
}
|
|
||||||
|
|
||||||
showContextMenu(e: any, item: ServerHistoryStateJob) {
|
|
||||||
if (!this.contextMenu.shown) {
|
|
||||||
e?.preventDefault()
|
|
||||||
this.contextMenu.shown = true
|
|
||||||
this.contextMenu.x = e?.clientX || e?.pageX || window.screenX / 2
|
|
||||||
this.contextMenu.y = e?.clientY || e?.pageY || window.screenY / 2
|
|
||||||
this.contextMenu.item = item
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.contextMenu.shown = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteJob(item: ServerJobQueueStateJob) {
|
|
||||||
this.$store.dispatch('server/jobQueue/deleteFromQueue', [item.job_id])
|
|
||||||
}
|
|
||||||
|
|
||||||
startJobqueue() {
|
startJobqueue() {
|
||||||
this.$store.dispatch('server/jobQueue/start')
|
this.$store.dispatch('server/jobQueue/start')
|
||||||
}
|
}
|
||||||
@ -190,35 +104,6 @@ export default class JobqueuePanel extends Mixins(BaseMixin) {
|
|||||||
this.$store.dispatch('server/jobQueue/pause')
|
this.$store.dispatch('server/jobQueue/pause')
|
||||||
}
|
}
|
||||||
|
|
||||||
getSmallThumbnail(item: ServerJobQueueStateJob) {
|
|
||||||
return this.$store.getters['server/jobQueue/getSmallThumbnail'](item)
|
|
||||||
}
|
|
||||||
|
|
||||||
getBigThumbnail(item: ServerJobQueueStateJob) {
|
|
||||||
return this.$store.getters['server/jobQueue/getBigThumbnail'](item)
|
|
||||||
}
|
|
||||||
|
|
||||||
getDescription(item: ServerJobQueueStateJob) {
|
|
||||||
let output = ''
|
|
||||||
|
|
||||||
output += this.$t('Files.Filament') + ': '
|
|
||||||
if (item.metadata?.filament_total || item.metadata.filament_weight_total) {
|
|
||||||
if (item.metadata?.filament_total) output += item.metadata.filament_total.toFixed() + ' mm'
|
|
||||||
if (item.metadata?.filament_total && item.metadata.filament_weight_total) output += ' / '
|
|
||||||
if (item.metadata?.filament_weight_total) output += item.metadata.filament_weight_total.toFixed(2) + ' g'
|
|
||||||
} else output += '--'
|
|
||||||
|
|
||||||
output += ', ' + this.$t('Files.PrintTime') + ': '
|
|
||||||
if (item.metadata?.estimated_time) output += formatPrintTime(item.metadata.estimated_time)
|
|
||||||
else output += '--'
|
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
existMetadata(item: ServerJobQueueStateJob) {
|
|
||||||
return item?.metadata?.metadataPulled
|
|
||||||
}
|
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.calcContentTdWidth()
|
this.calcContentTdWidth()
|
||||||
}
|
}
|
||||||
@ -235,7 +120,7 @@ export default class JobqueuePanel extends Mixins(BaseMixin) {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
.jobqueue-panel {
|
.jobqueue-panel {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,12 @@
|
|||||||
<v-icon class="mr-1">{{ mdiPlaylistPlus }}</v-icon>
|
<v-icon class="mr-1">{{ mdiPlaylistPlus }}</v-icon>
|
||||||
{{ $t('Files.AddToQueue') }}
|
{{ $t('Files.AddToQueue') }}
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
|
<v-list-item
|
||||||
|
v-if="moonrakerComponents.includes('job_queue')"
|
||||||
|
@click="openAddBatchToQueueDialog(contextMenu.item)">
|
||||||
|
<v-icon class="mr-1">{{ mdiPlaylistPlus }}</v-icon>
|
||||||
|
{{ $t('Files.AddBatchToQueue') }}
|
||||||
|
</v-list-item>
|
||||||
<v-list-item
|
<v-list-item
|
||||||
v-if="contextMenu.item.preheat_gcode !== null"
|
v-if="contextMenu.item.preheat_gcode !== null"
|
||||||
:disabled="['error', 'printing', 'paused'].includes(printer_state)"
|
:disabled="['error', 'printing', 'paused'].includes(printer_state)"
|
||||||
@ -137,6 +143,53 @@
|
|||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</panel>
|
</panel>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
|
<v-dialog v-model="dialogAddBatchToQueue.show" max-width="400">
|
||||||
|
<panel
|
||||||
|
:title="$t('Files.AddToQueue').toString()"
|
||||||
|
card-class="gcode-files-add-to-queue-dialog"
|
||||||
|
:icon="mdiPlaylistPlus"
|
||||||
|
:margin-bottom="false">
|
||||||
|
<template #buttons>
|
||||||
|
<v-btn icon tile @click="dialogAddBatchToQueue.show = false">
|
||||||
|
<v-icon>{{ mdiCloseThick }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field
|
||||||
|
ref="inputFieldAddToQueueCount"
|
||||||
|
v-model="dialogAddBatchToQueue.count"
|
||||||
|
:label="$t('Files.Count')"
|
||||||
|
required
|
||||||
|
hide-spin-buttons
|
||||||
|
type="number"
|
||||||
|
:rules="countInputRules"
|
||||||
|
@keyup.enter="addBatchToQueueAction">
|
||||||
|
<template #append-outer>
|
||||||
|
<div class="_spin_button_group">
|
||||||
|
<v-btn class="mt-n3" icon plain small @click="dialogAddBatchToQueue.count++">
|
||||||
|
<v-icon>{{ mdiChevronUp }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
:disabled="dialogAddBatchToQueue.count <= 1"
|
||||||
|
class="mb-n3"
|
||||||
|
icon
|
||||||
|
plain
|
||||||
|
small
|
||||||
|
@click="dialogAddBatchToQueue.count--">
|
||||||
|
<v-icon>{{ mdiChevronDown }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn color="" text @click="dialogAddBatchToQueue.show = false">{{ $t('Files.Cancel') }}</v-btn>
|
||||||
|
<v-btn color="primary" text @click="addBatchToQueueAction">{{ $t('Files.AddToQueue') }}</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</panel>
|
||||||
|
</v-dialog>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -148,6 +201,8 @@ import ControlMixin from '@/components/mixins/control'
|
|||||||
import { FileStateGcodefile } from '@/store/files/types'
|
import { FileStateGcodefile } from '@/store/files/types'
|
||||||
import StartPrintDialog from '@/components/dialogs/StartPrintDialog.vue'
|
import StartPrintDialog from '@/components/dialogs/StartPrintDialog.vue'
|
||||||
import {
|
import {
|
||||||
|
mdiChevronDown,
|
||||||
|
mdiChevronUp,
|
||||||
mdiFile,
|
mdiFile,
|
||||||
mdiPlay,
|
mdiPlay,
|
||||||
mdiPlaylistPlus,
|
mdiPlaylistPlus,
|
||||||
@ -166,12 +221,20 @@ interface dialogRenameObject {
|
|||||||
item: FileStateGcodefile
|
item: FileStateGcodefile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface dialogAddBatchToQueue {
|
||||||
|
show: boolean
|
||||||
|
count: number
|
||||||
|
item: FileStateGcodefile
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
StartPrintDialog,
|
StartPrintDialog,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class StatusPanelGcodefiles extends Mixins(BaseMixin, ControlMixin) {
|
export default class StatusPanelGcodefiles extends Mixins(BaseMixin, ControlMixin) {
|
||||||
|
mdiChevronDown = mdiChevronDown
|
||||||
|
mdiChevronUp = mdiChevronUp
|
||||||
mdiFile = mdiFile
|
mdiFile = mdiFile
|
||||||
mdiPlay = mdiPlay
|
mdiPlay = mdiPlay
|
||||||
mdiPlaylistPlus = mdiPlaylistPlus
|
mdiPlaylistPlus = mdiPlaylistPlus
|
||||||
@ -199,6 +262,7 @@ export default class StatusPanelGcodefiles extends Mixins(BaseMixin, ControlMixi
|
|||||||
last_status: null,
|
last_status: null,
|
||||||
last_start_time: null,
|
last_start_time: null,
|
||||||
last_total_duration: null,
|
last_total_duration: null,
|
||||||
|
preheat_gcode: null,
|
||||||
}
|
}
|
||||||
private currentPath = ''
|
private currentPath = ''
|
||||||
private contentTdWidth = 100
|
private contentTdWidth = 100
|
||||||
@ -222,6 +286,17 @@ export default class StatusPanelGcodefiles extends Mixins(BaseMixin, ControlMixi
|
|||||||
item: { ...this.dialogFile },
|
item: { ...this.dialogFile },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private dialogAddBatchToQueue: dialogAddBatchToQueue = {
|
||||||
|
show: false,
|
||||||
|
count: 1,
|
||||||
|
item: { ...this.contextMenu.item },
|
||||||
|
}
|
||||||
|
|
||||||
|
private countInputRules = [
|
||||||
|
(value: string) => !!value || this.$t('JobQueue.InvalidCountEmpty'),
|
||||||
|
(value: string) => parseInt(value) > 0 || this.$t('JobQueue.InvalidCountGreaterZero'),
|
||||||
|
]
|
||||||
|
|
||||||
get gcodeFiles() {
|
get gcodeFiles() {
|
||||||
let gcodes = this.$store.getters['files/getAllGcodes'] ?? []
|
let gcodes = this.$store.getters['files/getAllGcodes'] ?? []
|
||||||
gcodes = gcodes
|
gcodes = gcodes
|
||||||
@ -343,6 +418,26 @@ export default class StatusPanelGcodefiles extends Mixins(BaseMixin, ControlMixi
|
|||||||
this.$store.dispatch('server/jobQueue/addToQueue', [item.filename])
|
this.$store.dispatch('server/jobQueue/addToQueue', [item.filename])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
openAddBatchToQueueDialog(item: FileStateGcodefile) {
|
||||||
|
this.dialogAddBatchToQueue.show = true
|
||||||
|
this.dialogAddBatchToQueue.count = 1
|
||||||
|
this.dialogAddBatchToQueue.item = item
|
||||||
|
}
|
||||||
|
|
||||||
|
async addBatchToQueueAction() {
|
||||||
|
let filename = [this.currentPath, this.dialogAddBatchToQueue.item.filename].join('/')
|
||||||
|
if (filename.startsWith('/')) filename = filename.slice(1)
|
||||||
|
|
||||||
|
const array: string[] = []
|
||||||
|
for (let i = 0; i < this.dialogAddBatchToQueue.count; i++) {
|
||||||
|
array.push(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.$store.dispatch('server/jobQueue/addToQueue', array)
|
||||||
|
|
||||||
|
this.dialogAddBatchToQueue.show = false
|
||||||
|
}
|
||||||
|
|
||||||
view3D(item: FileStateGcodefile) {
|
view3D(item: FileStateGcodefile) {
|
||||||
this.$router.push({ path: '/viewer', query: { filename: 'gcodes/' + item.filename } })
|
this.$router.push({ path: '/viewer', query: { filename: 'gcodes/' + item.filename } })
|
||||||
}
|
}
|
||||||
@ -419,8 +514,15 @@ export default class StatusPanelGcodefiles extends Mixins(BaseMixin, ControlMixi
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style scoped>
|
||||||
.filesGcodeCard {
|
.filesGcodeCard {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
._spin_button_group {
|
||||||
|
width: 24px;
|
||||||
|
margin-top: -6px;
|
||||||
|
margin-left: -6px;
|
||||||
|
margin-bottom: -6px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -5,77 +5,27 @@
|
|||||||
hide-default-footer
|
hide-default-footer
|
||||||
class="dashboard-jobqueue-table"
|
class="dashboard-jobqueue-table"
|
||||||
sort-by="time_added"
|
sort-by="time_added"
|
||||||
mobile-breakpoint="0"
|
mobile-breakpoint="0">
|
||||||
@current-items="setFirst">
|
|
||||||
<template #no-data>
|
<template #no-data>
|
||||||
<div class="text-center">{{ $t('Panels.StatusPanel.EmptyJobqueue') }}</div>
|
<div class="text-center">{{ $t('Panels.StatusPanel.EmptyJobqueue') }}</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #item="{ item }">
|
<template #item="{ item, index }">
|
||||||
<tr
|
<jobqueue-entry
|
||||||
:key="item.job_id"
|
:key="item.job_id"
|
||||||
v-longpress:600="(e) => showContextMenu(e, item)"
|
:item="item"
|
||||||
class="cursor-pointer"
|
:isFirst="index === 0"
|
||||||
@contextmenu="showContextMenu($event, item)">
|
:content-td-width="contentTdWidth" />
|
||||||
<td class="pr-0 text-center" style="width: 32px">
|
|
||||||
<template v-if="getSmallThumbnail(item) && getBigThumbnail(item)">
|
|
||||||
<v-tooltip
|
|
||||||
v-if="!item.isDirectory && getSmallThumbnail(item) && getBigThumbnail(item)"
|
|
||||||
top
|
|
||||||
content-class="tooltip__content-opacity1">
|
|
||||||
<template #activator="{ on, attrs }">
|
|
||||||
<vue-load-image>
|
|
||||||
<img
|
|
||||||
slot="image"
|
|
||||||
:src="getSmallThumbnail(item)"
|
|
||||||
width="32"
|
|
||||||
height="32"
|
|
||||||
v-bind="attrs"
|
|
||||||
v-on="on" />
|
|
||||||
<v-progress-circular
|
|
||||||
slot="preloader"
|
|
||||||
indeterminate
|
|
||||||
color="primary"></v-progress-circular>
|
|
||||||
<v-icon slot="error">{{ mdiFile }}</v-icon>
|
|
||||||
</vue-load-image>
|
|
||||||
</template>
|
</template>
|
||||||
<span><img :src="getBigThumbnail(item)" width="250" /></span>
|
<template v-if="jobsRest.length" #body.append>
|
||||||
</v-tooltip>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="getSmallThumbnail(item)">
|
|
||||||
<vue-load-image>
|
|
||||||
<img slot="image" :src="getSmallThumbnail(item)" width="32" height="32" />
|
|
||||||
<v-progress-circular
|
|
||||||
slot="preloader"
|
|
||||||
indeterminate
|
|
||||||
color="primary"></v-progress-circular>
|
|
||||||
<v-icon slot="error">{{ mdiFile }}</v-icon>
|
|
||||||
</vue-load-image>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<v-icon>{{ mdiFile }}</v-icon>
|
|
||||||
</template>
|
|
||||||
</td>
|
|
||||||
<td class="pr-2">
|
|
||||||
<template v-if="item.isFirst && !printerIsPrinting">
|
|
||||||
<v-btn icon color="success" class="float-right minwidth-0 mt-1" @click="startJobqueue">
|
|
||||||
<v-icon>{{ mdiPlay }}</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</template>
|
|
||||||
<div class="d-block text-truncate" :style="styleContentTdWidth">{{ item.filename }}</div>
|
|
||||||
<small v-if="existMetadata(item)">{{ getDescription(item) }}</small>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
<template v-if="jobs.length > jobsTable.length" #body.append>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td class="pr-0 text-center" style="width: 32px">
|
<td class="pr-0 text-center" style="width: 32px">
|
||||||
<v-icon>{{ mdiFileMultiple }}</v-icon>
|
<v-icon>{{ mdiFileMultiple }}</v-icon>
|
||||||
</td>
|
</td>
|
||||||
<td class="pr-2">
|
<td class="pr-2">
|
||||||
{{
|
{{
|
||||||
$tc('Panels.StatusPanel.JobqueueMoreFiles', jobs.length - jobsTable.length, {
|
$tc('Panels.StatusPanel.JobqueueMoreFiles', restJobsLength, {
|
||||||
count: jobs.length - jobsTable.length,
|
count: restJobsLength,
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
<br />
|
<br />
|
||||||
@ -85,14 +35,6 @@
|
|||||||
</template>
|
</template>
|
||||||
</v-data-table>
|
</v-data-table>
|
||||||
<resize-observer @notify="handleResize" />
|
<resize-observer @notify="handleResize" />
|
||||||
<v-menu v-model="contextMenu.shown" :position-x="contextMenu.x" :position-y="contextMenu.y" absolute offset-y>
|
|
||||||
<v-list>
|
|
||||||
<v-list-item @click="removeFromJobqueue(contextMenu.item)">
|
|
||||||
<v-icon class="mr-1">{{ mdiPlaylistRemove }}</v-icon>
|
|
||||||
{{ $t('JobQueue.RemoveFromQueue') }}
|
|
||||||
</v-list-item>
|
|
||||||
</v-list>
|
|
||||||
</v-menu>
|
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -101,24 +43,15 @@ import Component from 'vue-class-component'
|
|||||||
import { Mixins } from 'vue-property-decorator'
|
import { Mixins } from 'vue-property-decorator'
|
||||||
import BaseMixin from '@/components/mixins/base'
|
import BaseMixin from '@/components/mixins/base'
|
||||||
import { ServerJobQueueStateJob } from '@/store/server/jobQueue/types'
|
import { ServerJobQueueStateJob } from '@/store/server/jobQueue/types'
|
||||||
import { mdiFile, mdiPlay, mdiFileMultiple, mdiPlaylistRemove } from '@mdi/js'
|
import { mdiFileMultiple } from '@mdi/js'
|
||||||
|
import JobqueueEntry from '@/components/panels/Status/JobqueueEntry.vue'
|
||||||
@Component({
|
@Component({
|
||||||
components: {},
|
components: { JobqueueEntry },
|
||||||
})
|
})
|
||||||
export default class StatusPanelJobqueue extends Mixins(BaseMixin) {
|
export default class StatusPanelJobqueue extends Mixins(BaseMixin) {
|
||||||
mdiFile = mdiFile
|
|
||||||
mdiPlay = mdiPlay
|
|
||||||
mdiFileMultiple = mdiFileMultiple
|
mdiFileMultiple = mdiFileMultiple
|
||||||
mdiPlaylistRemove = mdiPlaylistRemove
|
|
||||||
|
|
||||||
private contentTdWidth = 100
|
private contentTdWidth = 100
|
||||||
private contextMenu = {
|
|
||||||
shown: false,
|
|
||||||
touchTimer: undefined,
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
item: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
declare $refs: {
|
declare $refs: {
|
||||||
filesJobqueue: any
|
filesJobqueue: any
|
||||||
@ -136,15 +69,27 @@ export default class StatusPanelJobqueue extends Mixins(BaseMixin) {
|
|||||||
return this.jobs.slice(5)
|
return this.jobs.slice(5)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get restJobsLength() {
|
||||||
|
let count = 0
|
||||||
|
|
||||||
|
this.jobsRest.forEach((item: ServerJobQueueStateJob) => {
|
||||||
|
count += (item.combinedIds?.length ?? 0) + 1
|
||||||
|
})
|
||||||
|
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
get descriptionRestJobs() {
|
get descriptionRestJobs() {
|
||||||
let filamentLength = 0
|
let filamentLength = 0
|
||||||
let filamentWeight = 0
|
let filamentWeight = 0
|
||||||
let printTime = 0
|
let printTime = 0
|
||||||
|
|
||||||
this.jobsRest.forEach((item: ServerJobQueueStateJob) => {
|
this.jobsRest.forEach((item: ServerJobQueueStateJob) => {
|
||||||
if (item.metadata?.filament_total) filamentLength += item.metadata?.filament_total
|
const count = (item.combinedIds?.length ?? 0) + 1
|
||||||
if (item.metadata?.filament_weight_total) filamentWeight += item.metadata?.filament_weight_total
|
|
||||||
if (item.metadata?.estimated_time) printTime = item.metadata.estimated_time
|
if (item.metadata?.filament_total) filamentLength += item.metadata?.filament_total * count
|
||||||
|
if (item.metadata?.filament_weight_total) filamentWeight += item.metadata?.filament_weight_total * count
|
||||||
|
if (item.metadata?.estimated_time) printTime = item.metadata.estimated_time * count
|
||||||
})
|
})
|
||||||
|
|
||||||
let output = ''
|
let output = ''
|
||||||
@ -163,49 +108,6 @@ export default class StatusPanelJobqueue extends Mixins(BaseMixin) {
|
|||||||
return output
|
return output
|
||||||
}
|
}
|
||||||
|
|
||||||
get styleContentTdWidth() {
|
|
||||||
return `width: ${this.contentTdWidth}px;`
|
|
||||||
}
|
|
||||||
|
|
||||||
getSmallThumbnail(item: ServerJobQueueStateJob) {
|
|
||||||
return this.$store.getters['server/jobQueue/getSmallThumbnail'](item)
|
|
||||||
}
|
|
||||||
|
|
||||||
getBigThumbnail(item: ServerJobQueueStateJob) {
|
|
||||||
return this.$store.getters['server/jobQueue/getBigThumbnail'](item)
|
|
||||||
}
|
|
||||||
|
|
||||||
getDescription(item: ServerJobQueueStateJob) {
|
|
||||||
let output = ''
|
|
||||||
|
|
||||||
output += this.$t('Panels.StatusPanel.Filament') + ': '
|
|
||||||
if (item.metadata?.filament_total || item.metadata.filament_weight_total) {
|
|
||||||
if (item.metadata?.filament_total) output += item.metadata.filament_total.toFixed() + ' mm'
|
|
||||||
if (item.metadata?.filament_total && item.metadata.filament_weight_total) output += ' / '
|
|
||||||
if (item.metadata?.filament_weight_total) output += item.metadata.filament_weight_total.toFixed(2) + ' g'
|
|
||||||
} else output += '--'
|
|
||||||
|
|
||||||
output += ', ' + this.$t('Panels.StatusPanel.PrintTime') + ': '
|
|
||||||
if (item.metadata?.estimated_time) output += this.formatPrintTime(item.metadata.estimated_time)
|
|
||||||
else output += '--'
|
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
existMetadata(item: ServerJobQueueStateJob) {
|
|
||||||
return item?.metadata?.metadataPulled
|
|
||||||
}
|
|
||||||
|
|
||||||
setFirst(currItems: ServerJobQueueStateJob[]) {
|
|
||||||
// first check that actually exists values
|
|
||||||
if (currItems.length) {
|
|
||||||
// toggle all to false
|
|
||||||
currItems.forEach((x: ServerJobQueueStateJob) => (x.isFirst = false))
|
|
||||||
// just set first to true
|
|
||||||
currItems[0].isFirst = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
formatPrintTime(totalSeconds: number) {
|
formatPrintTime(totalSeconds: number) {
|
||||||
if (totalSeconds) {
|
if (totalSeconds) {
|
||||||
let output = ''
|
let output = ''
|
||||||
@ -232,27 +134,10 @@ export default class StatusPanelJobqueue extends Mixins(BaseMixin) {
|
|||||||
return '--'
|
return '--'
|
||||||
}
|
}
|
||||||
|
|
||||||
showContextMenu(e: any, item: ServerJobQueueStateJob) {
|
|
||||||
if (!this.contextMenu.shown) {
|
|
||||||
e?.preventDefault()
|
|
||||||
this.contextMenu.shown = true
|
|
||||||
this.contextMenu.x = e?.clientX || e?.pageX || window.screenX / 2
|
|
||||||
this.contextMenu.y = e?.clientY || e?.pageY || window.screenY / 2
|
|
||||||
this.contextMenu.item = item
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.contextMenu.shown = true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startJobqueue() {
|
startJobqueue() {
|
||||||
this.$store.dispatch('server/jobQueue/start')
|
this.$store.dispatch('server/jobQueue/start')
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromJobqueue(item: ServerJobQueueStateJob) {
|
|
||||||
this.$store.dispatch('server/jobQueue/deleteFromQueue', [item.job_id])
|
|
||||||
}
|
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.calcContentTdWidth()
|
this.calcContentTdWidth()
|
||||||
|
271
src/components/panels/Status/JobqueueEntry.vue
Normal file
271
src/components/panels/Status/JobqueueEntry.vue
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
<template>
|
||||||
|
<tr
|
||||||
|
v-longpress:600="(e) => showContextMenu(e, item)"
|
||||||
|
class="cursor-pointer"
|
||||||
|
@contextmenu="showContextMenu($event, item)">
|
||||||
|
<td class="pr-0 text-center" style="width: 32px">
|
||||||
|
<template v-if="smallThumbnail && bigThumbnail">
|
||||||
|
<v-tooltip v-if="smallThumbnail && bigThumbnail" top content-class="tooltip__content-opacity1">
|
||||||
|
<template #activator="{ on, attrs }">
|
||||||
|
<vue-load-image>
|
||||||
|
<img slot="image" :src="smallThumbnail" width="32" height="32" v-bind="attrs" v-on="on" />
|
||||||
|
<div slot="preloader">
|
||||||
|
<v-progress-circular indeterminate color="primary" />
|
||||||
|
</div>
|
||||||
|
<div slot="error">
|
||||||
|
<v-icon>{{ mdiFile }}</v-icon>
|
||||||
|
</div>
|
||||||
|
</vue-load-image>
|
||||||
|
</template>
|
||||||
|
<span><img :src="bigThumbnail" width="250" /></span>
|
||||||
|
</v-tooltip>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="smallThumbnail">
|
||||||
|
<vue-load-image>
|
||||||
|
<img slot="image" :src="smallThumbnail" width="32" height="32" />
|
||||||
|
<div slot="preloader">
|
||||||
|
<v-progress-circular indeterminate color="primary" />
|
||||||
|
</div>
|
||||||
|
<div slot="error">
|
||||||
|
<v-icon>{{ mdiFile }}</v-icon>
|
||||||
|
</div>
|
||||||
|
</vue-load-image>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<v-icon>{{ mdiFile }}</v-icon>
|
||||||
|
</template>
|
||||||
|
</td>
|
||||||
|
<td class="pr-2">
|
||||||
|
<template v-if="isFirst && !printerIsPrinting">
|
||||||
|
<v-btn icon color="success" class="float-right minwidth-0 mt-1" @click="startJobqueue">
|
||||||
|
<v-icon>{{ mdiPlay }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
<div class="d-block text-truncate" :style="styleContentTdWidth">
|
||||||
|
<strong v-if="item.combinedIds.length">{{ item.combinedIds.length + 1 }}x</strong>
|
||||||
|
{{ item.filename }}
|
||||||
|
</div>
|
||||||
|
<small v-if="item?.metadata?.metadataPulled">{{ description }}</small>
|
||||||
|
</td>
|
||||||
|
<v-menu v-model="contextMenu.shown" :position-x="contextMenu.x" :position-y="contextMenu.y" absolute offset-y>
|
||||||
|
<v-list>
|
||||||
|
<v-list-item @click="openChangeCountDialog(contextMenu.item)">
|
||||||
|
<v-icon class="mr-1">{{ mdiCounter }}</v-icon>
|
||||||
|
{{ $t('JobQueue.ChangeCount') }}
|
||||||
|
</v-list-item>
|
||||||
|
<v-list-item @click="removeFromJobqueue(contextMenu.item)">
|
||||||
|
<v-icon class="mr-1">{{ mdiPlaylistRemove }}</v-icon>
|
||||||
|
{{ $t('JobQueue.RemoveFromQueue') }}
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
|
</v-menu>
|
||||||
|
<v-dialog v-model="dialogChangeCount.show" max-width="400">
|
||||||
|
<panel
|
||||||
|
:title="$t('JobQueue.ChangeCount').toString()"
|
||||||
|
:icon="mdiCounter"
|
||||||
|
card-class="jobqueue-change-count-dialog"
|
||||||
|
:margin-bottom="false">
|
||||||
|
<template #buttons>
|
||||||
|
<v-btn icon tile @click="dialogChangeCount.show = false">
|
||||||
|
<v-icon>{{ mdiCloseThick }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<v-card-text>
|
||||||
|
<v-text-field
|
||||||
|
ref="inputFieldAddToQueueCount"
|
||||||
|
v-model="dialogChangeCount.count"
|
||||||
|
:label="$t('JobQueue.Count')"
|
||||||
|
required
|
||||||
|
:rules="countInputRules"
|
||||||
|
hide-spin-buttons
|
||||||
|
type="number"
|
||||||
|
@keyup.enter="changeCountAction">
|
||||||
|
<template #append-outer>
|
||||||
|
<div class="_spin_button_group">
|
||||||
|
<v-btn class="mt-n3" icon plain small @click="dialogChangeCount.count++">
|
||||||
|
<v-icon>{{ mdiChevronUp }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
:disabled="dialogChangeCount.count <= 1"
|
||||||
|
class="mb-n3"
|
||||||
|
icon
|
||||||
|
plain
|
||||||
|
small
|
||||||
|
@click="dialogChangeCount.count--">
|
||||||
|
<v-icon>{{ mdiChevronDown }}</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</v-text-field>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-spacer />
|
||||||
|
<v-btn color="" text @click="dialogChangeCount.show = false">{{ $t('JobQueue.Cancel') }}</v-btn>
|
||||||
|
<v-btn color="primary" text @click="changeCountAction">{{ $t('JobQueue.ChangeCount') }}</v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</panel>
|
||||||
|
</v-dialog>
|
||||||
|
</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 { ServerJobQueueStateJob } from '@/store/server/jobQueue/types'
|
||||||
|
import { mdiChevronDown, mdiChevronUp, mdiCloseThick, mdiCounter, mdiFile, mdiPlay, mdiPlaylistRemove } from '@mdi/js'
|
||||||
|
import NumberInput from '@/components/inputs/NumberInput.vue'
|
||||||
|
@Component({
|
||||||
|
components: { NumberInput },
|
||||||
|
})
|
||||||
|
export default class StatusPanelJobqueueEntry extends Mixins(BaseMixin) {
|
||||||
|
mdiChevronDown = mdiChevronDown
|
||||||
|
mdiChevronUp = mdiChevronUp
|
||||||
|
mdiCloseThick = mdiCloseThick
|
||||||
|
mdiCounter = mdiCounter
|
||||||
|
mdiFile = mdiFile
|
||||||
|
mdiPlay = mdiPlay
|
||||||
|
mdiPlaylistRemove = mdiPlaylistRemove
|
||||||
|
|
||||||
|
@Prop({ type: Object, required: true }) declare item: ServerJobQueueStateJob
|
||||||
|
@Prop({ type: Number, required: true }) declare contentTdWidth: number
|
||||||
|
@Prop({ type: Boolean, default: false }) declare isFirst: boolean
|
||||||
|
private contextMenu: {
|
||||||
|
shown: boolean
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
item: ServerJobQueueStateJob | any
|
||||||
|
} = {
|
||||||
|
shown: false,
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
item: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
private dialogChangeCount: {
|
||||||
|
show: boolean
|
||||||
|
count: number
|
||||||
|
item: ServerJobQueueStateJob | any
|
||||||
|
} = {
|
||||||
|
show: false,
|
||||||
|
count: 1,
|
||||||
|
item: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
private countInputRules = [
|
||||||
|
(value: string) => !!value || this.$t('JobQueue.InvalidCountEmpty'),
|
||||||
|
(value: string) => parseInt(value) > 0 || this.$t('JobQueue.InvalidCountGreaterZero'),
|
||||||
|
]
|
||||||
|
|
||||||
|
declare $refs: {
|
||||||
|
filesJobqueue: any
|
||||||
|
}
|
||||||
|
|
||||||
|
get styleContentTdWidth() {
|
||||||
|
return `width: ${this.contentTdWidth}px;`
|
||||||
|
}
|
||||||
|
|
||||||
|
get smallThumbnail() {
|
||||||
|
return this.$store.getters['server/jobQueue/getSmallThumbnail'](this.item)
|
||||||
|
}
|
||||||
|
|
||||||
|
get bigThumbnail() {
|
||||||
|
return this.$store.getters['server/jobQueue/getBigThumbnail'](this.item)
|
||||||
|
}
|
||||||
|
|
||||||
|
get description() {
|
||||||
|
let output = ''
|
||||||
|
|
||||||
|
output += this.$t('Panels.StatusPanel.Filament') + ': '
|
||||||
|
if (this.item.metadata?.filament_total || this.item.metadata.filament_weight_total) {
|
||||||
|
if (this.item.metadata?.filament_total) output += this.item.metadata.filament_total.toFixed() + ' mm'
|
||||||
|
if (this.item.metadata?.filament_total && this.item.metadata.filament_weight_total) output += ' / '
|
||||||
|
if (this.item.metadata?.filament_weight_total)
|
||||||
|
output += this.item.metadata.filament_weight_total.toFixed(2) + ' g'
|
||||||
|
} else output += '--'
|
||||||
|
|
||||||
|
output += ', ' + this.$t('Panels.StatusPanel.PrintTime') + ': '
|
||||||
|
if (this.item.metadata?.estimated_time) output += this.formatPrintTime(this.item.metadata.estimated_time)
|
||||||
|
else output += '--'
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
formatPrintTime(totalSeconds: number) {
|
||||||
|
if (totalSeconds) {
|
||||||
|
let output = ''
|
||||||
|
|
||||||
|
const days = Math.floor(totalSeconds / (3600 * 24))
|
||||||
|
if (days) {
|
||||||
|
totalSeconds %= 3600 * 24
|
||||||
|
output += days + 'd'
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = Math.floor(totalSeconds / 3600)
|
||||||
|
totalSeconds %= 3600
|
||||||
|
if (hours) output += ' ' + hours + 'h'
|
||||||
|
|
||||||
|
const minutes = Math.floor(totalSeconds / 60)
|
||||||
|
if (minutes) output += ' ' + minutes + 'm'
|
||||||
|
|
||||||
|
const seconds = totalSeconds % 60
|
||||||
|
if (seconds) output += ' ' + seconds.toFixed(0) + 's'
|
||||||
|
|
||||||
|
return output
|
||||||
|
}
|
||||||
|
|
||||||
|
return '--'
|
||||||
|
}
|
||||||
|
|
||||||
|
showContextMenu(e: any, item: ServerJobQueueStateJob) {
|
||||||
|
if (!this.contextMenu.shown) {
|
||||||
|
e?.preventDefault()
|
||||||
|
this.contextMenu.shown = true
|
||||||
|
this.contextMenu.x = e?.clientX || e?.pageX || window.screenX / 2
|
||||||
|
this.contextMenu.y = e?.clientY || e?.pageY || window.screenY / 2
|
||||||
|
this.contextMenu.item = item
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.contextMenu.shown = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startJobqueue() {
|
||||||
|
this.$store.dispatch('server/jobQueue/start')
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFromJobqueue(item: ServerJobQueueStateJob) {
|
||||||
|
const ids = [...(item.combinedIds ?? [])]
|
||||||
|
ids.push(item.job_id)
|
||||||
|
|
||||||
|
this.$store.dispatch('server/jobQueue/deleteFromQueue', ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
openChangeCountDialog(item: ServerJobQueueStateJob) {
|
||||||
|
this.dialogChangeCount.show = true
|
||||||
|
this.dialogChangeCount.count = (item.combinedIds?.length ?? 0) + 1
|
||||||
|
this.dialogChangeCount.item = item
|
||||||
|
}
|
||||||
|
|
||||||
|
changeCountAction() {
|
||||||
|
this.$store.dispatch('server/jobQueue/changeCount', {
|
||||||
|
job_id: this.dialogChangeCount.item.job_id,
|
||||||
|
count: this.dialogChangeCount.count,
|
||||||
|
})
|
||||||
|
this.dialogChangeCount.show = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.filesJobqueue {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
._spin_button_group {
|
||||||
|
width: 24px;
|
||||||
|
margin-top: -6px;
|
||||||
|
margin-left: -6px;
|
||||||
|
margin-bottom: -6px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -133,7 +133,7 @@ export default class StatusPanel extends Mixins(BaseMixin) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get jobsCount() {
|
get jobsCount() {
|
||||||
return this.jobs?.length ?? 0
|
return this.$store.getters['server/jobQueue/getJobsCount'] ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
get current_filename() {
|
get current_filename() {
|
||||||
|
@ -164,11 +164,13 @@
|
|||||||
"Yes": "Yes"
|
"Yes": "Yes"
|
||||||
},
|
},
|
||||||
"Files": {
|
"Files": {
|
||||||
|
"AddBatchToQueue": "Add batch to Queue",
|
||||||
"AddToQueue": "Add to Queue",
|
"AddToQueue": "Add to Queue",
|
||||||
"AllFiles": "All",
|
"AllFiles": "All",
|
||||||
"BedTemp": "Bed Temp.",
|
"BedTemp": "Bed Temp.",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
"ChamberTemp": "Chamber Temp.",
|
"ChamberTemp": "Chamber Temp.",
|
||||||
|
"Count": "Count",
|
||||||
"Create": "Create",
|
"Create": "Create",
|
||||||
"CreateNewDirectory": "Create new Directory",
|
"CreateNewDirectory": "Create new Directory",
|
||||||
"CurrentPath": "Current path",
|
"CurrentPath": "Current path",
|
||||||
@ -369,7 +371,12 @@
|
|||||||
},
|
},
|
||||||
"JobQueue": {
|
"JobQueue": {
|
||||||
"AllJobs": "All Jobs",
|
"AllJobs": "All Jobs",
|
||||||
|
"ChangeCount": "Change count",
|
||||||
|
"Cancel": "Cancel",
|
||||||
|
"Count": "Count",
|
||||||
"Empty": "Empty",
|
"Empty": "Empty",
|
||||||
|
"InvalidCountEmpty": "Input must not be empty!",
|
||||||
|
"InvalidCountGreaterZero": "Input must be greater than 0!",
|
||||||
"JobQueue": "Job Queue",
|
"JobQueue": "Job Queue",
|
||||||
"Jobs": "Jobs",
|
"Jobs": "Jobs",
|
||||||
"Pause": "Pause",
|
"Pause": "Pause",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import { ActionTree } from 'vuex'
|
import { ActionTree } from 'vuex'
|
||||||
import { RootState } from '@/store/types'
|
import { RootState } from '@/store/types'
|
||||||
import { ServerJobQueueState } from '@/store/server/jobQueue/types'
|
import { ServerJobQueueState, ServerJobQueueStateJob } from '@/store/server/jobQueue/types'
|
||||||
|
|
||||||
export const actions: ActionTree<ServerJobQueueState, RootState> = {
|
export const actions: ActionTree<ServerJobQueueState, RootState> = {
|
||||||
reset({ commit }) {
|
reset({ commit }) {
|
||||||
@ -28,6 +28,31 @@ export const actions: ActionTree<ServerJobQueueState, RootState> = {
|
|||||||
Vue.$socket.emit('server.job_queue.post_job', { filenames: filenames })
|
Vue.$socket.emit('server.job_queue.post_job', { filenames: filenames })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
changeCount({ getters }, payload: { job_id: string; count: number }) {
|
||||||
|
const filenames: string[] = []
|
||||||
|
const jobs = getters['getJobs']
|
||||||
|
|
||||||
|
jobs.forEach((job: ServerJobQueueStateJob) => {
|
||||||
|
if (job.job_id === payload.job_id) {
|
||||||
|
for (let i = 0; i < payload.count; i++) {
|
||||||
|
filenames.push(job.filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const count = (job.combinedIds?.length ?? 0) + 1
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
filenames.push(job.filename)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Vue.$socket.emit('server.job_queue.post_job', {
|
||||||
|
filenames,
|
||||||
|
reset: true,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
deleteFromQueue(_, job_ids: string[]) {
|
deleteFromQueue(_, job_ids: string[]) {
|
||||||
Vue.$socket.emit('server.job_queue.delete_job', { job_ids })
|
Vue.$socket.emit('server.job_queue.delete_job', { job_ids })
|
||||||
},
|
},
|
||||||
|
@ -10,10 +10,17 @@ export const getters: GetterTree<ServerJobQueueState, any> = {
|
|||||||
|
|
||||||
state.queued_jobs.forEach((queuedJob) => {
|
state.queued_jobs.forEach((queuedJob) => {
|
||||||
const job = { ...queuedJob }
|
const job = { ...queuedJob }
|
||||||
|
|
||||||
|
if (jobs.length && jobs[jobs.length - 1].filename === job.filename) {
|
||||||
|
jobs[jobs.length - 1].combinedIds?.push(job.job_id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const file = rootGetters['files/getFile']('gcodes/' + job.filename)
|
const file = rootGetters['files/getFile']('gcodes/' + job.filename)
|
||||||
if (!file?.metadataPulled)
|
if (!file?.metadataPulled)
|
||||||
Vue.$socket.emit('server.files.metadata', { filename: job.filename }, { action: 'files/getMetadata' })
|
Vue.$socket.emit('server.files.metadata', { filename: job.filename }, { action: 'files/getMetadata' })
|
||||||
job['metadata'] = file
|
job.metadata = file
|
||||||
|
job.combinedIds = []
|
||||||
|
|
||||||
jobs.push(job)
|
jobs.push(job)
|
||||||
})
|
})
|
||||||
@ -21,6 +28,10 @@ export const getters: GetterTree<ServerJobQueueState, any> = {
|
|||||||
return jobs
|
return jobs
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getJobsCount: (state) => {
|
||||||
|
return state.queued_jobs.length
|
||||||
|
},
|
||||||
|
|
||||||
getSmallThumbnail: (state, getters, rootState, rootGetters) => (item: ServerJobQueueStateJob) => {
|
getSmallThumbnail: (state, getters, rootState, rootGetters) => (item: ServerJobQueueStateJob) => {
|
||||||
if (item?.metadata?.thumbnails?.length) {
|
if (item?.metadata?.thumbnails?.length) {
|
||||||
const thumbnail = item?.metadata?.thumbnails.find(
|
const thumbnail = item?.metadata?.thumbnails.find(
|
||||||
|
@ -10,4 +10,5 @@ export interface ServerJobQueueStateJob {
|
|||||||
time_in_queue: number
|
time_in_queue: number
|
||||||
metadata?: any
|
metadata?: any
|
||||||
isFirst?: boolean
|
isFirst?: boolean
|
||||||
|
combinedIds?: string[]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user