frontend overhaul, minor backend bugfixes

This commit is contained in:
katsu 2025-07-02 16:29:09 +02:00
parent acc38950e7
commit c1325c0eda
28 changed files with 767 additions and 481 deletions

View file

@ -1,66 +1,77 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
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') // if no param specified
const debugFlag = ref(urlParams.get('debug') == 'true');
// should also check if valid room parameter ampak se ne mudi
// TODO does this make sense al se naj naprej defaulta kr na p01?
const pageNum = ref(0)
const srvcUnlocked = ref(true)
const showPinPopup = ref(false)
function lockServicePage() {
srvcUnlocked.value = false
}
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 style="">
<div v-if="currentRoom == 'none'">
<h1>Incorrect or missing room parameter!</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" />
<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; lockServicePage()" :selected="pageNum==0">Priprava</Tab>
<Tab @click="pageNum=1; lockServicePage()" :selected="pageNum==1">Video</Tab>
<Tab @click="pageNum=2; lockServicePage()" :selected="pageNum==2">Audio</Tab>
<Tab @click="pageNum=3; lockServicePage()" :selected="pageNum==3">Lučke</Tab>
<Tab @click="pageNum=4; lockServicePage()" :selected="pageNum==4">Servis</Tab>
<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>
<large style="display: flex;">turbo odličen super mega kontrol panel</large>
</header>
<main style="flex-grow: 1">
<MainPage :class="{'hiddenPage': pageNum != 0}" :room="currentRoom" />
<VideoPage :class="{'hiddenPage': pageNum != 1}" :room="currentRoom" />
<LightingPage :class="{'hiddenPage': pageNum != 3}" :room="currentRoom" />
<div class="mstatus" v-if="$mqtt.status() != 'connected'">{{ $mqtt.status()?.toUpperCase() }}</div>
<ServisPage :class="{'hiddenPage': pageNum != 4}" :room="currentRoom" />
</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>
</div>
</template>
<style scoped>
@ -78,22 +89,28 @@ function lockServicePage() {
main {
flex: 1;
padding: 1rem;
max-height: 100vh;
overflow-y: auto;
display: flex;
}
h1 {
text-align: center;
main > * {
flex: 1;
}
.sidebar {
max-width: 20vw;
}
.logo {
max-width: 100%;
padding: .4rem;
}
* {
cursor: none;
.mstatus {
text-align: center;
opacity: .4;
}
</style>

View file

@ -1,32 +0,0 @@
*,
*::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: sans-serif;
font-size: 17px;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

View file

@ -1,7 +1,47 @@
@import './base.css';
*,
*::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;
}
#app {
max-width: 1280px;
height: 100vh;
margin: 0 auto;
}
@ -26,12 +66,46 @@ a,
}
}
button {
border: none;
background-color: #aaa;
cursor: pointer;
.mstatus {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
button:hover, button:focus, button:active {
background-color: #ccc;
button {
background: lightgray;
font-size: 1.3em;
font-weight: bold;
}
button:focus:not(:active) {
background: lightgray;
}
button:focus-visible(:active) {
background: gray;
}
button {
border: none;
background-color: lightgray;
}
button:active {
background: gray;
}
h1 {
text-align: center;
}
h3 {
font-weight: bold;
}
.currentlyActive {
background-color: orange !important
}

View file

@ -6,6 +6,7 @@ import { $mqtt } from 'vue-paho-mqtt'
const props = defineProps({
room: String,
big: [Boolean, null]
})
const topicstrs = [ //TODO everything else
@ -65,23 +66,20 @@ const roomState = ref(0)
<template>
<div>
<!-- TODO lepš -->
<div>
<h3>Ozvočenje</h3>
<button style="" @click="setAudio()">
{{ audioStatus ? 'UGASNI' : 'PRIŽGI' }}
</button>
</div>
<h3>Ozvočenje</h3>
<button @click="setAudio()" :class="{big:big}">
{{ audioStatus ? 'IZKLOP' : 'VKLOP' }}
</button>
</div>
</template>
<style scoped>
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
width: 100%;
}
.big {
font-size: 1.8rem;
height: 5em;
}
</style>

View file

@ -1,47 +1,57 @@
<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'
import {ref, onMounted, reactive, watch} from 'vue'
import {$mqtt} from 'vue-paho-mqtt'
const props = defineProps({
room: String,
position: String,
})
const projUnreachable = ref(false)
const topicstrs = [ //TODO everything else
props.room + '/projectors/' + props.position + '/status/power',
props.room + '/projectors/' + props.position + '/platno/status',
props.room + '/projectors/' + props.position + '/lift/status'
props.room + '/projectors/' + props.position + '/lift/status',
props.room + '/projectors/' + props.position + '/status'
]
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
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)
let typ: string = ''
if (topic.includes('power')) {
typ = topic.split('/')[4]
} else if (topic.includes('platno')) {
} else if (topic.includes('platno')) {
typ = topic.split('/')[3]
} else if (topic.includes('lift')) {
} else if (topic.includes('lift')) {
typ = topic.split('/')[3]
} else { return }
handleIncStatus(typ, msg)
} else if (topic.endsWith('error')) {
projUnreachable.value = msg == "UNREACHABLE"
} else {
return
}
handleIncStatus(typ, msg)
}
function handleIncStatus(typ: string, msg: string) {
console.log('handling status')
//console.log(projStatus)
if (typ == 'power') { roomStatus.proj_power = msg == '1' }
else if (typ == 'platno') { roomStatus.platno_state = msg }
if (typ == 'power') {
roomStatus.proj_power = msg == '1'
} else if (typ == 'platno') {
roomStatus.platno_state = msg
}
//else if (typ == 'lift') { roomStatus.lift_state = msg }
}
@ -52,37 +62,41 @@ onMounted(() => {
})
function sleep(ms: number) {
return new Promise(resolve => {
setTimeout(resolve, ms)
})
return new Promise(resolve => {
setTimeout(resolve, ms)
})
}
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
$mqtt.publish(topic, msg, 'Fnr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
async function setLecture(pos?: String) {
if (!pos) { return }
let topicPref = props.room + "/projectors/" + props.position + "/"
let command = '0'
if (roomState.value == 0) {
command = '1'
} // else if (roomState.value == 1) {
// command = '0'
// } else { return }
publishMQTTMsg((topicPref + 'command/power'), command)
publishMQTTMsg((topicPref + 'platno/goto'), command == '1' ? 'DOWN' : 'UP')
publishMQTTMsg((topicPref + 'lift/move/' + (command == '1' ? 'down' : 'down') ), '1')
await sleep(1000)
// publishMQTTMsg((topicPref + 'platno/goto'), 'STOP')
publishMQTTMsg((topicPref + 'lift/move/' + (command == '1' ? 'down' : 'down') ), '0')
if (!pos) {
return
}
let topicPref = props.room + "/projectors/" + props.position + "/"
let command = '0'
if (roomState.value == 0 || roomState.value == 3) {
command = '1'
} // else if (roomState.value == 1) {
// command = '0'
// } else { return }
publishMQTTMsg((props.room + '/power/master/set'), '1')
publishMQTTMsg((props.room + '/power/projectors/set'), '1')
await sleep(500)
publishMQTTMsg((topicPref + 'command/power'), command)
publishMQTTMsg((topicPref + 'platno/goto'), command == '1' ? 'DOWN' : 'UP')
publishMQTTMsg((topicPref + 'lift/move/' + (command == '1' ? 'down' : 'down')), '1')
await sleep(1000)
// publishMQTTMsg((topicPref + 'platno/goto'), 'STOP')
publishMQTTMsg((topicPref + 'lift/move/' + (command == '1' ? 'down' : 'down')), '0')
}
//TODO organize better, binds, etc.
const roomStatus = reactive({
@ -91,56 +105,77 @@ const roomStatus = reactive({
platno_state: 'UNKNOWN',
})
enum RoomStatus {
OFF = 0,
ON = 1,
UNKNOWN = 2,
ERROR = 3,
}
const roomState = ref(0)
// OFF -> 0; ON -> 1; IN BETWEEN -> 2
watch (roomStatus, (_, _newState) => {
if (!_newState) { return }
let allOn = _newState.proj_power && _newState.platno_state == 'DOWN' //&& _newState.lift_state == 'DOWN'
let allOff = !_newState.proj_power && _newState.platno_state == 'UP' //&& _newState.lift_state == 'UP'
let anyUnknown = _newState.platno_state == 'UNKNOWN'
if (allOn) {
roomState.value = 1
} else if (allOff) {
roomState.value = 0
} else if (anyUnknown) {
roomState.value = 3
} else {
roomState.value = 2
}
watch(roomStatus, (_, _newState) => {
if (!_newState) {
return
}
let allOn = _newState.proj_power && _newState.platno_state == 'DOWN' //&& _newState.lift_state == 'DOWN'
let allOff = !_newState.proj_power && _newState.platno_state == 'UP' //&& _newState.lift_state == 'UP'
let anyUnknown = _newState.platno_state == 'UNKNOWN'
if (_newState.platno_state == 'DOWN') {
roomState.value = RoomStatus.ON
} else if (_newState.platno_state == 'UP') {
roomState.value = 0
} else if (anyUnknown) {
roomState.value = 3
} else {
roomState.value = 2
}
}, {immediate: true})
</script>
<template>
<span v-if="projUnreachable">CONNECTION FAILED</span>
<div>
<!-- TODO lepš -->
<div>
<h3 v-if="roomState == 1">AKTIVNO</h3>
<h3 v-else-if="roomState == 0">PRIPRAVLJENOST</h3>
<h3 v-else-if="roomState == 2">POČAKAJTE</h3>
<h3 v-else-if="roomState == 3">NEZNANO STANJE</h3>
<h3 v-else>NAPAKA</h3>
<button style="" @click="setLecture(props.position)" :disabled="roomState == 2">
{{ roomState == 1 ? 'UGASNI' : (roomState == 0 ? 'PRIŽGI' : '...') }}
<div class="status" v-if="roomState == 1">VKLOPLJENO</div>
<div class="status" v-else-if="roomState == 0">IZKLOPLJENO</div>
<div class="status" v-else-if="roomState == 2">POČAKAJTE</div>
<div class="status" v-else-if="roomState == 3">NEZNANO STANJE</div>
<div class="status" v-else>NAPAKA</div>
<button style="" @click="setLecture(props.position)" :disabled="roomState == 2">
{{ roomState == 1 ? 'IZKLOP' : (roomState == 0 || roomState == 3 ? 'VKLOP' : '...') }}
</button>
</div>
<h6>Projektor status: {{ roomStatus.proj_power ? "ON" : "OFF" }}</h6>
<h6>Platno pozicija: {{ roomStatus.platno_state }}</h6>
<!-- <h6>Dvigalo pozicija: {{ roomStatus.lift_state }}</h6>-->
<!--TODO ce hoces naj bo kot nek debug flag -->
<!-- <div>Projektor status: {{ roomStatus.proj_power ? "ON" : "OFF" }}</div>
<div>Platno pozicija: {{ roomStatus.platno_state }}</div>-->
<!-- <h6>Dvigalo pozicija: {{ roomStatus.lift_state }}</h6>-->
</div>
</template>
<style scoped>
.status {
font-size: 1.1em;
font-weight: normal;
}
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
width: 100%;
height: 10rem;
font-size: 1.8rem;
}
</style>

View file

@ -1,14 +1,14 @@
<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 {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
room: String,
position: String
})
const topicstrs = [ //TODO everything else
@ -16,14 +16,14 @@ const topicstrs = [ //TODO everything else
props.room + '/projectors/' + props.position + 'lift/status',
]
const subscriptions =
topicstrs.map(topic => {
// console.log('subbing to', topic)
$mqtt.subscribe(topic, (msg) => {
// console.log('received:', topic, msg)
handleIncomingMQTT(topic, msg)
})
})
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)
@ -66,15 +66,16 @@ function handleIncomingMQTT(topic: string, msg: string) {
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
$mqtt.publish(topic, msg, 'Fnr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
const publishPrefix = ref(props.room + '/projectors/' + props.position + '/lift/')
@ -85,31 +86,44 @@ TODO: NE HARDCODANO
-->
<template>
<div style="display:flex; gap: 1rem">
<div>
<h4>Lifti {{ props.position }}</h4>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/up', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/up', '0')" >
<UpIcon /></button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/down', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/down', '0')" >
<DownIcon /></button>
</div><div>
<h4>Servis lifti {{ props.position }}</h4>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/service_up', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/service_up', '0')" >
<UpIcon /></button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/service_down', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/service_down', '0')" >
<DownIcon /></button>
</div>
<div style="display:flex; gap: 1rem">
<div>
<h4>Lifti</h4>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/up', '1')"
@touchstart="publishMQTTMsg(publishPrefix + 'move/up', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/up', '0')"
@touchend="publishMQTTMsg(publishPrefix + 'move/up', '0')">
<UpIcon/>
</button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/down', '1')"
@touchstart="publishMQTTMsg(publishPrefix + 'move/down', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/down', '0')"
@touchend="publishMQTTMsg(publishPrefix + 'move/down', '0')">
<DownIcon/>
</button>
</div>
<div>
<h4>Servis</h4>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/service_up', '1')"
@touchstart="publishMQTTMsg(publishPrefix + 'move/service_up', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/service_up', '0')"
@touchend="publishMQTTMsg(publishPrefix + 'move/service_up', '0')">
<UpIcon/>
</button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move/service_down', '1')"
@touchstart="publishMQTTMsg(publishPrefix + 'move/service_down', '1')"
@mouseup="publishMQTTMsg(publishPrefix + 'move/service_down', '0')"
@touchend="publishMQTTMsg(publishPrefix + 'move/service_down', '0')">
<DownIcon/>
</button>
</div>
</div>
</template>
<style scoped>
@ -117,6 +131,7 @@ TODO: NE HARDCODANO
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
margin: 0.1rem;

View file

@ -69,7 +69,7 @@ const roomState = ref(0)
<div>
<h3>Sistem</h3>
<button style="" @click="setMaster()">
{{ MasterStatus ? 'UGASNI' : 'PRIŽGI' }}
{{ MasterStatus ? 'IZKLOP' : 'VKLOP' }}
</button>
</div>
</div>

View file

@ -0,0 +1,125 @@
<script setup lang="ts">
import {ref, computed} from 'vue'
const emit = defineEmits(['submitPasscode'])
defineProps([])
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 class="">
{{ 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 v-if="status" class="">-->
<!-- {{ status }}-->
<!-- </div>-->
</div>
</template>
<style scoped>
button {
margin: 0.1em;
flex: max-content;
padding: 1.5em;
text-align: center;
align-self: inherit;
justify-content: space-evenly;
}
.keypad {
display: flex;
flex-direction: column;
margin: 2em;
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;
//border: 2px solid darkgray;
background-color: lightgray;
background-clip: border-box;
font-size: 2em;
min-height: 2em;
margin-bottom: 1em;
}
</style>

View file

@ -1,78 +0,0 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const props = defineProps({
onClose: [Function, null],
onSuccess: [Function, null]
})
const pin = ref("1337")
const testPin = ref("0000")
// export default {
// props: ['onClose', 'onSuccess'],
// data() {
// return {
// pin: '',
// };
// },
// methods: {
function checkPin() {
// Your hard-coded pin
if (testPin.value === pin.value) {
if (props.onSuccess)
props.onSuccess(); // Let parent component know pin is correct
if (props.onClose)
props.onClose();
} else {
alert('Incorrect pin');
}
}
function close() {
if (props.onClose)
props.onClose();
}
</script>
<template>
<div class="pin-popup">
<div class="overlay" @click="close"></div>
<div class="popup">
<h2>Enter Pin Code</h2>
<input v-model="pin" type="password" placeholder="Pin Code" />
<button @click="checkPin">Submit</button>
</div>
</div>
</template>
<style scoped>
.pin-popup {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
}
.popup {
background: white;
padding: 20px;
border-radius: 8px;
display: flex;
flex-direction: column;
gap: 10px;
}
</style>

View file

@ -1,15 +1,15 @@
<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 {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]
room: String,
position: String,
ctrlType: [String, null]
})
const topicstrs = [ //TODO everything else
@ -17,14 +17,14 @@ const topicstrs = [ //TODO everything else
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)
})
})
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)
@ -59,7 +59,7 @@ function handlePlatnoStatus(msg: string) {
onMounted(() => {
// console.log('test')
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
})
function publishMQTTMsg(topic: string, msg: string) {
@ -70,7 +70,6 @@ function publishMQTTMsg(topic: string, msg: string) {
}
const publishPrefix = ref(props.room + '/projectors/' + props.position + '/platno/')
//TODO organize better, binds, etc.
@ -87,37 +86,56 @@ const platnoStatus = ref(platnoState.UNKNOWN)
</script>
<template>
<div style="display:flex; gap: 1rem">
<!-- <h3>{{ props.room }}</h3> -->
<!-- <form> -->
<!-- TODO lepš -->
<div>
<h4>platna {{ props.position }}</h4>
<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>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move', 'UP')"
@mouseup="publishMQTTMsg(publishPrefix + 'move', 'STOP')" >
<UpIcon /></button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'move', 'DOWN')"
@mouseup="publishMQTTMsg(publishPrefix + 'move', 'STOP')" >
<DownIcon /></button>
</div>
<div>
<!-- </form> -->
<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>

View file

@ -69,7 +69,7 @@ const roomState = ref(0)
<div>
<h3>Projektorji</h3>
<button style="" @click="setProjectors()">
{{ projectorsStatus ? 'UGASNI' : 'PRIŽGI' }}
{{ projectorsStatus ? 'IZKLOP' : 'VKLOP' }}
</button>
</div>
</div>

View file

@ -14,8 +14,11 @@ 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)
@ -27,10 +30,12 @@ const subscriptions =
function handleIncomingMQTT(topic: string, msg: string) {
console.log('Received on', topic, 'with message', msg)
if (topic.includes('status')) {
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
}
}
@ -51,7 +56,7 @@ onMounted(() => {
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
$mqtt.publish(topic, msg, 'Fnr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
@ -71,20 +76,21 @@ const projStatus = reactive({
<template>
<div>
<span v-if="isUnreachable">CONNECTION FAILED</span>
<!-- TODO lepš -->
<div>
<h5>Power: {{ (projStatus.power ? "ON" : "OFF") }}</h5>
<h4>Power</h4>
<button @click="publishMQTTMsg(publishPrefix + 'power', (!projStatus.power ? '1' : '0'))">
Turn {{ projStatus.power ? 'OFF' : 'ON' }}</button>
</div>
<div :class="{ disabled: !projStatus.power }">
<div>
<h5>Shutter: {{ (projStatus.shutter ? "ON" : "OFF") }}</h5>
<h4>Shutter</h4>
<button @click="publishMQTTMsg(publishPrefix + 'shutter', (!projStatus.shutter ? '1' : '0'))">
Turn {{ projStatus.shutter ? 'OFF' : 'ON' }}</button>
</div>
<div>
<h5>Freeze image: {{ (projStatus.freeze ? "ON" : "OFF") }}</h5>
<h4>Freeze image</h4>
<button @click="publishMQTTMsg(publishPrefix + 'freeze', (!projStatus.freeze ? '1' : '0'))">
Turn {{ projStatus.freeze ? 'OFF' : 'ON' }}</button>
</div>
@ -94,7 +100,7 @@ const projStatus = reactive({
<style scoped>
.disabled {
opacity: .8;
opacity: .5;
pointer-events: none;
}
button {

View file

@ -63,7 +63,7 @@ async function resetAll() {
<div>
<!-- TODO lepš -->
<div>
<h3>Reset sistema</h3>
<h3>Reset sist.</h3>
<button style="" @click="resetAll()">
RESET
</button>

View file

@ -1,3 +1,3 @@
<template>
🢃
</template>

View file

@ -1,3 +1,3 @@
<template>
🢁
</template>

View 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>

View file

@ -1,32 +1,32 @@
<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 {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
room: String,
position: String
})
const topicstrs = [ //TODO everything else
props.room + '/projectors/' + props.position + 'platno/status',
props.room + '/projectors/' + props.position + 'platno/status',
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)
})
})
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)
@ -45,11 +45,13 @@ enum firankState {
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":
@ -71,7 +73,7 @@ function handlePlatnoStatus(msg: string) {
onMounted(() => {
// console.log('test')
//$mqtt.publish('peepee', 'poopoo', 'Qr') // dela
})
function publishMQTTMsg(topic: string, msg: string) {
@ -80,56 +82,81 @@ function publishMQTTMsg(topic: string, msg: string) {
$mqtt.publish(topic, msg, 'Qr') //todo refactor to 1 or 0 maybe
console.log('sent')
}
const publishPrefix = ref(props.room + '/')
</script>
<template>
<div>
<div style="display:flex; gap: 1rem">
<div>
<h4>Firanki</h4>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'firanki/move', 'MOVE_UP')"
@mouseup="publishMQTTMsg(publishPrefix + 'firanki/move', 'STOP')" >
<UpIcon /></button>
<button
@mousedown="publishMQTTMsg(publishPrefix + 'firanki/move', 'MOVE_DOWN')"
@mouseup="publishMQTTMsg(publishPrefix + 'firanki/move', 'STOP')" >
<DownIcon /></button>
<div class="page">
<div class="razsvetljava">
<div style="display: flex">
<h3>Razsvetljava</h3>
</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 style="display: flex; gap: 1rem;">
<div>
<h4>Lučka presets</h4>
<h5>CURRENT SCENE: {{ lastPreset }} -- TODO MAKE BUTTON LIGHT UP</h5>
<button :class="{currentlySelectedPreset: (lastPreset == '1')}" @click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '1')"A>1</button>
<button :class="{currentlySelectedPreset: (lastPreset == '2')}" @click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '2')"A>2</button>
<button :class="{currentlySelectedPreset: (lastPreset == '3')}" @click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '3')"A>3</button>
<button :class="{currentlySelectedPreset: (lastPreset == '4')}" @click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', '4')"A>4</button>
<button :class="{currentlySelectedPreset: (lastPreset == '9')}" @click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', 'E')"A>Plchldr E</button>
<button :class="{currentlySelectedPreset: (lastPreset == '9')}" @click="publishMQTTMsg(publishPrefix + 'lucke/preset/recall', 'F')"A>Plchldr F</button>
</div>
</div>
<div style="justify-content: center">
<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>
.disabled {
opacity: .8;
pointer-events: none;
}
button {
padding: 1rem;
margin: 0.1rem;
width: 45%;
}
.currentlySelectedPreset {
background-color: orange;
.lightButtons {
display: flex;
}
.lightButtons button {
width: fit-content;
flex: 1;
height: 4em;
}
</style>

View file

@ -1,6 +1,6 @@
<script setup lang="ts">
import {ref, onMounted, reactive } from 'vue'
import { $mqtt } from 'vue-paho-mqtt'
import {ref, onMounted, reactive} from 'vue'
import {$mqtt} from 'vue-paho-mqtt'
import Projektor from '../Projektor.vue';
import Platno from '../Platno.vue';
import Lift from '../Lift.vue';
@ -8,7 +8,7 @@ import LectureModule from '../LectureModule.vue';
import AudioControlModule from '../AudioControlModule.vue';
const props = defineProps({
room: String
room: String
})
const _glavni_position = ref('main')
@ -20,21 +20,28 @@ let _stranski = ref('side')
</script>
<template>
<div style="display:flex; gap: 1rem; margin:inherit">
<div style="width: 50%;">
<h4>Glavni:</h4>
<LectureModule :room="props.room" :position="_glavni" />
<div class="page">
<div style="display: flex; gap: 1rem; margin:inherit">
<div style="width: 50%;">
<h3>Glavni projektor</h3>
<LectureModule :room="props.room" :position="_glavni"/>
</div>
<div style="width: 50%;">
<h3>Stranski projektor</h3>
<LectureModule :room="props.room" :position="_stranski"/>
</div>
</div>
<div style="width: 50%;">
<h4>Stranski:</h4>
<LectureModule :room="props.room" :position="_stranski" />
<div style="text-align: center">
<AudioControlModule :room="props.room" :big="true"/>
</div>
<AudioControlModule :room="props.room" />
</div>
</template>
<style lang="css">
* {
cursor: pointer;
<style scoped>
.page {
text-align: center;
display: flex;
flex-direction: column;
justify-content: space-around;
}
</style>

View file

@ -1,38 +0,0 @@
<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'
import Lift from '../Lift.vue';
import Projektor from '../Projektor.vue';
const props = defineProps({
room: String,
})
const _glavni_position = ref('glavni')
const _stranski_position = ref('stranski')
const _ctrl_type = ref('service')
</script>
<template>
<div 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>
</template>
<style scoped>
</style>

View file

@ -1,8 +1,8 @@
<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 {ref, onMounted, reactive, watch} from 'vue'
import {$mqtt} from 'vue-paho-mqtt'
// import Projektor from '../Projektor.vue'
import Platno from '../Platno.vue'
import Lift from '../Lift.vue';
@ -11,43 +11,66 @@ 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,
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 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>
<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" />
<ProjectorPowerModule :room="props.room"/>
<AudioControlModule :room="props.room"/>
<MasterPowerControlModule :room="props.room"/>
</div>
<div></div>
<ResetButton :room="props.room"/>
<ResetButton :room="props.room"/>
</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 -->

View file

@ -16,20 +16,27 @@ const _test = ref('test')
</script>
<template>
<div style="display: flex; gap: 1rem">
<div>
<h4>Glavni</h4>
<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>
<h4>Stranski</h4>
<Projektor :room="props.room" :position="_stranski_position" :ctrltype="_test"/>
<Platno :room="props.room" :position="_stranski_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>

View file

@ -16,7 +16,6 @@ const props = defineProps({
font-weight: bold;
}
.tab:hover {
cursor: pointer;
}

View file

@ -8,7 +8,6 @@
<style scoped>
.tab:hover {
cursor: pointer;
}

View file

@ -10,20 +10,20 @@ createApp(App)
createPahoMqttPlugin({
PluginOptions: {
autoConnect: true,
showNotifications: true,
showNotifications: false,
},
MqttOptions: {
//host: import.meta.env.VITE_MQTT_HOST,
//host: "localhost",
host: urlParams.get('mqtt') || '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
enableMainTopic: false,
},
})
)