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">&times;</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>