Skip to content

Commit ba9d1da

Browse files
committed
Merge branch 'develop'
Signed-off-by: Pedro Lamas <pedrolamas@gmail.com>
2 parents 513523f + 5b6a44e commit ba9d1da

File tree

17 files changed

+162
-23
lines changed

17 files changed

+162
-23
lines changed

docs/features/spoolman.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@ Fluidd offers support for the [Spoolman](https://github.com/Donkie/Spoolman) fil
2222
### Print start
2323
On print start, Fluidd will show a modal asking you to select the spool you want to use for printing.
2424
The modal shows all available (i.e. not archived) spools.
25-
<!-- TODO uncomment when QR scanning is available
2625
A spool can either be selected by selecting it directly, or by scanning an associated QR code using an attached webcam.
2726

2827
![screenshot](/assets/images/spoolman-scan-spool.png)
29-
-->
3028

3129
Automatically opening the spool selection modal can be disabled from the Fluidd settings.
3230

src/components/settings/SpoolmanSettings.vue

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
/>
1919
</app-setting>
2020

21-
<!-- TODO uncomment when QR scanning is available
2221
<v-divider />
2322
<app-setting
2423
:title="$tc('app.spoolman.setting.auto_open_qr_camera')"
@@ -32,7 +31,28 @@
3231
:items="supportedCameras"
3332
/>
3433
</app-setting>
35-
-->
34+
35+
<v-divider />
36+
<app-setting
37+
:title="$t('app.spoolman.setting.prefer_device_camera')"
38+
>
39+
<v-switch
40+
v-model="preferDeviceCamera"
41+
hide-details
42+
class="mt-0 mb-4"
43+
/>
44+
</app-setting>
45+
46+
<v-divider />
47+
<app-setting
48+
:title="$t('app.spoolman.setting.auto_select_spool_on_match')"
49+
>
50+
<v-switch
51+
v-model="autoSelectSpoolOnMatch"
52+
hide-details
53+
class="mt-0 mb-4"
54+
/>
55+
</app-setting>
3656

3757
<v-divider />
3858
<app-setting :title="$t('app.setting.label.reset')">
@@ -91,6 +111,30 @@ export default class SpoolmanSettings extends Mixins(StateMixin) {
91111
})
92112
}
93113
114+
get preferDeviceCamera () {
115+
return this.$store.state.config.uiSettings.spoolman.preferDeviceCamera
116+
}
117+
118+
set preferDeviceCamera (value: boolean) {
119+
this.$store.dispatch('config/saveByPath', {
120+
path: 'uiSettings.spoolman.preferDeviceCamera',
121+
value,
122+
server: true
123+
})
124+
}
125+
126+
get autoSelectSpoolOnMatch () {
127+
return this.$store.state.config.uiSettings.spoolman.autoSelectSpoolOnMatch
128+
}
129+
130+
set autoSelectSpoolOnMatch (value: boolean) {
131+
this.$store.dispatch('config/saveByPath', {
132+
path: 'uiSettings.spoolman.autoSelectSpoolOnMatch',
133+
value,
134+
server: true
135+
})
136+
}
137+
94138
handleReset () {
95139
this.$store.dispatch('config/saveByPath', {
96140
path: 'uiSettings.spoolman',

src/components/widgets/camera/CameraItem.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
:is="cameraComponent"
1010
ref="component-instance"
1111
:camera="camera"
12+
:crossorigin="crossorigin"
1213
class="camera-image"
1314
@raw-camera-url="rawCameraUrl = $event"
1415
@frames-per-second="framesPerSecond = $event"
16+
@playback="setupFrameEvents()"
1517
/>
1618
</template>
1719
<div v-else>
@@ -32,7 +34,7 @@
3234
</div>
3335

3436
<div
35-
v-if="!fullscreen && (fullscreenMode === 'embed' || !rawCameraUrl)"
37+
v-if="!fullscreen && (fullscreenMode === 'embed' || !rawCameraUrl) && camera.service !== 'device'"
3638
class="camera-fullscreen"
3739
>
3840
<a :href="`/#/camera/${camera.id}`">
@@ -68,14 +70,21 @@ export default class CameraItem extends Vue {
6870
@Prop({ type: Boolean, required: false, default: false })
6971
readonly fullscreen!: boolean
7072
73+
@Prop({ type: String })
74+
readonly crossorigin?: 'anonymous' | 'use-credentials' | ''
75+
7176
@Ref('component-instance')
7277
readonly componentInstance!: CameraMixin
7378
7479
rawCameraUrl: string | null = null
7580
framesPerSecond : string | null = null
7681
7782
mounted () {
78-
if (this.$listeners?.frame) {
83+
this.setupFrameEvents()
84+
}
85+
86+
setupFrameEvents () {
87+
if (this.$listeners?.frame && this.componentInstance) {
7988
if (this.componentInstance.streamingElement instanceof HTMLImageElement) {
8089
this.componentInstance.streamingElement.addEventListener('load', () => this.handleFrame())
8190
} else if (this.componentInstance.streamingElement instanceof HTMLVideoElement) {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<template>
2+
<video
3+
ref="streamingElement"
4+
autoplay
5+
muted
6+
:style="cameraStyle"
7+
/>
8+
</template>
9+
10+
<script lang="ts">
11+
import { Component, Ref, Mixins } from 'vue-property-decorator'
12+
import CameraMixin from '@/mixins/camera'
13+
14+
@Component({})
15+
export default class DeviceCamera extends Mixins(CameraMixin) {
16+
@Ref('streamingElement')
17+
readonly cameraVideo!: HTMLVideoElement
18+
19+
startPlayback () {
20+
navigator.mediaDevices.getUserMedia({ video: true })
21+
.then(stream => (this.cameraVideo.srcObject = stream))
22+
.then(() => this.$emit('playback'))
23+
}
24+
25+
stopPlayback () {
26+
this.cameraVideo.srcObject = null
27+
}
28+
}
29+
</script>

src/components/widgets/camera/services/HlsstreamCamera.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
ref="streamingElement"
44
autoplay
55
muted
6-
crossorigin="anonymous"
6+
:crossorigin="crossorigin"
77
:style="cameraStyle"
88
/>
99
</template>

src/components/widgets/camera/services/IpstreamCamera.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
:src="cameraVideoSource"
55
autoplay
66
muted
7-
crossorigin="anonymous"
7+
:crossorigin="crossorigin"
88
:style="cameraStyle"
99
/>
1010
</template>

src/components/widgets/camera/services/MjpegstreamerAdaptiveCamera.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
ref="streamingElement"
44
:src="cameraImageSource"
55
:style="cameraStyle"
6-
crossorigin="anonymous"
6+
:crossorigin="crossorigin"
77
@load="handleImageLoad"
88
>
99
</template>

src/components/widgets/camera/services/MjpegstreamerCamera.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
ref="streamingElement"
44
:src="cameraImageSource"
55
:style="cameraStyle"
6-
crossorigin="anonymous"
6+
:crossorigin="crossorigin"
77
>
88
</template>
99

src/components/widgets/camera/services/WebrtcCamerastreamerCamera.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
autoplay
55
muted
66
:style="cameraStyle"
7-
crossorigin="anonymous"
7+
:crossorigin="crossorigin"
88
/>
99
</template>
1010

src/components/widgets/spoolman/QRReader.vue

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<CameraItem
1717
:camera="camera"
1818
:embedded="true"
19+
crossorigin="anonymous"
1920
@frame="handlePrinterCameraFrame"
2021
/>
2122
</v-card-text>
@@ -34,12 +35,14 @@ import BrowserMixin from '@/mixins/browser'
3435
components: { CameraItem }
3536
})
3637
export default class QRReader extends Mixins(StateMixin, BrowserMixin) {
37-
dataPatterns = [/\/spool\/show\/(\d+)\/?/]
38+
dataPatterns = [
39+
/web\+spoolman:s-(\d+)/,
40+
/\/spool\/show\/(\d+)\/?/
41+
]
42+
3843
statusMessage = 'info.howto'
3944
lastScanTimestamp = Date.now()
4045
processing = false
41-
42-
video!: HTMLVideoElement | HTMLImageElement
4346
context!: CanvasRenderingContext2D
4447
4548
@VModel({ type: String, default: null })
@@ -49,6 +52,9 @@ export default class QRReader extends Mixins(StateMixin, BrowserMixin) {
4952
canvas!: HTMLCanvasElement
5053
5154
get camera () {
55+
if (this.source === 'device') {
56+
return { name: this.$t('app.spoolman.label.device_camera'), service: 'device' }
57+
}
5258
return this.$store.getters['cameras/getCameraById'](this.source)
5359
}
5460
@@ -90,13 +96,23 @@ export default class QRReader extends Mixins(StateMixin, BrowserMixin) {
9096
this.canvas.height = image.naturalHeight
9197
}
9298
99+
if (!this.canvas.width || !this.canvas.height) {
100+
// no image drawn yet
101+
this.processing = false
102+
return
103+
}
104+
93105
try {
94106
this.context.drawImage(image, 0, 0, this.canvas.width, this.canvas.height)
95107
const result = await QrScanner.scanImage(this.canvas, { returnDetailedScanResult: true })
96108
if (result.data) { this.handleCodeFound(result.data) }
97109
} catch (err) {
98110
if (err instanceof DOMException) {
99-
this.statusMessage = 'error.no_image_data'
111+
if (err.name === 'SecurityError') {
112+
this.statusMessage = 'error.cors'
113+
} else {
114+
this.statusMessage = 'error.no_image_data'
115+
}
100116
}
101117
102118
// no QR code found

0 commit comments

Comments
 (0)