Merge branch 'master' into develop

# Conflicts:
#	package.json
#	src/App.vue
#	src/components/panels/ControlPanel.vue
#	src/components/panels/FarmPrinterPanel.vue
#	src/pages/Files.vue
#	src/store/farm/printer/getters.ts
#	src/store/farm/printer/mutations.ts
#	src/store/files/getters.ts
#	src/store/printer/mutations.ts
#	src/store/server/history/getters.ts
#	src/store/socket/getters.ts
This commit is contained in:
Stefan Dej 2021-09-09 00:27:19 +02:00
commit 7ae0fe2209
14 changed files with 117 additions and 72 deletions

View File

@ -23,7 +23,7 @@
<the-sidebar></the-sidebar> <the-sidebar></the-sidebar>
<the-topbar></the-topbar> <the-topbar></the-topbar>
<v-main id="content" :style="{ background: mainBackground }"> <v-main id="content" :style="mainStyle">
<v-container fluid id="page-container" class="container px-3 px-sm-6 py-sm-6 mx-auto"> <v-container fluid id="page-container" class="container px-3 px-sm-6 py-sm-6 mx-auto">
<router-view></router-view> <router-view></router-view>
</v-container> </v-container>
@ -70,8 +70,18 @@ export default class App extends Mixins(BaseMixin) {
return this.$store.getters['files/getMainBackground'] return this.$store.getters['files/getMainBackground']
} }
get customStylesheet(): string | null { get mainStyle() {
return this.$store.getters['files/getCustomStylesheet'] let style = ''
if (this.mainBackground !== null) {
style = 'background-image: url('+this.mainBackground+');'
}
return style
}
get customStylesheet () {
return this.$store.getters["files/getCustomStylesheet"]
} }
get customFavicons(): string | null { get customFavicons(): string | null {

View File

@ -8,6 +8,10 @@ export default class BaseMixin extends Vue {
return this.$store.getters['socket/getUrl'] return this.$store.getters['socket/getUrl']
} }
get hostUrl(): boolean {
return this.$store.getters['socket/getHostUrl']
}
get remoteMode() { get remoteMode() {
return this.$store.state.socket.remoteMode return this.$store.state.socket.remoteMode
} }

View File

@ -31,7 +31,7 @@
<span class="subheading"><v-icon left>mdi-printer-3d</v-icon>{{ printer_name }}</span> <span class="subheading"><v-icon left>mdi-printer-3d</v-icon>{{ printer_name }}</span>
</v-toolbar-title> </v-toolbar-title>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-item-group v-if="this.printer_webcams.length"> <v-item-group v-if="printer.socket.isConnected && this.printer_webcams.length">
<v-menu :offset-y="true" title="Webcam"> <v-menu :offset-y="true" title="Webcam">
<template v-slot:activator="{ on, attrs }"> <template v-slot:activator="{ on, attrs }">
<v-btn small class="px-2 minwidth-0" color="grey darken-3" v-bind="attrs" v-on="on"> <v-btn small class="px-2 minwidth-0" color="grey darken-3" v-bind="attrs" v-on="on">
@ -64,11 +64,12 @@
<template v-slot:default="{ hover }"> <template v-slot:default="{ hover }">
<div> <div>
<v-img <v-img
height="200px" :height="imageHeight"
:src="printer_image" :src="printer_image"
class="d-flex align-end" class="d-flex align-end"
ref="imageDiv"
> >
<div v-if="currentCamName !== 'off' && currentWebcam" class="webcamContainer"> <div v-if="printer.socket.isConnected && currentCamName !== 'off' && currentWebcam" class="webcamContainer">
<template v-if="'service' in currentWebcam && currentWebcam.service === 'mjpegstreamer'"> <template v-if="'service' in currentWebcam && currentWebcam.service === 'mjpegstreamer'">
<webcam-mjpegstreamer :cam-settings="currentWebcam"></webcam-mjpegstreamer> <webcam-mjpegstreamer :cam-settings="currentWebcam"></webcam-mjpegstreamer>
</template> </template>
@ -79,7 +80,12 @@
<v-card-title class="white--text py-2" style="background-color: rgba(0,0,0,0.3); backdrop-filter: blur(3px);"> <v-card-title class="white--text py-2" style="background-color: rgba(0,0,0,0.3); backdrop-filter: blur(3px);">
<v-row> <v-row>
<v-col class="col-auto pr-0 d-flex align-center" style="width: 58px"> <v-col class="col-auto pr-0 d-flex align-center" style="width: 58px">
<img class="my-auto" :src="printer_logo" style="width: 100%;" /> <template v-if="printer_logo">
<img :src="printer_logo" style="width: 100%;" class="my-auto" alt="Logo" />
</template>
<template v-else>
<mainsail-logo :color="printerLogoColor" style="width: 100%;" class="my-auto"></mainsail-logo>
</template>
</v-col> </v-col>
<v-col class="col" style="width: 100px"> <v-col class="col" style="width: 100px">
<h3 class="font-weight-regular">{{ printer_status }}</h3> <h3 class="font-weight-regular">{{ printer_status }}</h3>
@ -100,7 +106,7 @@
</v-card-text> </v-card-text>
<v-fade-transition> <v-fade-transition>
<v-overlay v-if="hover" absolute :z-index="4" > <v-overlay v-if="hover" absolute :z-index="4" >
<v-btn color="primary" @click="clickPrinter">{{ $t("Panels.FarmPrinterPanel.SwitchToPrinter") }}</v-btn> <v-btn color="primary" @click="clickPrinter">{{ printer.socket.isConnected ? $t("Panels.FarmPrinterPanel.SwitchToPrinter") : $t("Panels.FarmPrinterPanel.ReconnectToPrinter") }}</v-btn>
</v-overlay> </v-overlay>
</v-fade-transition> </v-fade-transition>
</div> </div>
@ -110,23 +116,25 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import {Component, Mixins, Prop, Ref, Vue} from 'vue-property-decorator'
import { Component, Mixins, Prop } from 'vue-property-decorator'
import BaseMixin from '@/components/mixins/base' import BaseMixin from '@/components/mixins/base'
import { FarmPrinterState } from '@/store/farm/printer/types' import { FarmPrinterState } from '@/store/farm/printer/types'
import Mjpegstreamer from '@/components/webcams/Mjpegstreamer.vue' import Mjpegstreamer from '@/components/webcams/Mjpegstreamer.vue'
import MjpegstreamerAdaptive from '@/components/webcams/MjpegstreamerAdaptive.vue' import MjpegstreamerAdaptive from '@/components/webcams/MjpegstreamerAdaptive.vue'
import MainsailLogo from '@/components/ui/MainsailLogo.vue'
@Component({ @Component({
components: { components: {
'webcam-mjpegstreamer': Mjpegstreamer, 'webcam-mjpegstreamer': Mjpegstreamer,
'webcam-mjpegstreamer-adaptive': MjpegstreamerAdaptive, 'webcam-mjpegstreamer-adaptive': MjpegstreamerAdaptive,
'mainsail-logo': MainsailLogo
} }
}) })
export default class FarmPrinterPanel extends Mixins(BaseMixin) { export default class FarmPrinterPanel extends Mixins(BaseMixin) {
private imageHeight = 200;
@Prop({ type: Object, required: true }) printer!: FarmPrinterState @Prop({ type: Object, required: true }) printer!: FarmPrinterState
@Ref() readonly imageDiv!: Vue
get printerUrl() { get printerUrl() {
const thisUrl = window.location.href.split('/') const thisUrl = window.location.href.split('/')
@ -170,6 +178,10 @@ export default class FarmPrinterPanel extends Mixins(BaseMixin) {
return this.$store.getters['farm/'+this.printer._namespace+'/getLogo'] return this.$store.getters['farm/'+this.printer._namespace+'/getLogo']
} }
get printerLogoColor() {
return this.$store.getters["farm/"+this.printer._namespace+"/getLogoColor"]
}
get printer_position() { get printer_position() {
return this.$store.getters['farm/'+this.printer._namespace+'/getPosition'] return this.$store.getters['farm/'+this.printer._namespace+'/getPosition']
} }
@ -196,5 +208,20 @@ export default class FarmPrinterPanel extends Mixins(BaseMixin) {
this.$store.dispatch('farm/'+this.printer._namespace+'/reconnect') this.$store.dispatch('farm/'+this.printer._namespace+'/reconnect')
} }
mounted() {
window.addEventListener("resize", this.resize)
this.resize()
}
beforeDestroy() {
window.addEventListener("resize", this.resize)
}
resize() {
if (this.imageDiv?.$el?.clientWidth) {
this.imageHeight = Math.round(this.imageDiv.$el.clientWidth / 3 * 2)
} else this.imageHeight = 200
}
} }
</script> </script>

View File

@ -35,9 +35,7 @@ export default class Mjpegstreamer extends Mixins(BaseMixin) {
get url() { get url() {
const baseUrl = this.camSettings.url const baseUrl = this.camSettings.url
const hostUrl = new URL(this.printerUrl === undefined ? document.URL : this.printerUrl) const url = new URL(baseUrl, this.printerUrl === undefined ? this.hostUrl.toString() : this.printerUrl)
const url = new URL(baseUrl, hostUrl.origin)
url.searchParams.append('bypassCache', this.refresh.toString()) url.searchParams.append('bypassCache', this.refresh.toString())
return decodeURIComponent(url.toString()) return decodeURIComponent(url.toString())

View File

@ -16,7 +16,7 @@
<v-progress-circular indeterminate color="primary"></v-progress-circular> <v-progress-circular indeterminate color="primary"></v-progress-circular>
</div> </div>
<canvas ref="mjpegstreamerAdaptive" width="600" height="400" :style="webcamStyle" :class="'webcamImage '+(isLoaded ? '' : 'hiddenWebcam')"></canvas> <canvas ref="mjpegstreamerAdaptive" width="600" height="400" :style="webcamStyle" :class="'webcamImage '+(isLoaded ? '' : 'hiddenWebcam')"></canvas>
<span class="webcamFpsOutput" v-if="isLoaded && showFps">{{ $t('Panels.WebcamPanel.FPS')}}: {{ currentFPS }}</span> <span class="webcamFpsOutput" v-if="isLoaded && showFps">{{ $t('Panels.WebcamPanel.FPS')}}: {{ fpsOutput }}</span>
</div> </div>
</template> </template>
@ -57,6 +57,10 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin) {
return '' return ''
} }
get fpsOutput() {
return (this.currentFPS < 10) ? '0'+this.currentFPS.toString() : this.currentFPS
}
visibilityChanged(isVisible: boolean) { visibilityChanged(isVisible: boolean) {
this.isVisible = isVisible this.isVisible = isVisible
if (isVisible) this.refreshFrame() if (isVisible) this.refreshFrame()
@ -95,15 +99,13 @@ export default class MjpegstreamerAdaptive extends Mixins(BaseMixin) {
async setFrame() { async setFrame() {
const baseUrl = this.camSettings.url const baseUrl = this.camSettings.url
const hostUrl = new URL(this.printerUrl === undefined ? document.URL : this.printerUrl) const url = new URL(baseUrl, this.printerUrl === undefined ? this.hostUrl.toString() : this.printerUrl)
const url = new URL(baseUrl, hostUrl.origin)
url.searchParams.append('bypassCache', this.refresh.toString()) url.searchParams.append('bypassCache', this.refresh.toString())
url.searchParams.set('action', 'snapshot') url.searchParams.set('action', 'snapshot')
this.request_start_time = performance.now() this.request_start_time = performance.now()
this.currentFPS = Math.round(1000 / this.time) this.currentFPS = (this.time > 0) ? Math.round(1000 / this.time) : 0
let canvas = this.$refs.mjpegstreamerAdaptive let canvas = this.$refs.mjpegstreamerAdaptive
if (canvas) { if (canvas) {

View File

@ -32,9 +32,7 @@ export default class Uv4lMjpeg extends Mixins(BaseMixin) {
get url() { get url() {
const baseUrl = this.camSettings.url const baseUrl = this.camSettings.url
const hostUrl = new URL(this.printerUrl === null ? document.URL : this.printerUrl) const url = new URL(baseUrl, this.printerUrl === null ? this.hostUrl.toString() : this.printerUrl)
const url = new URL(baseUrl, hostUrl.origin)
return decodeURIComponent(url.toString()) return decodeURIComponent(url.toString())
} }

View File

@ -256,7 +256,8 @@
}, },
"FarmPrinterPanel": { "FarmPrinterPanel": {
"WebcamOff": "Off", "WebcamOff": "Off",
"SwitchToPrinter": "Switch to Printer" "SwitchToPrinter": "Switch to Printer",
"ReconnectToPrinter": "Reconnect"
}, },
"KlippyStatePanel": { "KlippyStatePanel": {
"KlippyState": "Klippy-State", "KlippyState": "Klippy-State",

View File

@ -496,7 +496,7 @@ export default class PageFiles extends Mixins(BaseMixin) {
} }
private input_rules = [ private input_rules = [
(value: string) => value.indexOf(' ') === -1 || 'Name contain spaces!' (value: string) => value.indexOf(' ') === -1 || 'Name contains spaces!'
] ]
get headers() { get headers() {
@ -792,11 +792,11 @@ export default class PageFiles extends Mixins(BaseMixin) {
} }
created() { created() {
this.$socket.emit('server.files.get_directory', { path: this.currentPath }, { action: 'files/getDirectory' })
this.loadPath() this.loadPath()
} }
loadPath() { loadPath() {
this.$socket.emit('server.files.get_directory', { path: this.currentPath }, { action: 'files/getDirectory' })
let dirArray = this.currentPath.split('/') let dirArray = this.currentPath.split('/')
this.files = findDirectory(this.filetree, dirArray) this.files = findDirectory(this.filetree, dirArray)
if (this.files !== null) { if (this.files !== null) {
@ -804,46 +804,27 @@ export default class PageFiles extends Mixins(BaseMixin) {
this.files = this.files.filter(file => file.filename !== 'thumbs' && file.filename.substr(0, 1) !== '.') this.files = this.files.filter(file => file.filename !== 'thumbs' && file.filename.substr(0, 1) !== '.')
} }
if (!this.showPrintedFiles) { if (!this.showPrintedFiles) {
this.files = this.files.filter(file => this.$store.getters['server/history/getPrintStatus']({ this.files = this.files.filter(file => {
filename: (this.currentPath+'/'+file.filename).substr(7), if (file.isDirectory) return true
modified: file.modified.getTime() else {
}) !== 'completed') return (this.$store.getters["server/history/getPrintStatusByFilename"](
(this.currentPath+"/"+file.filename).substr(7),
file.modified.getTime()
) !== 'completed')
}
})
} }
} }
} }
@Watch('filetree', { deep: true }) @Watch('filetree', { deep: true })
filetreeChanged(newVal: FileStateFile[]) { filetreeChanged() {
let dirArray = this.currentPath.split('/') this.loadPath()
this.files = findDirectory(newVal, dirArray)
if (this.files?.length && !this.showHiddenFiles) {
this.files = this.files.filter(file => file.filename !== 'thumbs' && file.filename.substr(0, 1) !== '.')
}
if (this.files?.length && !this.showPrintedFiles) {
this.files = this.files.filter(file => this.$store.getters['server/history/getPrintStatus']({
filename: (this.currentPath+'/'+file.filename).substr(7),
modified: new Date(file.modified).getTime()
}) !== 'completed')
}
} }
@Watch('currentPath') @Watch('currentPath')
currentPathChanged(newVal: string) { currentPathChanged() {
let dirArray = newVal.split('/') this.loadPath()
this.files = findDirectory(this.filetree, dirArray)
if (this.files?.length && !this.showHiddenFiles) {
this.files = this.files.filter(file => file.filename !== 'thumbs' && file.filename.substr(0, 1) !== '.')
}
if (this.files?.length && !this.showPrintedFiles) {
this.files = this.files.filter(file => this.$store.getters['server/history/getPrintStatus']({
filename: (this.currentPath+'/'+file.filename).substr(7),
modified: new Date(file.modified).getTime()
}) !== 'completed')
}
} }
formatPrintTime(totalSeconds: number) { formatPrintTime(totalSeconds: number) {

View File

@ -1,4 +1,4 @@
import { themeDir } from '@/store/variables' import {defaultLogoColor, themeDir} from '@/store/variables'
import {convertName} from '@/plugins/helpers' import {convertName} from '@/plugins/helpers'
import {GetterTree} from 'vuex' import {GetterTree} from 'vuex'
import {FarmPrinterState} from '@/store/farm/printer/types' import {FarmPrinterState} from '@/store/farm/printer/types'
@ -37,6 +37,10 @@ export const getters: GetterTree<FarmPrinterState, any> = {
return state.socket.port !== 80 ? state.socket.hostname+':'+state.socket.port : state.socket.hostname return state.socket.port !== 80 ? state.socket.hostname+':'+state.socket.port : state.socket.hostname
}, },
getLogoColor: (state) => {
return state.data.gui?.theme?.logo ?? defaultLogoColor
},
getStatus: (state, getters) => { getStatus: (state, getters) => {
if (!state.socket.isConnected) { if (!state.socket.isConnected) {
return state.socket.isConnecting ? 'Connecting...' : 'Disconnected' return state.socket.isConnecting ? 'Connecting...' : 'Disconnected'
@ -99,9 +103,9 @@ export const getters: GetterTree<FarmPrinterState, any> = {
getLogo: (state, getters) => { getLogo: (state, getters) => {
const acceptName = 'sidebar-logo' const acceptName = 'sidebar-logo'
const acceptExtensions = ['gif', 'jpg', 'png', 'gif'] const acceptExtensions = ['gif', 'jpg', 'png', 'gif', 'svg']
return getters['getThemeFileUrl'](acceptName, acceptExtensions) ?? '/img/logo.svg' return getters['getThemeFileUrl'](acceptName, acceptExtensions)
}, },
getPosition: state => { getPosition: state => {

View File

@ -58,10 +58,8 @@ export const mutations: MutationTree<FarmPrinterState> = {
setConfigDir(state, payload) { setConfigDir(state, payload) {
// eslint-disable-next-line // eslint-disable-next-line
Object.values(payload).forEach((file: any) => { Object.values(payload).forEach((file: any) => {
if ('filename' in file) { if (file.path?.startsWith(".theme/")) {
if (file.filename.startsWith('.theme/')) { state.theme_files.push(file.path)
state.theme_files.push(file.filename)
}
} }
}) })
}, },

View File

@ -36,7 +36,7 @@ export const getters: GetterTree<FileState, any> = {
const acceptName = 'main-background' const acceptName = 'main-background'
const acceptExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg'] const acceptExtensions = ['jpg', 'jpeg', 'png', 'gif', 'svg']
return getters['getThemeFileUrl'](acceptName, acceptExtensions) ?? 'transparent' return getters['getThemeFileUrl'](acceptName, acceptExtensions)
}, },
getCustomStylesheet: (state, getters) => { getCustomStylesheet: (state, getters) => {

View File

@ -25,7 +25,13 @@ export const mutations: MutationTree<PrinterState> = {
Object.keys(payload).forEach((key: string) => { Object.keys(payload).forEach((key: string) => {
const value = payload[key] const value = payload[key]
if (typeof value === 'object' && !Array.isArray(value) && key in currentState && value !== null) { if (
typeof value === 'object' &&
!Array.isArray(value) &&
key in currentState &&
value !== null &&
currentState[key] !== null
) {
setDataDeep(currentState[key], value) setDataDeep(currentState[key], value)
} else if (key === 'temperature') { } else if (key === 'temperature') {
const newValue = Math.round(value * 10) / 10 const newValue = Math.round(value * 10) / 10

View File

@ -183,6 +183,18 @@ export const getters: GetterTree<ServerHistoryState, any> = {
return '' return ''
}, },
getPrintStatusByFilename: (state) => (filename: string, modified: number) => {
if (state.jobs.length) {
const job = state.jobs.find((job) => {
return job.filename === filename && Math.round(job.metadata?.modified*1000) === modified
})
return job?.status ?? ""
}
return ""
},
getPrintStatusChipColor: () => (status: string) => { getPrintStatusChipColor: () => (status: string) => {
switch(status) { switch(status) {
case 'in_progress': return 'blue accent-3' //'blue-grey darken-1' case 'in_progress': return 'blue accent-3' //'blue-grey darken-1'

View File

@ -8,6 +8,10 @@ export const getters: GetterTree<SocketState, RootState> = {
return '//' + state.hostname + (state.port !== 80 ? ':'+state.port : '') return '//' + state.hostname + (state.port !== 80 ? ':'+state.port : '')
}, },
getHostUrl: (state) => {
return (state.protocol === 'wss' ? 'https' : 'http')+"://" + state.hostname + '/'
},
getWebsocketUrl: (state, getters) => { getWebsocketUrl: (state, getters) => {
return state.protocol + ':' + getters['getUrl'] + '/websocket' return state.protocol + ':' + getters['getUrl'] + '/websocket'
}, },