Remote control
parent
568f4e5c26
commit
01aa50fd78
|
@ -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>
|
||||
|
|
|
@ -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",
|
||||
|
|
80
src/App.vue
80
src/App.vue
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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/>'
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
]
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
Loading…
Reference in New Issue