31 Commits

Author SHA1 Message Date
a4fe525b9e highlight due date
All checks were successful
CI / lint (push) Successful in 14s
CI / build (push) Successful in 17s
CI / build-and-push-docker (push) Successful in 31s
2025-08-28 22:51:18 +02:00
d37dbc4f62 sort chores using due date, color tasks according to due level 2025-08-28 22:51:18 +02:00
c7b03a93f0 fix code style, use names from api instead of hardcoding for flatmates users 2025-08-28 22:51:18 +02:00
c924ab893e Full tram stop names and final prompt in terminal 2025-08-28 22:48:06 +02:00
05c62ed6bb New Terminal 2025-08-28 22:37:20 +02:00
481f103ba0 Make clock divider blink 2025-08-28 21:36:53 +02:00
aa8da0bb56 Terminal window
All checks were successful
CI / lint (pull_request) Successful in 8s
CI / build (pull_request) Successful in 10s
CI / build-and-push-docker (pull_request) Successful in 13s
2025-08-28 20:38:45 +02:00
42419e2e3b Clock widget and more copypasta 2025-08-28 18:08:58 +02:00
b1adbe74de Windows look update 2025-08-28 18:08:58 +02:00
241db688c2 A e s t h e t i c s 2025-08-28 18:08:58 +02:00
7f12cb59d8 HomeAssistant update 2025-08-28 18:08:58 +02:00
0cb9867bc7 Footer 2025-08-28 18:08:58 +02:00
97d9a04b29 Dashboard with cards 2025-08-28 18:08:58 +02:00
97737def29 Timetable styling 2025-08-28 18:08:58 +02:00
ef67915621 Devcontainer support 2025-08-28 18:08:58 +02:00
040178e395 manual trigger for ci
All checks were successful
CI / lint (push) Successful in 10s
CI / build (push) Successful in 10s
CI / build-and-push-docker (push) Successful in 14s
2025-08-28 18:06:36 +02:00
01f6a07672 use password to login
All checks were successful
CI / lint (push) Successful in 9s
CI / build (push) Successful in 10s
CI / build-and-push-docker (push) Successful in 14s
2025-08-28 16:02:05 +02:00
10508f5ecd add debug code in ci.yml
Some checks failed
CI / lint (push) Successful in 8s
CI / build (push) Successful in 10s
CI / build-and-push-docker (push) Failing after 13s
2025-08-28 15:59:00 +02:00
5fe9376533 use docker compose instead of docker in ci.yml
Some checks failed
CI / lint (push) Successful in 9s
CI / build (push) Successful in 10s
CI / build-and-push-docker (push) Failing after 13s
2025-08-28 15:55:12 +02:00
f694284c29 fix false username in ci.yml
Some checks failed
CI / lint (push) Successful in 8s
CI / build (push) Successful in 10s
CI / build-and-push-docker (push) Failing after 12s
2025-08-28 15:51:06 +02:00
414b59ddb1 quick commit
Some checks failed
CI / build (push) Successful in 9s
CI / lint (push) Successful in 17s
CI / build-and-push-docker (push) Failing after 25s
2025-08-28 15:48:32 +02:00
63f906bb02 update server on main changes
Some checks failed
CI / lint (push) Successful in 10s
CI / build (push) Successful in 9s
Build Docker Image / build-and-push-docker (push) Has been cancelled
2025-08-28 15:47:45 +02:00
194b0b58ed fix typo in url
All checks were successful
CI / build (push) Successful in 10s
CI / lint (push) Successful in 16s
Build Docker Image / build-and-push-docker (push) Successful in 11s
2025-08-28 13:36:27 +02:00
9b17385043 also push image
Some checks failed
CI / build (push) Successful in 11s
CI / lint (push) Successful in 7s
Build Docker Image / build-and-push-docker (push) Failing after 19s
2025-08-28 13:32:51 +02:00
7875b8edef add docker build.yml
All checks were successful
CI / build (push) Successful in 10s
CI / lint (push) Successful in 8s
Build Docker Image / build-docker (push) Successful in 55s
2025-08-28 13:28:01 +02:00
e17829e7fa create github action
All checks were successful
CI / build (push) Successful in 19s
CI / lint (push) Successful in 8s
2025-08-28 13:08:32 +02:00
1e634ed122 Merge branch 'runner' into 'main'
Runner

See merge request arifhasanic/monitor-im-flur!1
2025-08-27 22:02:55 +00:00
16131521bc remove tags from gitlab yaml 2025-08-27 23:38:47 +02:00
741720ed07 add build image script 2025-08-27 23:34:53 +02:00
4ae7e7968b fix gitlab ci yml 2025-08-27 22:54:09 +02:00
90e6842d71 setup ci, fix files using biome 2025-08-27 22:51:50 +02:00
34 changed files with 634 additions and 188 deletions

View File

@@ -1,57 +1,55 @@
{
"name": "bun",
"image": "oven/bun:debian",
"privileged": true,
"features": {
"ghcr.io/devcontainers/features/common-utils:1": {
"version": "latest",
"configureZshAsDefaultShell": true,
"username": "bun",
"userUid": "1000",
"userGid": "1000"
"name": "bun",
"image": "oven/bun:debian",
"privileged": true,
"features": {
"ghcr.io/devcontainers/features/common-utils:1": {
"version": "latest",
"configureZshAsDefaultShell": true,
"username": "bun",
"userUid": "1000",
"userGid": "1000"
},
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
"packages": "stow,tmux,ripgrep,python3-venv,python3-virtualenv"
}
},
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
"packages": "stow,tmux,ripgrep,python3-venv,python3-virtualenv"
},
},
"remoteUser": "bun",
"workspaceFolder": "/home/bun/ws",
"workspaceMount": "source=${localWorkspaceFolder},target=/home/bun/ws,type=bind",
"containerEnv": {
},
"runArgs": [
"--net=host",
"-e",
"DISPLAY=${env:DISPLAY}",
"-e",
"TERM=${env:TERM}",
"-e",
"SHELL=${env:SHELL}",
"-v",
"${env:SSH_AUTH_SOCK}:/tmp/ssh-agent.socket",
"-e",
"SSH_AUTH_SOCK=/tmp/ssh-agent.socket"
],
"mounts": [
{
"source": "/tmp/.X11-unix",
"target": "/tmp/.X11-unix",
"type": "bind",
"consistency": "cached"
},
{
"source": "/dev/dri",
"target": "/dev/dri",
"type": "bind",
"consistency": "cached"
},
{
"source": "/dev/shm",
"target": "/dev/shm",
"type": "bind",
"consistency": "cached"
}
],
"postCreateCommand": {
}
"remoteUser": "bun",
"workspaceFolder": "/home/bun/ws",
"workspaceMount": "source=${localWorkspaceFolder},target=/home/bun/ws,type=bind",
"containerEnv": {},
"runArgs": [
"--net=host",
"-e",
"DISPLAY=${env:DISPLAY}",
"-e",
"TERM=${env:TERM}",
"-e",
"SHELL=${env:SHELL}",
"-v",
"${env:SSH_AUTH_SOCK}:/tmp/ssh-agent.socket",
"-e",
"SSH_AUTH_SOCK=/tmp/ssh-agent.socket"
],
"mounts": [
{
"source": "/tmp/.X11-unix",
"target": "/tmp/.X11-unix",
"type": "bind",
"consistency": "cached"
},
{
"source": "/dev/dri",
"target": "/dev/dri",
"type": "bind",
"consistency": "cached"
},
{
"source": "/dev/shm",
"target": "/dev/shm",
"type": "bind",
"consistency": "cached"
}
],
"postCreateCommand": {}
}

63
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
lint:
runs-on: ubuntu-latest
container:
image: oven/bun
steps:
- uses: actions/checkout@v4
- name: Install Biome
run: bun install -g biome
- name: Biome CI
run: biome ci
build:
runs-on: ubuntu-latest
needs: lint
container:
image: oven/bun
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: bun install
- name: Build
run: bun run build
build-and-push-docker:
needs: [build]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Log in to Docker Registry
uses: docker/login-action@v2
with:
registry: git.rivercry.com
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build Docker image
run: docker build -t git.rivercry.com/wg/monitor-im-flur .
- name: Push Docker image
run: docker push git.rivercry.com/wg/monitor-im-flur
- name: Deploy via SSH
uses: appleboy/ssh-action@v0.1.10
with:
host: rivercry.com
port: 20022
username: docker
password: ${{ secrets.GARRISON_DOCKER_PASSWORD }}
script: |
cd monitor-im-flur
echo "Deploying Docker container..."
docker-compose pull
docker-compose stop || true
docker-compose rm || true
docker-compose up -d

View File

@@ -1,3 +0,0 @@
# Ignore artifacts:
build
coverage

View File

@@ -1,4 +0,0 @@
{
"tabWidth": 4,
"useTabs": false
}

View File

@@ -1,5 +1,4 @@
{
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",

View File

@@ -1,6 +1,6 @@
services:
monitor-im-flur:
build: .
image: git.rivercry.com/wg/monitor-im-flur:latest
ports:
- "9123:5173"
restart: unless-stopped

View File

@@ -1,9 +1,9 @@
import js from "@eslint/js";
import globals from "globals";
import { globalIgnores } from "eslint/config";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import globals from "globals";
import tseslint from "typescript-eslint";
import { globalIgnores } from "eslint/config";
export default tseslint.config([
globalIgnores(["dist"]),

1
pipeline/build-image.sh Executable file
View File

@@ -0,0 +1 @@
#!/bin/bash

View File

@@ -3,5 +3,5 @@
max-width: 1280px;
margin: 0 auto;
padding: 0;
text-align: center;
text-align: left;
}

View File

@@ -3,11 +3,7 @@ import "@/App.css";
import Dashboard from "@/components/Dashboard/Dashboard";
function App() {
return (
<>
<Dashboard />
</>
);
return <Dashboard />;
}
export default App;

View File

@@ -1,12 +1,14 @@
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkMjQ1OTg0YjliYzE0OTNjYTdmZDJmNTA3ODgzN2U1YSIsImlhdCI6MTc1MzQwMjAzNiwiZXhwIjoyMDY4NzYyMDM2fQ.fnLSFKPdk8lkAEB-4ekdGUJ1PSCBxcAyasQF1PyrD3k";
const token =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkMjQ1OTg0YjliYzE0OTNjYTdmZDJmNTA3ODgzN2U1YSIsImlhdCI6MTc1MzQwMjAzNiwiZXhwIjoyMDY4NzYyMDM2fQ.fnLSFKPdk8lkAEB-4ekdGUJ1PSCBxcAyasQF1PyrD3k";
async function fetchTentHumidity() {
const url = "/api/states/sensor.third_reality_inc_3rths0224z_luftfeuchtigkeit_2";
const url =
"/api/states/sensor.third_reality_inc_3rths0224z_luftfeuchtigkeit_2";
const response = await fetch(url, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
}
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
@@ -18,8 +20,8 @@ async function fetchTentTemperature() {
const response = await fetch(url, {
method: "GET",
headers: {
"Authorization": `Bearer ${token}`,
}
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
@@ -27,4 +29,4 @@ async function fetchTentTemperature() {
return data.state;
}
export { fetchTentHumidity, fetchTentTemperature };
export { fetchTentHumidity, fetchTentTemperature };

BIN
src/assets/clock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
src/assets/weed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -1,7 +1,8 @@
import Datetime from "@/components/Datetime/Datetime";
import Flatastic from "@/components/Flatastic/Flatastic";
import HomeAssistant from "@/components/HomeAssistant/HomeAssistant";
import Timetable from "@/components/Timetable/Timetable";
import Footer from "@/components/Footer/Footer";
import Terminal from "@/components/Terminal/Terminal";
import Timetable from "@/components/Timetable/Timetable";
import style from "./style.module.css";
@@ -10,13 +11,31 @@ export default function Dashboard() {
<div className={style.dashboard}>
<div className={style.cardWrapper}>
<div className={style.card}>
<Timetable />
<div className={style.cardHeaderInactive}>🚊 Timetable</div>
<div className={style.cardContent}>
<Timetable />
</div>
</div>
<div className={style.card}>
<Flatastic />
<div className={style.clock}>
<div className={style.card}>
<div className={style.cardHeaderInactive}>🕐 Clock</div>
<Datetime />
</div>
</div>
<div className={style.card}>
<HomeAssistant />
<div className={style.terminal}>
<div className={style.cardHeader}>🔔 Terminal</div>
<Terminal />
</div>
</div>
<div className={style.card}>
<div className={style.cardHeaderInactive}>🧹 Flatastic</div>
<div className={style.cardContent}>
<Flatastic />
</div>
</div>
</div>

View File

@@ -2,6 +2,7 @@
display: flex;
flex-direction: column;
height: 100%;
background-color: #007c7d;
}
.cardWrapper {
@@ -14,12 +15,41 @@
}
.card {
border-radius: 10px;
flex-direction: column;
justify-content: flex-start;
background-color: #c0c0c0;
border-top: 2px solid white;
border-left: 2px solid white;
border-bottom: 2px solid #828282;
border-right: 2px solid #828282;
}
.cardContent {
padding: 1px 100px 30px 100px;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
}
.cardHeader {
height: 30px;
color: white;
background-color: #000082;
text-align: left;
padding-left: 5px;
font-weight: bold;
}
.cardHeaderInactive {
height: 30px;
color: #c0c0c0;
background-color: #808080;
text-align: left;
padding-left: 5px;
font-weight: bold;
}
.clock {
width: 45%;
}
.footer {
background-color: rgba(220, 220, 220, 0.5);
background-color: #c0c0c0;
}

View File

@@ -0,0 +1,41 @@
import { useEffect, useState } from "react";
import style from "./style.module.css";
export default function Datetime() {
const locale = "de";
const [today, setDate] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
setDate(new Date());
}, 20 * 1000);
return () => {
clearInterval(timer);
};
}, []);
const date = today.toLocaleDateString(locale, {
month: "long",
day: "numeric",
year: "numeric",
});
const hour = today.getHours().toString().padStart(2, "0");
const minute = today.getMinutes().toString().padStart(2, "0");
return (
<div className={style.container}>
<div className={style.icon}>
<img src="src/assets/clock.png" alt="Clock" />
</div>
<div className={style.textContainer}>
<div className={style.time}>
{hour}
<span className={style.divider}>:</span>
{minute}
</div>
<div className={style.date}>{date}</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,29 @@
.container {
display: flex;
flex-direction: row;
align-items: center;
padding-right: 25px;
}
img {
height: 100px;
scale: 130%;
object-fit: cover;
}
.textContainer {
font-size: 16pt;
font-weight: bold;
display: flex;
flex-direction: column;
}
.divider {
animation: blink 3s step-end infinite;
}
@keyframes blink {
50% {
opacity: 0;
}
}

View File

@@ -2,21 +2,22 @@
display: flex;
flex-direction: column;
align-items: stretch;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
border-radius: 10px;
padding: 0 10px 20px 10px;
margin-bottom: 20px;
border-top: 2px solid white;
border-left: 2px solid white;
border-bottom: 2px solid #828282;
border-right: 2px solid #828282;
}
.departureLists {
display: flex;
flex-direction: row;
justify-content: space-between;
justify-content: space-around;
width: 100%;
gap: 120px;
}
.heading {
text-align: left;
padding-left: 30px;
}

View File

@@ -1,42 +1,64 @@
import { useFlatasticStore } from "@/store/flatastic";
import { useEffect } from "react";
import { useFlatasticStore } from "@/store/flatastic";
import type { FlatasticChore } from "@/types/flatasticChore";
import type { FlatasticChore, FlatasticUser } from "@/types/flatasticChore";
import style from "./style.module.css";
const idToNameMap: Record<number, string> = {
1836104: "Gruber",
1836101: "Darius",
1593610: "Arif",
1860060: "Rishab",
};
export default function Flatastic() {
const fetchChores = useFlatasticStore((state) => state.fetch);
const chores = useFlatasticStore((state) => state.chores);
const fetchFlatasticData = useFlatasticStore((state) => state.fetch);
const flatasticData = useFlatasticStore((state) => state.flatasticData);
const chores = (flatasticData?.chores as FlatasticChore[]) || [];
chores.sort(
(a, b) =>
a.timeLeftNext - b.timeLeftNext && b.rotationTime - a.rotationTime,
);
const users = flatasticData?.users;
const idToNameMap: Record<number, string> = {};
users.forEach((user: FlatasticUser) => {
idToNameMap[user.id] = user.firstName;
});
useEffect(() => {
fetchChores();
fetchFlatasticData();
const interval = setInterval(() => {
fetchChores();
fetchFlatasticData();
}, 60000);
return () => clearInterval(interval);
}, [fetchChores]);
}, [fetchFlatasticData]);
const choresRender = chores.map((chore) => {
let className = "";
let timeLeftInDays = null;
if (chore.rotationTime === -1) {
className = "irregular";
} else {
className = chore.timeLeftNext <= 0 ? "due" : "notDue";
timeLeftInDays = <span className={style.timeLeft}>
{Math.abs(
Math.floor(chore.timeLeftNext / (60 * 60 * 24)),
)}d
</span>;
}
return (
<li
key={chore.id}
className={[style.chore, style[className]].join(" ")}
>
<span className={style.userName}>
{`${idToNameMap[chore.currentUser]}`}
</span>
: {chore.title} {timeLeftInDays} {"🪙".repeat(chore.points)}
</li>
);
});
return (
<div>
<h1>Flatastic Chores</h1>
<ul className={style.choreList}>
{chores.map((chore: FlatasticChore) => (
<li key={chore.id} className={style.chore}>
<span className={style.userName}>
{idToNameMap[chore.currentUser]}
</span>
: {chore.title} - {"🪙".repeat(chore.points)}
</li>
))}
</ul>
<h1>Chores</h1>
<ul className={style.choreList}>{choresRender}</ul>
</div>
);
}

View File

@@ -1,20 +1,38 @@
.choreList {
list-style-type: none;
display: flex;
flex-direction: row;
flex-wrap: wrap;
border-radius: 10px;
gap: 10px;
padding: 10px 0;
list-style-type: none;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 10px;
padding: 10px 0;
}
.chore {
padding: 5px 10px;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
text-align: left;
padding: 5px 10px;
text-align: left;
border-top: 2px solid white;
border-left: 2px solid white;
border-bottom: 2px solid #828282;
border-right: 2px solid #828282;
}
.userName {
font-weight: bold;
font-weight: bold;
}
.irregular {
background-color: #aaaaaa;
}
.due {
background-color: #e4877e;
}
.notDue {
background-color: #a6cfa6;
}
.timeLeft {
font-weight: bold;
}

View File

@@ -1,16 +1,28 @@
import { useEffect, useState } from "react";
import Marquee from "react-fast-marquee";
import pasta from "./pasta.ts";
import style from "./style.module.css";
export default function Footer() {
let pasta =
'Ok, so I was playing the hit game Among Us the other day, and when the game started, a red bean-shaped character that appeared to be wearing a spacesuit told me "shh," while having his index finger in front of where his mouth should be. I believe this act made this red bean character extremely suspicious. To understand why this red bean character is suspicious, we first must understand how the game “Among Us” works. The game consists of 10 bean-shaped characters, called crewmates, that are given tasks for them to complete. As these characters do their tasks, they may witness abnormal things that are not supposed to happen, such as the lights turning off on their own, sudden reactor meltdown and other crewmates dying. These acts show that there is an imposter among the crewmates that is sabotaging and is trying to kill everyone. Now why is this important to determine why the red bean is suspicious? Well now we know how the game works, now we must analyze the red beans actions. At the beginning of the game, the red bean tells us “shh” while having his index finger in front of where his mouth should be. This action suggests that the red bean wants us to be quiet, or keep our mouths shut. Now why would the red bean want us to do this? This could be because the red bean wants to limit our communication in order to prevent us from spreading information. What information does the red bean want to prevent from spreading? We can assume that the reason why the red bean wants to prevent us from spreading information, is because he is actually the imposter, and he is planning on committing the crimes mentioned earlier. He does not want others to find out about actions he will cause, therefore he does not want us to communicate with each other. This concludes the reason for why I believe the red bean from the hit game Among Us is suspicious. So if you happen to see a red bean-shaped character wearing a spacesuit, please be careful.';
return (
<div className={style.container}>
<div className={style.info}>BREAKING</div>
<div className={style.marquee}>
<Marquee>{pasta}</Marquee>
<div className={style.taskbar}>
<div className={style.startButton}>
<img
className={style.startIcon}
src="src/assets/weed.png"
alt="4:20"
/>
Start
</div>
<span className={style.divider}></span>
<div className={style.windows}>
<span className={style.window}>🚊 Timetable</span>
<span className={style.window}>🕐 Clock</span>
<span className={style.windowActive}>🔔 Terminal</span>
<span className={style.window}>🧹 Flatastic</span>
</div>
</div>
</div>
);

View File

@@ -0,0 +1,9 @@
const pasta = [
'Ok, so I was playing the hit game Among Us the other day, and when the game started, a red bean-shaped character that appeared to be wearing a spacesuit told me "shh," while having his index finger in front of where his mouth should be. I believe this act made this red bean character extremely suspicious. To understand why this red bean character is suspicious, we first must understand how the game “Among Us” works. The game consists of 10 bean-shaped characters, called crewmates, that are given tasks for them to complete. As these characters do their tasks, they may witness abnormal things that are not supposed to happen, such as the lights turning off on their own, sudden reactor meltdown and other crewmates dying. These acts show that there is an imposter among the crewmates that is sabotaging and is trying to kill everyone. Now why is this important to determine why the red bean is suspicious? Well now we know how the game works, now we must analyze the red beans actions. At the beginning of the game, the red bean tells us “shh” while having his index finger in front of where his mouth should be. This action suggests that the red bean wants us to be quiet, or keep our mouths shut. Now why would the red bean want us to do this? This could be because the red bean wants to limit our communication in order to prevent us from spreading information. What information does the red bean want to prevent from spreading? We can assume that the reason why the red bean wants to prevent us from spreading information, is because he is actually the imposter, and he is planning on committing the crimes mentioned earlier. He does not want others to find out about actions he will cause, therefore he does not want us to communicate with each other. This concludes the reason for why I believe the red bean from the hit game Among Us is suspicious. So if you happen to see a red bean-shaped character wearing a spacesuit, please be careful.',
"「真正的Emo」只有DC的硬核emo和90年代末 的 Screamo 局,而所謂的「中西部 Emo」 不過 是被真正 Emo所影響的另類搖滾罷了。每次聽到 有人說什麼 My Chemical Romance 不是正宗的 Emo,但又覺得 Sunny Day Real Estate 是的時 候,我他媽就覺得有夠智障,因為他們和 My Chemical Romance 其實一樣都是假 Emo(只能 說他們是被emo影響的樂團)。真正的 Emo 應該 聽起來很強烈、甚至帶著一些憤怒的活力!假 Emo則軟弱、自卑,那些廢物把精力和情感錯誤 地注入音樂當中,只能說他們所謂的emo是失敗 的嘗試罷了。一些真正的Emo 團包括Pg 99、 Rites of Spring、Cap'n Jazz(絕對是中西部樂 團圈裡面唯一真正的 Emo 樂團)和Loma Prieta。那些假的 Emo 團像是 American Football My Chemical Romance Mineral... 等。EMO就該屬於硬核,不是獨立曲風、更別說 是流行龐克、另類搖滾、或是他媽的任何其他主 流類型!!!",
'"YoU tOucHEd ThAt BlOcK So yOu HavE tO PuLl tHat OnE Out" Go fuck yourself, that\'s not how the fucking game is played, you dumb, the fuck, asshole. Quoted from the official Jenga rules: "Players may tap a block to find a loose one. Any blocks moved but not played should be replaced, unless doing so would make the tower fall." You\'ve never even fucking read the rules have you, you shithead idiot. What, is the game over in 3 seconds, if you just so happen to touch a load bearing block first? FUCKING NO DUMBASS. Learn to read you illiterate fuck.',
"I can't fucking take it any more. Among Us has singlehandedly ruined my life. The other day my teacher was teaching us Greek Mythology and he mentioned a pegasus and I immediately thought 'Pegasus? more like Mega Sus!!!!' and I've never wanted to kms more. I can't look at a vent without breaking down and fucking crying. I can't eat pasta without thinking 'IMPASTA??? THATS PRETTY SUS!!!!' Skit 4 by Kanye West. The lyrics ruined me. A Mongoose, or the 25th island of greece. The scientific name for pig. I can't fucking take it anymore. Please fucking end my suffering.",
"What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy Seals, and I've been involved in numerous secret raids on Al-Quaeda, and I have over 300 confirmed kills. I am trained in gorilla warfare and I'm the top sniper in the entire US armed forces. You are nothing to me but just another target. I will wipe you the fuck out with precision the likes of which has never been seen before on this Earth, mark my fucking words. You think you can get away with saying that shit to me over the Internet? Think again, fucker. As we speak I am contacting my secret network of spies across the USA and your IP is being traced right now so you better prepare for the storm, maggot. The storm that wipes out the pathetic little thing you call your life. You're fucking dead, kid. I can be anywhere, anytime, and I can kill you in over seven hundred ways, and that's just with my bare hands. Not only am I extensively trained in unarmed combat, but I have access to the entire arsenal of the United States Marine Corps and I will use it to its full extent to wipe your miserable ass off the face of the continent, you little shit. If only you could have known what unholy retribution your little \"clever\" comment was about to bring down upon you, maybe you would have held your fucking tongue. But you couldn't, you didn't, and now you're paying the price, you goddamn idiot. I will shit fury all over you and you will drown in it. You're fucking dead, kiddo.",
];
export default pasta;

View File

@@ -1,17 +1,69 @@
.container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
height: 35px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 10px;
margin: 2px;
}
.info {
background-color: red;
color: white;
font-weight: bold;
padding: 5px;
.taskbar {
font-weight: bold;
display: flex;
flex-direction: row;
text-align: left;
}
.marquee {
.startButton {
display: inline-flex;
justify-content: space-around;
align-items: center;
width: 100px;
border-top: 2px solid white;
border-left: 2px solid white;
border-bottom: 2px solid #828282;
border-right: 2px solid #828282;
}
.startIcon {
height: 30px;
}
.divider {
margin: auto 8px;
width: 4px;
height: 25px;
border-top: 2px solid white;
border-left: 2px solid white;
border-bottom: 2px solid #828282;
border-right: 2px solid #828282;
}
.windows {
display: flex;
flex-direction: row;
gap: 5px;
}
.window {
min-width: 150px;
padding-left: 10px;
display: inline-flex;
align-items: center;
border-top: 2px solid white;
border-left: 2px solid white;
border-bottom: 2px solid #828282;
border-right: 2px solid #828282;
}
.windowActive {
min-width: 150px;
padding-left: 10px;
display: inline-flex;
align-items: center;
border-bottom: 2px solid white;
border-right: 2px solid white;
border-left: 2px solid #828282;
border-top: 2px solid #828282;
}

View File

@@ -22,7 +22,6 @@ export default function Timetable() {
return (
<div>
<h1>Tent</h1>
<div className={style.cardContainer}>
<div className={style.card}>
<h4>Temperature</h4>

View File

@@ -1,7 +1,8 @@
.cardContainer {
padding-top: 20px;
display: flex;
flex-direction: row;
justify-content: center;
justify-content: flex-start;
align-items: center;
gap: 20px;
}
@@ -12,7 +13,9 @@
display: flex;
flex-direction: column;
align-items: stretch;
border: 1px solid rgba(220, 220, 220, 0.4);
box-shadow: 5px 5px 7px rgba(220, 220, 220, 0.5);
border-radius: 10px;
text-align: center;
border-top: 2px solid white;
border-left: 2px solid white;
border-bottom: 2px solid #828282;
border-right: 2px solid #828282;
}

View File

@@ -0,0 +1,93 @@
import { useHomeAssistantStore } from "@/store/homeAssistant";
import { useEffect, useState } from "react";
import style from "./style.module.css";
import pasta from "./pasta.ts";
export default function Terminal() {
const [index, setIndex] = useState(0);
// random shitpost every minute
useEffect(() => {
const timer = setInterval(() => {
setIndex(Math.floor(Math.random() * pasta.length));
}, 60 * 1000);
return () => {
clearInterval(timer);
};
}, []);
const text = pasta[index];
const fetchHomeAssistantData = useHomeAssistantStore(
(state) => state.fetch,
);
const tentTemperature = useHomeAssistantStore(
(state) => state.tentTemperature,
);
const tentHumidity = useHomeAssistantStore((state) => state.tentHumidity);
useEffect(() => {
fetchHomeAssistantData();
const interval = setInterval(() => {
fetchHomeAssistantData();
}, 60000);
return () => clearInterval(interval);
}, [fetchHomeAssistantData]);
return (
<div className={style.container}>
<div className={style.input}>
<span className={style.prompt}>[sus@home ~/hallway]{"$"}</span>{" "}
tentfetch
</div>
<div className={style.fetch}>
<span>
<pre>
{" "}-///:.{" "}
<span className={style.username}>tent</span>@
<span className={style.hostname}>home</span>
</pre>
</span>
<span>
<pre>
{" "}smhhhhmh\`{" "}os{" "}Arch Linux
</pre>
</span>
<span>
<pre>
{" "}:N{" "}Ns{" "}temp{" "}
<span className={style.temp}>{tentTemperature}°C</span>
</pre>
</span>
<span>
<pre>
{" "}hNNmmmmNNN{" "}humidity{" "}
<span className={style.humidity}>{tentHumidity}%</span>
</pre>
</span>
<span>
<pre>
{" "}NNssussNNN{" "}plants{" "}
<span className={style.plants}>4</span>
</pre>
</span>
<span>
<pre>
{" "}sNn:{" "}sNNo
</pre>
</span>
</div>
<div className={style.input}>
<span className={style.prompt}>[sus@home ~/hallway]{"$"}</span>{" "}
cat msg.txt
</div>
<div className={style.msg}>{text}</div>
<div className={style.input}>
<span className={style.prompt}>
[sus@home ~/hallway]{"$"}
</span>{" "}
</div>
</div>
);
}

View File

@@ -0,0 +1,9 @@
const pasta = [
'Ok, so I was playing the hit game Among Us the other day, and when the game started, a red bean-shaped character that appeared to be wearing a spacesuit told me "shh," while having his index finger in front of where his mouth should be. I believe this act made this red bean character extremely suspicious. To understand why this red bean character is suspicious, we first must understand how the game “Among Us” works. The game consists of 10 bean-shaped characters, called crewmates, that are given tasks for them to complete. As these characters do their tasks, they may witness abnormal things that are not supposed to happen, such as the lights turning off on their own, sudden reactor meltdown and other crewmates dying. These acts show that there is an imposter among the crewmates that is sabotaging and is trying to kill everyone. Now why is this important to determine why the red bean is suspicious? Well now we know how the game works, now we must analyze the red beans actions. At the beginning of the game, the red bean tells us “shh” while having his index finger in front of where his mouth should be. This action suggests that the red bean wants us to be quiet, or keep our mouths shut. Now why would the red bean want us to do this? This could be because the red bean wants to limit our communication in order to prevent us from spreading information. What information does the red bean want to prevent from spreading? We can assume that the reason why the red bean wants to prevent us from spreading information, is because he is actually the imposter, and he is planning on committing the crimes mentioned earlier. He does not want others to find out about actions he will cause, therefore he does not want us to communicate with each other. This concludes the reason for why I believe the red bean from the hit game Among Us is suspicious. So if you happen to see a red bean-shaped character wearing a spacesuit, please be careful.',
"「真正的Emo」只有DC的硬核emo和90年代末 的 Screamo 局,而所謂的「中西部 Emo」 不過 是被真正 Emo所影響的另類搖滾罷了。每次聽到 有人說什麼 My Chemical Romance 不是正宗的 Emo,但又覺得 Sunny Day Real Estate 是的時 候,我他媽就覺得有夠智障,因為他們和 My Chemical Romance 其實一樣都是假 Emo(只能 說他們是被emo影響的樂團)。真正的 Emo 應該 聽起來很強烈、甚至帶著一些憤怒的活力!假 Emo則軟弱、自卑,那些廢物把精力和情感錯誤 地注入音樂當中,只能說他們所謂的emo是失敗 的嘗試罷了。一些真正的Emo 團包括Pg 99、 Rites of Spring、Cap'n Jazz(絕對是中西部樂 團圈裡面唯一真正的 Emo 樂團)和Loma Prieta。那些假的 Emo 團像是 American Football My Chemical Romance Mineral... 等。EMO就該屬於硬核,不是獨立曲風、更別說 是流行龐克、另類搖滾、或是他媽的任何其他主 流類型!!!",
'"YoU tOucHEd ThAt BlOcK So yOu HavE tO PuLl tHat OnE Out" Go fuck yourself, that\'s not how the fucking game is played, you dumb, the fuck, asshole. Quoted from the official Jenga rules: "Players may tap a block to find a loose one. Any blocks moved but not played should be replaced, unless doing so would make the tower fall." You\'ve never even fucking read the rules have you, you shithead idiot. What, is the game over in 3 seconds, if you just so happen to touch a load bearing block first? FUCKING NO DUMBASS. Learn to read you illiterate fuck.',
"I can't fucking take it any more. Among Us has singlehandedly ruined my life. The other day my teacher was teaching us Greek Mythology and he mentioned a pegasus and I immediately thought 'Pegasus? more like Mega Sus!!!!' and I've never wanted to kms more. I can't look at a vent without breaking down and fucking crying. I can't eat pasta without thinking 'IMPASTA??? THATS PRETTY SUS!!!!' Skit 4 by Kanye West. The lyrics ruined me. A Mongoose, or the 25th island of greece. The scientific name for pig. I can't fucking take it anymore. Please fucking end my suffering.",
"What the fuck did you just fucking say about me, you little bitch? I'll have you know I graduated top of my class in the Navy Seals, and I've been involved in numerous secret raids on Al-Quaeda, and I have over 300 confirmed kills. I am trained in gorilla warfare and I'm the top sniper in the entire US armed forces. You are nothing to me but just another target. I will wipe you the fuck out with precision the likes of which has never been seen before on this Earth, mark my fucking words. You think you can get away with saying that shit to me over the Internet? Think again, fucker. As we speak I am contacting my secret network of spies across the USA and your IP is being traced right now so you better prepare for the storm, maggot. The storm that wipes out the pathetic little thing you call your life. You're fucking dead, kid. I can be anywhere, anytime, and I can kill you in over seven hundred ways, and that's just with my bare hands. Not only am I extensively trained in unarmed combat, but I have access to the entire arsenal of the United States Marine Corps and I will use it to its full extent to wipe your miserable ass off the face of the continent, you little shit. If only you could have known what unholy retribution your little \"clever\" comment was about to bring down upon you, maybe you would have held your fucking tongue. But you couldn't, you didn't, and now you're paying the price, you goddamn idiot. I will shit fury all over you and you will drown in it. You're fucking dead, kiddo.",
];
export default pasta;

View File

@@ -0,0 +1,49 @@
.container {
display: flex;
flex-direction: column;
font-family: monospace;
font-size: 10pt;
background-color: black;
color: white;
display: flex;
flex-direction: column;
width: 100%;
height: 290px;
overflow: hidden;
}
.fetch {
display: flex;
flex-direction: column;
padding-bottom: 10px;
}
.prompt {
color: lightgreen;
}
.username {
color: violet;
}
.hostname {
color: skyblue;
}
.temp {
color: pink;
font-weight: bold;
}
.humidity {
color: skyblue;
font-weight: bold;
}
.plants {
color: lightgreen;
}
pre {
margin: 0;
}

View File

@@ -19,9 +19,15 @@ export default function Timetable() {
return (
<div className={style.wrapper}>
<h1>Timetable 🚉</h1>
<DepartureList departures={pStreet.departureList} name="P-Street" />
<DepartureList departures={hStreet.departureList} name="H-Street" />
<h1>Departures</h1>
<DepartureList
departures={pStreet.departureList}
name="Philippstraße"
/>
<DepartureList
departures={hStreet.departureList}
name="Händelstraße"
/>
</div>
);
}

View File

@@ -29,15 +29,12 @@ const lineNumberToStyle = (number) => {
switch (number) {
case "S2":
return styles.S2;
break;
case "S5":
case "S51":
return styles.S5;
break;
case "S1":
case "S11":
return styles.S1;
break;
default:
return styles.lineNumberDefault;
}

View File

@@ -1,11 +1,11 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
font-family: Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
color: #213547;
background-color: #ffffff;
font-synthesis: none;
text-rendering: optimizeLegibility;
@@ -18,9 +18,6 @@ a {
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
@@ -42,7 +39,7 @@ button {
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
background-color: #f9f9f9;
cursor: pointer;
transition: border-color 0.25s;
}
@@ -53,16 +50,3 @@ button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}

View File

@@ -1,12 +1,23 @@
import { create } from "zustand";
import { devtools } from "zustand/middleware";
import Flatastic from "@/api/flatastic";
import type { FlatasticChore } from "@/types/flatasticChore";
import type { FlatasticChore, FlatasticUser } from "@/types/flatasticChore";
// biome-ignore format: deep
function parseInformationData(data): FlatasticUser[] {
return data.flatmates.map((user: { id: string; firstName: string }) => ({
id: user.id as string,
firstName: user.firstName as string,
}));
}
const useFlatasticStore = create(
devtools(
(set) => ({
chores: [],
flatasticData: {
chores: [] as FlatasticChore[],
users: [] as FlatasticUser[],
},
fetch: async () => {
if (!import.meta.env.VITE_FLATTASTIC_API_KEY) {
throw new Error("Flatastic API Key is not set");
@@ -15,9 +26,14 @@ const useFlatasticStore = create(
import.meta.env.VITE_FLATTASTIC_API_KEY,
);
const data = await flatastic.getTaskList();
const dataB = await flatastic.getInformation();
console.log("Flatastic chores fetched:", data);
set({ chores: data as FlatasticChore[] });
set({
flatasticData: {
chores: data as FlatasticChore[],
users: parseInformationData(dataB),
},
});
},
}),
{

View File

@@ -16,11 +16,6 @@ const useKVVStore = create(
const hStreetJson = await hStreetData.json();
const pStreetJson = await pStreetData.json();
console.log("KVV departures fetched:", {
hStreet: hStreetJson,
pStreet: pStreetJson,
});
set({
hStreet: hStreetJson as DepartureType[],
pStreet: pStreetJson as DepartureType[],

View File

@@ -1,3 +1,13 @@
interface Flatastic {
users: Array<FlatasticUser>;
chores: Array<FlatasticChore>;
}
interface FlatasticUser {
id: number;
firstName: string;
}
interface FlatasticChore {
id: number;
title: string;
@@ -10,4 +20,4 @@ interface FlatasticChore {
timeLeftNext: number;
}
export type { FlatasticChore };
export type { Flatastic, FlatasticChore, FlatasticUser };