Add sauna

This commit is contained in:
Thomas Klaehn
2023-01-30 15:54:49 +01:00
parent f8b2047cff
commit 89a78993b5
39 changed files with 241 additions and 127 deletions

12
webui/src/app.html Normal file
View File

@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/haus.svg"/>
<meta name="viewport" content="width=device-width"/>
%sveltekit.head%
</head>
<body>
<div>%sveltekit.body%</div>
</body>
</html>

566
webui/src/css/style.css Normal file
View File

@@ -0,0 +1,566 @@
html,
body {
margin: 0;
padding: 0;
font-size: 18px !important;
font-family: 'Muli', arial, verdana, helvetica, sans-serif;
font-weight: 300;
font-style: normal;
height: 100%;
color: #b6b6b6;
background: #282929;
}
body {
display: flex;
flex-direction: column;
}
.modalbg {
background: rgba(0, 0, 0, 0.6);
height: 100%;
width: 100%;
position: fixed;
top: 0px;
z-index: 999;
overflow-x: hidden;
overflow-y: auto;
color: white;
}
a {
outline: none;
color: #b6b6b6;
text-decoration-line: none;
}
li {
list-style: none;
}
.content {
padding: 10rem 0;
margin: 2%;
}
section {
margin: auto 0;
align-items: center;
text-align: center;
}
section h1 {
margin: 20px auto;
font-size: 2rem;
}
section h2 {
font-size: 1.4rem;
}
table {
margin: auto;
}
td p {
margin: 0;
}
td.left {
text-align: right;
font-weight: 700;
width: 33%;
padding: 5px;
vertical-align: text-top;
}
td.right {
text-align: left;
}
td.input {
text-align: left;
width: 65%;
outline: none;
vertical-align: text-top;
}
form {
font-size: 1.2rem;
}
td select {
width: 51%;
outline: none;
width: 62%;
outline: none;
margin-left: 2%;
}
td input {
width: 60%;
outline: none;
}
td textarea {
text-align: left;
}
td input span {
font-size: 1.0rem;
outline: none;
border: none;
background: none;
}
select,
input {
border: 1px #999999 solid;
background-color: #f6f6f6;
padding: 2px 4px 2px 4px;
margin: 0 0 0.5rem 0;
color: #b6b6b6 !important;
font-size: 1.0rem;
border-radius: 8px;
outline: none;
}
td div input {
color: #b6b6b6 !important;
border: none;
background: none;
}
textarea {
border-radius: 10px;
width: 98%;
height: 10rem;
position: relative;
resize: none;
outline: none;
margin-left: 2%;
}
header {
width: 100%;
height: 50px;
line-height: 50px;
text-align: center;
background-color: #282929;
}
/*logo placement*/
header figure {
float: left;
color: #b6b6b6;
}
header figure.logo {
position: absolute;
right: 0px;
top: -15px;
}
/*Navigation*/
.header {
background-color: #282929;
/*position: fixed;*/
width: 100%;
z-index: 510;
}
.header ul {
margin: 0;
padding: 0;
list-style: none;
overflow: hidden;
/* background-color: #282929; */
background-color: transparent;
}
.header li a {
display: block;
padding: 30px 5px 10px 5px;
font-size: 1.5rem;
border-right: 1px solid #282929;
text-decoration: none;
/* background: rgba(255, 255, 255, 0.9) */
background-color: #282929;
}
.header li a:hover,
.header .menu-btn:hover {
background-color: #282929;
}
.header .logo {
display: block;
float: left;
font-size: 2em;
padding: 5px;
text-decoration: none;
}
/* menu */
.header .menu {
clear: both;
max-height: 0;
transition: max-height .2s ease-out;
}
/* menu icon */
.header .menu-icon {
cursor: pointer;
float: right;
margin: 2%;
padding: 10px 20px;
position: relative;
user-select: none;
}
.header .menu-icon .nav-icon {
background: #000;
display: block;
height: 5px;
position: relative;
transition: background .2s ease-out;
width: 30px;
}
.header .menu-icon .nav-icon:before,
.header .menu-icon .nav-icon:after {
background: #000;
content: '';
display: block;
height: 100%;
position: absolute;
transition: all .2s ease-out;
width: 100%;
}
.header .menu-icon .nav-icon:before {
top: 20px;
}
.header .menu-icon .nav-icon:after {
top: 10px;
}
/* menu btn */
.header .menu-btn {
display: none;
}
.header .menu-btn:checked~.menu {
max-height: 1200px;
}
.header .menu-btn:checked~.menu-icon .nav-icon {
background: transparent;
}
.header .menu-btn:checked~.menu-icon .nav-icon:before {
transform: rotate(-45deg);
top: 0;
}
.header .menu-btn:checked~.menu-icon .nav-icon:after {
transform: rotate(45deg);
top: 0;
}
/* menu content - larger screen*/
@media (min-width: 1132px) {
.header li {
float: left;
}
.header li a {
padding: 20px 15px 5px 0px;
font-size: 1rem;
border: none;
}
.header .menu {
clear: none;
float: right;
max-height: none;
}
.header .menu-icon {
display: none;
}
}
/* smaller screen*/
@media (max-width: 924px) {
.header li {
padding: 30px 5px 10px 5px;
}
.header li a {
font-size: 1.2rem;
}
}
#overlay {
position: absolute;
display: none;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 999;
background-color: rgba(0, 0, 0, 0.5);
/*dim the background*/
}
.update-container {
display: flex;
align-items: center;
justify-content: center;
}
#update-plain {
position: relative;
}
#update-overlay {
position: absolute;
z-index: 502;
transform-origin: 50% 50%;
transition: transform 1.00s;
}
address {
text-align: center;
margin-top: 5px;
}
address table {
margin: 0 auto;
width: 100%;
font-style: normal;
}
address td {
width: 50%;
}
input[type="submit" i] {
margin: 0px 0 6px 0;
vertical-align: middle;
position: relative;
font-size: 16px;
font-weight: bold;
width: auto;
height: 45px;
z-index: 200;
width: 65%;
outline: none;
}
input[type="submit"] {
border-radius: 30px;
background: #282929;
border: 1px solid #d2d2d2;
-webkit-transition: all 1s;
transition: all 1s;
margin-top: 2%;
margin-left: 2%;
outline: none;
z-index: auto;
}
input[type="submit" i]:hover {
background-color: #f6f6f6;
color: #282929;
outline: none;
}
input[type="file"] {
border-radius: 10px;
margin: 1%;
width: 390px;
margin-left: 45px;
margin-right: 28px;
outline: none;
}
input {
border-radius: 10px;
background: #282929;
margin-left: 2%;
outline: none;
}
.confi table td.input {
padding: 1px 0px 10px 0px;
outline: none;
}
.confi table td.left {
padding: 0px 25px 0px 0px;
}
.content #uploadbutton {
width: 400px;
outline: none;
}
footer {
background-color: #f6f6f6;
padding: 1px 50px 1px 0px;
text-align: center;
text-decoration: none;
font-size: 0.8rem;
bottom: 0;
width: 100%;
position: fixed;
}
footer nav :hover {
color: #b6b6b6;
font-weight: 600;
}
footer td p {
text-align: left;
padding-left: 40%;
}
/* upload loader animation*/
.loader {
display: flex;
justify-content: center;
align-items: center;
width: 100px;
height: 100px;
background: transparent;
margin: 30px auto 0 auto;
border: solid 10px #b6b6b6;
border-top: solid 10px #c2d100;
border-radius: 50%;
opacity: 0;
}
.loader.active {
animation: loading 4s ease-in-out;
animation-fill-mode: forwards;
}
@keyframes loading {
5% {
opacity: 1;
border-color: #b6b6b6;
}
15% {
opacity: 1;
border-color: #c2d100;
}
30% {
opacity: 1;
transform: rotate(180deg);
border-color: #b6b6b6;
}
45% {
opacity: 1;
border-color: #c2d100;
}
60% {
opacity: 1;
transform: rotate(180deg);
border-color: #b6b6b6;
}
70% {
opacity: 1;
border-color: #c2d100;
}
85% {
opacity: 1;
transform: rotate(180deg);
border-color: #c2d100;
}
90% {
opacity: 1;
transform: rotate(1080deg);
border-color: #c2d100;
}
99% {
opacity: 1;
transform: rotate(1080deg);
border-color: #c2d100;
}
100% {
opacity: 1;
transform: rotate(1080deg);
border-color: #282929;
}
}
/*Chrome runable slider*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
input[type='range'] {
overflow: hidden;
/* Standard */
width: calc(99.9% - 1.5px);
-webkit-appearance: none;
background-color: transparent;
margin: 0 0 0;
}
input[type='range']::-webkit-slider-runnable-track {
height: fit-content;
/* -webkit-appearance: none; */
color: #C2D100;
/* margin-top: -5px; */
}
input[type='range']::-webkit-slider-thumb {
width: 20px;
-webkit-appearance: none;
height: 20px;
cursor: ew-resize;
border-radius: 5px;
background: #292659;
box-shadow: -1080px 10px 10px 1080px #C1D10A;
}
input:focus {
outline: none;
border-color: #C1D10A;
}
}
/** FF*/
input[type="range"]::-moz-range-progress {
background-color: #C1D10A;
}
input[type="range"]::-moz-range-track {
background-color: transparent;
}
/* IE*/
input[type="range"]::-ms-fill-lower {
background-color: #C1D10A;
}
input[type="range"]::-ms-fill-upper {
background-color: #C1D10A;
}

14
webui/src/hooks.server.js Normal file
View File

@@ -0,0 +1,14 @@
export const handle = async ({ event, resolve }) => {
let userid = event.cookies.get('userid');
if (!userid) {
// if this is the first time the user has visited this app,
// set a cookie so that we recognise them when they return
userid = crypto.randomUUID();
event.cookies.set('userid', userid, { path: '/' });
}
event.locals.userid = userid;
return resolve(event);
};

View File

@@ -0,0 +1,22 @@
<script>
import perinetLogo from '../../static/perinet-logo.png'
import favicon from '../../static/favicon.png'
</script>
<header class="header">
<!-- <a href="https://www.perinet.io" target="_blank" class="logo">
<img src={perinetLogo} alt="Perinet Logo" width="180">
</a>
<link rel="shortcut icon" type="image/png" href={favicon} /> -->
<input class="menu-btn" type="checkbox" id="menu-btn" />
<!-- <label class="menu-icon" for="menu-btn"><span class="nav-icon"></span></label> -->
<ul class="menu">
<li><a href="/">Home</a></li>
<li><a href="/sauna">Sauna</a></li>
<!-- <li><a href="/chicken">Chicken</a></li> -->
</ul>
</header>

View File

@@ -0,0 +1,13 @@
<script>
import Header from '$lib/Header.svelte';
// import Footer from '$lib/Footer.svelte';
import '../css/style.css'
</script>
<Header />
<main>
<slot />
</main>
<!-- <Footer/> -->

View File

@@ -0,0 +1 @@
export const prerender = true;

View File

@@ -0,0 +1,15 @@
<script>
import icon from "../../static/haus.svg"
</script>
<svelte:head>
<title>Home</title>
<meta name="description" content="Home"/>
</svelte:head>
<section id='content_id' class='content'>
<figure>
<img src={icon} alt="Home" width=150/>
</figure>
<h1>Home</h1>
</section>

View File

@@ -0,0 +1 @@
export const prerender = true;

View File

@@ -0,0 +1,152 @@
<script>
import { bind, onMount } from "svelte/internal";
import icon from "../../../static/hahn.svg"
// base api url
let backend_url = "http://localhost:5000/gates";
let value_state_outer = "";
let value_open_times_outer = "";
let value_close_times_outer = "";
let options_state = [
{id: 1, text: "Schließen" }, // means "is open"
{id: 2, text: "Öffnen" },
{id: 3, text: "Schließt..." },
{id: 4, text: "Öffnet..." },
];
let options_open = [
{id: 1, text: "Sonnenaufgang"},
{id: 2, text: "08:00"},
{id: 3, text: "09:00"},
{id: 4, text: "10:00"},
{id: 5, text: "11:00"},
{id: 6, text: "12:00"}
];
let options_close = [
{id: 1, text: "Sonnenuntergang"},
{id: 2, text: "Sonnenuntergang + 30min"},
{id: 3, text: "16:00"},
{id: 4, text: "16:30"},
{id: 5, text: "17:00"},
{id: 6, text: "17:30"},
{id: 7, text: "18:00"},
{id: 8, text: "18:30"},
{id: 9, text: "19:00"},
{id: 10, text: "19:30"},
{id: 11, text: "20:00"},
{id: 12, text: "20:30"},
{id: 13, text: "21:00"},
{id: 14, text: "21:30"},
{id: 15, text: "22:00"},
];
function get_next_state(state) {
if(state == 1) {
return 2;
}
if(state == 2) {
return 1;
}
}
function handle_button_outer(event) {
console.log(value_state_outer)
for( var i = 0; i < options_state.length; i++) {
if(options_state[i].text == value_state_outer) {
fetch(backend_url + "/outer", {
method: "PATCH",
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
outer_gate: {state: get_next_state(options_state[i].id)}
})
})
.then(response => response.json())
// .then(result => console.log(result))
break;
}
}
}
function handle_open_times_outer() {
alert(value_open_times_outer);
}
function handle_close_times_outer() {
alert(value_close_times_outer);
}
function set_state_outer(index) {
for( var i = 0; i < options_state.length; i++) {
if(options_state[i].id == index) {
value_state_outer = options_state[i].text;
break;
}
}
}
function get_gates() {
fetch(backend_url)
.then(response => response.json())
.then(data => {
set_state_outer(data.outer_gate.state)
}).catch(error => {
console.log(error);
return [];
});
}
setInterval(() => {
get_gates();
}, 1000)
onMount(async () => {
get_gates();
});
</script>
<svelte:head>
<title>Chicken</title>
<meta name="description" content="Chicken"/>
</svelte:head>
<section id='content_id' class='content'>
<figure>
<img src={icon} alt="Chicken" width=150/>
</figure>
<h1>Chicken</h1>
<hr>
<table>
<tr>
<td class="left">
<h2>Außenklappe</h2>
</td>
<td>
<input type="submit" enabled id="button_outer" value={value_state_outer} style="margin-left:55px;width:200px" on:click={(event) => handle_button_outer(event)}/>
</td>
</tr>
<tr>
<td class="left">Öffnen:</td>
<td class="input">
<select style="margin-left:55px;width:250px;" bind:value={value_open_times_outer} on:change={handle_open_times_outer}>
{#each options_open as option}
<option value={option}>{option.text}</option>
{/each}
</select>
</td>
</tr>
<tr>
<td class="left">Schließen:</td>
<td class="input">
<select style="margin-left:55px;width:250px;" bind:value={value_close_times_outer} on:change={handle_close_times_outer}>
{#each options_close as option}
<option value={option}>{option.text}</option>
{/each}
</select>
</td>
</tr>
</table>
<hr>
</section>

View File

@@ -0,0 +1 @@
export const prerender = true;

View File

@@ -0,0 +1,41 @@
<script>
import { onMount } from "../../../node_modules/svelte/internal";
import icon from "../../../static/sauna.svg"
let backend_url = "https://home.blackfinn.de/sauna/sample";
let temperature_value = 0.0
let temperature_unit = "°C"
function get_temperature() {
fetch(backend_url)
.then(response => response.json())
.then(data => {
temperature_unit = data.unit
temperature_value = data.value
}).catch(error => {
console.log(error);
return [];
});
}
setInterval(() => {
get_temperature();
}, 1000)
onMount(async () => {
get_temperature();
});
</script>
<svelte:head>
<title>Sauna</title>
<meta name="description" content="Sauna"/>
</svelte:head>
<section id='content_id' class='content'>
<figure>
<img src={icon} alt="Sauna" width=150/>
</figure>
<h1>{temperature_value} {temperature_unit}</h1>
</section>