Skip to content

Commit ae6e4eb

Browse files
committed
feat: show job queue totals
Signed-off-by: Pedro Lamas <[email protected]>
1 parent c799808 commit ae6e4eb

File tree

16 files changed

+241
-91
lines changed

16 files changed

+241
-91
lines changed

src/components/widgets/filesystem/FileSystem.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
<script lang="ts">
137137
import { Component, Prop, Mixins, Watch } from 'vue-property-decorator'
138138
import { SocketActions } from '@/api/socketActions'
139-
import type { AppDirectory, AppFile, AppFileWithMeta, FileFilterType, FileBrowserEntry, RootProperties } from '@/store/files/types'
139+
import type { AppDirectory, AppFile, AppFileWithMeta, FileFilterType, FileBrowserEntry, RootProperties, MoonrakerPathContent } from '@/store/files/types'
140140
import StateMixin from '@/mixins/state'
141141
import FilesMixin from '@/mixins/files'
142142
import ServicesMixin from '@/mixins/services'
@@ -621,9 +621,9 @@ export default class FileSystem extends Mixins(StateMixin, FilesMixin, ServicesM
621621
if (!this.disabled) {
622622
this.currentPath = path
623623
624-
const directoryLoaded = path in this.$store.state.files.pathFiles
624+
const pathContent: MoonrakerPathContent | undefined = this.$store.state.files.pathContent[path]
625625
626-
if (!directoryLoaded) {
626+
if (pathContent == null || pathContent.partial === true) {
627627
this.handleRefresh()
628628
}
629629
}

src/components/widgets/filesystem/FileSystemBrowser.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
>
3131
<template #item="{ headers, item, isSelected, select }">
3232
<app-data-table-row
33+
:key="item.name"
3334
:headers="headers"
3435
:item="item"
3536
:is-selected="isSelected && item.name !== '..'"
@@ -62,7 +63,7 @@
6263
class="no-pointer-events"
6364
>
6465
<v-icon
65-
v-if="!item.thumbnails || !item.thumbnails.length"
66+
v-if="!item.thumbnails?.length"
6667
:small="dense"
6768
:color="(item.type === 'file') ? 'grey' : 'primary'"
6869
>

src/components/widgets/history/JobHistory.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
>
4343
<template #item="{ headers, item }">
4444
<app-data-table-row
45+
:key="item.job_id"
4546
:headers="headers"
4647
:item="item"
4748
:class="{
@@ -54,7 +55,7 @@
5455
<v-icon
5556
v-if="!item.exists"
5657
class="mr-2"
57-
color="secondary"
58+
color="grey"
5859
>
5960
$fileCancel
6061
</v-icon>
@@ -63,7 +64,7 @@
6364
<v-icon
6465
v-else-if="!item.metadata.thumbnails?.length"
6566
class="mr-2"
66-
color="secondary"
67+
color="grey"
6768
>
6869
$file
6970
</v-icon>

src/components/widgets/job-queue/JobQueue.vue

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
<job-queue-browser
2525
v-model="selected"
26+
:jobs="jobs"
2627
:headers="headers"
2728
:dense="dense"
2829
:bulk-actions="bulkActions"
@@ -57,7 +58,7 @@
5758

5859
<script lang="ts">
5960
import { SocketActions } from '@/api/socketActions'
60-
import type { QueuedJob } from '@/store/jobQueue/types'
61+
import type { QueuedJobWithAppFile } from '@/store/jobQueue/types'
6162
import { Component, Prop, Vue } from 'vue-property-decorator'
6263
import JobQueueToolbar from './JobQueueToolbar.vue'
6364
import JobQueueBulkActions from './JobQueueBulkActions.vue'
@@ -90,7 +91,7 @@ export default class JobQueue extends Vue {
9091
job: null
9192
}
9293
93-
selected: QueuedJob[] = []
94+
selected: QueuedJobWithAppFile[] = []
9495
overlay = false
9596
9697
@Prop({ type: Boolean })
@@ -99,6 +100,12 @@ export default class JobQueue extends Vue {
99100
@Prop({ type: Boolean })
100101
readonly bulkActions?: boolean
101102
103+
get jobs (): QueuedJobWithAppFile[] {
104+
this.selected = []
105+
106+
return this.$store.getters['jobQueue/getQueuedJobsWithFiles']
107+
}
108+
102109
get configurableHeaders (): AppDataTableHeader[] {
103110
const headers: AppDataTableHeader[] = [
104111
{
@@ -129,6 +136,12 @@ export default class JobQueue extends Vue {
129136
sortable: false,
130137
width: '24px'
131138
},
139+
{
140+
text: '',
141+
value: 'data-table-icons',
142+
sortable: false,
143+
width: this.dense ? '28px' : '56px'
144+
},
132145
{
133146
text: this.$tc('app.general.table.header.name'),
134147
value: 'filename',
@@ -139,7 +152,7 @@ export default class JobQueue extends Vue {
139152
]
140153
}
141154
142-
handleRowClick (item: QueuedJob, event: MouseEvent) {
155+
handleRowClick (item: QueuedJobWithAppFile, event: MouseEvent) {
143156
if (this.contextMenuState.open) {
144157
this.contextMenuState.open = false
145158
@@ -181,22 +194,22 @@ export default class JobQueue extends Vue {
181194
SocketActions.serverJobQueueStatus()
182195
}
183196
184-
handleRemove (jobs: QueuedJob | QueuedJob[]) {
197+
handleRemove (jobs: QueuedJobWithAppFile | QueuedJobWithAppFile[]) {
185198
const jobIds = Array.isArray(jobs)
186199
? jobs.map(job => job.job_id)
187200
: [jobs.job_id]
188201
189202
SocketActions.serverJobQueueDeleteJobs(jobIds)
190203
}
191204
192-
handleMultiplyDialog (jobs: QueuedJob | QueuedJob[]) {
205+
handleMultiplyDialog (jobs: QueuedJobWithAppFile | QueuedJobWithAppFile[]) {
193206
this.multiplyJobDialogState = {
194207
open: true,
195208
job: jobs
196209
}
197210
}
198211
199-
handleMultiply (jobs: QueuedJob | QueuedJob[], copies: number) {
212+
handleMultiply (jobs: QueuedJobWithAppFile | QueuedJobWithAppFile[], copies: number) {
200213
const filenames = Array.isArray(jobs)
201214
? jobs.map(job => job.filename)
202215
: [jobs.filename]

src/components/widgets/job-queue/JobQueueBrowser.vue

Lines changed: 130 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<template>
22
<div class="file-system">
33
<app-draggable
4-
v-model="jobs"
4+
v-model="jobsWithKey"
55
:options="{
66
group: 'jobQueue',
77
}"
@@ -23,27 +23,95 @@
2323
disable-pagination
2424
disable-sort
2525
fixed-header
26-
@click:row="handleRowClick"
27-
@contextmenu:row.prevent="handleContextMenu"
2826
>
29-
<template #[`item.data-table-select`]="{ isSelected, select }">
30-
<v-simple-checkbox
31-
:value="isSelected"
32-
class="mt-1"
33-
@click.stop="select(!isSelected)"
34-
/>
27+
<template #item="{ headers, item, isSelected, select }">
28+
<app-data-table-row
29+
:key="item.key"
30+
:headers="headers"
31+
:item="item"
32+
:is-selected="isSelected"
33+
:class="{
34+
'v-data-table__inactive': item.file == null
35+
}"
36+
@click.prevent="$emit('row-click', item, $event)"
37+
@contextmenu.prevent="$emit('row-click', item, $event)"
38+
>
39+
<template #[`item.data-table-select`]>
40+
<v-simple-checkbox
41+
:value="isSelected"
42+
class="mt-1"
43+
@click.stop="select(!isSelected)"
44+
/>
45+
</template>
46+
47+
<template #[`item.handle`]>
48+
<app-drag-icon />
49+
</template>
50+
51+
<template #[`item.data-table-icons`]>
52+
<v-layout
53+
justify-center
54+
class="no-pointer-events"
55+
>
56+
<v-icon
57+
v-if="item.file == null"
58+
:small="dense"
59+
color="grey"
60+
>
61+
$fileCancel
62+
</v-icon>
63+
<v-icon
64+
v-else-if="!item.file.thumbnails?.length"
65+
:small="dense"
66+
color="grey"
67+
>
68+
$file
69+
</v-icon>
70+
<img
71+
v-else
72+
class="file-icon-thumb"
73+
:src="getThumbUrl(item.file, 'gcodes', getFilePaths(item.filename).path, dense != true, item.file.modified)"
74+
:width="dense ? 16 : 32"
75+
>
76+
</v-layout>
77+
</template>
78+
79+
<template #[`item.filename`]="{ value }">
80+
{{ value }}
81+
</template>
82+
83+
<template #[`item.time_added`]="{ value }">
84+
{{ $filters.formatAbsoluteDateTime(value * 1000) }}
85+
</template>
86+
87+
<template #[`item.time_in_queue`]="{ value }">
88+
{{ $filters.formatCounterSeconds(value) }}
89+
</template>
90+
</app-data-table-row>
3591
</template>
36-
<template #[`item.handle`]>
37-
<app-drag-icon />
38-
</template>
39-
<template #[`item.filename`]="{ value }">
40-
{{ value }}
41-
</template>
42-
<template #[`item.time_added`]="{ value }">
43-
{{ $filters.formatAbsoluteDateTime(value * 1000) }}
44-
</template>
45-
<template #[`item.time_in_queue`]="{ value }">
46-
{{ $filters.formatCounterSeconds(value) }}
92+
93+
<template #footer>
94+
<div class="v-data-footer py-2">
95+
<v-chip
96+
small
97+
class="mr-1"
98+
>
99+
{{ $t('app.job_queue.label.filament') }}: {{ $filters.getReadableLengthString(jobTotals.filament_length) }} / {{ $filters.getReadableWeightString(jobTotals.filament_weight) }}
100+
</v-chip>
101+
<v-chip
102+
small
103+
class="mr-1"
104+
>
105+
{{ $t('app.job_queue.label.print_time') }}: {{ $filters.formatCounterSeconds(jobTotals.time) }}
106+
</v-chip>
107+
108+
<v-chip
109+
small
110+
class="mr-1"
111+
>
112+
{{ $t('app.job_queue.label.eta') }}: {{ $filters.formatDateTime(Date.now() + jobTotals.time * 1000) }}
113+
</v-chip>
114+
</div>
47115
</template>
48116
</v-data-table>
49117
</app-draggable>
@@ -52,19 +120,30 @@
52120

53121
<script lang="ts">
54122
import { Component, Mixins, Prop, VModel } from 'vue-property-decorator'
55-
import type { QueuedJob } from '@/store/jobQueue/types'
123+
import type { QueuedJobWithAppFile } from '@/store/jobQueue/types'
56124
import { SocketActions } from '@/api/socketActions'
57125
import StateMixin from '@/mixins/state'
58-
import type { DataTableHeader, DataTableItemProps } from 'vuetify'
126+
import type { DataTableHeader } from 'vuetify'
127+
import FilesMixin from '@/mixins/files'
128+
import getFilePaths from '@/util/get-file-paths'
129+
130+
type JobTotals = {
131+
filament_length: number,
132+
filament_weight: number,
133+
time: number
134+
}
59135
60-
type QueueJobWithKey = QueuedJob & {
136+
type QueueJobWithKey = QueuedJobWithAppFile & {
61137
key: string
62138
}
63139
64140
@Component({})
65-
export default class JobQueueBrowser extends Mixins(StateMixin) {
141+
export default class JobQueueBrowser extends Mixins(StateMixin, FilesMixin) {
66142
@VModel({ type: Array, default: () => [] })
67-
selected!: QueuedJob[]
143+
selected!: QueuedJobWithAppFile[]
144+
145+
@Prop({ type: Array, required: true })
146+
readonly jobs!: QueuedJobWithAppFile[]
68147
69148
@Prop({ type: Boolean })
70149
readonly dense?: boolean
@@ -75,13 +154,17 @@ export default class JobQueueBrowser extends Mixins(StateMixin) {
75154
@Prop({ type: Array, required: true })
76155
readonly headers!: DataTableHeader[]
77156
78-
get jobs (): QueuedJob[] {
79-
this.selected = []
157+
get jobsWithKey (): QueueJobWithKey[] {
158+
const refresh = Date.now()
80159
81-
return this.$store.state.jobQueue.queued_jobs
160+
return this.jobs
161+
.map(job => ({
162+
...job,
163+
key: `${job.job_id}-${refresh}`
164+
}))
82165
}
83166
84-
set jobs (value: QueuedJob[]) {
167+
set jobsWithKey (value: QueueJobWithKey[]) {
85168
const filenames = value
86169
.map(job => job.filename)
87170
@@ -93,22 +176,28 @@ export default class JobQueueBrowser extends Mixins(StateMixin) {
93176
}
94177
}
95178
96-
get jobsWithKey (): QueueJobWithKey[] {
97-
const refresh = Date.now()
179+
get jobTotals (): JobTotals {
180+
return this.jobs.reduce<JobTotals>((totals, job) => {
181+
if (job.file) {
182+
if ('filament_total' in job.file) {
183+
totals.filament_length += job.file.filament_total ?? 0
184+
}
98185
99-
return this.jobs
100-
.map(job => ({
101-
...job,
102-
key: `${job.job_id}-${refresh}`
103-
}))
104-
}
186+
if ('filament_weight_total' in job.file) {
187+
totals.filament_weight += job.file.filament_weight_total ?? 0
188+
}
189+
190+
if ('estimated_time' in job.file) {
191+
totals.time += job.file.estimated_time ?? 0
192+
}
193+
}
105194
106-
handleRowClick (_data: unknown, props: DataTableItemProps, event: MouseEvent) {
107-
this.$emit('row-click', props.item, event)
195+
return totals
196+
}, { filament_length: 0, filament_weight: 0, time: 0 })
108197
}
109198
110-
handleContextMenu (event: MouseEvent, props: DataTableItemProps) {
111-
this.$emit('row-click', props.item, event)
199+
getFilePaths (filename: string) {
200+
return getFilePaths(filename, 'gcodes')
112201
}
113202
}
114203
</script>

src/components/widgets/spoolman/SpoolSelectionDialog.vue

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@
113113
>
114114
<template #item="{ headers, item }">
115115
<app-data-table-row
116+
:key="item.id"
116117
:headers="headers"
117118
:item="item"
118119
:is-selected="item.id === selectedSpoolId"
@@ -242,15 +243,8 @@ export default class SpoolSelectionDialog extends Mixins(StateMixin, BrowserMixi
242243
this.selectedSpoolId = macro?.variables.spool_id ?? null
243244
}
244245
245-
if (this.currentFileName) {
246-
const { rootPath } = getFilePaths(this.currentFileName, 'gcodes')
247-
248-
const directoryLoaded = rootPath in this.$store.state.files.pathFiles
249-
250-
// Load the containing the currently printing file if we haven't done that already
251-
if (!directoryLoaded) {
252-
SocketActions.serverFilesGetDirectory(rootPath)
253-
}
246+
if (this.currentFileName && this.currentFile == null) {
247+
SocketActions.serverFilesMetadata(this.currentFileName)
254248
}
255249
256250
if (this.hasDeviceCamera && this.preferDeviceCamera) {

0 commit comments

Comments
 (0)