mirror of
https://github.com/GuerillaStudio/souvenir.git
synced 2025-01-20 22:30:20 +00:00
feat: Add preview screen before encoding
This commit is contained in:
parent
da154ef249
commit
318c4ef7e1
7 changed files with 159 additions and 36 deletions
|
@ -56,6 +56,45 @@
|
||||||
background: var(--btn-primary);
|
background: var(--btn-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Loading state
|
||||||
|
-------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.btn--loading,
|
||||||
|
.btn--loading:link,
|
||||||
|
.btn--loading:visited {
|
||||||
|
position: relative;
|
||||||
|
color: transparent;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--loading svg {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn--loading::after {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(50% - (2em / 2));
|
||||||
|
left: calc(50% - (2em / 2));
|
||||||
|
display: block;
|
||||||
|
width: 2em;
|
||||||
|
height: 2em;
|
||||||
|
border: .2rem solid transparent;
|
||||||
|
border-color: transparent transparent #fff #fff;
|
||||||
|
border-radius: 42rem;
|
||||||
|
content: "";
|
||||||
|
animation: spinAround 500ms infinite linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spinAround {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: rotate(359deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* size
|
/* size
|
||||||
-------------------------------------------------------------- */
|
-------------------------------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
margin-bottom: auto;
|
margin-bottom: auto;
|
||||||
|
padding-left: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.capture-progress {
|
.capture-progress {
|
||||||
|
@ -105,12 +106,13 @@
|
||||||
.download {
|
.download {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
padding-right: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-btn,
|
.download-btn,
|
||||||
.download-btn:visited {
|
.download-btn:visited {
|
||||||
margin: auto 0;
|
margin: auto 0;
|
||||||
margin-right: 2rem;
|
margin-right: auto;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import VueRouter from 'vue-router'
|
||||||
|
|
||||||
import Welcome from '/views/screens/welcome'
|
import Welcome from '/views/screens/welcome'
|
||||||
import Capture from '/views/screens/capture'
|
import Capture from '/views/screens/capture'
|
||||||
|
import Preview from '/views/screens/preview'
|
||||||
import Download from '/views/screens/download'
|
import Download from '/views/screens/download'
|
||||||
|
|
||||||
Vue.use(VueRouter)
|
Vue.use(VueRouter)
|
||||||
|
@ -12,6 +13,7 @@ export default new VueRouter({
|
||||||
routes: [
|
routes: [
|
||||||
{ name: 'home', path: '/', component: Welcome },
|
{ name: 'home', path: '/', component: Welcome },
|
||||||
{ name: 'capture', path: '/capture', component: Capture },
|
{ name: 'capture', path: '/capture', component: Capture },
|
||||||
|
{ name: 'preview', path: '/preview', component: Preview },
|
||||||
{ name: 'download', path: '/download', component: Download }
|
{ name: 'download', path: '/download', component: Download }
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
|
@ -9,18 +9,14 @@
|
||||||
<video ref="preview" class="preview-visual" :class="{ 'preview--flip': shouldFlip }" preload="yes" autoplay muted playsinline webkit-playsinline></video>
|
<video ref="preview" class="preview-visual" :class="{ 'preview--flip': shouldFlip }" preload="yes" autoplay muted playsinline webkit-playsinline></video>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button class="capture-btn" :class="{ 'capture-btn--capturing': capturing }" :disabled="!camera || encoding" @click.prevent="startCapturing">Capture</button>
|
<button class="capture-btn" :class="{ 'capture-btn--capturing': capturing }" :disabled="!camera" @click.prevent="startCapturing">Capture</button>
|
||||||
|
|
||||||
<encoding-overlay v-if="encoding" :value="encodingProgress"></encoding-overlay>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import captureOptions from '/views/components/capture-options'
|
import captureOptions from '/views/components/capture-options'
|
||||||
import captureProgress from '/views/components/capture-progress'
|
import captureProgress from '/views/components/capture-progress'
|
||||||
import encodingOverlay from '/views/components/encoding'
|
|
||||||
import { capture } from '/services/capture.js'
|
import { capture } from '/services/capture.js'
|
||||||
import { encode } from '/services/encode.js'
|
|
||||||
|
|
||||||
import 'objectFitPolyfill'
|
import 'objectFitPolyfill'
|
||||||
|
|
||||||
|
@ -30,14 +26,11 @@ export default {
|
||||||
name: 'capture',
|
name: 'capture',
|
||||||
components: {
|
components: {
|
||||||
captureOptions,
|
captureOptions,
|
||||||
captureProgress,
|
captureProgress
|
||||||
encodingOverlay
|
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
capturing: false,
|
capturing: false,
|
||||||
capturingProgress: 0,
|
capturingProgress: 0
|
||||||
encoding: false,
|
|
||||||
encodingProgress: 0
|
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
...mapState([
|
...mapState([
|
||||||
|
@ -82,28 +75,8 @@ export default {
|
||||||
this.capturing = false
|
this.capturing = false
|
||||||
this.capturingProgress = 0
|
this.capturingProgress = 0
|
||||||
this.$store.commit('updateCapture', captureData)
|
this.$store.commit('updateCapture', captureData)
|
||||||
this.startEncoding()
|
this.$router.push({ name: 'preview' })
|
||||||
})
|
// this.startEncoding()
|
||||||
},
|
|
||||||
startEncoding () {
|
|
||||||
this.encoding = true
|
|
||||||
const encoding = encode(this.capture)
|
|
||||||
|
|
||||||
encoding.once('error', error => {
|
|
||||||
console.error(error)
|
|
||||||
this.encoding = false
|
|
||||||
this.encodingProgress = 0
|
|
||||||
})
|
|
||||||
|
|
||||||
encoding.on('progress', value => {
|
|
||||||
this.encodingProgress = value
|
|
||||||
})
|
|
||||||
|
|
||||||
encoding.once('done', gif => {
|
|
||||||
this.encoding = false
|
|
||||||
this.encodingProgress = 0
|
|
||||||
this.$store.commit('updateGif', gif)
|
|
||||||
this.$router.push({ name: 'download' })
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async ensureCamera () {
|
async ensureCamera () {
|
||||||
|
|
|
@ -26,7 +26,10 @@ export default {
|
||||||
'gif'
|
'gif'
|
||||||
]),
|
]),
|
||||||
timestamp () {
|
timestamp () {
|
||||||
return this.gif.createdAt.getTime()
|
if (this.gif) {
|
||||||
|
return this.gif.createdAt.getTime()
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -37,9 +40,9 @@ export default {
|
||||||
created () {
|
created () {
|
||||||
if (!this.gif) {
|
if (!this.gif) {
|
||||||
this.$router.push({ name: 'home' })
|
this.$router.push({ name: 'home' })
|
||||||
|
} else {
|
||||||
|
this.objectUrl = URL.createObjectURL(this.gif.blob)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.objectUrl = URL.createObjectURL(this.gif.blob)
|
|
||||||
},
|
},
|
||||||
destroyed () {
|
destroyed () {
|
||||||
URL.revokeObjectURL(this.objectUrl)
|
URL.revokeObjectURL(this.objectUrl)
|
||||||
|
|
103
src/views/screens/preview.vue
Normal file
103
src/views/screens/preview.vue
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
<template lang="html">
|
||||||
|
<div class="download">
|
||||||
|
<div class="options">
|
||||||
|
<span></span>
|
||||||
|
<button class="options__btn" @click="back">← back</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="preview">
|
||||||
|
<canvas ref="previewCanvas" class="preview-visual"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="download-btn btn btn--primary w100" :class="{ 'btn--loading': encoding }" :disabled="encoding" @click="startEncoding">Generate GIF</button>
|
||||||
|
|
||||||
|
<encoding-overlay v-if="encoding" :value="encodingProgress"></encoding-overlay>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { encode } from '/services/encode.js'
|
||||||
|
import encodingOverlay from '/views/components/encoding'
|
||||||
|
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'preview',
|
||||||
|
components: {
|
||||||
|
encodingOverlay
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
encoding: false,
|
||||||
|
encodingProgress: 0,
|
||||||
|
previewTimeout: null
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
...mapState([
|
||||||
|
'camera',
|
||||||
|
'capture'
|
||||||
|
])
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
back () {
|
||||||
|
this.$router.push({ name: 'capture' })
|
||||||
|
},
|
||||||
|
backHome () {
|
||||||
|
this.$router.push({ name: 'home' })
|
||||||
|
},
|
||||||
|
printPreview (context, frameNumber) {
|
||||||
|
this.previewTimeout = setTimeout(() => {
|
||||||
|
// Looper
|
||||||
|
frameNumber < (this.capture.imageDataList.length - 1) ? frameNumber++ : frameNumber = 0
|
||||||
|
// Printer
|
||||||
|
const image = this.capture.imageDataList[frameNumber]
|
||||||
|
context.putImageData(image, 0, 0)
|
||||||
|
this.printPreview(context, frameNumber)
|
||||||
|
}, this.capture.delayTime)
|
||||||
|
},
|
||||||
|
startPreview () {
|
||||||
|
if (!this.capture) {
|
||||||
|
this.backHome()
|
||||||
|
} else {
|
||||||
|
const canvas = this.$refs.previewCanvas
|
||||||
|
canvas.width = this.capture.imageWidth
|
||||||
|
canvas.height = this.capture.imageHeight
|
||||||
|
const canvasContext = canvas.getContext('2d')
|
||||||
|
|
||||||
|
this.printPreview(canvasContext, 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
startEncoding () {
|
||||||
|
this.encoding = true
|
||||||
|
const encoding = encode(this.capture)
|
||||||
|
|
||||||
|
encoding.once('error', error => {
|
||||||
|
console.error(error)
|
||||||
|
this.encoding = false
|
||||||
|
this.encodingProgress = 0
|
||||||
|
})
|
||||||
|
|
||||||
|
encoding.on('progress', value => {
|
||||||
|
this.encodingProgress = value
|
||||||
|
})
|
||||||
|
|
||||||
|
encoding.once('done', gif => {
|
||||||
|
this.encoding = false
|
||||||
|
this.encodingProgress = 0
|
||||||
|
this.$store.commit('updateGif', gif)
|
||||||
|
this.$router.push({ name: 'download' })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
if (!this.capture) {
|
||||||
|
this.backHome()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.startPreview()
|
||||||
|
},
|
||||||
|
destroyed () {
|
||||||
|
window.clearTimeout(this.previewTimeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
Loading…
Reference in a new issue