358 lines
7.7 KiB
Vue
358 lines
7.7 KiB
Vue
<template>
|
|
<div>
|
|
|
|
<md-dialog :md-active.sync="show.editor">
|
|
<md-dialog-title>{{ capitalizedType }} preferences</md-dialog-title>
|
|
|
|
<form class="editor-form">
|
|
|
|
<div v-if="type === 'ingest' && apiId !== null">
|
|
|
|
<md-field>
|
|
<label>URL</label>
|
|
<md-input :readonly="true" v-model="editorData.url"></md-input>
|
|
</md-field>
|
|
|
|
<md-field>
|
|
<label>Stream Key</label>
|
|
<md-input :readonly="true" type="password" v-model="editorData.streamkey"></md-input>
|
|
</md-field>
|
|
|
|
|
|
</div>
|
|
<div v-if="type === 'ingest' && apiId === null">
|
|
|
|
<b>Press APPLY first, to generate your connection details!</b>
|
|
|
|
</div>
|
|
<div v-else-if="type === 'encoder'">
|
|
|
|
<md-field>
|
|
<label>Width</label>
|
|
<md-input type="number" v-model="editorData.width"></md-input>
|
|
</md-field>
|
|
|
|
<md-field>
|
|
<label>Height</label>
|
|
<md-input type="number" v-model="editorData.height"></md-input>
|
|
</md-field>
|
|
|
|
<md-field>
|
|
<label>Bitrate</label>
|
|
<md-input type="number" v-model="editorData.bitrate"></md-input>
|
|
</md-field>
|
|
|
|
|
|
</div>
|
|
<div v-else-if="type === 'restreamer'">
|
|
|
|
<md-field>
|
|
<label>URL</label>
|
|
<md-input v-model="editorData.url"></md-input>
|
|
</md-field>
|
|
|
|
<md-field>
|
|
<label>Stream Key</label>
|
|
<md-input type="password" v-model="editorData.streamkey"></md-input>
|
|
</md-field>
|
|
|
|
|
|
</div>
|
|
|
|
</form>
|
|
|
|
|
|
<md-dialog-actions>
|
|
<md-button @click="show.editor = false">{{ discardButtonLabel }}</md-button>
|
|
<md-button class="md-primary" @click="saveEdit" v-if="type !== 'ingest'">Save</md-button>
|
|
</md-dialog-actions>
|
|
</md-dialog>
|
|
|
|
<div class="flowchart-node" :style="nodeStyle"
|
|
@mousedown.prevent="handleMousedown"
|
|
@mouseover.prevent="handleMouseOver"
|
|
@mouseleave.prevent="handleMouseLeave"
|
|
@dblclick.prevent="editProperties"
|
|
v-bind:class="{selected: options.selected === id}">
|
|
|
|
<!--- Input port --->
|
|
<div class="node-port node-input"
|
|
v-if="haveInput"
|
|
@mousedown.prevent
|
|
@mouseup.prevent="inputMouseUp">
|
|
</div>
|
|
|
|
<!--- The box itself --->
|
|
<div class="node-main">
|
|
<div class="node-type">{{ capitalizedType }}<span v-if="apiId === null">*</span></div>
|
|
<div class="node-content node-content-long" v-if="type === 'ingest'">
|
|
|
|
<span v-if="this.apiId === null">Apply first!</span>
|
|
<span v-else><b>URL:</b> {{this.data.url}}</span>
|
|
|
|
</div>
|
|
<div class="node-content" v-else-if="type === 'encoder'">
|
|
<table>
|
|
<tr>
|
|
<th>width</th>
|
|
<td>{{ this.data.width }}</td>
|
|
<td>px</td>
|
|
</tr>
|
|
<tr>
|
|
<th>height</th>
|
|
<td>{{ this.data.height }}</td>
|
|
<td>px</td>
|
|
</tr>
|
|
<tr>
|
|
<th>bitrate</th>
|
|
<td>{{ this.data.bitrate }}</td>
|
|
<td>kbps</td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
<div class="node-content node-content-long" v-else-if="type === 'restreamer'">
|
|
<b>Target: </b> {{ this.data.url }}
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
<!--- Output port --->
|
|
<div class="node-port node-output"
|
|
v-if="haveOutput"
|
|
@mousedown.prevent="outputMouseDown">
|
|
</div>
|
|
|
|
<div v-show="show.delete" class="node-delete">×</div>
|
|
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
|
|
import {cloneDeep, capitalize} from 'lodash';
|
|
|
|
export default {
|
|
name: 'FlowchartNode',
|
|
props: {
|
|
id: {
|
|
type: Number,
|
|
default: 1000,
|
|
validator(val) {
|
|
return typeof val === 'number'
|
|
}
|
|
},
|
|
x: {
|
|
type: Number,
|
|
default: 0,
|
|
validator(val) {
|
|
return typeof val === 'number'
|
|
}
|
|
},
|
|
y: {
|
|
type: Number,
|
|
default: 0,
|
|
validator(val) {
|
|
return typeof val === 'number'
|
|
}
|
|
},
|
|
type: {
|
|
type: String,
|
|
default: 'Default'
|
|
},
|
|
data: {
|
|
type: Object,
|
|
default: () => {
|
|
}
|
|
},
|
|
apiId: {
|
|
type: String,
|
|
default: null
|
|
},
|
|
options: {
|
|
type: Object,
|
|
default() {
|
|
return {
|
|
centerX: 0,
|
|
scale: 1,
|
|
centerY: 0,
|
|
}
|
|
}
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
show: {
|
|
delete: false,
|
|
editor: false
|
|
},
|
|
editorData: {}
|
|
}
|
|
},
|
|
mounted() {
|
|
if (this.apiId === null && this.type !== 'ingest') {
|
|
this.$nextTick(() => this.show.editor = true);
|
|
}
|
|
},
|
|
computed: {
|
|
nodeStyle() {
|
|
return {
|
|
top: this.options.centerY + this.y * this.options.scale + 'px', // remove: this.options.offsetTop +
|
|
left: this.options.centerX + this.x * this.options.scale + 'px', // remove: this.options.offsetLeft +
|
|
transform: `scale(${this.options.scale})`,
|
|
}
|
|
},
|
|
haveInput() {
|
|
return this.type !== "ingest"
|
|
},
|
|
haveOutput() {
|
|
return this.type !== "restreamer"
|
|
},
|
|
capitalizedType() {
|
|
return capitalize(this.type);
|
|
},
|
|
discardButtonLabel() {
|
|
if (this.type === 'ingest') {
|
|
return "CLOSE";
|
|
} else {
|
|
return "DISCARD";
|
|
}
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
handleMousedown(e) {
|
|
const target = e.target || e.srcElement;
|
|
// console.log(target);
|
|
if (target.className.indexOf('node-input') < 0 && target.className.indexOf('node-output') < 0) {
|
|
this.$emit('nodeSelected', e);
|
|
}
|
|
e.preventDefault();
|
|
},
|
|
handleMouseOver() {
|
|
this.show.delete = true;
|
|
},
|
|
handleMouseLeave() {
|
|
this.show.delete = false;
|
|
},
|
|
outputMouseDown() {
|
|
this.$emit('linkingStart')
|
|
},
|
|
inputMouseUp() {
|
|
this.$emit('linkingStop')
|
|
},
|
|
editProperties() {
|
|
this.editorData = cloneDeep(this.data);
|
|
this.show.editor = true;
|
|
},
|
|
saveEdit() {
|
|
this.show.editor = false;
|
|
this.$emit('update:data', this.editorData);
|
|
this.$emit("dataUpdated", this.data);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
|
<style scoped lang="scss">
|
|
$themeColor: rgb(0, 0, 0);
|
|
$portSize: 14;
|
|
|
|
.flowchart-node {
|
|
margin: 0;
|
|
width: 200px;
|
|
height: 150px;
|
|
position: absolute;
|
|
box-sizing: border-box;
|
|
border: none;
|
|
background: white;
|
|
z-index: 1;
|
|
opacity: .9;
|
|
cursor: move;
|
|
transform-origin: top left;
|
|
|
|
.node-main {
|
|
|
|
overflow: hidden;
|
|
|
|
.node-type {
|
|
background: $themeColor;
|
|
color: white;
|
|
font-size: 13px;
|
|
padding: 6px;
|
|
text-align: center;
|
|
}
|
|
|
|
.node-content {
|
|
font-size: 13px;
|
|
padding: 0.5em;
|
|
|
|
th {
|
|
width: 50%;
|
|
}
|
|
|
|
}
|
|
|
|
.node-content-long {
|
|
word-break: break-all;
|
|
}
|
|
|
|
}
|
|
|
|
.node-port {
|
|
position: absolute;
|
|
width: #{$portSize}px;
|
|
height: #{$portSize}px;
|
|
left: 50%;
|
|
transform: translate(-50%);
|
|
border: 1px solid #ccc;
|
|
border-radius: 100px;
|
|
background: white;
|
|
|
|
&:hover {
|
|
background: $themeColor;
|
|
border: 1px solid $themeColor;
|
|
}
|
|
}
|
|
|
|
.node-input {
|
|
top: #{-2+$portSize/-2}px;
|
|
}
|
|
|
|
.node-output {
|
|
bottom: #{-2+$portSize/-2}px;
|
|
}
|
|
|
|
.node-delete {
|
|
position: absolute;
|
|
right: -8px;
|
|
top: -8px;
|
|
font-size: 14px;
|
|
width: 16px;
|
|
height: 16px;
|
|
color: $themeColor;
|
|
cursor: pointer;
|
|
background: white;
|
|
border: 1px solid $themeColor;
|
|
border-radius: 100px;
|
|
text-align: center;
|
|
line-height: 16px;
|
|
|
|
&:hover {
|
|
background: $themeColor;
|
|
color: white;
|
|
}
|
|
}
|
|
}
|
|
|
|
.editor-form {
|
|
padding: 1.5em;
|
|
}
|
|
|
|
.selected {
|
|
box-shadow: 0 0 0 2px $themeColor;
|
|
}
|
|
</style>
|