diff --git a/src/components/panels/GcodefilesPanel.vue b/src/components/panels/GcodefilesPanel.vue index bd903e684..46ab7d926 100644 --- a/src/components/panels/GcodefilesPanel.vue +++ b/src/components/panels/GcodefilesPanel.vue @@ -235,7 +235,7 @@ class="pa-0 mr-0" @click.stop="select(!isSelected)"> - + @@ -271,6 +271,10 @@ + + + {{ mdiPin }} + {{ item.filename }} @@ -311,6 +315,16 @@ @closeDialog="closeStartPrint" /> + + {{ mdiPin }} + {{ $t('Files.Pin') }} + + + {{ mdiPinOff }} + {{ $t('Files.Unpin') }} + diff --git a/src/components/panels/Status/Gcodefiles.vue b/src/components/panels/Status/Gcodefiles.vue index b65a32e48..16351805f 100644 --- a/src/components/panels/Status/Gcodefiles.vue +++ b/src/components/panels/Status/Gcodefiles.vue @@ -79,6 +79,14 @@ @closeDialog="closeDialog" /> + + {{ mdiPin }} + {{ $t('Files.Pin') }} + + + {{ mdiPinOff }} + {{ $t('Files.Unpin') }} + {{ mdiPlay }} {{ $t('Files.PrintStart') }} @@ -228,7 +236,7 @@ import Component from 'vue-class-component' import { Mixins } from 'vue-property-decorator' import BaseMixin from '@/components/mixins/base' import ControlMixin from '@/components/mixins/control' -import { FileStateGcodefile } from '@/store/files/types' +import { FileStateFile, FileStateGcodefile } from '@/store/files/types' import StartPrintDialog from '@/components/dialogs/StartPrintDialog.vue' import { mdiChevronDown, @@ -243,6 +251,8 @@ import { mdiRenameBox, mdiDelete, mdiCloseThick, + mdiPin, + mdiPinOff, } from '@mdi/js' import Panel from '@/components/ui/Panel.vue' import { defaultBigThumbnailBackground } from '@/store/variables' @@ -278,6 +288,8 @@ export default class StatusPanelGcodefiles extends Mixins(BaseMixin, ControlMixi mdiRenameBox = mdiRenameBox mdiDelete = mdiDelete mdiCloseThick = mdiCloseThick + mdiPin = mdiPin + mdiPinOff = mdiPinOff private deleteDialog = false private showDialogBool = false @@ -499,6 +511,14 @@ export default class StatusPanelGcodefiles extends Mixins(BaseMixin, ControlMixi window.open(href) } + pinFile(item: FileStateFile) { + this.$store.dispatch('gui/addPinnedFile', item.filename) + } + + unPinFile(item: FileStateFile) { + this.$store.dispatch('gui/removePinnedFile', item.filename) + } + editFile(item: FileStateGcodefile) { const pos = item.filename.lastIndexOf('/') const path = pos > 0 ? item.filename.slice(0, pos + 1) : '' diff --git a/src/components/panels/Status/PinnedGcodefiles.vue b/src/components/panels/Status/PinnedGcodefiles.vue new file mode 100644 index 000000000..61982c34e --- /dev/null +++ b/src/components/panels/Status/PinnedGcodefiles.vue @@ -0,0 +1,593 @@ + + + + + diff --git a/src/components/panels/StatusPanel.vue b/src/components/panels/StatusPanel.vue index a193f0a5b..1335d11a8 100644 --- a/src/components/panels/StatusPanel.vue +++ b/src/components/panels/StatusPanel.vue @@ -89,12 +89,18 @@ - {{ $t('Panels.StatusPanel.Status') }} - {{ $t('Panels.StatusPanel.Files') }} + + {{ mdiPrinter3d }} + + + {{ mdiFile }} + + + {{ mdiPin }} + - - {{ $t('Panels.StatusPanel.Jobqueue') }} - + {{ mdiFormatListCheckbox }} + @@ -105,6 +111,9 @@ + + + @@ -121,6 +130,7 @@ import MinSettingsPanel from '@/components/panels/MinSettingsPanel.vue' import KlippyStatePanel from '@/components/panels/KlippyStatePanel.vue' import StatusPanelPrintstatus from '@/components/panels/Status/Printstatus.vue' import StatusPanelGcodefiles from '@/components/panels/Status/Gcodefiles.vue' +import StatusPanelPinnedGcodefiles from '@/components/panels/Status/PinnedGcodefiles.vue' import StatusPanelJobqueue from '@/components/panels/Status/Jobqueue.vue' import StatusPanelExcludeObject from '@/components/panels/Status/ExcludeObject.vue' import StatusPanelPrintstatusThumbnail from '@/components/panels/Status/PrintstatusThumbnail.vue' @@ -139,6 +149,10 @@ import { mdiCloseCircle, mdiLayersPlus, mdiDotsVertical, + mdiFile, + mdiPin, + mdiFormatListCheckbox, + mdiPrinter3d, } from '@mdi/js' import { PrinterStateMacro } from '@/store/printer/types' @@ -149,6 +163,7 @@ import { PrinterStateMacro } from '@/store/printer/types' Panel, StatusPanelExcludeObject, StatusPanelGcodefiles, + StatusPanelPinnedGcodefiles, StatusPanelJobqueue, StatusPanelPrintstatus, StatusPanelPrintstatusThumbnail, @@ -161,6 +176,10 @@ export default class StatusPanel extends Mixins(BaseMixin) { mdiCloseCircle = mdiCloseCircle mdiDotsVertical = mdiDotsVertical mdiAlertOutline = mdiAlertOutline + mdiPin = mdiPin + mdiFile = mdiFile + mdiFormatListCheckbox = mdiFormatListCheckbox + mdiPrinter3d = mdiPrinter3d private boolShowObjects = false private boolShowPauseAtLayer = false diff --git a/src/locales/en.json b/src/locales/en.json index 643ccf94a..74bbe9872 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -223,6 +223,7 @@ "NewDirectory": "New Directory", "NozzleDiameter": "Nozzle Diameter", "ObjectHeight": "Object Height", + "Pin": "Pin", "Preheat": "Preheat", "PrintedFiles": "Printed files", "PrintStart": "Print start", @@ -242,6 +243,7 @@ "SuccessfullyRenamed": "Successfully renamed {filename}.", "SuccessfullyUploaded": "Upload of {filename} successful!", "Total": "Total", + "Unpin": "Unpin", "UploadNewGcode": "Upload new G-Code", "Used": "Used", "View3D": "View 3D" diff --git a/src/plugins/helpers.ts b/src/plugins/helpers.ts index 0641ec209..9213b4a8d 100644 --- a/src/plugins/helpers.ts +++ b/src/plugins/helpers.ts @@ -167,6 +167,9 @@ export const sortFiles = (items: FileStateFile[] | null, sortBy: string[], sortD // Then make sure directories come first items.sort((a: any, b: any) => (a.isDirectory === b.isDirectory ? 0 : a.isDirectory ? -1 : 1)) + + // Place pinned files on top + items.sort((a: any, b: any) => (a.isPinned === b.isPinned ? 0 : a.isPinned ? -1 : 1)) } return items ?? [] diff --git a/src/store/files/actions.ts b/src/store/files/actions.ts index a67e1f114..73dddcf44 100644 --- a/src/store/files/actions.ts +++ b/src/store/files/actions.ts @@ -11,6 +11,7 @@ import { RootState } from '@/store/types' import i18n from '@/plugins/i18n' import { hiddenDirectories, validGcodeExtensions } from '@/store/variables' import axios from 'axios' +import store from '@/store' export const actions: ActionTree = { reset({ commit }) { @@ -279,6 +280,7 @@ export const actions: ActionTree = { if (payload.error) { Vue.$toast.error(payload.error.message) } else { + store.dispatch('gui/removePinnedFile', payload.item.path) const delPath = payload.item.path.substr(payload.item.path.lastIndexOf('/') + 1) const fileExtension = payload.item.path.substr(payload.item.path.lastIndexOf('.') + 1) diff --git a/src/store/files/getters.ts b/src/store/files/getters.ts index e55b4deae..b292b58cd 100644 --- a/src/store/files/getters.ts +++ b/src/store/files/getters.ts @@ -8,6 +8,7 @@ import { import { GetterTree } from 'vuex' import { FileState, FileStateFile, FileStateGcodefile } from '@/store/files/types' import { ServerHistoryStateJob } from '@/store/server/history/types' +import store from '@/store' // eslint-disable-next-line export const getters: GetterTree = { @@ -183,6 +184,7 @@ export const getters: GetterTree = { } } + tmp.isPinned = store?.state?.gui?.view.gcodefiles.pinnedFiles.some((f) => f === tmp.filename) || false if (boolShowPrintedFiles) output.push(tmp) else if (tmp.count_printed === 0) output.push(tmp) }) diff --git a/src/store/files/types.ts b/src/store/files/types.ts index 55197c159..23c593526 100644 --- a/src/store/files/types.ts +++ b/src/store/files/types.ts @@ -43,6 +43,7 @@ export interface FileStateFile { } export interface FileStateGcodefile extends FileStateFile { + isPinned?: boolean preheat_gcode: string | null small_thumbnail: string | null big_thumbnail: string | null diff --git a/src/store/gui/actions.ts b/src/store/gui/actions.ts index ea4dd7363..9ee81338d 100644 --- a/src/store/gui/actions.ts +++ b/src/store/gui/actions.ts @@ -199,6 +199,22 @@ export const actions: ActionTree = { Vue.$socket.emit('server.database.post_item', { namespace: 'mainsail', key: keyName, value: newState }) }, + addPinnedFile({ commit, dispatch, state }, value) { + commit('addPinnedFile', value) + dispatch('updateSettings', { + keyName: 'gcodefiles.pinnedFiles', + newVal: state.view.gcodefiles.pinnedFiles, + }) + }, + + removePinnedFile({ commit, dispatch, state }, value) { + commit('removePinnedFile', value) + dispatch('updateSettings', { + keyName: 'gcodefiles.pinnedFiles', + newVal: state.view.gcodefiles.pinnedFiles, + }) + }, + setGcodefilesMetadata({ commit, dispatch, state }, data) { commit('setGcodefilesMetadata', data) dispatch('updateSettings', { diff --git a/src/store/gui/index.ts b/src/store/gui/index.ts index 1e192c60f..785b335b3 100644 --- a/src/store/gui/index.ts +++ b/src/store/gui/index.ts @@ -214,6 +214,7 @@ export const getDefaultState = (): GuiState => { ], currentPath: '', selectedFiles: [], + pinnedFiles: [], }, heightmap: { probed: true, diff --git a/src/store/gui/mutations.ts b/src/store/gui/mutations.ts index 2fcc87e50..2be0a8f34 100644 --- a/src/store/gui/mutations.ts +++ b/src/store/gui/mutations.ts @@ -43,6 +43,16 @@ export const mutations: MutationTree = { Vue.set(state.view.gcodefiles, 'hideMetadataColumns', array) }, + addPinnedFile(state, value) { + const array = [value, ...state.view.gcodefiles.pinnedFiles] + Vue.set(state.view.gcodefiles, 'pinnedFiles', array) + }, + + removePinnedFile(state, value) { + const array = state.view.gcodefiles.pinnedFiles.filter((n) => n !== value) + Vue.set(state.view.gcodefiles, 'pinnedFiles', array) + }, + setGcodefilesShowHiddenFiles(state, value) { Vue.set(state.view.gcodefiles, 'showHiddenFiles', value) }, diff --git a/src/store/gui/types.ts b/src/store/gui/types.ts index 930961603..a8a96a2e1 100644 --- a/src/store/gui/types.ts +++ b/src/store/gui/types.ts @@ -150,6 +150,7 @@ export interface GuiState { orderMetadataColumns: string[] currentPath: string selectedFiles: FileStateGcodefile[] + pinnedFiles: string[] } heightmap: { probed: boolean