#42 first prototype

pull/1/head
Thomas Ballmann 4 years ago
parent fa6dda4985
commit c1ded8cb7a

@ -11,4 +11,8 @@ module.exports = {
// add your custom rules here
rules: {
},
globals: {
__BUILD_TIME__: 'readonly',
__COMMIT_HASH__: 'readonly',
}
}

@ -28,11 +28,11 @@
"sass": "^1.19.0",
"sass-loader": "^10.0.2",
"vue-cli-plugin-vuetify": "~2.0.4",
"vue-moment": "^4.1.0",
"vue-router": "^3.1.5",
"vue-svg-loader": "^0.16.0",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.3.0",
"vuex": "^3.5.1",
"webpack-version-file": "^0.1.6"
},
"eslintConfig": {

@ -1,5 +1,166 @@
<template>
<v-app _style="background: #e2e2e2">
<v-app>
<template v-if="isLoading">
<v-overlay
:absolute="true"
:value="true"
>
<v-progress-circular
indeterminate
size="64"
/>
</v-overlay>
</template>
<template v-if="1">
<component :is="layout" />
</template>
<template v-if="0">
<v-navigation-drawer
v-model="drawer"
app
_mini-variant-width="150"
_mini-variant
:clipped="clipped"
>
<v-sheet rounded="lg">
<v-img
:aspect-ratio="16/9"
src="/api/device/screen"
>
<template #placeholder>
<v-row
class="fill-height ma-0 grey"
align="center"
justify="center"
>
<v-progress-circular
indeterminate
color="grey lighten-5"
/>
</v-row>
</template>
</v-img>
</v-sheet>
<v-list nav>
<v-list-item
v-for="page in pages"
:key="page.label"
:to="page.to"
link
>
<v-list-item-content class="text-center">
<v-icon>{{ page.icon }}</v-icon>
<v-list-item-title>{{ page.label }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider />
<v-list-item
link
to="/setup"
>
<v-list-item-content class="text-center">
<v-icon>$support</v-icon>
<v-list-item-title>Setup Assistent</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
<v-app-bar
app
color="orange darken-2"
dark
flat_
:clipped-left="clipped"
>
<v-btn
_class="hidden-xs-and-down"
to="/"
icon
color="transparent"
>
<img
width="48"
height="48"
src="@/assets/logo.png"
alt="logo"
>
</v-btn>
<v-toolbar-title>paperdash</v-toolbar-title>
<v-app-bar-nav-icon
@click.stop="drawer = !drawer"
/>
<v-spacer />
<v-progress-circular
:rotate="-90"
:value="40"
class="mx-3"
size="40"
>
<v-icon>$playlist</v-icon>
</v-progress-circular>
<v-progress-circular
:rotate="-90"
:value="40"
class="mx-3"
size="40"
>
<v-icon>$storage</v-icon>
</v-progress-circular>
<template v-if="stats.wifi.connected">
<v-btn icon>
<v-icon>{{ stats.wifi.rssi | wifiIcon(0) }}</v-icon>
</v-btn>
<span>{{ new Date(stats.device.time * 1000).toLocaleString() }}</span>
</template>
<template v-else>
<v-btn
to="/setup/wifi"
icon
>
<v-icon color="red">
$signalWifiOff
</v-icon>
</v-btn>
</template>
<!--
<v-responsive max-width="260">
<v-text-field
dense
flat
hide-details
rounded
solo-inverted
/>
</v-responsive>
-->
</v-app-bar>
<v-main class="grey lighten-3">
<v-container>
<v-sheet
min-height="70vh"
rounded="lg"
>
<router-view />
</v-sheet>
</v-container>
</v-main>
</template>
</v-app>
<!--
<v-app
v-if="0"
_style="background: #e2e2e2"
>
<template v-if="isLoading">
<v-overlay
:absolute="true"
@ -52,55 +213,44 @@
</v-main>
</template>
</v-app>
-->
</template>
<script>
import apiDevice from './api/device'
import { mapState, mapActions } from 'vuex'
import '@/assets/app.css'
import transitionPage from '@/components/TransitionPage'
export default {
components: {
transitionPage,
// eslint-disable-next-line vue/no-unused-components
'layout-default': () => import('@/layouts/default'),
// eslint-disable-next-line vue/no-unused-components
'layout-setup': () => import('@/layouts/setup'),
},
data: () => ({
isLoading: true,
settings: null,
layout: 'layout-default',
}),
computed: {
stats () {
return this.$root._data.stats
},
},
watch: {
stats () {
this.isLoading = false
},
...mapState(['stats']),
},
created () {
apiDevice.getSettings(settings => {
this.settings = settings
// switch layout if necessary
this.$router.beforeEach((to, from, next) => {
const layout = to.meta.layout || 'default'
this.layout = 'layout-' + layout
next()
})
this.autoReloadStats()
// load device stats
this.loadStats().then(() => {
this.isLoading = false
})
},
methods: {
autoReloadStats () {
apiDevice.getStats(stats => {
// give esp some extra time befor fetch new data
stats.playlist.remaining += 2
// reset old so reactive watcher can detect a change
if (this.$root._data.stats) {
this.$root._data.stats.playlist.remaining = 0
}
this.$root._data.stats = stats
setTimeout(() => {
this.autoReloadStats()
}, stats.playlist.remaining * 1000)
})
},
...mapActions([
'loadStats',
]),
},
}
</script>

@ -34,7 +34,7 @@
<v-list-item>
<v-list-item-icon class="mr-3">
<v-icon>$memory</v-icon>
<v-icon>$storage</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>i8n:Storage</v-list-item-title>

@ -0,0 +1,191 @@
<template>
<div class="d-flex flex-grow-1">
<v-navigation-drawer
v-model="drawer"
class="grey darken-3"
dark
app
_mini-variant-width="150"
_mini-variant
:clipped="clipped"
>
<v-sheet rounded="lg">
<v-img
:aspect-ratio="16/9"
src="/api/device/screen"
>
<template #placeholder>
<v-row
class="fill-height ma-0 grey"
align="center"
justify="center"
>
<v-progress-circular
indeterminate
color="grey lighten-5"
/>
</v-row>
</template>
</v-img>
</v-sheet>
<v-list nav>
<v-list-item
v-for="page in pages"
:key="page.label"
:to="page.to"
link
>
<v-list-item-content class="text-center">
<v-icon>{{ page.icon }}</v-icon>
<v-list-item-title>{{ page.label }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider />
<v-list-item
link
to="/setup"
>
<v-list-item-content class="text-center">
<v-icon>$support</v-icon>
<v-list-item-title>Assistant</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
<v-app-bar
app
color="orange darken-2"
dark
flat_
:clipped-left="clipped"
>
<v-btn
_class="hidden-xs-and-down"
to="/"
icon
color="transparent"
>
<img
width="48"
height="48"
src="@/assets/logo.png"
alt="logo"
>
</v-btn>
<v-toolbar-title>paperdash</v-toolbar-title>
<v-app-bar-nav-icon
@click.stop="drawer = !drawer"
/>
<v-spacer />
<div>box name</div>
<v-spacer />
<v-progress-circular
:rotate="-90"
:value="40"
class="mx-3"
size="40"
>
<v-icon>$playlist</v-icon>
</v-progress-circular>
<v-progress-circular
:rotate="-90"
:value="40"
class="mx-3"
size="40"
>
<v-icon>$storage</v-icon>
</v-progress-circular>
<template v-if="stats.wifi.connected">
<v-btn icon>
<v-icon>{{ stats.wifi.rssi | wifiIcon(0) }}</v-icon>
</v-btn>
<span>{{ new Date(stats.device.time * 1000).toLocaleString() }}</span>
</template>
<template v-else>
<v-btn
to="/setup/wifi"
icon
>
<v-icon color="red">
$signalWifiOff
</v-icon>
</v-btn>
</template>
<!--
<v-responsive max-width="260">
<v-text-field
dense
flat
hide-details
rounded
solo-inverted
/>
</v-responsive>
-->
</v-app-bar>
<v-main class="grey lighten-3">
<v-container>
<v-sheet
min-height="70vh"
rounded="lg"
>
<router-view />
</v-sheet>
</v-container>
</v-main>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data: () => ({
drawer: true,
clipped: true,
pages: [
{
label: 'Dashboard',
icon: '$lock',
to: '/',
},
{
label: 'Device',
icon: '$device',
to: '/device',
},
{
label: 'Playlist',
icon: '$playlist',
to: '/playlist',
},
{
label: 'Weather',
icon: '$wb_sunny',
to: '/weather',
},
{
label: 'System',
icon: '$settings',
to: '/system',
},
],
}),
computed: {
...mapState(['stats']),
},
}
</script>
<style scoped>
</style>

@ -0,0 +1,15 @@
<template>
<v-main>
<router-view />
</v-main>
</template>
<script>
export default {
name: 'Setup',
}
</script>
<style scoped>
</style>

@ -1,17 +1,14 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import vuetify from './plugins/vuetify'
Vue.config.productionTip = false
Vue.use(require('vue-moment'))
new Vue({
vuetify,
router,
data: {
stats: null,
},
store,
render: h => h(App),
}).$mount('#app')

@ -12,7 +12,7 @@ const MY_ICONS = {
// delete: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/delete/baseline.svg')},
// clear: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/clear/baseline.svg')},
// success: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/check_circle/baseline.svg')},
// info: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/info/baseline.svg')},
info: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/info/baseline.svg') },
// warning: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/priority_high/baseline.svg')},
// error: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/warning/baseline.svg')},
prev: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/chevron_left/baseline.svg') },
@ -23,7 +23,7 @@ const MY_ICONS = {
// delimiter: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/fiber_manual_record/baseline.svg')}, // for carousel
// sort: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/arrow_upward/baseline.svg')},
// expand: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/keyboard_arrow_down/baseline.svg')},
// menu: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/menu/baseline.svg')},
menu: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/menu/baseline.svg') },
// subgroup: {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/arrow_drop_down/baseline.svg')},
dropdown: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/arrow_drop_down/baseline.svg') },
radioOn: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/radio_button_checked/baseline.svg') },
@ -49,6 +49,11 @@ const MY_ICONS = {
memory: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/memory/baseline.svg') },
lock: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/lock/baseline.svg') },
settings: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/settings/baseline.svg') },
storage: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/sd_storage/baseline.svg') },
playlist: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/live_tv/baseline.svg') },
support: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/support/baseline.svg') },
device: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/video_label/baseline.svg') },
update: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/update/baseline.svg') },
// wifi
signalWifiOff: { component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/signal_wifi_off/baseline.svg') },

@ -3,6 +3,10 @@ import VueRouter from 'vue-router'
const Dashboard = () => import('../views/Dashboard')
const Settings = () => import('../views/Settings')
const Device = () => import('../views/Device')
const Playlist = () => import('../views/Playlist')
const Weather = () => import('../views/Weather')
const System = () => import('../views/System')
const SetupStart = () => import('../views/Setup/Start')
const SetupCountry = () => import('../views/Setup/Country')
@ -18,15 +22,20 @@ export default new VueRouter({
routes: [
{ path: '/', component: Dashboard },
{ path: '/settings', component: Settings, meta: { transitionName: 'slide' } },
{ path: '/device', component: Device, meta: { transitionName: 'slide' } },
{ path: '/playlist', component: Playlist, meta: { transitionName: 'slide' } },
{ path: '/weather', component: Weather, meta: { transitionName: 'slide' } },
{ path: '/system', component: System, meta: { transitionName: 'slide' } },
// setup wizard
{ path: '/setup/start', component: SetupStart, meta: { transitionName: 'slide' } },
{ path: '/setup/country', component: SetupCountry, meta: { transitionName: 'slide' } },
{ path: '/setup/wifi', component: SetupWifi, meta: { transitionName: 'slide' } },
{ path: '/setup/weather', component: SetupWeather, meta: { transitionName: 'slide' } },
{ path: '/setup/name', component: SetupName, meta: { transitionName: 'slide' } },
{ path: '/setup/appearance', component: SetupAppearance, meta: { transitionName: 'slide' } },
{ path: '/setup/done', component: SetupDone, meta: { transitionName: 'slide' } },
{ path: '/setup', redirect: '/setup/start' },
{ path: '/setup/start', component: SetupStart, meta: { transitionName: 'slide', layout: 'setup' } },
{ path: '/setup/country', component: SetupCountry, meta: { transitionName: 'slide', layout: 'setup' } },
{ path: '/setup/wifi', component: SetupWifi, meta: { transitionName: 'slide', layout: 'setup' } },
{ path: '/setup/weather', component: SetupWeather, meta: { transitionName: 'slide', layout: 'setup' } },
{ path: '/setup/name', component: SetupName, meta: { transitionName: 'slide', layout: 'setup' } },
{ path: '/setup/appearance', component: SetupAppearance, meta: { transitionName: 'slide', layout: 'setup' } },
{ path: '/setup/done', component: SetupDone, meta: { transitionName: 'slide', layout: 'setup' } },
{ path: '*', redirect: '/' },
],

@ -0,0 +1,142 @@
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
// import device from '@/api/device'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
stats: {},
settings: {},
notifications: {},
},
mutations: {
setStats (state, payload) {
state.stats = payload
},
setSettings (state, payload) {
state.settings = payload
},
/*
setSensors (state, payload) {
state.sensors = payload
},
updateSensor (state, payload) {
// update sensor
const i = state.sensors.findIndex(item => item.id === payload.id)
if (i >= 0) {
state.sensors[i].temperature = payload.temperature
state.sensors[i].humidity = payload.humidity
state.sensors[i].last_update = payload.last_update
if (payload.label) {
state.sensors[i].label = payload.label
}
// update state
state.sensors = [
...state.sensors,
]
}
},
deleteSensor (state, id) {
// const i = state.sensors.findIndex(item => item.id === id)
state.sensors = state.sensors.filter(item => item.id !== id)
},
setPushUpdate (state, enable) {
state.pushUpdate = enable
},
addSensorHistory (state, payload) {
state.sensorHistory.push(payload)
if (state.sensorHistory.length > 20) {
state.sensorHistory = state.sensorHistory.slice(1)
}
},
notification (state, payload) {
state.notifications = payload
},
*/
},
actions: {
async loadStats ({ commit }) {
try {
const response = await axios.get('/stats')
commit('setStats', response.data)
} catch (error) {
commit('setStats', {})
}
},
async loadSettings ({ commit }) {
try {
const response = await axios.get('/api/settings')
commit('setSettings', response.data)
} catch (error) {
commit('setSettings', {})
}
},
/*
async getSensors ({ commit }) {
try {
const response = await axios.get('/api/sensors')
commit('setSensors', response.data)
} catch (error) {
commit('setSensors', [])
}
},
async putSensor ({ commit }, [id, sensor]) {
try {
await axios.put('/api/sensor/' + id, {
label: sensor.label,
})
commit('updateSensor', sensor)
} catch (error) {
console.warn(error)
}
},
async deleteSensor ({ commit }, id) {
try {
await axios.delete('/api/sensor/' + id)
commit('deleteSensor', id)
commit('notification', 'sensor #' + id + ' deleted')
} catch (error) {
console.warn(error)
}
},
*/
},
getters: {
/*
isAuthenticated(state) {
return state.user !== null && state.user !== undefined;
}
*/
},
})
// sensor push data
/*
const connection = new WebSocket('ws://' + window.location.host + '/ws')
connection.onmessage = (message) => {
const log = JSON.parse(message.data)
log.last_update = new Date()
store.commit('updateSensor', log)
store.commit('addSensorHistory', log)
store.commit('notification', log)
}
store.watch(
state => state.pushUpdate,
(value) => {
if (value) {
console.info('TODO:: enable websocket')
} else {
console.info('TODO:: disable websocket')
}
},
)
*/
export default store

@ -9,6 +9,18 @@
md="6"
>
<screen-card />
<!--
<v-chip
outlined
:title="firmware.rev"
color="text--disabled"
>
{{ (new Date(firmware.created * 1000).toLocaleDateString()) }}
@
{{ firmware.rev.substring(0,8) }}
</v-chip>
-->
</v-col>
<v-col
cols="12"
@ -36,6 +48,16 @@
data: () => ({
isLoading: false,
}),
computed: {
firmware () {
return {
// eslint-disable-next-line no-undef
created: __BUILD_TIME__ || JSON.stringify(new Date().getTime() / 1000 | 0),
// eslint-disable-next-line no-undef
rev: __COMMIT_HASH__ || 'dev-master',
}
},
},
}
</script>

@ -0,0 +1,25 @@
<template>
<div class="pa-5">
device settings<br>
- orientation<br>
- theme<br>
- auflösung<br>
- system time<br>
</div>
</template>
<script>
export default {
components: {
},
data: () => ({
isLoading: false,
}),
computed: {
},
}
</script>
<style scoped>
</style>

@ -0,0 +1,22 @@
<template>
<div class="pa-5">
playlist settings<br>
- switch every x seconds<br>
</div>
</template>
<script>
export default {
components: {
},
data: () => ({
isLoading: false,
}),
computed: {
},
}
</script>
<style scoped>
</style>

@ -1,450 +0,0 @@
<template>
<v-container
class="_fill-height"
fluid
>
<v-btn
v-if="currentStep > 0"
text
color="primary"
class="pl-0"
@click="currentStep--"
>
<v-icon>$prev</v-icon>Back
</v-btn>
<template v-if="currentStep === 0">
<!-- 1. hello -->
<v-card
flat
class="mx-auto"
width="520"
>
<v-card-title class="display-2 mt-12 justify-center text-center">
Hello paperdash
</v-card-title>
<case
v-if="0"
id="device_"
:class="[device.theme, device.case, device.front, 'case_orange', 'my-12']"
/>
<svg
id="device"
:class="[device.theme, device.case, device.front, 'case_orange front_orange', 'my-12 mx-auto']"
version="1.0"
xmlns="http://www.w3.org/2000/svg"
_width="561px"
_height="527px"
viewBox="0 0 5610 5270"
preserveAspectRatio="xMidYMid meet"
width="400"
>
<!--
<defs>
<pattern id="pattern_b" width="100%" height="100%">
<image x="0" y="0" height="84%" href="/face-weather-b.png"/>
</pattern>
<pattern id="pattern_w" width="100%" height="100%">
<image x="0" y="0" height="84%" href="/face-weather-w.png"/>
</pattern>
</defs>
-->
<g
id="border"
fill="#262626"
stroke="none"
>
<path
d="M442 5110 c-227 -86 -417 -161 -422 -166 -4 -5 -12 -1063 -16 -2350 -7 -2292 -6 -2342 12 -2352 31 -16 817 -242 844 -242 14 0 1086 158 2383 352 l2358 353 6 25 c9 35 -66 3683 -76 3693 -4 4 -1049 194 -2322 422 -1273 228 -2323 416 -2334 419 -11 2 -206 -67 -433 -154z"
/>
</g>
<g
fill="#f3f3f3"
stroke="none"
>
<path
id="front"
d="M860 2630 l0 -2611 33 6 c17 3 1080 163 2360 355 1281 191 2330 350 2332 352 6 6 -70 3666 -76 3672 -4 4 -4627 836 -4645 836 -2 0 -4 -1175 -4 -2610z m2378 1721 c1217 -175 2214 -320 2216 -322 7 -6 69 -3179 63 -3185 -8 -9 -4489 -624 -4499 -618 -10 6 -11 4444 0 4444 4 0 1003 -144 2220 -319z m2249 2 c-4 -3 -7 0 -7 7 0 7 3 10 7 7 3 -4 3 -10 0 -14z m60 -2890 c-4 -3 -7 0 -7 7 0 7 3 10 7 7 3 -4 3 -10 0 -14z"
/>
<path
id="image"
d="M1022 4642 c-7 -19 5 -4393 13 -4400 5 -5 4465 605 4472 611 4 4 -59 3161 -63 3166 -3 3 -3200 465 -4387 634 -19 2 -31 -1 -35 -11z"
/>
<path
id="case"
d="M438 5081 c-214 -81 -390 -150 -393 -153 -7 -6 -28 -4658 -22 -4664 2 -2 182 -56 400 -119 218 -64 403 -118 412 -121 13 -6 15 259 15 2600 0 2078 -3 2606 -12 2605 -7 0 -187 -67 -400 -148z"
/>
</g>
</svg>
<v-card-actions>
<v-btn
depressed
block
color="primary"
@click="currentStep = 1"
>
Continue
</v-btn>
</v-card-actions>
</v-card>
</template>
<template v-else-if="currentStep === 1">
<!-- country -->
<v-card
flat
class="mx-auto"
width="520"
>
<v-card-title
class="display-2 mb-12 justify-center text-center"
>
Select Your Country or Region
</v-card-title>
<v-list class="ml-5 pa-0">
<template v-for="(country, code) in countries">
<div :key="code">
<v-divider />
<v-list-item
class="pl-1"
@click="commitCountry(code, country)"
>
<!--<v-list-item-icon>{{ country.emoji }}</v-list-item-icon>-->
<v-list-item-content>{{ country.native }}</v-list-item-content>
<v-list-item-action>
<v-icon>$next</v-icon>
</v-list-item-action>
</v-list-item>
</div>
</template>
</v-list>
</v-card>
</template>
<template v-else-if="currentStep === 2">
<!-- timezone if needed -->
<v-card
flat
class="mx-auto"
width="520"
>
<v-card-title class="display-2 mb-12 justify-center text-center">
Select Your Timezone
</v-card-title>
<v-list class="ml-5 pa-0">
<template v-for="(zone, i) in timeZones">
<div :key="i">
<v-divider />
<v-list-item
class="pl-1"
@click="commitTimezone(zone)"
>
<v-list-item-content>{{ zone }}</v-list-item-content>
<v-list-item-action>
<v-icon>$next</v-icon>
</v-list-item-action>
</v-list-item>
</div>
</template>
<v-divider />
</v-list>
</v-card>
</template>
<template v-else-if="currentStep === 3">
<!-- wifi select & reboot -->
<v-card
flat
class="mx-auto"
width="520"
>
<v-card-title class="display-2 mb-12 justify-center text-center">
Choose a Wi-Fi
<br>Network
</v-card-title>
<v-list class="_ml-5 pa-0">
<v-list-item-group v-model="settings.wifi">
<template v-for="(wifi, i) in wifiAvailable">
<div :key="i">
<v-divider v-if="i > 0" />
<v-list-item
class="px-1"
:value="wifi.ssid"
@click.stop="wifiConnectModal = true"
>
<v-list-item-content>
<v-list-item-title v-text="wifi.ssid" />
<v-list-item-subtitle v-text="wifi.bssid" />
</v-list-item-content>
<v-list-item-icon>
<v-icon
v-if="wifi.secure"
class="mx-2"
>
$lock
</v-icon>
<v-icon class="mx-2">
{{ wifi.rssi | wifiIcon(0) }}
</v-icon>
<v-icon class="ml-3">
$next
</v-icon>
</v-list-item-icon>
</v-list-item>
</div>
</template>
</v-list-item-group>
<v-divider />
<v-btn
text
color="primary"
class="px-0 my-2"
>
Choose Another Network
</v-btn>
</v-list>
<v-dialog
v-model="wifiConnectModal"
max-width="400"
>
<setup-wifi-connect
:ssid="settings.wifi"
@connected="commitWifi()"
@cancel="wifiConnectModal = false"
/>
</v-dialog>
<v-divider />
</v-card>
</template>
<template v-else-if="currentStep === 4">
<!-- appearance -->
<v-card
flat
class="mx-auto"
width="520"
>
<v-card-title class="display-2 mb-12 justify-center text-center">
Appearance
</v-card-title>
<v-radio-group
v-model="settings.theme"
row
>
<v-radio
label="Light"
value="white"
/>
<v-radio
label="Dark"
value="black"
/>
</v-radio-group>
<v-card-actions>
<v-btn
depressed
block
color="primary"
@click="currentStep++"
>
Continue
</v-btn>
</v-card-actions>
</v-card>
</template>
<template v-else-if="currentStep === 5">
<!-- weather -->
<v-card
flat
class="mx-auto"
width="520"
>
<v-card-title class="display-2 mb-12 justify-center text-center">
Weather
</v-card-title>
<setup-weather :settings="settings" />
<v-card-actions class="flex-column">
<v-btn
depressed
block
color="primary"
@click="currentStep++"
>
Continue
</v-btn>
<v-btn
class="ma-0 mt-3"
text
block
color="primary"
>
Set Up Later in Settings
</v-btn>
</v-card-actions>
</v-card>
</template>
<template v-else-if="currentStep === 6">
<!-- completed -->
<v-card
flat
class="mx-auto mt-12"
width="600"
>
<!--<v-card-title class="display-2 mb-12 justify-center text-center">This is Your <br/> paperdash</v-card-title>-->
<v-card-title class="display-2 mb-12 justify-center text-center">
Welcome to paperdash
</v-card-title>
<v-card-actions>
<v-btn
depressed
block
color="primary"
to="/"
>
Get Started
</v-btn>
</v-card-actions>
</v-card>
</template>
</v-container>
</template>
<script>
import apiDevice from '@/api/device'
import { countries } from 'countries-list'
import timezones from 'countries-and-timezones'
import setupWifiConnect from '@/components/SetupWifiConnect'
import setupWeather from '@/components/SetupWeather'
import Case from '!vue-svg-loader!@/assets/case.svg'
export default {
components: {
Case,
setupWifiConnect,
setupWeather,
},
data: () => ({
// weather + ggf. image pool
currentStep: 5,
settings: {
country: 'DE',
language: 'en', // (alpha-2 codes)
timezone: 'Europe/Berlin',
wifi: null,
theme: null,
weather: {
api: '883b3c87223430d6f3a399645f8ba12b',
location: null,
lang: null,
unit: null,
},
},
device: {
theme: 'theme_w',
case: 'case_whitey',
front: 'front_whitey',
},
countries: countries,
timeZones: [],
wifi: {
available: [],
connectSSID: null,
},
wifiAvailable: [],
wifiConnectModal: false,
}),
created () {
apiDevice.wifiScan(list => {
this.wifiAvailable = list
this.isLoading = false
})
},
methods: {
commitCountry (code, country) {
this.settings.country = code
this.settings.language = country.languages[0]
// get also timezone
const zone = timezones.getCountry(code)
if (zone.timezones.length > 1) {
this.timeZones = zone.timezones
this.currentStep++
} else {
this.settings.timezone = zone.timezones[0]
this.currentStep += 2
}
},
commitTimezone (zone) {
this.settings.timezone = zone
this.currentStep++
},
commitWifi () {},
},
}
</script>
<style scoped>
#device #border,
#device > path {
fill: #343434;
}
/* TODO device theme */
#device.theme_w #image {
fill: url(#pattern_w);
}
#device.theme_b #image {
fill: url(#pattern_b);
}
/* front color */
#device.front_blacky #front {
fill: #000;
}
#device.front_whitey #front {
fill: #f3f3f3;
}
#device.front_orange #front {
fill: #ee8816;
}
/* case color */
#device.case_blacky #case {
fill: #000;
}
#device.case_whitey #case {
fill: #f3f3f3;
}
#device.case_orange #case {
fill: #ee8816;
}
</style>

@ -76,6 +76,7 @@
text
color="primary"
class="_px-0 my-2"
disabled
>
Choose Another Network
</v-btn>

@ -0,0 +1,147 @@
<template>
<div class="pa-5">
<v-card
flat
width="400"
class="mx-auto"
>
<v-list-item>
<v-list-item-icon class="mr-3">
<v-icon>$info</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Software</v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-btn
outlined
color="primary"
>
Update
</v-btn>
</v-list-item-action>
</v-list-item>
<v-divider class="mx-4" />
<v-list
class="pb-0"
>
<v-list-item three-line>
<v-list-item-content>
<v-list-item-title>Firmware</v-list-item-title>
<v-list-item-subtitle class="text--disabled">
Rev: {{ stats.firmware.rev }}
</v-list-item-subtitle>
<v-list-item-subtitle class="text--disabled">
Build: {{ new Date(stats.firmware.created * 1000).toLocaleString() }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item three-line>
<v-list-item-content>
<v-list-item-title>App</v-list-item-title>
<v-list-item-subtitle class="text--disabled">
Rev: {{ stats.app.rev }}
</v-list-item-subtitle>
<v-list-item-subtitle class="text--disabled">
Build: {{ new Date(stats.app.created * 1000).toLocaleString() }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
<v-list-item class="mt-10">
<v-list-item-icon class="mr-3">
<v-icon>$storage</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Storage</v-list-item-title>
</v-list-item-content>
<v-list-item-avatar>
<v-progress-circular
:rotate="-90"
:value="fsUsage"
class="caption"
>
{{ fsUsage }}
</v-progress-circular>
</v-list-item-avatar>
</v-list-item>
<v-divider class="mx-4" />
<v-list
class="pb-0"
>
<v-list-item>
<v-list-item-title>Total</v-list-item-title>
<v-list-item-subtitle class="text-right">
{{ fs.total | prettyBytes }}
</v-list-item-subtitle>
</v-list-item>
<v-list-item>
<v-list-item-title>Free</v-list-item-title>
<v-list-item-subtitle class="text-right">
{{ fs.free | prettyBytes }}
</v-list-item-subtitle>
</v-list-item>
</v-list>
<v-progress-linear
height="25"
:value="fsUsage"
dark
rounded
>
<template #default="{ value }">
<strong>{{ Math.ceil(value) }}%</strong>
</template>
</v-progress-linear>
<v-list-item class="mt-10">
<v-list-item-icon class="mr-3">
<v-icon>$memory</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>Memory</v-list-item-title>
</v-list-item-content>
<v-list-item-avatar>
<v-progress-circular
:rotate="-90"
:value="fsUsage"
class="caption"
>
{{ fsUsage }}
</v-progress-circular>
</v-list-item-avatar>
</v-list-item>
<v-divider class="mx-4" />
</v-card>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
components: {
},
data: () => ({
isLoading: false,
}),
computed: {
...mapState(['stats']),
fsUsage () {
return Math.round(
(100 / this.stats.device.fs.total) * this.stats.device.fs.used,
)
},
fs () {
return this.stats.device.fs
},
},
}
</script>
<style scoped>
</style>

@ -0,0 +1,22 @@
<template>
<div class="pa-5">
weather settings<br>
- input fields<br>
</div>
</template>
<script>
export default {
components: {
},
data: () => ({
isLoading: false,
}),
computed: {
},
}
</script>
<style scoped>
</style>

@ -1,258 +0,0 @@
<template>
<v-row
class="fluid fill-height"
>
<template v-if="isLoading">
<v-overlay
:absolute="true"
:value="true"
>
<v-progress-circular
indeterminate
size="64"
/>
</v-overlay>
</template>
<template v-if="true">
<v-container>
<v-snackbar
v-model="isSnackbar"
:timeout="3000"
color="success"
>
i8n:saved
</v-snackbar>
<!-- status current wifi -->
<v-card
max-width="344"
class="mx-auto"
>
<v-template v-if="!wifiStats.connected">
<v-card-text>
<v-icon>$signalWifi0</v-icon>
not connected
<v-btn
color="primary"
depressed
small
>
i8n:scan
</v-btn>
</v-card-text>
</v-template>
<v-template v-else>
<v-toolbar flat>
<v-toolbar-title class="title font-weight-light">
<v-icon left>
$signalWifi3Lock
</v-icon>
xd-design.info
</v-toolbar-title>
<v-spacer />
<v-menu offset-y>
<template #activator="{ on }">
<v-btn
icon
small
v-on="on"
>
<v-icon>mdi-dots-vertical</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item @click="scan()">
<v-list-item-title>i8n:scan</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-toolbar>
<v-divider class="mx-4" />
<v-list dense>
<v-list-item
v-for="(value, key) in wifiStats"
:key="key"
>
<v-list-item-title>{{ key }}</v-list-item-title>
<v-list-item-subtitle class="text-right">
{{ value }}
</v-list-item-subtitle>
</v-list-item>
</v-list>
</v-template>
</v-card>
<br><br>
<!-- connect to wifi -->
<v-dialog
v-model="wifiConnectModal"
max-width="400"
>
<v-card>
<v-card-title class="headline">
{{ wifiConnectSSID }}
</v-card-title>
<v-card-text>
<v-text-field
v-model="wifiConnectPassword"
:append-icon="show1 ? 'mdi-eye' : 'mdi-eye-off'"
:type="show1 ? 'text' : 'password'"
label="i8n:Password"
@click:append="show1 = !show1"
/>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="wifiConnectModal = false"
>
i8n:Cancel
</v-btn>
<v-btn
depressed
:loading="isConnecting"
color="primary darken-1"
@click="onWifiConnect()"
>
i8n:Connect
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-card
class="mx-auto"
max-width="344"
tile
>
<v-list>
<v-subheader>Wifi found</v-subheader>
<v-list-item-group
v-model="wifiConnectSSID"
color="primary"
>
<v-list-item
v-for="(wifi, i) in wifiAvailable"
:key="i"
:value="wifi.ssid"
@click.stop="wifiConnectModal = true"
>
<v-list-item-icon>
<v-icon v-text="getWifiIcon(wifi)" />
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="wifi.ssid" />
<v-list-item-subtitle v-text="wifi.bssid" />
</v-list-item-content>
<v-list-item-avatar>
<v-avatar
color="teal"
size="24"
>
<span class="white--text headline caption">{{ wifi.channel }}</span>
</v-avatar>
</v-list-item-avatar>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</v-container>
</template>
</v-row>
</template>
<script>
import apiDevice from '../api/device'
export default {
name: 'Wifi',
data: () => ({
isLoading: true,
isSnackbar: false,
isConnecting: false,
mode: 'AP_initial', // AP_initial, AP_lost, Default
// todo load
wifiStats: {
connected: true,
ip: 'xxx.xxx.xxx.xxx',
mac: 'xxxx-xxxx-xxxx-xxxx',
channel: 11,
dns: 'xxx.xxx.xxx.xxx',
gateway: 'xxx.xxx.xxx.xxx',
},
wifiAvailable: [],
wifiConnectModal: false,
wifiConnectSSID: null,
wifiConnectPassword: null,
show1: false,
}),
computed: {
},
created () {
// this.$vuetify.icons.values.signalWifi0 = {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/signal_wifi_0_bar/baseline.svg')}
// this.$vuetify.icons.values.signalWifi1 = {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/signal_wifi_1_bar/baseline.svg')}
// this.$vuetify.icons.values.signalWifi1Lock = {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/signal_wifi_1_bar_lock/baseline.svg')}
// this.$vuetify.icons.values.signalWifi2 = {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/signal_wifi_2_bar/baseline.svg')}
// this.$vuetify.icons.values.signalWifi2Lock = {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/signal_wifi_2_bar_lock/baseline.svg')}
// this.$vuetify.icons.values.signalWifi3 = {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/signal_wifi_3_bar/baseline.svg')}
// this.$vuetify.icons.values.signalWifi3Lock = {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/signal_wifi_3_bar_lock/baseline.svg')}
// this.$vuetify.icons.values.signalWifi4 = {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/signal_wifi_4_bar/baseline.svg')}
// this.$vuetify.icons.values.signalWifi4Lock = {component: () => import(/* webpackChunkName: "icons" */'!vue-svg-loader!@material-icons/svg/svg/signal_wifi_4_bar_lock/baseline.svg')}
apiDevice.wifiScan(list => {
this.wifiAvailable = list
this.isLoading = false
})
},
methods: {
getWifiIcon (wifi) {
let icon = '$signalWifi'
// strength
if (wifi.rssi >= -67) {
icon += 4
} else if (wifi.rssi >= -70) {
icon += 3
} else if (wifi.rssi >= -80) {
icon += 2
} else if (wifi.rssi >= -90) {
icon += 1
} else {
icon += 0
}
// secure
if (wifi.secure !== 0 && wifi.rssi >= -90) {
icon += 'Lock'
}
return icon
},
onWifiConnect () {
this.isConnecting = true
apiDevice.wifiConnect(this.wifiConnectSSID, this.wifiConnectPassword)
},
},
}
</script>
<style scoped>
</style>

@ -6967,11 +6967,6 @@ modify-values@^1.0.0:
resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==
moment@^2.19.2:
version "2.29.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.0.tgz#fcbef955844d91deb55438613ddcec56e86a3425"
integrity sha512-z6IJ5HXYiuxvFTI6eiQ9dm77uE0gyy1yXNApVHqTcnIKfY9tIwEjlzsZ6u1LQXvVgKeTnv9Xm7NDvJ7lso3MtA==
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
@ -10306,13 +10301,6 @@ vue-loader@^15.9.2:
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
vue-moment@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/vue-moment/-/vue-moment-4.1.0.tgz#092a8ff723a96c6f85a0a8e23ad30f0bf320f3b0"
integrity sha512-Gzisqpg82ItlrUyiD9d0Kfru+JorW2o4mQOH06lEDZNgxci0tv/fua1Hl0bo4DozDV2JK1r52Atn/8QVCu8qQw==
dependencies:
moment "^2.19.2"
vue-router@^3.1.5:
version "3.4.5"
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-3.4.5.tgz#d396ec037b35931bdd1e9b7edd86f9788dc15175"
@ -10370,6 +10358,11 @@ vuetify@^2.2.28:
resolved "https://registry.yarnpkg.com/vuetify/-/vuetify-2.3.15.tgz#ea148acce8f5bc272f64f03a0d2ace09e58e092c"
integrity sha512-YVJN/ld60S2mmFCKxoVFkB8X2kmuLT0E28ql4kr8HQUeCYdK8axoca/N8ZIP1hFH4NIz392f0nVpZKS4ZFZBVA==
vuex@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.5.1.tgz#f1b8dcea649bc25254cf4f4358081dbf5da18b3d"
integrity sha512-w7oJzmHQs0FM9LXodfskhw9wgKBiaB+totOdb8sNzbTB2KDCEEwEs29NzBZFh/lmEK1t5tDmM1vtsO7ubG1DFw==
watchpack-chokidar2@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0"

Loading…
Cancel
Save