feat:(encoding&download): Add new design

This commit is contained in:
Tixie 2019-05-01 00:44:47 +02:00
parent 777f66380b
commit f91b072528
16 changed files with 401 additions and 126 deletions

View file

@ -28,6 +28,7 @@
vertical-align: middle;
text-align: center;
text-decoration: none !important;
font-weight: bold;
line-height: normal;
cursor: pointer;
transition: background .4s;

View file

@ -10,6 +10,9 @@
width: 100%;
}
/* Default layout
-------------------------------------------------------------- */
.layout {
display: flex;
flex-direction: column;
@ -20,3 +23,55 @@
max-width: calc(100% - 2rem);
width: 32rem;
}
/* Overlay layout
-------------------------------------------------------------- */
.layoutOverlay {
display: flex;
flex-direction: column;
align-items: center;
flex-grow: 1;
margin-right: auto;
margin-left: auto;
padding-top: 7rem;
max-width: calc(100% - 2rem);
width: 32rem;
}
.layoutOverlay-body {
background: #000;
}
.layoutOverlay-close,
.layoutOverlay-close:visited {
position: absolute;
top: 0;
right: 0;
align-self: flex-end;
padding: 0;
border: none;
border-radius: 0;
background-color: transparent;
color: #fff;
line-height: 1;
cursor: pointer;
}
/* Content */
.layoutOverlay-content {
padding-top: 2rem;
padding-bottom: 4rem;
}
.layoutOverlay-title {
margin-bottom: .7rem;
font-size: 2.4rem;
}
.layoutOverlay-subtitle {
color: rgba(255, 255, 255, .7);
font-size: 1.4rem;
line-height: 120%;
}

View file

@ -3,68 +3,44 @@
/* ----------------------------------------------------------- */
.encoding {
position: fixed;
top: 0;
left: 0;
z-index: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .9);
color: #fff;
flex-grow: 1;
padding-bottom: 4rem;
text-align: center;
font-size: 2.4rem;
}
.encoding-illu {
/* Loader
-------------------------------------------------------------- */
.encoding-loader {
position: relative;
margin-bottom: 5rem;
flex-shrink: 0;
margin-bottom: 1rem;
line-height: 1;
}
.encoding-illu::before {
.encoding-loader__preview {
position: absolute;
top: .5rem;
right: -1rem;
left: -1rem;
height: .5rem;
border-radius: .5rem;
border-radius: .4rem;
background: var(--color-primary);
box-shadow: 0 .4rem .4rem rgba(0, 0, 0, .25);
content: "";
animation: encodingProgress .3s ease-in-out infinite alternate;
}
@keyframes encodingProgress {
from {
top: .5rem;
}
to {
top: calc(100% - 1rem);
}
}
.encoding-label {
font-size: 2rem;
}
.encoding-progressBar {
top: 1.4rem;
left: 1.4rem;
overflow: hidden;
margin: 2rem 0;
max-width: 30rem;
width: 90%;
border-radius: .5rem;
background-color: rgba(255, 255, 255, .2);
width: 11.4rem;
height: 11.4rem;
border-radius: 100%;
object-fit: cover;
}
.encoding-progressBar__state {
height: .5rem;
background: linear-gradient(91.78deg, var(--color-primary) 0%,var(--color-secondary) 100%);
transition: .3s all ease-in-out;
/* Progress bar
-------------------------------------------------------------- */
.encoding-progress__value {
transition: stroke-dashoffset .2s ease-in-out;
will-change: auto;
stroke-miterlimit: round;
}
.encoding-percent {
@ -72,3 +48,39 @@
font-size: 5rem;
line-height: 1;
}
/* Percent
-------------------------------------------------------------- */
.encoding-percent {
color: rgba(255, 255, 255, .5);
font-weight: bold;
font-size: 1.5rem;
line-height: 1.8rem;
}
/* Notif
-------------------------------------------------------------- */
.encoding-notif,
.encoding-notif:visited {
margin-top: auto;
padding: .5rem 2rem;
border: none;
border-radius: .3rem;
background-color: transparent;
color: #fff;
text-align: center;
font-size: 1.4rem;
cursor: pointer;
}
.encoding-notif:focus {
outline: none;
box-shadow: var(--focus-ring);
}
.encoding-notif
.encoding-notif__icon {
margin-bottom: .7rem;
}

View file

@ -7,10 +7,39 @@
flex-direction: column;
flex-grow: 1;
max-width: 100%;
text-align: center;
}
/* Preview
-------------------------------------------------------------- */
.download-preview {
position: relative;
align-self: center;
}
.download-preview__visual {
width: 16.2rem;
border-radius: 42rem;
}
.download-preview__flower {
position: absolute;
right: -.6rem;
bottom: .2rem;
width: 7.1rem;
height: 7.9rem;
}
/* Button
-------------------------------------------------------------- */
.download-btn,
.download-btn:visited {
margin-top: auto;
margin-bottom: 4rem;
display: flex;
justify-content: center;
align-items: center;
}
.download-btn svg {
margin-right: .6rem;
}

View file

@ -7,6 +7,23 @@
flex-grow: inherit;
}
/* Overlay layout
-------------------------------------------------------------- */
.layoutOverlay-body {
background: var(--color-tertiary);
}
.layoutOverlay-close {
top: 1rem;
right: 1rem;
}
.layoutOverlay-close svg {
width: 8rem;
height: 8rem;
}
/* Welcome screen
-------------------------------------------------------------- */
@ -35,4 +52,11 @@
.capture-btn:visited {
margin-top: 0;
}
/* Encoding screen
-------------------------------------------------------------- */
.encoding-notif {
margin-top: 7rem;
}
}

View file

@ -6,7 +6,9 @@ import store from '/store'
import registerServiceWorker from '/register-service-worker.js'
import LayoutDefault from '/views/layout/default'
import LayoutOverlay from '/views/layout/overlay'
Vue.component('layout-default', LayoutDefault)
Vue.component('layout-overlay', LayoutOverlay)
Vue.config.productionTip = false

View file

@ -0,0 +1,36 @@
<template>
<svg width="142" height="142" viewBox="0 0 142 142" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="71" cy="71" r="69" stroke="#fff" stroke-opacity=".2" stroke-width="4" stroke-miterlimit="3.864" stroke-linecap="round" stroke-linejoin="round"/>
<path :stroke-dasharray="progressPerimeter" :stroke-dashoffset="pathDashOffset" class="encoding-progress__value" d="m 72.239,2.011 c 37.9809,0.6782829 68.26967,31.931708 67.75989,69.895071 C 139.48913,109.8675 108.37484,140.24197 70.395213,139.90478 32.415584,139.56759 1.8454901,108.64548 2.0098165,70.680986 2.1741514,32.714556 33.013046,2.0038306 71,2" stroke="url(#paint0_linear)" stroke-width="4" stroke-miterlimit="3.864" stroke-linecap="round" stroke-linejoin="round"/>
<defs>
<linearGradient id="paint0_linear" x1="4" y1="4" x2="71" y2="138" gradientUnits="userSpaceOnUse">
<stop stop-color="#DD28D3"/>
<stop offset="1" stop-color="#8420A7"/>
</linearGradient>
</defs>
</svg>
</template>
<script>
export default {
name: 'encodingLoader',
props: {
percent: {
type: Number,
default: 0
}
},
data: () => ({
progressPerimeter: 432.3168640136719
}),
computed: {
pathDashOffset () {
if (this.percent) {
let to = this.progressPerimeter * ((100 - this.percent) / 100)
return Math.max(0, to) // Prevent negative number
}
return this.progressPerimeter
}
}
}
</script>

View file

@ -1,29 +0,0 @@
<template lang="html">
<div class="encoding">
<div class="encoding-illu">
<img src="/assets/img/video-encoding.svg" alt="">
</div>
<div class="encoding-label">Encoding</div>
<div class="encoding-progressBar">
<div class="encoding-progressBar__state" :style="'width: ' + percentage + '%;'"></div>
</div>
<div class="encoding-percent">{{ roundedPercentage }}%</div>
</div>
</template>
<script>
export default {
name: 'encodingOverlay',
props: {
value: Number
},
computed: {
percentage () {
return this.value * 100
},
roundedPercentage () {
return Math.round(this.percentage)
}
}
}
</script>

View file

@ -0,0 +1,10 @@
<template>
<svg width="71" height="79" viewBox="0 0 71 79" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.687 78.493s11.14-19.695 27.93-25.373A34.35 34.35 0 0 0 53.01 40.137a57.882 57.882 0 0 0 5.115-9.197" stroke="#535461" stroke-width="2" stroke-miterlimit="10"/>
<path d="M68.664 27.284c-1.913 1.85-10.815 3.765-10.815 3.765s2.185-8.822 4.097-10.675a4.825 4.825 0 0 1 5.272-.964 4.826 4.826 0 0 1 2.57 2.644 4.807 4.807 0 0 1-1.124 5.23zM63.64 43.253c-2.613.518-11.13-2.707-11.13-2.707s6.64-6.222 9.252-6.74a4.834 4.834 0 0 1 3.554.776 4.81 4.81 0 0 1 1.311 6.599 4.826 4.826 0 0 1-2.987 2.072zM45.301 60.534c-2.517-.871-8.241-7.944-8.241-7.944s8.881-2.025 11.398-1.157a4.823 4.823 0 0 1 2.68 2.46 4.81 4.81 0 0 1-2.207 6.36 4.834 4.834 0 0 1-3.63.281zM28.475 70.49c-2.627-.435-9.464-6.441-9.464-6.441s8.407-3.494 11.034-3.06a4.825 4.825 0 0 1 3.135 1.946 4.807 4.807 0 0 1-1.11 6.717 4.83 4.83 0 0 1-3.595.837zM45.085 33.65c0 2.661 4.82 10.376 4.82 10.376s4.825-7.711 4.829-10.369a4.813 4.813 0 0 0-1.459-3.321 4.83 4.83 0 0 0-6.732 0 4.813 4.813 0 0 0-1.458 3.321v-.007zM25.555 45.543c.796 2.54 7.708 8.47 7.708 8.47s2.294-8.798 1.499-11.335a4.817 4.817 0 0 0-2.397-2.657 4.831 4.831 0 0 0-6.347 1.986 4.806 4.806 0 0 0-.449 3.547l-.014-.011zM8.164 59.705c.354 2.632 6.166 9.634 6.166 9.634s3.751-8.283 3.398-10.919a4.816 4.816 0 0 0-1.93-2.975 4.833 4.833 0 0 0-6.544.88 4.811 4.811 0 0 0-1.072 3.38h-.018z" fill="#DD28D3"/>
<path opacity=".25" d="M68.664 27.284c-1.913 1.85-10.815 3.765-10.815 3.765s2.185-8.822 4.097-10.675a4.825 4.825 0 0 1 5.272-.964 4.826 4.826 0 0 1 2.57 2.644 4.807 4.807 0 0 1-1.124 5.23zM63.64 43.253c-2.613.518-11.13-2.707-11.13-2.707s6.64-6.222 9.252-6.74a4.834 4.834 0 0 1 3.554.776 4.81 4.81 0 0 1 1.311 6.599 4.826 4.826 0 0 1-2.987 2.072zM45.301 60.534c-2.517-.871-8.241-7.944-8.241-7.944s8.881-2.025 11.398-1.157a4.823 4.823 0 0 1 2.68 2.46 4.81 4.81 0 0 1-2.207 6.36 4.834 4.834 0 0 1-3.63.281zM28.475 70.49c-2.627-.435-9.464-6.441-9.464-6.441s8.407-3.494 11.034-3.06a4.825 4.825 0 0 1 3.135 1.946 4.807 4.807 0 0 1-1.11 6.717 4.83 4.83 0 0 1-3.595.837zM45.085 33.65c0 2.661 4.82 10.376 4.82 10.376s4.825-7.711 4.829-10.369a4.813 4.813 0 0 0-1.459-3.321 4.83 4.83 0 0 0-6.732 0 4.813 4.813 0 0 0-1.458 3.321v-.007zM25.555 45.543c.796 2.54 7.708 8.47 7.708 8.47s2.294-8.798 1.499-11.335a4.817 4.817 0 0 0-2.397-2.657 4.831 4.831 0 0 0-6.347 1.986 4.806 4.806 0 0 0-.449 3.547l-.014-.011zM8.164 59.705c.354 2.632 6.166 9.634 6.166 9.634s3.751-8.283 3.398-10.919a4.816 4.816 0 0 0-1.93-2.975 4.833 4.833 0 0 0-6.544.88 4.811 4.811 0 0 0-1.072 3.38h-.018z" fill="#000"/>
<path d="M8.327 78.101s2.188-22.511 15.202-34.488a34.253 34.253 0 0 0 10.628-18.905c.638-3.462.956-6.975.951-10.495" stroke="#535461" stroke-width="2" stroke-miterlimit="10"/>
<path d="M43.293 6.615c-.997 2.47-8.358 7.82-8.358 7.82s-1.584-8.953-.59-11.42A4.82 4.82 0 0 1 36.93.385a4.832 4.832 0 0 1 3.692-.036 4.82 4.82 0 0 1 2.635 2.58c.501 1.176.514 2.501.036 3.686zM45.174 23.254c-2.182 1.528-11.275 2.029-11.275 2.029s3.535-8.374 5.724-9.906a4.83 4.83 0 0 1 6.721 1.168 4.808 4.808 0 0 1-1.17 6.709zM35.43 46.468c-2.655.222-10.759-3.928-10.759-3.928s7.294-5.445 9.95-5.668a4.83 4.83 0 0 1 3.517 1.12 4.812 4.812 0 0 1-2.708 8.476zM24.084 62.376c-2.58.667-11.267-2.057-11.267-2.057s6.265-6.596 8.838-7.266a4.834 4.834 0 0 1 3.602.562 4.81 4.81 0 0 1 1.694 6.522 4.825 4.825 0 0 1-2.874 2.24h.007zM24.314 21.98c1.079 2.431 8.616 7.534 8.616 7.534s1.284-8.999.205-11.43a4.82 4.82 0 0 0-2.669-2.55 4.836 4.836 0 0 0-5.238 1.158 4.818 4.818 0 0 0-.931 5.274l.017.014zM11.286 40.75c1.767 2.002 10.482 4.617 10.482 4.617s-1.474-8.971-3.231-10.969a4.823 4.823 0 0 0-3.312-1.628 4.834 4.834 0 0 0-3.496 1.185 4.817 4.817 0 0 0-1.631 3.306 4.806 4.806 0 0 0 1.188 3.49zM1.135 60.732c1.414 2.265 9.546 6.317 9.546 6.317s.067-9.091-1.33-11.357a4.816 4.816 0 0 0-2.99-2.217 4.832 4.832 0 0 0-5.073 1.864 4.809 4.809 0 0 0-.153 5.393z" fill="#DD28D3"/>
<path opacity=".1" d="M13.747 16.447s-4.187 2.117-3.36 3c.828.881 3.36-3 3.36-3z" fill="#000"/>
</svg>
</template>

View file

@ -0,0 +1,5 @@
<template>
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M39.657 21.74a1.02 1.02 0 0 0-1.445-1.44l-8.235 8.257-8.235-8.257a1.02 1.02 0 1 0-1.444 1.44l8.239 8.262-8.24 8.261a1.02 1.02 0 1 0 1.445 1.44l8.235-8.257 8.235 8.258a1.02 1.02 0 0 0 1.445-1.44l-8.239-8.262 8.239-8.262z"/>
</svg>
</template>

View file

@ -0,0 +1,5 @@
<template>
<svg width="10" height="16" viewBox="0 0 10 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M4.23 15.71a1 1 0 0 0 1.41 0l4-4a1 1 0 0 0-1.41-1.41l-2.29 2.29V1a1 1 0 0 0-2 0v11.59l-2.29-2.3A1 1 0 0 0 .24 11.7l3.99 4.01z"/>
</svg>
</template>

View file

@ -0,0 +1,26 @@
<template>
<svg width="51" height="51" viewBox="0 0 51 51" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="25.5" cy="25.5" r="19.5" fill="url(#paint0_linear)"/>
<circle opacity=".5" cx="25.5" cy="25.5" r="24.5" stroke="url(#paint1_linear)" stroke-width="2"/>
<path d="M27.007 19.843c-.068-.617-.579-.843-1.199-.843-.62 0-1.131.227-1.199.843-1.829.534-3.168 2.259-3.168 4.305 0 3.296-1.441 4.553-1.441 5.034-.002.605 1.342.978 3.97 1.237.236.815.965 1.412 1.837 1.412.871 0 1.6-.598 1.836-1.412 2.628-.259 3.973-.632 3.973-1.237 0-.48-1.44-1.738-1.44-5.034 0-2.047-1.34-3.77-3.17-4.305z" fill="#fff" filter="url(#filter0_d)"/>
<defs>
<filter id="filter0_d" x="17.952" y="16.952" width="15.712" height="16.927" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.024"/>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0"/>
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
<linearGradient id="paint0_linear" x1="6" y1="6" x2="48.313" y2="10.006" gradientUnits="userSpaceOnUse">
<stop stop-color="#DD28D3"/>
<stop offset="1" stop-color="#8420A7"/>
</linearGradient>
<linearGradient id="paint1_linear" x2="55.332" y2="5.239" gradientUnits="userSpaceOnUse">
<stop stop-color="#DD28D3"/>
<stop offset="1" stop-color="#8420A7"/>
</linearGradient>
</defs>
</svg>
</template>

View file

@ -0,0 +1,30 @@
<template>
<div class="layoutOverlay">
<button v-if="close" class="layoutOverlay-close" aria-label="Close" @click="close">
<icon-close></icon-close>
</button>
<slot></slot>
</div>
</template>
<script>
import iconClose from '/views/icons/ico-close-overlay'
export default {
components: {
iconClose
},
props: {
close: {
type: Function,
required: false
}
},
mounted: function () {
document.body.classList.add('layoutOverlay-body')
},
destroyed: function () {
document.body.classList.remove('layoutOverlay-body')
}
}
</script>

View file

@ -1,30 +1,47 @@
<template lang="html">
<layout-default>
<div class="download">
<div class="options">
<span></span>
<button class="options__btn" @click="back"> back</button>
<layout-overlay :close="back">
<encoding-screen v-if="encoding" :value="encodingProgress"></encoding-screen>
<div v-if="gif" class="download">
<div class="download-preview">
<img class="download-preview__visual" :src="objectUrl" alt="">
<illu-flower class="download-preview__flower"></illu-flower>
</div>
<div class="preview preview--novideo">
<img class="preview-visualImg" :src="objectUrl" alt="">
<div class="layoutOverlay-content">
<div class="layoutOverlay-title">Ready just for you~</div>
<div class="layoutOverlay-subtitle">
Thank you for your patience.<br>
You can now download your souvenir.
</div>
</div>
<a class="download-btn btn btn--primary w100" :href="objectUrl" :download="`souvenir${timestamp}.gif`">Download GIF</a>
<a class="download-btn btn btn--primary w100" :href="objectUrl" :download="`souvenir${timestamp}.gif`"><icon-dl></icon-dl>Save as GIF</a>
</div>
</layout-default>
</layout-overlay>
</template>
<script>
import { mapState } from 'vuex'
import { encode } from '/services/encode.js'
import encodingScreen from '/views/screens/encoding'
import iconDl from '/views/icons/ico-download'
import illuFlower from '/views/icons/flower'
export default {
name: 'download',
components: {
encodingScreen,
iconDl,
illuFlower
},
data: () => ({
objectUrl: null
encoding: false,
encodingProgress: 0,
objectUrl: null,
downloadReady: false
}),
computed: {
...mapState([
'capture',
'boomerang',
'gif'
]),
timestamp () {
@ -36,18 +53,52 @@ export default {
},
methods: {
back () {
this.$router.push({ name: 'capture' })
this.$router.push({ name: 'preview' })
},
backHome () {
this.$router.push({ name: 'home' })
},
fillGIF () {
this.objectUrl = URL.createObjectURL(this.gif.blob)
},
emptyGIF () {
URL.revokeObjectURL(this.objectUrl)
this.$store.commit('updateGif', null)
},
startEncoding () {
this.encoding = true
const encoding = encode(this.capture, { boomerangEffect: this.boomerang })
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.fillGIF()
this.downloadReady = true
})
}
},
created () {
if (!this.gif) {
if (!this.capture) {
this.$router.push({ name: 'home' })
} else if (!this.gif) {
this.startEncoding()
} else {
this.objectUrl = URL.createObjectURL(this.gif.blob)
this.fillGIF()
}
},
destroyed () {
URL.revokeObjectURL(this.objectUrl)
this.emptyGIF()
}
}
</script>

View file

@ -0,0 +1,46 @@
<template lang="html">
<div class="encoding">
<div class="encoding-loader">
<encoding-loader :percent="roundedPercentage" class="encoding-progress"></encoding-loader>
<preview-canvas class="encoding-loader__preview"></preview-canvas>
</div>
<div class="encoding-percent">{{ roundedPercentage }}%</div>
<div class="layoutOverlay-content">
<div class="layoutOverlay-title">Almost ready~</div>
<div class="layoutOverlay-subtitle">Encoding may take some time depending on your device</div>
</div>
<button class="encoding-notif">
<icon-notif class="encoding-notif__icon"></icon-notif>
<div>Get notified when Its done</div>
</button>
</div>
</template>
<script>
import encodingLoader from '/views/components/encoding-loader'
import previewCanvas from '/views/components/preview-canvas'
import iconNotif from '/views/icons/ico-notif'
export default {
name: 'encoding',
components: {
encodingLoader,
previewCanvas,
iconNotif
},
props: {
value: Number
},
computed: {
percentage () {
return this.value * 100
},
roundedPercentage () {
return Math.round(this.percentage)
}
}
}
</script>

View file

@ -7,16 +7,12 @@
<preview-canvas v-if="capture" class="preview-visual"></preview-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>
<button class="download-btn btn btn--primary w100" @click="startEncoding">Generate GIF</button>
</div>
</layout-default>
</template>
<script>
import { encode } from '/services/encode.js'
import encodingOverlay from '/views/components/encoding'
import captureOptions from '/views/components/capture-options'
import previewCanvas from '/views/components/preview-canvas'
@ -25,15 +21,9 @@ import { mapState } from 'vuex'
export default {
name: 'preview',
components: {
encodingOverlay,
captureOptions,
previewCanvas
},
data: () => ({
encoding: false,
encodingProgress: 0,
previewInterval: null
}),
computed: {
...mapState([
'camera',
@ -49,25 +39,7 @@ export default {
this.$router.push({ name: 'home' })
},
startEncoding () {
this.encoding = true
const encoding = encode(this.capture, { boomerangEffect: this.boomerang })
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' })
})
this.$router.push({ name: 'download' })
}
},
created () {