Move frontend files
This commit is contained in:
parent
b7ce9d850d
commit
36da67f369
36 changed files with 2 additions and 5 deletions
114
frontend/src/App.vue
Normal file
114
frontend/src/App.vue
Normal file
|
@ -0,0 +1,114 @@
|
|||
<script setup lang="ts">
|
||||
import {ref, onMounted, watch} from 'vue'
|
||||
import MainPage from './components/pages/MainPage.vue';
|
||||
import VideoPage from './components/pages/VideoPage.vue';
|
||||
import VerticalTabs from './components/tabs/VerticalTabs.vue';
|
||||
import Tab from './components/tabs/Tab.vue';
|
||||
import LightingPage from './components/pages/LightingPage.vue';
|
||||
import ServisPage from './components/pages/ServisPage.vue';
|
||||
import {$mqtt} from "vue-paho-mqtt"
|
||||
import AudioPage from "@/components/pages/AudioPage.vue";
|
||||
|
||||
|
||||
document.addEventListener('contextmenu', event => event.preventDefault());
|
||||
|
||||
|
||||
let urlParams = new URLSearchParams(window.location.search);
|
||||
const currentRoom = ref(urlParams.get('room') || 'none')
|
||||
|
||||
const pageNum = ref(0)
|
||||
|
||||
const mqttStat = ref($mqtt.status())
|
||||
|
||||
watch(mqttStat, (_, newState) => {
|
||||
|
||||
})
|
||||
|
||||
const servisActuve = ref(false)
|
||||
|
||||
|
||||
watch(pageNum, (_, newState) => {
|
||||
console.log(pageNum)
|
||||
// console.log(newState)
|
||||
servisActuve.value = (pageNum.value == 4);
|
||||
})
|
||||
|
||||
//TODO display none namest uno
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="currentRoom == 'none'">
|
||||
<h1>Missing room parameter! Add e.g. <code>?room=P01</code> to the URL</h1>
|
||||
</div>
|
||||
<div v-else id="wrapper">
|
||||
<header class="sidebar">
|
||||
<img class="logo" src="https://fri.uni-lj.si/sites/all/themes/fri_theme/images/fri_logo.png"/>
|
||||
<h1>{{ currentRoom.toUpperCase() }}</h1>
|
||||
|
||||
<VerticalTabs id="nav">
|
||||
<Tab @click="pageNum=0" :selected="pageNum==0">Priprava</Tab>
|
||||
<Tab @click="pageNum=1" :selected="pageNum==1">Video</Tab>
|
||||
<Tab @click="pageNum=2" :selected="pageNum==2">Audio</Tab>
|
||||
<Tab @click="pageNum=3" :selected="pageNum==3">Lučke</Tab>
|
||||
<Tab @click="pageNum=4" :selected="pageNum==4">Servis</Tab>
|
||||
</VerticalTabs>
|
||||
<div class="mstatus" v-if="$mqtt.status() != 'connected'">{{ $mqtt.status()?.toUpperCase() }}</div>
|
||||
<button class="reload" v-if="$mqtt.status() != 'connected'" onclick="window.location.reload()">RELOAD</button>
|
||||
|
||||
</header>
|
||||
<main>
|
||||
<MainPage :class="{'hiddenPage': pageNum != 0}" :room="currentRoom"/>
|
||||
<VideoPage :class="{'hiddenPage': pageNum != 1}" :room="currentRoom"/>
|
||||
<AudioPage :class="{'hiddenPage': pageNum != 2}" :room="currentRoom"/>
|
||||
<LightingPage :class="{'hiddenPage': pageNum != 3}" :room="currentRoom"/>
|
||||
<ServisPage :class="{'hiddenPage': pageNum != 4}" :room="currentRoom" :currently-active="servisActuve"/>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.reload {
|
||||
opacity: .8;
|
||||
font-size: .8em;
|
||||
margin: 0 3em;
|
||||
}
|
||||
|
||||
.hiddenPage {
|
||||
display: none !important
|
||||
}
|
||||
|
||||
#wrapper {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
max-height: 100vh;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
main > * {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
max-width: 10em;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 100%;
|
||||
padding-left: .8rem;
|
||||
padding-top: .8rem;
|
||||
}
|
||||
|
||||
.mstatus {
|
||||
text-align: center;
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
113
frontend/src/assets/main.css
Normal file
113
frontend/src/assets/main.css
Normal file
|
@ -0,0 +1,113 @@
|
|||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-text: #000;
|
||||
--color-background: #EEE;
|
||||
|
||||
--color-brand-ul-red: #e03127;
|
||||
--color-brand-ul-light-grey: #E6E7E8;
|
||||
--color-brand-ul-medium-grey: #A7A8AA;
|
||||
--color-brand-ul-dark-grey: #58595b;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family: "Noto Sans", sans-serif;
|
||||
font-size: 17px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
|
||||
* {
|
||||
user-select: none;
|
||||
}
|
||||
html, body {
|
||||
touch-action: manipulation;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-weight: bold;
|
||||
font-size: 1.5em;
|
||||
color: #58595b;
|
||||
}
|
||||
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.mstatus {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
button {
|
||||
--bg-default: #ffffff;
|
||||
--bg-active: lightgray;
|
||||
background: var(--bg-default);
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: .1rem;
|
||||
border: 1px solid #000000;
|
||||
border-radius: 3px;
|
||||
background: #ffffff;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
}
|
||||
|
||||
/* The last touched button keeps focus, so we shoudln't highlight that */
|
||||
button:focus:not(:active) {
|
||||
background: var(--bg-default);
|
||||
}
|
||||
|
||||
button:active {
|
||||
background: var(--bg-active);
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
h3 {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.currentlyActive {
|
||||
background-color: orange !important
|
||||
}
|
85
frontend/src/components/AudioControlModule.vue
Normal file
85
frontend/src/components/AudioControlModule.vue
Normal file
|
@ -0,0 +1,85 @@
|
|||
<script setup lang="ts">
|
||||
//import HelloWorld from './components/HelloWorld.vue'
|
||||
//import TheWelcome from './components/TheWelcome.vue'
|
||||
import { ref, onMounted, reactive, watch } from 'vue'
|
||||
import { $mqtt } from 'vue-paho-mqtt'
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
big: [Boolean, null]
|
||||
})
|
||||
|
||||
const topicstrs = [ //TODO everything else
|
||||
props.room + '/power/audio/status',
|
||||
]
|
||||
|
||||
const subscriptions =
|
||||
topicstrs.map(topic => {
|
||||
// console.log('subbing to', topic)
|
||||
$mqtt.subscribe(topic, (msg) => {
|
||||
// console.log('received:', topic, msg)
|
||||
handleIncomingMQTT(topic, msg)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
const audioStatus = ref(false)
|
||||
|
||||
function handleIncomingMQTT(topic: string, msg: string) {
|
||||
console.log('Received on', topic, 'with message', msg)
|
||||
audioStatus.value = msg == '1'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('test')
|
||||
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
|
||||
|
||||
})
|
||||
|
||||
function publishMQTTMsg(topic: string, msg: string) {
|
||||
//msg = msg.toString()
|
||||
console.log('Sending to [', topic, '] with message [', msg, ']')
|
||||
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
|
||||
console.log('sent')
|
||||
}
|
||||
|
||||
async function setAudio() {
|
||||
let topicPref = props.room + "/power/audio/set"
|
||||
let command = '0'
|
||||
if (!audioStatus.value) {
|
||||
command = '1'
|
||||
}
|
||||
publishMQTTMsg(topicPref, command)
|
||||
//audioStatus.value = command == '1'
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO organize better, binds, etc.
|
||||
|
||||
|
||||
const roomState = ref(0)
|
||||
// OFF -> 0; ON -> 1; IN BETWEEN -> 2
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<h3>Ozvočenje</h3>
|
||||
<button @click="setAudio()" :class="{big:big}">
|
||||
{{ audioStatus ? 'IZKLOP' : 'VKLOP' }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
button {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
.big {
|
||||
font-size: 1.8rem;
|
||||
height: 5em;
|
||||
}
|
||||
</style>
|
128
frontend/src/components/LectureModule.vue
Normal file
128
frontend/src/components/LectureModule.vue
Normal file
|
@ -0,0 +1,128 @@
|
|||
<script setup lang="ts">
|
||||
import {ref, onMounted, reactive, watch} from 'vue'
|
||||
import {$mqtt} from 'vue-paho-mqtt'
|
||||
import ProjectorShutter from './ProjectorShutter.vue'
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
position: String,
|
||||
})
|
||||
|
||||
const status = reactive({
|
||||
power: '?',
|
||||
platno: '?',
|
||||
lift: '?',
|
||||
wait: false,
|
||||
})
|
||||
|
||||
$mqtt.subscribe(props.room + '/projectors/' + props.position + '/status/power', (message) => {
|
||||
console.debug("a")
|
||||
status.power = message;
|
||||
});
|
||||
$mqtt.subscribe(props.room + '/projectors/' + props.position + '/platno/status', (message) => {
|
||||
status.platno = message;
|
||||
});
|
||||
$mqtt.subscribe(props.room + '/projectors/' + props.position + '/lift/status', (message) => {
|
||||
status.lift = message;
|
||||
});
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, ms)
|
||||
})
|
||||
}
|
||||
|
||||
function publishMQTTMsg(topic: string, msg: string) {
|
||||
console.log('Sending to [', topic, '] with message [', msg, ']')
|
||||
$mqtt.publish(topic, msg, 'Fnr')
|
||||
console.log('sent')
|
||||
}
|
||||
|
||||
async function startLecture() {
|
||||
publishMQTTMsg(props.room + '/power/master/set', '1')
|
||||
publishMQTTMsg(props.room + '/power/audio/set', '1')
|
||||
publishMQTTMsg(props.room + '/power/projectors/set', '1')
|
||||
await sleep(500)
|
||||
publishMQTTMsg(props.room + '/projectors/' + props.position + '/set/power', '1')
|
||||
publishMQTTMsg(props.room + '/projectors/' + props.position + '/platno/goto', "DOWN")
|
||||
publishMQTTMsg(props.room + '/projectors/' + props.position + '/lift/goto', 'DOWN')
|
||||
|
||||
status.wait = true
|
||||
setTimeout(() => {
|
||||
status.wait = false
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
async function stopLecture() {
|
||||
publishMQTTMsg(props.room + '/projectors/' + props.position + '/set/power', '0')
|
||||
publishMQTTMsg(props.room + '/projectors/' + props.position + '/platno/goto', "UP")
|
||||
// publishMQTTMsg(props.room + '/projectors/' + props.position + 'lift/goto', 'DOWN')
|
||||
status.wait = true
|
||||
setTimeout(() => {
|
||||
status.wait = false
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
//TODO organize better, binds, etc.
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- TODO lepš -->
|
||||
<div>
|
||||
<small class="status">
|
||||
PROJ: {{ status.power }}
|
||||
PLAT: {{ status.platno }}
|
||||
LIFT: {{ status.lift }}
|
||||
</small>
|
||||
|
||||
<button class="currentlyActive" @click="stopLecture()" v-if="status.power == '1' || status.platno == 'DOWN'" :class="{waiting: status.wait}">
|
||||
IZKLOP
|
||||
</button>
|
||||
<button @click="startLecture()" v-else="" :class="{waiting: status.wait}">
|
||||
VKLOP
|
||||
</button>
|
||||
|
||||
<ProjectorShutter :room="props.room" :position="props.position"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.status {
|
||||
font-size: .8em;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
opacity: .8;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
|
||||
height: 9rem;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.wait {
|
||||
animation: fade-in-out 1s infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes fade-in-out {
|
||||
0% {
|
||||
opacity: .2;
|
||||
}
|
||||
50% {
|
||||
opacity: .8;
|
||||
}
|
||||
100% {
|
||||
opacity: .2;
|
||||
}
|
||||
}
|
||||
</style>
|
104
frontend/src/components/Lift.vue
Normal file
104
frontend/src/components/Lift.vue
Normal file
|
@ -0,0 +1,104 @@
|
|||
<script setup lang="ts">
|
||||
//import HelloWorld from './components/HelloWorld.vue'
|
||||
//import TheWelcome from './components/TheWelcome.vue'
|
||||
import {ref, onMounted, reactive} from 'vue'
|
||||
import {$mqtt} from 'vue-paho-mqtt'
|
||||
import DownIcon from './icons/DownIcon.vue';
|
||||
import UpIcon from './icons/UpIcon.vue';
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
position: String
|
||||
})
|
||||
|
||||
const topicPrefix = `${props.room}/projectors/${props.position}`
|
||||
|
||||
const status = reactive({
|
||||
status: "-",
|
||||
})
|
||||
|
||||
$mqtt.subscribe(`${topicPrefix}/lift/status`, (message) => {
|
||||
status.status = message;
|
||||
});
|
||||
|
||||
function publishMQTTMsg(topic: string, msg: string) {
|
||||
console.log('Sending to [', topic, '] with message [', msg, ']')
|
||||
$mqtt.publish(topic, msg, 'Fnr')
|
||||
}
|
||||
|
||||
const publishPrefix = ref(props.room + '/projectors/' + props.position + '/lift/')
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<!--
|
||||
TODO: NE HARDCODANO
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div style="display:flex; gap: 1rem">
|
||||
<div>
|
||||
<h4>Lifti ({{status.status}})</h4>
|
||||
|
||||
<button
|
||||
@mousedown="publishMQTTMsg(publishPrefix + 'manual/up', '1')"
|
||||
@touchstart="publishMQTTMsg(publishPrefix + 'manual/up', '1')"
|
||||
@mouseup="publishMQTTMsg(publishPrefix + 'manual/up', '0')"
|
||||
@touchend="publishMQTTMsg(publishPrefix + 'manual/up', '0')">
|
||||
<UpIcon/>
|
||||
</button>
|
||||
<button
|
||||
@mousedown="publishMQTTMsg(publishPrefix + 'manual/down', '1')"
|
||||
@touchstart="publishMQTTMsg(publishPrefix + 'manual/down', '1')"
|
||||
@mouseup="publishMQTTMsg(publishPrefix + 'manual/down', '0')"
|
||||
@touchend="publishMQTTMsg(publishPrefix + 'manual/down', '0')">
|
||||
<DownIcon/>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Servis</h4>
|
||||
|
||||
<button
|
||||
@mousedown="publishMQTTMsg(publishPrefix + 'manual/service_up', '1')"
|
||||
@touchstart="publishMQTTMsg(publishPrefix + 'manual/service_up', '1')"
|
||||
@mouseup="publishMQTTMsg(publishPrefix + 'manual/service_up', '0')"
|
||||
@touchend="publishMQTTMsg(publishPrefix + 'manual/service_up', '0')">
|
||||
<UpIcon/>
|
||||
</button>
|
||||
<button
|
||||
@mousedown="publishMQTTMsg(publishPrefix + 'manual/service_down', '1')"
|
||||
@touchstart="publishMQTTMsg(publishPrefix + 'manual/service_down', '1')"
|
||||
@mouseup="publishMQTTMsg(publishPrefix + 'manual/service_down', '0')"
|
||||
@touchend="publishMQTTMsg(publishPrefix + 'manual/service_down', '0')">
|
||||
<DownIcon/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<h4>GOTO</h4>
|
||||
|
||||
<button
|
||||
@click="publishMQTTMsg(publishPrefix + 'goto', 'UP')">
|
||||
<UpIcon/>
|
||||
</button>
|
||||
<button
|
||||
@click="publishMQTTMsg(publishPrefix + 'goto', 'DOWN')">
|
||||
<DownIcon/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disabled {
|
||||
opacity: .8;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 1rem;
|
||||
margin: 0.1rem;
|
||||
width: 45%;
|
||||
}
|
||||
</style>
|
122
frontend/src/components/LightControl.vue
Normal file
122
frontend/src/components/LightControl.vue
Normal file
|
@ -0,0 +1,122 @@
|
|||
<script setup lang="ts">
|
||||
import { reactive } from "vue";
|
||||
import { $mqtt } from "vue-paho-mqtt";
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
id: Number,
|
||||
name: String,
|
||||
dimmable: Boolean,
|
||||
});
|
||||
|
||||
const topicPrefix = `${props.room}/lucke`
|
||||
|
||||
const status = reactive({
|
||||
brightness: 0,
|
||||
})
|
||||
|
||||
$mqtt.subscribe(`${topicPrefix}/brightness/${props.id}`, (message) => {
|
||||
status.brightness = parseInt(message);
|
||||
});
|
||||
|
||||
function doBrightness(brightness: Number) {
|
||||
$mqtt.publish(`${topicPrefix}/set/${props.id}`, brightness.toFixed(0), 'Fnr');
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="props.dimmable">
|
||||
<label>{{props.name}}</label>
|
||||
<input type="range" min="0" max="100" step="10" :value="status.brightness" @input="doBrightness(parseInt(($event.target as HTMLInputElement).value))" >
|
||||
</div>
|
||||
<span v-else>
|
||||
<label>{{props.name}}</label>
|
||||
<button @click="doBrightness(status.brightness != 0 ? 0 : 100)" :class="{currentlyActive: status.brightness != 0 }">
|
||||
</button>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
button {
|
||||
border: 1px solid #000000;
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
border-radius: 3px;
|
||||
background: #ffffff;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-flex
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
label {
|
||||
padding: .5rem
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
flex: 1;
|
||||
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
|
||||
--width: 100%; /* Specific width is required for Firefox. */
|
||||
background: transparent; /* Otherwise white in Chrome */
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
input[type=range]:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
input[type=range]::-moz-range-track {
|
||||
width: 100%;
|
||||
height: 8.4px;
|
||||
cursor: pointer;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
background: lightgray;
|
||||
border-radius: 1.3px;
|
||||
border: 0.2px solid #010101;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-runnable-track {
|
||||
width: 100%;
|
||||
height: 8.4px;
|
||||
cursor: pointer;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
background: lightgray;
|
||||
border-radius: 1.3px;
|
||||
border: 0.2px solid #010101;
|
||||
}
|
||||
|
||||
|
||||
input[type=range]::-moz-range-thumb {
|
||||
border: 1px solid #000000;
|
||||
height: 36px;
|
||||
width: 32px;
|
||||
border-radius: 3px;
|
||||
background: #ffffff;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
}
|
||||
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
border: 1px solid #000000;
|
||||
height: 36px;
|
||||
width: 32px;
|
||||
border-radius: 3px;
|
||||
background: #ffffff;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
margin-top: -14px; /* You need to specify a margin in Chrome, but in Firefox and IE it is automatic */
|
||||
}
|
||||
</style>
|
87
frontend/src/components/MasterPowerControlModule.vue
Normal file
87
frontend/src/components/MasterPowerControlModule.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<script setup lang="ts">
|
||||
//import HelloWorld from './components/HelloWorld.vue'
|
||||
//import TheWelcome from './components/TheWelcome.vue'
|
||||
import { ref, onMounted, reactive, watch } from 'vue'
|
||||
import { $mqtt } from 'vue-paho-mqtt'
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
})
|
||||
|
||||
const topicstrs = [ //TODO everything else
|
||||
props.room + '/power/master/status',
|
||||
]
|
||||
|
||||
const subscriptions =
|
||||
topicstrs.map(topic => {
|
||||
// console.log('subbing to', topic)
|
||||
$mqtt.subscribe(topic, (msg) => {
|
||||
// console.log('received:', topic, msg)
|
||||
handleIncomingMQTT(topic, msg)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
const MasterStatus = ref(false)
|
||||
|
||||
function handleIncomingMQTT(topic: string, msg: string) {
|
||||
console.log('Received on', topic, 'with message', msg)
|
||||
MasterStatus.value = msg == '1'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('test')
|
||||
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
|
||||
|
||||
})
|
||||
|
||||
function publishMQTTMsg(topic: string, msg: string) {
|
||||
//msg = msg.toString()
|
||||
console.log('Sending to [', topic, '] with message [', msg, ']')
|
||||
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
|
||||
console.log('sent')
|
||||
}
|
||||
|
||||
async function setMaster() {
|
||||
let topicPref = props.room + "/power/master/set"
|
||||
let command = '0'
|
||||
if (!MasterStatus.value) {
|
||||
command = '1'
|
||||
}
|
||||
publishMQTTMsg(topicPref, command)
|
||||
//audioStatus.value = command == '1'
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO organize better, binds, etc.
|
||||
|
||||
|
||||
const roomState = ref(0)
|
||||
// OFF -> 0; ON -> 1; IN BETWEEN -> 2
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- TODO lepš -->
|
||||
<div>
|
||||
<h3>Sistem</h3>
|
||||
<button style="" @click="setMaster()">
|
||||
{{ MasterStatus ? 'IZKLOP' : 'VKLOP' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disabled {
|
||||
opacity: .8;
|
||||
pointer-events: none;
|
||||
}
|
||||
button {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
123
frontend/src/components/Numpad.vue
Normal file
123
frontend/src/components/Numpad.vue
Normal file
|
@ -0,0 +1,123 @@
|
|||
<script setup lang="ts">
|
||||
import {ref, computed} from 'vue'
|
||||
|
||||
const emit = defineEmits(['submitPasscode'])
|
||||
defineProps([])
|
||||
|
||||
// TODO: unhardocde this shit
|
||||
const correctCode = '1337'
|
||||
const passcodeLength = correctCode.length
|
||||
const enteredCode = ref('')
|
||||
const status = ref('')
|
||||
const showStatus = ref(false)
|
||||
|
||||
|
||||
const appendDigit = (digit: string) => {
|
||||
if (enteredCode.value.length < passcodeLength) {
|
||||
enteredCode.value += digit
|
||||
// console.log('entered code', enteredCode.value)
|
||||
}
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
enteredCode.value = ''
|
||||
status.value = ''
|
||||
}
|
||||
|
||||
const submit = () => {
|
||||
if (enteredCode.value === correctCode) {
|
||||
status.value = 'Access Granted'
|
||||
emit('submitPasscode', true)
|
||||
} else {
|
||||
status.value = 'Access Denied'
|
||||
enteredCode.value = "Access Denied"
|
||||
showStatus.value = true
|
||||
}
|
||||
setTimeout(() => {
|
||||
clear()
|
||||
showStatus.value = false
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div class="keypad">
|
||||
<div class="keypadFeedback">
|
||||
<span v-show="showStatus">Access Denied</span>
|
||||
<span v-show="!showStatus" v-for="(_, i) in passcodeLength" :key="i">
|
||||
<span>
|
||||
{{ enteredCode[i] ? '•' : ' ' }}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="">
|
||||
<div class="keypadButtons">
|
||||
<div class="keypadRow">
|
||||
<button @click="appendDigit('1')">1</button>
|
||||
<button @click="appendDigit('2')">2</button>
|
||||
<button @click="appendDigit('3')">3</button>
|
||||
</div>
|
||||
<div class="keypadRow">
|
||||
<button @click="appendDigit('4')">4</button>
|
||||
<button @click="appendDigit('5')">5</button>
|
||||
<button @click="appendDigit('6')">6</button>
|
||||
</div>
|
||||
<div class="keypadRow">
|
||||
<button @click="appendDigit('7')">7</button>
|
||||
<button @click="appendDigit('8')">8</button>
|
||||
<button @click="appendDigit('9')">9</button>
|
||||
</div>
|
||||
<div class="keypadRow">
|
||||
<button class="" @click="clear">Del</button>
|
||||
<button @click="appendDigit('0')">0</button>
|
||||
<button class="" @click="submit">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped>
|
||||
button {
|
||||
margin: 0.1em;
|
||||
flex: max-content;
|
||||
padding: 1em;
|
||||
text-align: center;
|
||||
align-self: inherit;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.keypad {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 1.5em;
|
||||
align-content: space-evenly;
|
||||
}
|
||||
|
||||
.keypadRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
align-content: space-evenly;
|
||||
}
|
||||
|
||||
.keypadFeedback {
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
background-color: lightgray;
|
||||
background-clip: border-box;
|
||||
font-size: 2em;
|
||||
min-height: 2em;
|
||||
margin-bottom: 1em;
|
||||
|
||||
border: 1px solid #000000;
|
||||
border-radius: 3px;
|
||||
background: #ffffff;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
}
|
||||
</style>
|
141
frontend/src/components/Platno.vue
Normal file
141
frontend/src/components/Platno.vue
Normal file
|
@ -0,0 +1,141 @@
|
|||
<script setup lang="ts">
|
||||
//import HelloWorld from './components/HelloWorld.vue'
|
||||
//import TheWelcome from './components/TheWelcome.vue'
|
||||
import {ref, onMounted, reactive} from 'vue'
|
||||
import {$mqtt} from 'vue-paho-mqtt'
|
||||
import DownIcon from './icons/DownIcon.vue';
|
||||
import UpIcon from './icons/UpIcon.vue';
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
position: String,
|
||||
ctrlType: [String, null]
|
||||
})
|
||||
|
||||
const topicstrs = [ //TODO everything else
|
||||
props.room + '/projectors/' + props.position + 'platno/status',
|
||||
props.room + '/projectors/' + props.position + 'platno/status',
|
||||
]
|
||||
|
||||
const subscriptions =
|
||||
topicstrs.map(topic => {
|
||||
// console.log('subbing to', topic)
|
||||
$mqtt.subscribe(topic, (msg) => {
|
||||
// console.log('received:', topic, msg)
|
||||
handleIncomingMQTT(topic, msg)
|
||||
})
|
||||
})
|
||||
|
||||
function handleIncomingMQTT(topic: string, msg: string) {
|
||||
console.log('Received on', topic, 'with message', msg)
|
||||
if (topic.includes('status')) {
|
||||
//console.log(topic.split('/'))
|
||||
let typ = topic.split('/')[4]
|
||||
handlePlatnoStatus(msg)
|
||||
}
|
||||
}
|
||||
|
||||
function handlePlatnoStatus(msg: string) {
|
||||
console.log('handling status')
|
||||
//console.log(projStatus)
|
||||
let newState: platnoState
|
||||
switch (msg) {
|
||||
case "UP":
|
||||
newState = platnoState.UP
|
||||
break
|
||||
case "DOWN":
|
||||
newState = platnoState.DOWN
|
||||
break
|
||||
case "MOVING":
|
||||
newState = platnoState.MOVING
|
||||
break
|
||||
default:
|
||||
newState = platnoState.UNKNOWN
|
||||
break
|
||||
}
|
||||
platnoStatus.value = newState
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('test')
|
||||
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
|
||||
|
||||
})
|
||||
|
||||
function publishMQTTMsg(topic: string, msg: string) {
|
||||
//msg = msg.toString()
|
||||
console.log('Sending to [', topic, '] with message [', msg, ']')
|
||||
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
|
||||
console.log('sent')
|
||||
}
|
||||
|
||||
|
||||
const publishPrefix = ref(props.room + '/projectors/' + props.position + '/platno/')
|
||||
|
||||
//TODO organize better, binds, etc.
|
||||
|
||||
enum platnoState {
|
||||
UP,
|
||||
DOWN,
|
||||
MOVING,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
const platnoStatus = ref(platnoState.UNKNOWN)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<h4>Platno</h4>
|
||||
<div class="updown">
|
||||
<button
|
||||
@click="publishMQTTMsg(publishPrefix + 'goto', 'UP')">
|
||||
<UpIcon/>
|
||||
</button>
|
||||
<button
|
||||
@click="publishMQTTMsg(publishPrefix + 'goto', 'DOWN')">
|
||||
<DownIcon/>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="props.ctrlType == 'service'">
|
||||
<h5>Manual control</h5>
|
||||
|
||||
<div class="updown">
|
||||
<button
|
||||
@mousedown="publishMQTTMsg(publishPrefix + 'move', 'UP')"
|
||||
@touchstart="publishMQTTMsg(publishPrefix + 'move', 'UP')"
|
||||
@mouseup="publishMQTTMsg(publishPrefix + 'move', 'STOP')"
|
||||
@touchend="publishMQTTMsg(publishPrefix + 'move', 'STOP')">
|
||||
<UpIcon/>
|
||||
</button>
|
||||
<button
|
||||
@mousedown="publishMQTTMsg(publishPrefix + 'move', 'DOWN')"
|
||||
@touchstart="publishMQTTMsg(publishPrefix + 'move', 'DOWN')"
|
||||
@mouseup="publishMQTTMsg(publishPrefix + 'move', 'STOP')"
|
||||
@touchend="publishMQTTMsg(publishPrefix + 'move', 'STOP')">
|
||||
<DownIcon/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
|
||||
button {
|
||||
padding: 1rem;
|
||||
margin: 0.1rem;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.updown {
|
||||
display: flex
|
||||
}
|
||||
.updown button {
|
||||
flex: 1
|
||||
}
|
||||
</style>
|
87
frontend/src/components/ProjectorPowerModule.vue
Normal file
87
frontend/src/components/ProjectorPowerModule.vue
Normal file
|
@ -0,0 +1,87 @@
|
|||
<script setup lang="ts">
|
||||
//import HelloWorld from './components/HelloWorld.vue'
|
||||
//import TheWelcome from './components/TheWelcome.vue'
|
||||
import { ref, onMounted, reactive, watch } from 'vue'
|
||||
import { $mqtt } from 'vue-paho-mqtt'
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
})
|
||||
|
||||
const topicstrs = [ //TODO everything else
|
||||
props.room + '/power/projectors/status',
|
||||
]
|
||||
|
||||
const subscriptions =
|
||||
topicstrs.map(topic => {
|
||||
// console.log('subbing to', topic)
|
||||
$mqtt.subscribe(topic, (msg) => {
|
||||
// console.log('received:', topic, msg)
|
||||
handleIncomingMQTT(topic, msg)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
const projectorsStatus = ref(false)
|
||||
|
||||
function handleIncomingMQTT(topic: string, msg: string) {
|
||||
console.log('Received on', topic, 'with message', msg)
|
||||
projectorsStatus.value = msg == '1'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('test')
|
||||
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
|
||||
|
||||
})
|
||||
|
||||
function publishMQTTMsg(topic: string, msg: string) {
|
||||
//msg = msg.toString()
|
||||
console.log('Sending to [', topic, '] with message [', msg, ']')
|
||||
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
|
||||
console.log('sent')
|
||||
}
|
||||
|
||||
async function setProjectors() {
|
||||
let topicPref = props.room + "/power/projectors/set"
|
||||
let command = '0'
|
||||
if (!projectorsStatus.value) {
|
||||
command = '1'
|
||||
}
|
||||
publishMQTTMsg(topicPref, command)
|
||||
//audioStatus.value = command == '1'
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TODO organize better, binds, etc.
|
||||
|
||||
|
||||
const roomState = ref(0)
|
||||
// OFF -> 0; ON -> 1; IN BETWEEN -> 2
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- TODO lepš -->
|
||||
<div>
|
||||
<h3>Projektorji</h3>
|
||||
<button style="" @click="setProjectors()">
|
||||
{{ projectorsStatus ? 'IZKLOP' : 'VKLOP' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disabled {
|
||||
opacity: .8;
|
||||
pointer-events: none;
|
||||
}
|
||||
button {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
63
frontend/src/components/ProjectorShutter.vue
Normal file
63
frontend/src/components/ProjectorShutter.vue
Normal file
|
@ -0,0 +1,63 @@
|
|||
<script setup lang="ts">
|
||||
import { reactive } from "vue";
|
||||
import { $mqtt } from "vue-paho-mqtt";
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
position: String,
|
||||
ctrltype: [String, null],
|
||||
});
|
||||
|
||||
const topicPrefix = `${props.room}/projectors/${props.position}`
|
||||
|
||||
const status = reactive({
|
||||
power: false,
|
||||
shutter: false,
|
||||
freeze: false,
|
||||
})
|
||||
|
||||
function bindBoolean(name: keyof typeof status) {
|
||||
$mqtt.subscribe(`${topicPrefix}/status/${name}`, (message) => {
|
||||
status[name] = message == "1";
|
||||
console.debug(name, "=", message == "1")
|
||||
});
|
||||
}
|
||||
|
||||
bindBoolean("power")
|
||||
bindBoolean("shutter")
|
||||
bindBoolean("freeze")
|
||||
|
||||
function doCommand(command: keyof typeof status, shutter: boolean) {
|
||||
$mqtt.publish(`${topicPrefix}/set/${command}`, shutter ? "1" : "0", 'Fnr');
|
||||
console.debug("SET SHUTTER", shutter)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="btns">
|
||||
<button @click="doCommand('shutter', !status.shutter)" :class="{ disabled: !status.power, currentlyActive: status.power && status.shutter }">
|
||||
SHUTTER
|
||||
</button>
|
||||
<button @click="doCommand('freeze', !status.freeze)" :class="{ disabled: !status.power, currentlyActive: status.power && status.freeze }">
|
||||
FREEZE
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
.btns {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: .5em;
|
||||
}
|
||||
button {
|
||||
padding: 1rem;
|
||||
min-width: 40%;
|
||||
}
|
||||
</style>
|
112
frontend/src/components/Projektor.vue
Normal file
112
frontend/src/components/Projektor.vue
Normal file
|
@ -0,0 +1,112 @@
|
|||
<script setup lang="ts">
|
||||
//import HelloWorld from './components/HelloWorld.vue'
|
||||
//import TheWelcome from './components/TheWelcome.vue'
|
||||
import { ref, onMounted, reactive } from 'vue'
|
||||
import { $mqtt } from 'vue-paho-mqtt'
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
position: String,
|
||||
ctrltype: [String, null]
|
||||
})
|
||||
|
||||
const topicstrs = [ //TODO everything else
|
||||
props.room + '/projectors/' + props.position + '/status/power',
|
||||
props.room + '/projectors/' + props.position + '/status/shutter',
|
||||
props.room + '/projectors/' + props.position + '/status/freeze',
|
||||
props.room + '/projectors/' + props.position + '/status',
|
||||
]
|
||||
|
||||
const isUnreachable = ref(false)
|
||||
|
||||
const subscriptions =
|
||||
topicstrs.map(topic => {
|
||||
// console.log('subbing to', topic)
|
||||
$mqtt.subscribe(topic, (msg) => {
|
||||
// console.log('received:', topic, msg)
|
||||
handleIncomingMQTT(topic, msg)
|
||||
})
|
||||
})
|
||||
|
||||
function handleIncomingMQTT(topic: string, msg: string) {
|
||||
console.log('Received on', topic, 'with message', msg)
|
||||
if (topic.includes('status') && !topic.endsWith('status')) {
|
||||
//console.log(topic.split('/'))
|
||||
let typ = topic.split('/')[4]
|
||||
handleProjectorStatus(typ, msg)
|
||||
} else {
|
||||
isUnreachable.value = msg.length > 1
|
||||
}
|
||||
}
|
||||
|
||||
function handleProjectorStatus(typ: string, msg: string) {
|
||||
console.log('handling status')
|
||||
//console.log(projStatus)
|
||||
console.debug(props.room, projStatus.power, projStatus.shutter)
|
||||
|
||||
if (typ == 'power') { projStatus.power = msg == '1' }
|
||||
else if (typ == 'shutter') { projStatus.shutter = msg == '1' }
|
||||
else if (typ == 'freeze') { projStatus.freeze = msg == '1' }
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('test')
|
||||
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
|
||||
|
||||
})
|
||||
|
||||
function publishMQTTMsg(topic: string, msg: string) {
|
||||
//msg = msg.toString()
|
||||
console.log('Sending to [', topic, '] with message [', msg, ']')
|
||||
$mqtt.publish(topic, msg, 'Fnr') //todo refactor to 1 or 0 maybe
|
||||
console.log('sent')
|
||||
}
|
||||
|
||||
|
||||
|
||||
const publishPrefix = ref(props.room + '/projectors/' + props.position + '/set/')
|
||||
|
||||
//TODO organize better, binds, etc.
|
||||
|
||||
const projStatus = reactive({
|
||||
power: false,
|
||||
shutter: false,
|
||||
freeze: false,
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<span v-if="isUnreachable">CONNECTION FAILED</span>
|
||||
<!-- TODO lepš -->
|
||||
<div>
|
||||
<h4>Power</h4>
|
||||
<button @click="publishMQTTMsg(publishPrefix + 'power', (!projStatus.power ? '1' : '0'))" :class="{currentlyActive: projStatus.power}">
|
||||
Turn {{ projStatus.power ? 'OFF' : 'ON' }}</button>
|
||||
</div>
|
||||
<div :class="{ disabled: !projStatus.power }">
|
||||
<div>
|
||||
<h4>Shutter</h4>
|
||||
<button @click="publishMQTTMsg(publishPrefix + 'shutter', (!projStatus.shutter ? '1' : '0'))" :class="{currentlyActive: projStatus.shutter}">
|
||||
Turn {{ projStatus.shutter ? 'OFF' : 'ON' }}</button>
|
||||
</div>
|
||||
<div>
|
||||
<h4>Freeze image</h4>
|
||||
<button @click="publishMQTTMsg(publishPrefix + 'freeze', (!projStatus.freeze ? '1' : '0'))" :class="{currentlyActive: projStatus.freeze}">
|
||||
Turn {{ projStatus.freeze ? 'OFF' : 'ON' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disabled {
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
||||
button {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
83
frontend/src/components/ResetButton.vue
Normal file
83
frontend/src/components/ResetButton.vue
Normal file
|
@ -0,0 +1,83 @@
|
|||
<script setup lang="ts">
|
||||
//import HelloWorld from './components/HelloWorld.vue'
|
||||
//import TheWelcome from './components/TheWelcome.vue'
|
||||
import { ref, onMounted, reactive, watch } from 'vue'
|
||||
import { $mqtt } from 'vue-paho-mqtt'
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
})
|
||||
|
||||
const topicstrs = [ //TODO everything else
|
||||
props.room + '',
|
||||
]
|
||||
|
||||
const subscriptions =
|
||||
topicstrs.map(topic => {
|
||||
// console.log('subbing to', topic)
|
||||
$mqtt.subscribe(topic, (msg) => {
|
||||
// console.log('received:', topic, msg)
|
||||
handleIncomingMQTT(topic, msg)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
const audioStatus = ref(false)
|
||||
|
||||
function handleIncomingMQTT(topic: string, msg: string) {
|
||||
console.log('Received on', topic, 'with message', msg)
|
||||
audioStatus.value = msg == '1'
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('test')
|
||||
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
|
||||
|
||||
})
|
||||
|
||||
function publishMQTTMsg(topic: string, msg: string) {
|
||||
//msg = msg.toString()
|
||||
console.log('Sending to [', topic, '] with message [', msg, ']')
|
||||
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
|
||||
console.log('sent')
|
||||
}
|
||||
|
||||
async function resetAll() {
|
||||
let topicPref = props.room + "/power/"
|
||||
publishMQTTMsg(topicPref + 'audio/set', '0')
|
||||
publishMQTTMsg(topicPref + 'projectors/set', '1')
|
||||
publishMQTTMsg(topicPref + 'master/set', '1')
|
||||
publishMQTTMsg(props.room + "/projectors/main/lift/move/up", '1')
|
||||
publishMQTTMsg(props.room + "/projectors/side/lift/move/up", '1')
|
||||
//TODO NOT HARDCODE THISEFEWGJREWGREW
|
||||
publishMQTTMsg(props.room + "/projectors/main/platno/goto", 'UP')
|
||||
publishMQTTMsg(props.room + "/projectors/side/platno/goto", 'UP')
|
||||
//audioStatus.value = command == '1'
|
||||
}
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- TODO lepš -->
|
||||
<div>
|
||||
<h3>Reset sist.</h3>
|
||||
<button style="" @click="resetAll()">
|
||||
RESET
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disabled {
|
||||
opacity: .8;
|
||||
pointer-events: none;
|
||||
}
|
||||
button {
|
||||
padding: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
3
frontend/src/components/icons/DownIcon.vue
Normal file
3
frontend/src/components/icons/DownIcon.vue
Normal file
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
▼
|
||||
</template>
|
3
frontend/src/components/icons/UpIcon.vue
Normal file
3
frontend/src/components/icons/UpIcon.vue
Normal file
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
▲
|
||||
</template>
|
53
frontend/src/components/pages/AudioPage.vue
Normal file
53
frontend/src/components/pages/AudioPage.vue
Normal file
|
@ -0,0 +1,53 @@
|
|||
<script setup lang="ts">
|
||||
//import HelloWorld from './components/HelloWorld.vue'
|
||||
//import TheWelcome from './components/TheWelcome.vue'
|
||||
import {ref, onMounted, reactive } from 'vue'
|
||||
import { $mqtt } from 'vue-paho-mqtt'
|
||||
import DownIcon from '../icons/DownIcon.vue';
|
||||
import UpIcon from '../icons/UpIcon.vue';
|
||||
import AudioControlModule from "@/components/AudioControlModule.vue";
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
position: String
|
||||
})
|
||||
|
||||
const topicstrs = [ //TODO everything else
|
||||
props.room + '/power/audio/set'
|
||||
]
|
||||
|
||||
|
||||
const subscriptions =
|
||||
topicstrs.map(topic => {
|
||||
// console.log('subbing to', topic)
|
||||
$mqtt.subscribe(topic, (msg) => {
|
||||
// console.log('received:', topic, msg)
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div style="display:flex; gap: 1rem">
|
||||
<AudioControlModule :room="room" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.disabled {
|
||||
opacity: .8;
|
||||
pointer-events: none;
|
||||
}
|
||||
button {
|
||||
padding: 1rem;
|
||||
margin: 0.1rem;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.currentlySelectedPreset {
|
||||
background-color: orange;
|
||||
}
|
||||
</style>
|
205
frontend/src/components/pages/LightingPage.vue
Normal file
205
frontend/src/components/pages/LightingPage.vue
Normal file
|
@ -0,0 +1,205 @@
|
|||
<script setup lang="ts">
|
||||
//import HelloWorld from './components/HelloWorld.vue'
|
||||
//import TheWelcome from './components/TheWelcome.vue'
|
||||
import {ref, onMounted, reactive} from 'vue'
|
||||
import {$mqtt} from 'vue-paho-mqtt'
|
||||
import DownIcon from '../icons/DownIcon.vue';
|
||||
import UpIcon from '../icons/UpIcon.vue';
|
||||
import LightControl from '../LightControl.vue';
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
position: String
|
||||
})
|
||||
|
||||
const topicstrs = [ //TODO everything else
|
||||
props.room + '/shades/status',
|
||||
props.room + '/lucke/preset/current',
|
||||
]
|
||||
|
||||
const lastPreset = ref("-1")
|
||||
const platnoStatus = ref("UNKNOWN")
|
||||
|
||||
const subscriptions =
|
||||
topicstrs.map(topic => {
|
||||
// console.log('subbing to', topic)
|
||||
$mqtt.subscribe(topic, (msg) => {
|
||||
// console.log('received:', topic, msg)
|
||||
handleIncomingMQTT(topic, msg)
|
||||
})
|
||||
})
|
||||
|
||||
function handleIncomingMQTT(topic: string, msg: string) {
|
||||
console.log('Received on', topic, 'with message', msg)
|
||||
if (topic.includes('status')) {
|
||||
//console.log(topic.split('/'))
|
||||
let typ = topic.split('/')[4]
|
||||
handlePlatnoStatus(msg)
|
||||
} else if (topic.includes('current')) {
|
||||
lastPreset.value = msg
|
||||
}
|
||||
}
|
||||
|
||||
enum firankState {
|
||||
UP,
|
||||
DOWN,
|
||||
MOVING,
|
||||
STOPPED
|
||||
}
|
||||
|
||||
const firankStatus = ref(firankState.STOPPED)
|
||||
|
||||
function handlePlatnoStatus(msg: string) {
|
||||
console.log('handling status')
|
||||
//console.log(projStatus)
|
||||
platnoStatus.value = msg
|
||||
let newState: firankState
|
||||
switch (msg) {
|
||||
case "UP":
|
||||
newState = firankState.UP
|
||||
break
|
||||
case "DOWN":
|
||||
newState = firankState.DOWN
|
||||
break
|
||||
case "MOVING":
|
||||
newState = firankState.MOVING
|
||||
break
|
||||
default:
|
||||
newState = firankState.STOPPED
|
||||
break
|
||||
}
|
||||
firankStatus.value = newState
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// console.log('test')
|
||||
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
|
||||
|
||||
})
|
||||
|
||||
function publishMQTTMsg(topic: string, msg: string) {
|
||||
//msg = msg.toString()
|
||||
console.log('Sending to [', topic, '] with message [', msg, ']')
|
||||
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
|
||||
console.log('sent')
|
||||
}
|
||||
|
||||
const publishPrefix = ref(props.room + '/')
|
||||
|
||||
// TODO: un-hard-code this
|
||||
const lights = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Tabla",
|
||||
dimmable: true,
|
||||
}, {
|
||||
id: 5,
|
||||
name: "Začetek",
|
||||
dimmable: true,
|
||||
}, {
|
||||
id: 2,
|
||||
name: "Sredina",
|
||||
dimmable: true,
|
||||
}, {
|
||||
id: 3,
|
||||
name: "Vrh",
|
||||
dimmable: true,
|
||||
}, {
|
||||
id: 4,
|
||||
name: "Vhod 1",
|
||||
dimmable: false,
|
||||
}, {
|
||||
id: 7,
|
||||
name: "Vhod 2",
|
||||
dimmable: false,
|
||||
// }, {
|
||||
// id: 6,
|
||||
// name: "Reflektorji 2 (ne dela)",
|
||||
// dimmable: false,
|
||||
}, {
|
||||
id: 8,
|
||||
name: "Stopnice",
|
||||
dimmable: false,
|
||||
}
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
|
||||
<div class="razsvetljava">
|
||||
<div style="display: flex">
|
||||
</div>
|
||||
<div class="lightButtons" style="display: flex; flex-direction: column; align-items: stretch">
|
||||
<div style="display: flex;">
|
||||
<button :class="{currentlyActive: (lastPreset == '4')}"
|
||||
@click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '4')">IZKLOP
|
||||
</button>
|
||||
<button :class="{currentlyActive: (lastPreset == '3')}"
|
||||
@click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '3')">PRIPRAVA
|
||||
</button>
|
||||
</div>
|
||||
<div style="display: flex">
|
||||
<button :class="{currentlyActive: (lastPreset == '2')}"
|
||||
@click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '2')">PREDAVANJE
|
||||
</button>
|
||||
<button :class="{currentlyActive: (lastPreset == '1')}"
|
||||
@click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '1')">PROJEKCIJA
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="justify-content: center">
|
||||
<LightControl v-for="light in lights" :key="light.id" v-bind="light" :room="room" />
|
||||
</div>
|
||||
|
||||
<div style="justify-content: center" class="sencila">
|
||||
<h3>Senčila</h3>
|
||||
<button
|
||||
:class="{currentlyActive: (platnoStatus == 'MOVING_UP')}"
|
||||
@mousedown="publishMQTTMsg(publishPrefix + 'shades/move', 'MOVE_UP')"
|
||||
@touchstart="publishMQTTMsg(publishPrefix + 'shades/move', 'MOVE_UP')"
|
||||
@mouseup="publishMQTTMsg(publishPrefix + 'shades/move', 'STOP')"
|
||||
@touchend="publishMQTTMsg(publishPrefix + 'shades/move', 'STOP')">
|
||||
<UpIcon/>
|
||||
</button>
|
||||
<button
|
||||
:class="{currentlyActive: (platnoStatus == 'MOVING_DOWN')}"
|
||||
@touchstart="publishMQTTMsg(publishPrefix + 'shades/move', 'MOVE_DOWN')"
|
||||
@mousedown="publishMQTTMsg(publishPrefix + 'shades/move', 'MOVE_DOWN')"
|
||||
@mouseup="publishMQTTMsg(publishPrefix + 'shades/move', 'STOP')"
|
||||
@touchend="publishMQTTMsg(publishPrefix + 'shades/move', 'STOP')">
|
||||
<DownIcon/>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
.sencila button {
|
||||
min-width: 3em;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 1rem;
|
||||
margin: 0.3rem;
|
||||
}
|
||||
|
||||
.lightButtons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.lightButtons button {
|
||||
width: fit-content;
|
||||
flex: 1;
|
||||
height: 3.5em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
</style>
|
36
frontend/src/components/pages/MainPage.vue
Normal file
36
frontend/src/components/pages/MainPage.vue
Normal file
|
@ -0,0 +1,36 @@
|
|||
<script setup lang="ts">
|
||||
import LectureModule from '../LectureModule.vue';
|
||||
import AudioControlModule from '../AudioControlModule.vue';
|
||||
|
||||
const props = defineProps({
|
||||
room: String
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div style="display: flex; gap: 1rem; margin:inherit">
|
||||
<div style="width: 50%;">
|
||||
<h3>Glavni projektor</h3>
|
||||
<LectureModule :room="props.room" position="main"/>
|
||||
</div>
|
||||
<div style="width: 50%;">
|
||||
<h3>Stranski projektor</h3>
|
||||
<LectureModule :room="props.room" position="side"/>
|
||||
</div>
|
||||
</div>
|
||||
<div style="text-align: center">
|
||||
<AudioControlModule :room="props.room" :big="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
}
|
||||
</style>
|
74
frontend/src/components/pages/ServisPage.vue
Normal file
74
frontend/src/components/pages/ServisPage.vue
Normal file
|
@ -0,0 +1,74 @@
|
|||
<script setup lang="ts">
|
||||
import {ref, onMounted, reactive, watch} from 'vue'
|
||||
import {$mqtt} from 'vue-paho-mqtt'
|
||||
import Platno from '../Platno.vue'
|
||||
import Lift from '../Lift.vue';
|
||||
import Projektor from '../Projektor.vue';
|
||||
import ProjectorPowerModule from "@/components/ProjectorPowerModule.vue";
|
||||
import AudioControlModule from "@/components/AudioControlModule.vue";
|
||||
import MasterPowerControlModule from "@/components/MasterPowerControlModule.vue";
|
||||
import ResetButton from "@/components/ResetButton.vue";
|
||||
import Numpad from "@/components/Numpad.vue";
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
currentlyActive: Boolean,
|
||||
})
|
||||
const _glavni_position = ref('main')
|
||||
const _stranski_position = ref('side')
|
||||
const _ctrl_type = ref('service')
|
||||
|
||||
const showServis = ref(false)
|
||||
|
||||
|
||||
|
||||
watch(props, (ca) => {
|
||||
if (!props.currentlyActive) {
|
||||
showServis.value = false
|
||||
}
|
||||
}, {
|
||||
immediate: true,
|
||||
deep: true,
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-show="!showServis" style="display: flex">
|
||||
<Numpad @submit-passcode="(b: boolean) => showServis = b"/>
|
||||
</div>
|
||||
<div v-show="showServis" style="display: flex; gap: 1rem">
|
||||
<div>
|
||||
<Projektor :room="props.room" :position="_glavni_position" :ctrlType="_ctrl_type"/>
|
||||
<Lift :room="props.room" :position="_glavni_position"/>
|
||||
<Platno :room="props.room" :position="_glavni_position" :ctrlType="_ctrl_type"/>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<Projektor :room="props.room" :position="_stranski_position" :ctrlType="_ctrl_type"/>
|
||||
<Lift :room="props.room" :position="_stranski_position"/>
|
||||
<Platno :room="props.room" :position="_stranski_position" :ctrlType="_ctrl_type"/>
|
||||
</div>
|
||||
<div>
|
||||
<h5>POWER CONTROL</h5>
|
||||
<div>
|
||||
<ProjectorPowerModule :room="props.room"/>
|
||||
<AudioControlModule :room="props.room"/>
|
||||
<MasterPowerControlModule :room="props.room"/>
|
||||
</div>
|
||||
<div></div>
|
||||
<ResetButton :room="props.room"/>
|
||||
<button onclick="window.location.reload()">RELOAD PAGE</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
<!-- scaling UIja, buttons as big as can, text as big (within reason), clear feedback, mqtt defaults to host font family noto sans, sans serif, chrome disable -->
|
||||
<!-- naj se odziva ono sam na platno status fuj fej -->
|
42
frontend/src/components/pages/VideoPage.vue
Normal file
42
frontend/src/components/pages/VideoPage.vue
Normal file
|
@ -0,0 +1,42 @@
|
|||
<script setup lang="ts">
|
||||
//import HelloWorld from './components/HelloWorld.vue'
|
||||
//import TheWelcome from './components/TheWelcome.vue'
|
||||
import {ref, onMounted, reactive } from 'vue'
|
||||
import { $mqtt } from 'vue-paho-mqtt'
|
||||
import Projektor from '../Projektor.vue'
|
||||
import Platno from '../Platno.vue'
|
||||
|
||||
const props = defineProps({
|
||||
room: String,
|
||||
})
|
||||
const _glavni_position = ref('main')
|
||||
const _stranski_position = ref('side')
|
||||
const _test = ref('test')
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<div class="col">
|
||||
<h3>Glavni</h3>
|
||||
<Projektor :room="props.room" :position="_glavni_position" :ctrl-type="_test" />
|
||||
<Platno :room="props.room" :position="_glavni_position" :ctrl-type="_test" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<h3>Stranski</h3>
|
||||
<Projektor :room="props.room" :position="_stranski_position" :ctrltype="_test"/>
|
||||
<Platno :room="props.room" :position="_stranski_position" :ctrl-type="_test"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.page {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 1rem;
|
||||
}
|
||||
.col {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
39
frontend/src/components/tabs/Tab.vue
Normal file
39
frontend/src/components/tabs/Tab.vue
Normal file
|
@ -0,0 +1,39 @@
|
|||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
selected: Boolean,
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="tab" :class="{ selected: props.selected }">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tab {
|
||||
background-color: var(--color-brand-ul-medium-grey);
|
||||
border: none;
|
||||
font-weight: bold;
|
||||
|
||||
border: 1px solid #000000;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
border-left: none;
|
||||
background: lightgray;
|
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d;
|
||||
}
|
||||
|
||||
.tab.selected {
|
||||
background-color: var(--color-brand-ul-red);
|
||||
}
|
||||
|
||||
.vertical-tabs .tab {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.vertical-tabs .tab:not(:last-child) {
|
||||
margin-bottom: 0.3rem;
|
||||
}
|
||||
</style>
|
16
frontend/src/components/tabs/VerticalTabs.vue
Normal file
16
frontend/src/components/tabs/VerticalTabs.vue
Normal file
|
@ -0,0 +1,16 @@
|
|||
<template>
|
||||
<div class="vertical-tabs">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.vertical-tabs .tab {
|
||||
width: 100%;
|
||||
padding: 1rem 2rem;
|
||||
}
|
||||
|
||||
.vertical-tabs .tab:not(:last-child) {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
</style>
|
31
frontend/src/main.ts
Normal file
31
frontend/src/main.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import { createPahoMqttPlugin } from 'vue-paho-mqtt'
|
||||
|
||||
let urlParams = new URLSearchParams(window.location.search);
|
||||
createApp(App)
|
||||
.use(
|
||||
createPahoMqttPlugin({
|
||||
PluginOptions: {
|
||||
autoConnect: true,
|
||||
showNotifications: false,
|
||||
},
|
||||
|
||||
MqttOptions: {
|
||||
//host: import.meta.env.VITE_MQTT_HOST,
|
||||
//host: "localhost",
|
||||
host: urlParams.get('mqtt') || window.location.hostname,
|
||||
port: 8080,
|
||||
useSSL: false,
|
||||
//port: parseInt(import.meta.env.VITE_MQTT_PORT),
|
||||
//useSSL: ["1", "true", "True"].includes(import.meta.env.VITE_MQTT_SSL),
|
||||
clientId: `vju-${Math.random() * 9999}`,
|
||||
//mainTopic: '',
|
||||
enableMainTopic: false,
|
||||
|
||||
},
|
||||
})
|
||||
)
|
||||
.mount('#app')
|
Loading…
Add table
Add a link
Reference in a new issue