Remote control

master
informatic 2018-06-18 10:12:31 +02:00
parent 568f4e5c26
commit 01aa50fd78
10 changed files with 300 additions and 131 deletions

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>antd-demo</title>
<title>Snowmix-Vue</title>
</head>
<body>
<div id="app"></div>

View File

@ -12,6 +12,7 @@
},
"dependencies": {
"axios": "^0.18.0",
"socket.io-client": "^2.1.1",
"vue": "^2.5.2",
"vue-antd-ui": "^0.6.0",
"vue-drag-resize": "^1.2.3",

View File

@ -1,81 +1,35 @@
<template>
<div id="app">
<!--router-view/>-->
<a-row :gutter=4>
<a-col :span="6">
<Scene v-bind:frames="frames" />
</a-col>
<a-col :span="6" v-for="item in frames" :key="item.id">
<Frame v-bind:frame="item" v-bind:sources="sources"/>
</a-col>
</a-row>
<!--
<a-row>
<a-col :span="6">
<a-card>
<DragParent v-model="positionScale" targetWidth="1280" targetHeight="720">
<DraggableFrame v-for="frame in frames" :key="frame.id" v-model="frame.position" v-bind:scale="positionScale" v-if="positionScale > 0">
</DraggableFrame>
</DragParent>
</a-card>
</a-col>
</a-row>
-->
<a-menu mode="horizontal" theme="dark">
<a-menu-item>
<router-link to="/">Home</router-link>
</a-menu-item>
<a-menu-item v-for="scene in scenes" :key="scene.id">
<router-link :to="{ name: 'SceneView', params: { id: scene.id } }">
<a-badge :status="scene.active ? 'success': 'error'" />{{ scene.name }}
</router-link>
</a-menu-item>
</a-menu>
<router-view />
</div>
</template>
<script>
import Scene from './components/Scene.vue'
import Frame from './components/Frame.vue'
import DraggableFrame from './components/DraggableFrame.vue'
import DragParent from './components/DragParent.vue'
export default {
name: 'App',
components: {
Scene,
Frame,
DraggableFrame,
DragParent
},
data: function () {
return {
frames: [
{
id: 5,
visible: true,
front: {
alpha: 0.9,
source: 'feed:1'
},
back: {
alpha: 0.9,
source: 'feed:2'
},
position: { x: 0, y: 0, width: 1280, height: 720 }
},
{
id: 10,
visible: false,
front: {
alpha: 0.9,
source: 'feed:2'
},
back: {
alpha: 0.9,
source: 'feed:1'
},
position: { x: 0, y: 0, width: 1280 / 2, height: 720 / 2 }
}
],
sources: [
{ id: 'feed:1', name: 'Camera', state: 'playing' },
{ id: 'feed:2', name: 'HDMI Capture', state: 'pending' },
{ id: 'image:1', name: 'Background' },
{ id: 'image:2', name: 'Dead stream' }
],
positionScale: null
}
computed: {
scenes () {
return this.$store.state.scenes
},
}
}
</script>

View File

@ -1,66 +1,53 @@
<template>
<a-card title="Frame 1">
<!--<a href="#" slot="extra">more</a>-->
<a-switch slot="extra" v-model="frame.visible" />
<a-row type="flex" justify="center" align="top" style="height: 200px">
<div style="float:left;height: 100%;">
<a-slider vertical :min="0" :max="1.0" :step="0.01" v-model="frame.front.alpha"/>
</div>
<div style="float:left;height: 100%;flex-grow:1">
<a-radio-group defaultValue="a" size="small">
<a-radio-button value="a">Hangzhou</a-radio-button>
<a-radio-button value="d">Chengdu</a-radio-button>
</a-radio-group>
</div>
<div style="float:left;height: 100%;">
<a-slider vertical :min="0" :max="1.0" :step="0.01" v-model="frame.back.alpha" />
</div>
</a-row>
<a-tabs defaultActiveKey="1" size="small" :tabBarGutter="1" slot="actions">
<a-tab-pane tab="Source" key="1">
<a-row :gutter=4>
<a-col :span=12>
<a-select defaultValue="lucy" style="width:100%" v-model="frame.front.source">
<a-select-opt-group>
<span slot="label"><a-icon type="video-camera"/> Feed</span>
<a-select-option value="feed:1"><a-badge status="success" text="Camera" /></a-select-option>
<a-select-option value="feed:2"><a-badge status="warning" text="HDMI" /></a-select-option>
</a-select-opt-group>
<a-select-opt-group>
<span slot="label"><a-icon type="picture"/> Image</span>
<a-select-option value="image:1">yiminghe</a-select-option>
</a-select-opt-group>
<a-select-opt-group>
<span slot="label">Others</span>
</a-select-opt-group>
</a-select>
</a-col>
<a-col :span=12>
<a-select defaultValue="lucy" style="width:100%">
<a-select-opt-group>
<span slot="label"><a-icon type="user"/> Feed</span>
<a-select-option value="jack">Jack</a-select-option>
<a-select-option value="lucy">Lucy</a-select-option>
</a-select-opt-group>
<a-select-opt-group label="Image">
<a-select-option value="Yiminghe">yiminghe</a-select-option>
</a-select-opt-group>
</a-select>
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</a-card>
<a-card :title="frameName">
<a-switch slot="extra" v-model="frame.active" />
<a-row type="flex" justify="center" align="top" style="height: 200px">
<div style="float:left;height: 100%;">
<a-slider vertical :min="0" :max="1.0" :step="0.01" v-model="frame.front.alpha"/>
</div>
<div style="float:left;height: 100%;flex-grow:1">
<a-radio-group defaultValue="a" size="small">
<a-radio-button value="a">Hangzhou</a-radio-button>
<a-radio-button value="d">Chengdu</a-radio-button>
</a-radio-group>
</div>
<div style="float:left;height: 100%;">
<a-slider vertical :min="0" :max="1.0" :step="0.01" v-model="frame.back.alpha" />
</div>
</a-row>
<a-tabs defaultActiveKey="1" size="small" :tabBarGutter="1" slot="actions">
<a-tab-pane tab="Source" key="1">
<a-row :gutter=4>
<a-col :span=12>
<SourceSelector v-model="frame.front.source"/>
</a-col>
<a-col :span=12>
<SourceSelector v-model="frame.back.source" />
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</a-card>
</template>
<script>
import SourceSelector from './SourceSelector.vue'
export default {
name: 'Frame',
props: ['frame', 'sources'],
components: {
SourceSelector
},
data () {
return {
msg: 'Welcome to Your Vue.js App'
}
},
computed: {
frameName () {
return this.frame.name || ('Frame ' + this.frame.id)
}
}
}
</script>

View File

@ -1,28 +1,29 @@
<template>
<a-card title="Scene 1">
<a-card :title="sceneName">
<a-row type="flex" justify="center" align="top" style="height: 200px">
<div style="float:left;height: 100%;">
<a-slider vertical :defaultValue="30" />
<a-slider vertical :min="0" :max="1.0" :step="0.01" :value="scene.alpha" v-on:change="setSceneAlpha([scene, null, $event])" />
</div>
<div style="float:left;height: 100%;flex-grow:1">
<div style="float:left;height: 100%;flex-grow:1; text-align: center">
<a-button-group>
<a-dropdown>
<a-menu slot="overlay" @click="handleMenuClick">
<a-menu-item key="1">1st menu item</a-menu-item>
<a-menu-item key="2">2nd menu item</a-menu-item>
<a-menu-item key="3">3rd item</a-menu-item>
<a-menu slot="overlay">
<a-menu-item v-for="s in scenes" v-if="s != scene">
<router-link :to="{ name: 'SceneView', params: { id: s.id }}" v-on:click.native="sceneFade(s)">{{ s.name }}</router-link>
</a-menu-item>
</a-menu>
<a-button style="margin-left: 8px">
<a-button>
<a-icon type="down" /> Fade
</a-button>
</a-dropdown>
<a-dropdown>
<a-menu slot="overlay" @click="handleMenuClick">
<a-menu-item key="1">1st menu item</a-menu-item>
<a-menu-item key="2">2nd menu item</a-menu-item>
<a-menu-item key="3">3rd item</a-menu-item>
<a-menu slot="overlay">
<a-menu-item v-for="s in scenes" v-if="s != scene">
<router-link :to="{ name: 'SceneView', params: { id: s.id }}" v-on:click.native="sceneCut(s)">{{ s.name }}</router-link>
</a-menu-item>
</a-menu>
<a-button style="margin-left: 8px">
<a-button>
Cut <a-icon type="down" />
</a-button>
</a-dropdown>
@ -32,14 +33,16 @@
<a-slider vertical :defaultValue="30" />
</div>
</a-row>
<a-tabs defaultActiveKey="1" size="small" tabBarGutter="1" slot="actions">
<a-tabs defaultActiveKey="1" size="small" :tabBarGutter="1" slot="actions">
<a-tab-pane tab="Placing" key="1">
<div style="margin: 0 6px 6px 6px; border: 1px solid #999">
<DragParent v-model="positionScale" targetWidth="1280" targetHeight="720">
<DraggableFrame v-for="frame in frames" :key="frame.id" v-model="frame.position" v-bind:scale="positionScale" v-if="positionScale > 0">
<span :class="{ activeFrame: frame.visible }">{{ frame.id }}</span>
</DraggableFrame>
</DragParent>
</div>
<div v-for="frame in frames" :key="frame.id">{{ frame.position }}</div>
</a-tab-pane>
<a-tab-pane tab="Timing" key="2">
<a-slider :defaultValue="30" />
@ -52,6 +55,8 @@
</template>
<script>
import { mapActions } from 'vuex'
import DraggableFrame from './DraggableFrame.vue'
import DragParent from './DragParent.vue'
@ -61,11 +66,29 @@ export default {
DragParent,
DraggableFrame
},
props: ['frames'],
props: ['scene'],
data () {
return {
positionScale: null
}
},
computed: {
frames () {
return this.scene.frames
},
sceneName () {
return this.scene.name || 'Scene ' + this.scene.id
},
scenes () {
return this.$store.state.scenes
}
},
methods: {
...mapActions([
'setSceneAlpha',
'sceneFade',
'sceneCut'
])
}
}
</script>
@ -87,4 +110,19 @@ padding: 6px 0;
.ant-card-extra {
padding: 6.5px 0;
}
.vdr {
display: grid;
}
.vdr span {
align-self: center;
text-align: center;
opacity: 0.5
}
.vdr span.activeFrame {
font-weight: bold;
opacity: 1;
}
</style>

View File

@ -0,0 +1,38 @@
<template>
<a-row :gutter=4>
<a-col :span="6">
<iframe src="http://10.8.0.95:8080/" width="100%"></iframe>
<Scene v-bind:scene="currentScene" />
</a-col>
<a-col :span="6" v-for="item in frames" :key="item.id">
<Frame v-bind:scene="currentScene" v-bind:frame="item" v-bind:sources="sources"/>
</a-col>
</a-row>
</template>
<script>
import Scene from '@/components/Scene.vue'
import Frame from '@/components/Frame.vue'
export default {
name: 'SceneView',
components: {
Scene,
Frame
},
computed: {
sources () {
return this.$store.state.sources
},
scenes () {
return this.$store.getters.scenes
},
frames () {
return this.currentScene.frames
},
currentScene () {
return this.$store.state.scenes[this.$route.params.id]
}
}
}
</script>

View File

@ -0,0 +1,39 @@
<template>
<a-select style="width:100%" :value="value" v-on:change="$emit('input', $event)">
<a-select-option value="-:-1"><a-badge status="default" text="Blank" /></a-select-option>
<a-select-opt-group>
<span slot="label"><a-icon type="video-camera"/> Feed</span>
<a-select-option :value="'feed:' + source.id" v-for="source in feeds">
<a-badge :status="mapState(source.state)" :text="source.name" />
</a-select-option>
</a-select-opt-group>
<a-select-opt-group>
<span slot="label"><a-icon type="picture"/> Image</span>
<a-select-option :value="'image:' + image.id" v-for="image in images">{{ image.source }}</a-select-option>
</a-select-opt-group>
<a-select-opt-group>
<span slot="label">Others</span>
</a-select-opt-group>
</a-select>
</template>
<script>
export default {
name: 'SourceSelector',
props: ['value'],
computed: {
feeds () { return this.$store.state.feeds },
images () { return this.$store.state.images },
},
methods: {
mapState (state) {
var map = {
'STALLED': 'warning',
'PENDING': 'warning',
'OK': 'success',
}
return map[state] || 'error'
}
}
}
</script>

View File

@ -5,7 +5,9 @@ import Vue from 'vue'
import Antd from 'vue-antd-ui'
import 'vue-antd-ui/dist/antd.css'
import VueDragResize from 'vue-drag-resize'
import router from './router'
import store from './store'
import App from './App'
Vue.config.productionTip = false
@ -17,6 +19,10 @@ Vue.use(Antd)
new Vue({
el: '#app',
router,
store,
created () {
this.$store.dispatch('initialize')
},
components: { App },
template: '<App/>'
})

View File

@ -1,6 +1,7 @@
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
import SceneView from '@/components/SceneView'
Vue.use(Router)
@ -10,6 +11,11 @@ export default new Router({
path: '/',
name: 'HelloWorld',
component: HelloWorld
},
{
path: '/scene/:id',
name: 'SceneView',
component: SceneView
}
]
})

100
src/store.js Normal file
View File

@ -0,0 +1,100 @@
import Vue from 'vue'
import Vuex from 'vuex'
import axios from 'axios'
import io from 'socket.io-client'
Vue.use(Vuex)
const API_HOST = '10.8.0.95'
const api = axios.create({
baseURL: 'http://' + API_HOST + ':5000/api/1',
timeout: 1000
})
export default new Vuex.Store({
state: {
feeds: {},
images: {},
scenes: {}
},
getters: {
scenes: state => state.scenes
},
mutations: {
LOAD_SCENES (state, data) {
console.info('Scenes:', data)
state.scenes = data
},
LOAD_IMAGES (state, data) {
console.info('Images:', data)
state.images = data
},
LOAD_FEEDS (state, data) {
console.info('Feeds:', data)
state.feeds = data
}
},
actions: {
initialize ({ commit }) {
this.socket = io('http://' + API_HOST + ':5000')
this.socket.on('connect', () => {
console.log('Socket.IO connected:', this.socket.id)
})
this.socket.on('feeds', (data) => commit('LOAD_FEEDS', data))
this.socket.on('scenes', (data) => commit('LOAD_SCENES', data))
this.socket.on('images', (data) => commit('LOAD_IMAGES', data))
},
setSceneAlpha ({ commit }, [scene, type, alpha]) {
console.info(scene.id, type, alpha)
if (!type) {
type = ''
} else {
type += '_'
}
scene[type + 'alpha'] = alpha
this.socket.emit('scene_mod', {
'id': scene.id,
[type + 'alpha']: alpha
})
/*return api.patch('/scenes/' + scene.id, {
[type + 'alpha']: alpha
})*/
},
setFrameAlpha ({ commit }, [sceneId, frameId, side, alpha]) {
console.info('Setting alpha for', frameId, alpha)
this.state.scenes[sceneId].frames[frameId][side].alpha = alpha
return api.patch('/scenes/' + sceneId + '/' + frameId, {
[side]: {
'alpha': alpha
}
})
},
setFrameSource ({ commit }, [sceneId, frameId, side, source]) {
return api.patch('/scenes/' + sceneId + '/' + frameId, {
[side]: {
'source': source
}
})
},
setFrameActive ({ commit }, [sceneId, frameId, active]) {
return api.patch('/scenes/' + sceneId + '/' + frameId, {
'active': active
})
},
sceneFade ({ commit }, scene) {
this.socket.emit('scene_fade', { id: scene.id })
console.info('Fading', scene)
},
sceneCut ({ commit }, scene) {
this.socket.emit('scene_cut', { id: scene.id })
console.info('Cutting', scene)
}
}
})