Compare commits
110 Commits
ui-update
...
5cb323a1d2
| Author | SHA1 | Date | |
|---|---|---|---|
| 5cb323a1d2 | |||
| 8314c4daf8 | |||
| cb4ce4188e | |||
| 53db7e795c | |||
| 3606f9b043 | |||
| 5da900c1c3 | |||
| 8653365af1 | |||
| d914f47640 | |||
| 668d260fd6 | |||
| 03dc0984f3 | |||
| 0d94904c50 | |||
| 2c805ab513 | |||
| 2b21310dfb | |||
| 756e709181 | |||
| 9872b64fbd | |||
| b6c9c51ec3 | |||
| 83f872577b | |||
| 2b489e83ba | |||
| 6189b80ff1 | |||
| df2999516d | |||
| 24f0aa75d5 | |||
| 8f9b2c6abe | |||
| 6595a7f8bb | |||
| c95c9de320 | |||
| f7de45e47c | |||
| 7556d0496a | |||
| 7d68cce351 | |||
| e051a1aa4f | |||
| e9ef31e7ee | |||
| bba4960276 | |||
| b1d639f618 | |||
| 1702e0c408 | |||
| f87d3da269 | |||
| bbee38af9b | |||
| 55d86dbcaf | |||
| e366379a57 | |||
| 19aa35195f | |||
| 4926c3b388 | |||
| 45a02b84f2 | |||
| 7face12353 | |||
| 59dc667c15 | |||
| 0347ead200 | |||
| 9dbb0d6b4d | |||
| 6c0e427d0e | |||
| 5f5f0c46fe | |||
| 12f1e0ed69 | |||
| 01081dd26d | |||
| 9d4b5db64d | |||
| 795df16f5b | |||
| 55af06c573 | |||
| a05eb48143 | |||
| 86e301cad6 | |||
| ae4b2b6179 | |||
| 8ffb850322 | |||
| 9d0d458fe3 | |||
| 2cb457c776 | |||
| 18bf7cb6ce | |||
| a625aee9d8 | |||
| 65fa8badd8 | |||
| a18a1bfc9b | |||
| aeb8e836eb | |||
| 8126813216 | |||
| 8d644dbd35 | |||
| 94b4c24bb3 | |||
| c80e5e9f07 | |||
| a40f8c875d | |||
| 085cbe3363 | |||
| 042cbe97c5 | |||
| abb4db5864 | |||
| 466b73652c | |||
| 63322a2c56 | |||
| 3a5e5ab2b9 | |||
| 20bf530f18 | |||
| 69f46059e0 | |||
| b27be08b87 | |||
| 66ad042bf1 | |||
| 79117da969 | |||
| afde557605 | |||
| 6dfa50f3ed | |||
| a4fe525b9e | |||
| d37dbc4f62 | |||
| c7b03a93f0 | |||
| c924ab893e | |||
| 05c62ed6bb | |||
| 481f103ba0 | |||
| aa8da0bb56 | |||
| 42419e2e3b | |||
| b1adbe74de | |||
| 241db688c2 | |||
| 7f12cb59d8 | |||
| 0cb9867bc7 | |||
| 97d9a04b29 | |||
| 97737def29 | |||
| ef67915621 | |||
| 040178e395 | |||
| 01f6a07672 | |||
| 10508f5ecd | |||
| 5fe9376533 | |||
| f694284c29 | |||
| 414b59ddb1 | |||
| 63f906bb02 | |||
| 194b0b58ed | |||
| 9b17385043 | |||
| 7875b8edef | |||
| e17829e7fa | |||
| 1e634ed122 | |||
| 16131521bc | |||
| 741720ed07 | |||
| 4ae7e7968b | |||
| 90e6842d71 |
55
.devcontainer/devcontainer.json
Normal file
55
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +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"
|
||||||
|
},
|
||||||
|
"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": {}
|
||||||
|
}
|
||||||
81
.gitea/workflows/ci.yml
Normal file
81
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: oven/bun
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Install dependencies
|
||||||
|
run: bun install
|
||||||
|
- name: Build
|
||||||
|
run: bun run build
|
||||||
|
- name: Write Git-Hash into html
|
||||||
|
run: ./pipeline/create-git-hash-html.sh
|
||||||
|
- name: Create Build Artifact
|
||||||
|
uses: christopherhx/gitea-upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
container:
|
||||||
|
image: oven/bun
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup Biome
|
||||||
|
uses: biomejs/setup-biome@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
token: ${{ secrets.BIOME_TOKEN }}
|
||||||
|
- name: Run Biome
|
||||||
|
run: biome ci .
|
||||||
|
|
||||||
|
create-and-publish-docker-image:
|
||||||
|
needs: [lint]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: christopherhx/gitea-download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: dist
|
||||||
|
- 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
|
||||||
|
git clean -dfx
|
||||||
|
git reset --hard HEAD
|
||||||
|
git pull
|
||||||
|
docker-compose pull
|
||||||
|
docker-compose down
|
||||||
|
docker-compose up -d
|
||||||
|
export HYPRLAND_INSTANCE_SIGNATURE=$(hyprctl instances -j | jq '.[0].instance' | tr -d "'\"")
|
||||||
|
hyprctl dispatch exec 'pkill firefox'
|
||||||
|
hyprctl dispatch exec 'firefox -kiosk localhost:9123'
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
# Ignore artifacts:
|
|
||||||
build
|
|
||||||
coverage
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"tabWidth": 4,
|
|
||||||
"useTabs": false
|
|
||||||
}
|
|
||||||
20
Dockerfile
20
Dockerfile
@@ -1,15 +1,5 @@
|
|||||||
FROM oven/bun
|
FROM nginx:alpine
|
||||||
|
COPY ./dist /usr/share/nginx/html
|
||||||
WORKDIR /app
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
EXPOSE 80
|
||||||
COPY package.json bun.lock ./
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|
||||||
RUN bun install
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN bun run build
|
|
||||||
|
|
||||||
EXPOSE 5173
|
|
||||||
|
|
||||||
CMD ["bun", "run", "dev", "--host"]
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://biomejs.dev/schemas/2.1.2/schema.json",
|
|
||||||
"vcs": {
|
"vcs": {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"clientKind": "git",
|
"clientKind": "git",
|
||||||
"useIgnoreFile": false
|
"useIgnoreFile": false
|
||||||
},
|
},
|
||||||
"files": {
|
"files": {
|
||||||
"ignoreUnknown": false
|
"ignoreUnknown": false,
|
||||||
|
"includes": ["**", "!**/dist"]
|
||||||
},
|
},
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
|
|||||||
34
bun.lock
34
bun.lock
@@ -7,9 +7,12 @@
|
|||||||
"@reduxjs/toolkit": "^2.8.2",
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"@types/lodash": "^4.17.20",
|
"@types/lodash": "^4.17.20",
|
||||||
"@types/node": "^24.1.0",
|
"@types/node": "^24.1.0",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"openmeteo": "^1.2.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-fast-marquee": "^1.6.5",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"zustand": "^5.0.6",
|
"zustand": "^5.0.6",
|
||||||
@@ -34,6 +37,7 @@
|
|||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.35.1",
|
"typescript-eslint": "^8.35.1",
|
||||||
"vite": "^7.0.4",
|
"vite": "^7.0.4",
|
||||||
|
"vite-plugin-mkcert": "^1.17.8",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -140,6 +144,8 @@
|
|||||||
|
|
||||||
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
|
||||||
|
|
||||||
|
"@openmeteo/sdk": ["@openmeteo/sdk@1.20.1", "", { "dependencies": { "flatbuffers": "^25.2.10" } }, "sha512-o5tw3+N617Ms8nDm649PWwWt6PDz8NHWBLjOOFB8bx/EJpvsvKEeHMMoapxQ71bjHzQM+4h39eCe6/nM+nBuwg=="],
|
||||||
|
|
||||||
"@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
|
"@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
|
||||||
|
|
||||||
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.8.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^10.0.3", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A=="],
|
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.8.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^10.0.3", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-MYlOhQ0sLdw4ud48FoC5w0dH9VfWQjtCjreKwYTT3l+r427qYC5Y8PihNutepr8XrNaBUDQo9khWUwQxZaqt5A=="],
|
||||||
@@ -288,10 +294,14 @@
|
|||||||
|
|
||||||
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
|
||||||
|
|
||||||
|
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
|
||||||
|
|
||||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||||
|
|
||||||
"axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="],
|
"axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="],
|
||||||
|
|
||||||
|
"axios": ["axios@1.11.0", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA=="],
|
||||||
|
|
||||||
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
|
||||||
|
|
||||||
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
|
||||||
@@ -310,10 +320,14 @@
|
|||||||
|
|
||||||
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
|
||||||
|
|
||||||
|
"classnames": ["classnames@2.5.1", "", {}, "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow=="],
|
||||||
|
|
||||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||||
|
|
||||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||||
|
|
||||||
|
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
|
||||||
|
|
||||||
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
|
||||||
|
|
||||||
"confusing-browser-globals": ["confusing-browser-globals@1.0.11", "", {}, "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA=="],
|
"confusing-browser-globals": ["confusing-browser-globals@1.0.11", "", {}, "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA=="],
|
||||||
@@ -338,6 +352,8 @@
|
|||||||
|
|
||||||
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
|
||||||
|
|
||||||
|
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
|
||||||
|
|
||||||
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
||||||
|
|
||||||
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
|
||||||
@@ -424,10 +440,16 @@
|
|||||||
|
|
||||||
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
|
||||||
|
|
||||||
|
"flatbuffers": ["flatbuffers@25.2.10", "", {}, "sha512-7JlN9ZvLDG1McO3kbX0k4v+SUAg48L1rIwEvN6ZQl/eCtgJz9UylTMzE9wrmYrcorgxm3CX/3T/w5VAub99UUw=="],
|
||||||
|
|
||||||
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
|
||||||
|
|
||||||
|
"follow-redirects": ["follow-redirects@1.15.11", "", {}, "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="],
|
||||||
|
|
||||||
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
"for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||||
|
|
||||||
|
"form-data": ["form-data@4.0.4", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow=="],
|
||||||
|
|
||||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||||
|
|
||||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||||
@@ -568,6 +590,10 @@
|
|||||||
|
|
||||||
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
|
||||||
|
|
||||||
|
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
|
||||||
|
|
||||||
|
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
|
||||||
|
|
||||||
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
|
||||||
|
|
||||||
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
|
||||||
@@ -594,6 +620,8 @@
|
|||||||
|
|
||||||
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
|
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
|
||||||
|
|
||||||
|
"openmeteo": ["openmeteo@1.2.0", "", { "dependencies": { "@openmeteo/sdk": "^1.19.0", "flatbuffers": "^25.2.10" } }, "sha512-YinFo02TM4wXdm9o2FBAO2u1ka3drNdnFsGNskiO8aCWvZa6nljh3ioH79ipwPdFhCrIiq/LCfpjDGXqH2RBFw=="],
|
||||||
|
|
||||||
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||||
|
|
||||||
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
|
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
|
||||||
@@ -626,6 +654,8 @@
|
|||||||
|
|
||||||
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
|
||||||
|
|
||||||
|
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
|
||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
|
||||||
@@ -634,6 +664,8 @@
|
|||||||
|
|
||||||
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
|
"react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="],
|
||||||
|
|
||||||
|
"react-fast-marquee": ["react-fast-marquee@1.6.5", "", { "peerDependencies": { "react": ">= 16.8.0 || ^18.0.0", "react-dom": ">= 16.8.0 || ^18.0.0" } }, "sha512-swDnPqrT2XISAih0o74zQVE2wQJFMvkx+9VZXYYNSLb/CUcAzU9pNj637Ar2+hyRw6b4tP6xh4GQZip2ZCpQpg=="],
|
||||||
|
|
||||||
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||||
|
|
||||||
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
|
"react-redux": ["react-redux@9.2.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
|
||||||
@@ -746,6 +778,8 @@
|
|||||||
|
|
||||||
"vite": ["vite@7.0.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw=="],
|
"vite": ["vite@7.0.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", "picomatch": "^4.0.2", "postcss": "^8.5.6", "rollup": "^4.40.0", "tinyglobby": "^0.2.14" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-1mncVwJxy2C9ThLwz0+2GKZyEXuC3MyWtAAlNftlZZXZDP3AJt5FmwcMit/IGGaNZ8ZOB2BNO/HFUB+CpN0NQw=="],
|
||||||
|
|
||||||
|
"vite-plugin-mkcert": ["vite-plugin-mkcert@1.17.8", "", { "dependencies": { "axios": "^1.8.3", "debug": "^4.4.0", "picocolors": "^1.1.1" }, "peerDependencies": { "vite": ">=3" } }, "sha512-S+4tNEyGqdZQ3RLAG54ETeO2qyURHWrVjUWKYikLAbmhh/iJ+36gDEja4OWwFyXNuvyXcZwNt5TZZR9itPeG5Q=="],
|
||||||
|
|
||||||
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||||
|
|
||||||
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
"which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
monitor-im-flur:
|
monitor-im-flur:
|
||||||
build: .
|
image: git.rivercry.com/wg/monitor-im-flur:latest
|
||||||
ports:
|
ports:
|
||||||
- "9123:5173"
|
- "9123:80"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import js from "@eslint/js";
|
import js from "@eslint/js";
|
||||||
import globals from "globals";
|
import { globalIgnores } from "eslint/config";
|
||||||
import reactHooks from "eslint-plugin-react-hooks";
|
import reactHooks from "eslint-plugin-react-hooks";
|
||||||
import reactRefresh from "eslint-plugin-react-refresh";
|
import reactRefresh from "eslint-plugin-react-refresh";
|
||||||
|
import globals from "globals";
|
||||||
import tseslint from "typescript-eslint";
|
import tseslint from "typescript-eslint";
|
||||||
import { globalIgnores } from "eslint/config";
|
|
||||||
|
|
||||||
export default tseslint.config([
|
export default tseslint.config([
|
||||||
globalIgnores(["dist"]),
|
globalIgnores(["dist"]),
|
||||||
|
|||||||
12
githooks/pre-commit
Executable file
12
githooks/pre-commit
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "Running pre-commit hooks..."
|
||||||
|
|
||||||
|
biome check --write
|
||||||
|
biome lint --write
|
||||||
|
|
||||||
|
if git diff --quiet; then
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
git add -u
|
||||||
|
fi
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
|
|||||||
27
nginx.conf
Normal file
27
nginx.conf
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
worker_processes 1;
|
||||||
|
|
||||||
|
events { worker_connections 1024; }
|
||||||
|
|
||||||
|
http {
|
||||||
|
include mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
sendfile on;
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html /git-hash.js;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||||
|
expires 1y;
|
||||||
|
log_not_found off;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,9 +13,12 @@
|
|||||||
"@reduxjs/toolkit": "^2.8.2",
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"@types/lodash": "^4.17.20",
|
"@types/lodash": "^4.17.20",
|
||||||
"@types/node": "^24.1.0",
|
"@types/node": "^24.1.0",
|
||||||
|
"classnames": "^2.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
|
"openmeteo": "^1.2.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-fast-marquee": "^1.6.5",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"zustand": "^5.0.6"
|
"zustand": "^5.0.6"
|
||||||
@@ -39,6 +42,7 @@
|
|||||||
"prettier": "3.6.2",
|
"prettier": "3.6.2",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "~5.8.3",
|
||||||
"typescript-eslint": "^8.35.1",
|
"typescript-eslint": "^8.35.1",
|
||||||
"vite": "^7.0.4"
|
"vite": "^7.0.4",
|
||||||
|
"vite-plugin-mkcert": "^1.17.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
11
pipeline/create-git-hash-html.sh
Executable file
11
pipeline/create-git-hash-html.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Git Hash</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<pre>GITHUB_SHA = '$GITHUB_SHA';</pre>
|
||||||
|
</body>
|
||||||
|
</html>" > dist/git-hash.html
|
||||||
BIN
public/img/amogus.png
Normal file
BIN
public/img/amogus.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
BIN
public/img/clock.png
Normal file
BIN
public/img/clock.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/img/imposter.png
Normal file
BIN
public/img/imposter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 MiB |
BIN
public/img/weed.png
Normal file
BIN
public/img/weed.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.5 KiB |
3
setup.sh
Executable file
3
setup.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
git config --local core.hooksPath githooks
|
||||||
41
src/App.css
41
src/App.css
@@ -1,42 +1,7 @@
|
|||||||
#root {
|
#root {
|
||||||
|
height: 100vh;
|
||||||
max-width: 1280px;
|
max-width: 1280px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
padding: 0;
|
||||||
text-align: center;
|
text-align: left;
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 6em;
|
|
||||||
padding: 1.5em;
|
|
||||||
will-change: filter;
|
|
||||||
transition: filter 300ms;
|
|
||||||
}
|
|
||||||
.logo:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #646cffaa);
|
|
||||||
}
|
|
||||||
.logo.react:hover {
|
|
||||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: no-preference) {
|
|
||||||
a:nth-of-type(2) .logo {
|
|
||||||
animation: logo-spin infinite 20s linear;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
padding: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.read-the-docs {
|
|
||||||
color: #888;
|
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/App.tsx
13
src/App.tsx
@@ -1,16 +1,9 @@
|
|||||||
import "@/App.css";
|
import "@/App.css";
|
||||||
import Flatastic from "@/components/Flatastic/Flatastic";
|
|
||||||
import HomeAssistant from "@/components/HomeAssistant/HomeAssistant";
|
import Dashboard from "@/components/Dashboard/Dashboard";
|
||||||
import Timetable from "@/components/Timetable/Timetable";
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return <Dashboard />;
|
||||||
<>
|
|
||||||
<Timetable />
|
|
||||||
<Flatastic />
|
|
||||||
<HomeAssistant />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
const token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkMjQ1OTg0YjliYzE0OTNjYTdmZDJmNTA3ODgzN2U1YSIsImlhdCI6MTc1MzQwMjAzNiwiZXhwIjoyMDY4NzYyMDM2fQ.fnLSFKPdk8lkAEB-4ekdGUJ1PSCBxcAyasQF1PyrD3k";
|
const token =
|
||||||
|
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI3N2JmOTk1ODI3MzA0ZWIzOWYwNThjMzQ4YTY3ZDJkYyIsImlhdCI6MTc1NjQ3NTM4OSwiZXhwIjoyMDcxODM1Mzg5fQ.TZZ4SUGlERuIVrhzC_wfCN-qS1wSAKNN9uMMDjkqOgA";
|
||||||
|
|
||||||
async function fetchTentHumidity() {
|
async function fetchTentHumidity() {
|
||||||
const url = "/api/states/sensor.third_reality_inc_3rths0224z_luftfeuchtigkeit_2";
|
const url = `https://home.rivercry.com/api/states/sensor.third_reality_inc_3rths0224z_luftfeuchtigkeit_2`;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
@@ -14,12 +15,12 @@ async function fetchTentHumidity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchTentTemperature() {
|
async function fetchTentTemperature() {
|
||||||
const url = "/api/states/sensor.third_reality_inc_3rths0224z_temperatur_2";
|
const url = `https://home.rivercry.com/api/states/sensor.third_reality_inc_3rths0224z_temperatur_2`;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
"Authorization": `Bearer ${token}`,
|
Authorization: `Bearer ${token}`,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
@@ -27,4 +28,4 @@ async function fetchTentTemperature() {
|
|||||||
return data.state;
|
return data.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { fetchTentHumidity, fetchTentTemperature };
|
export { fetchTentHumidity, fetchTentTemperature };
|
||||||
|
|||||||
34
src/api/weather.ts
Normal file
34
src/api/weather.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { fetchWeatherApi } from "openmeteo";
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
latitude: 49.0094,
|
||||||
|
longitude: 8.4044,
|
||||||
|
daily: ["temperature_2m_max", "temperature_2m_min"],
|
||||||
|
hourly: [
|
||||||
|
"temperature_2m",
|
||||||
|
"precipitation",
|
||||||
|
"rain",
|
||||||
|
"precipitation_probability",
|
||||||
|
],
|
||||||
|
current: [
|
||||||
|
"temperature_2m",
|
||||||
|
"precipitation",
|
||||||
|
"rain",
|
||||||
|
"showers",
|
||||||
|
"snowfall",
|
||||||
|
"relative_humidity_2m",
|
||||||
|
"apparent_temperature",
|
||||||
|
"weather_code",
|
||||||
|
"cloud_cover",
|
||||||
|
"is_day",
|
||||||
|
],
|
||||||
|
timezone: "Europe/Berlin",
|
||||||
|
timeformat: "unixtime",
|
||||||
|
};
|
||||||
|
const url = "https://api.open-meteo.com/v1/forecast";
|
||||||
|
|
||||||
|
function fetchWeatherData() {
|
||||||
|
return fetchWeatherApi(url, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default fetchWeatherData;
|
||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
|
||||||
|
Before Width: | Height: | Size: 4.0 KiB |
132
src/components/Amogus/Amogus.tsx
Normal file
132
src/components/Amogus/Amogus.tsx
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import amogus from "/img/amogus.png";
|
||||||
|
import imposter from "/img/imposter.png";
|
||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
|
type Amogus = {
|
||||||
|
isImposter: boolean;
|
||||||
|
posX: number;
|
||||||
|
posY: number;
|
||||||
|
speedX: number;
|
||||||
|
speedY: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const amogusWidth = 70;
|
||||||
|
const amogusHeight = 70;
|
||||||
|
|
||||||
|
const { innerWidth: width, innerHeight: height } = window;
|
||||||
|
|
||||||
|
const getImage = (sus: Amogus) => (sus.isImposter ? imposter : amogus);
|
||||||
|
|
||||||
|
const makeInitialCrewmates = (): Amogus[] => {
|
||||||
|
return [
|
||||||
|
makeCrewmate(true),
|
||||||
|
makeCrewmate(false),
|
||||||
|
makeCrewmate(false),
|
||||||
|
makeCrewmate(false),
|
||||||
|
makeCrewmate(false),
|
||||||
|
makeCrewmate(false),
|
||||||
|
makeCrewmate(false),
|
||||||
|
makeCrewmate(false),
|
||||||
|
makeCrewmate(false),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const randNum = (min: number, max: number): number =>
|
||||||
|
Math.random() * (max - min) + min;
|
||||||
|
|
||||||
|
const makeCrewmate = (imposter: boolean): Amogus => ({
|
||||||
|
isImposter: imposter,
|
||||||
|
posX: randNum(0, width - amogusWidth),
|
||||||
|
posY: randNum(0, height - amogusHeight),
|
||||||
|
speedX: Math.random() > 0.5 ? randNum(3, 10) : randNum(-3, -10),
|
||||||
|
speedY: Math.random() > 0.5 ? randNum(3, 10) : randNum(-3, -10),
|
||||||
|
});
|
||||||
|
|
||||||
|
const intersect = (c1: Amogus, c2: Amogus): boolean =>
|
||||||
|
Math.abs(c1.posX - c2.posX) < amogusWidth - 20 &&
|
||||||
|
Math.abs(c1.posY - c2.posY) < amogusHeight - 20;
|
||||||
|
|
||||||
|
const doMove = (crewmates: Amogus[]) =>
|
||||||
|
crewmates.map((c) => ({
|
||||||
|
...c,
|
||||||
|
posX: c.posX + c.speedX,
|
||||||
|
posY: c.posY + c.speedY,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const doCollisionWalls = (crewmates: Amogus[]) =>
|
||||||
|
crewmates.map((c) => {
|
||||||
|
if (c.posX > width - amogusWidth) {
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
posX: width - amogusWidth,
|
||||||
|
speedX: c.speedX * -1,
|
||||||
|
};
|
||||||
|
} else if (c.posX < -20) {
|
||||||
|
return { ...c, posX: -20, speedX: c.speedX * -1 };
|
||||||
|
} else if (c.posY > height - amogusHeight) {
|
||||||
|
return {
|
||||||
|
...c,
|
||||||
|
posY: height - amogusHeight,
|
||||||
|
speedY: c.speedY * -1,
|
||||||
|
};
|
||||||
|
} else if (c.posY < -20) {
|
||||||
|
return { ...c, posY: -20, speedY: c.speedY * -1 };
|
||||||
|
} else return c;
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: This is ugly
|
||||||
|
const doCollisionOthers = (crewmates: Amogus[]) =>
|
||||||
|
crewmates.map((c) => {
|
||||||
|
const intersections = crewmates
|
||||||
|
.filter((o) => o !== c) // don't collide with yourself pls
|
||||||
|
.filter((o) => intersect(c, o));
|
||||||
|
|
||||||
|
if (intersections.some((o) => Math.abs(o.posX - c.posX) < 50)) {
|
||||||
|
return { ...c, speedX: c.speedX * -1 };
|
||||||
|
}
|
||||||
|
if (intersections.some((o) => Math.abs(o.posY - c.posY) < 50)) {
|
||||||
|
return { ...c, speedY: c.speedY * -1 };
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
});
|
||||||
|
|
||||||
|
const doKills = (crewmates: Amogus[]) => {
|
||||||
|
const imposters = crewmates.filter((c) => c.isImposter);
|
||||||
|
const alive = crewmates
|
||||||
|
.filter((c) => !c.isImposter)
|
||||||
|
.filter((c) => imposters.every((i) => !intersect(i, c)));
|
||||||
|
return imposters.concat(alive);
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkReset = (crewmates: Amogus[]) =>
|
||||||
|
crewmates.every((c) => c.isImposter) ? makeInitialCrewmates() : crewmates;
|
||||||
|
|
||||||
|
const renderCrewmate = (crewmate: Amogus, key: number) => (
|
||||||
|
<div
|
||||||
|
key={key}
|
||||||
|
className={style.container}
|
||||||
|
style={{ top: crewmate.posY, left: crewmate.posX }}
|
||||||
|
>
|
||||||
|
<img src={getImage(crewmate)} alt="Amogus" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function Amogus() {
|
||||||
|
const [crewmates, setCrewmates] = useState(() => makeInitialCrewmates());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setCrewmates((c) => doMove(c));
|
||||||
|
setCrewmates((c) => doCollisionWalls(c));
|
||||||
|
setCrewmates((c) => doCollisionOthers(c));
|
||||||
|
setCrewmates((c) => doKills(c));
|
||||||
|
setCrewmates((c) => checkReset(c));
|
||||||
|
}, 50);
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <>{crewmates.map((c, index) => renderCrewmate(c, index))}</>;
|
||||||
|
}
|
||||||
8
src/components/Amogus/style.module.css
Normal file
8
src/components/Amogus/style.module.css
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.container {
|
||||||
|
z-index: 100;
|
||||||
|
position: absolute;
|
||||||
|
scale: 50%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
26
src/components/Card/Card.tsx
Normal file
26
src/components/Card/Card.tsx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import CardHeader from "@/components/CardHeader/CardHeader";
|
||||||
|
|
||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
|
export default function Card({
|
||||||
|
icon,
|
||||||
|
name,
|
||||||
|
children,
|
||||||
|
active = false,
|
||||||
|
header = true,
|
||||||
|
}: {
|
||||||
|
icon: string;
|
||||||
|
name: string;
|
||||||
|
active?: boolean;
|
||||||
|
header?: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div className={style.card}>
|
||||||
|
{header ? (
|
||||||
|
<CardHeader icon={icon} content={name} active={active} />
|
||||||
|
) : null}
|
||||||
|
<div className={style.cardContent}>{children}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
16
src/components/Card/style.module.css
Normal file
16
src/components/Card/style.module.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
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;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardContent {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
9
src/components/CardContainers/CardContainers.tsx
Normal file
9
src/components/CardContainers/CardContainers.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
|
export function CardRow({ children }) {
|
||||||
|
return <div className={style.cardRow}>{children}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CardColumn({ children }) {
|
||||||
|
return <div className={style.cardColumn}>{children}</div>;
|
||||||
|
}
|
||||||
16
src/components/CardContainers/style.module.css
Normal file
16
src/components/CardContainers/style.module.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
.cardColumn {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cardRow {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
18
src/components/CardHeader/CardHeader.tsx
Normal file
18
src/components/CardHeader/CardHeader.tsx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import classNames from "classnames";
|
||||||
|
|
||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
|
export default function CardHeader({ icon, content, active = false }) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames(style.container, {
|
||||||
|
[`${style.active}`]: active,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className={style.title}>
|
||||||
|
<div className={style.icon}>{icon}</div>
|
||||||
|
<div className={style.content}>{content}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
src/components/CardHeader/style.module.css
Normal file
27
src/components/CardHeader/style.module.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
.container {
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 2px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
color: #c0c0c0;
|
||||||
|
background-color: #808080;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
color: white;
|
||||||
|
background-color: #000082;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
font-weight: bold;
|
||||||
|
gap: 7px;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
80
src/components/Dashboard/Dashboard.tsx
Normal file
80
src/components/Dashboard/Dashboard.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import FourTwenty from "@components/FourTwenty/FourTwenty";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Amogus from "@/components/Amogus/Amogus";
|
||||||
|
import Card from "@/components/Card/Card";
|
||||||
|
import {
|
||||||
|
CardColumn,
|
||||||
|
CardRow,
|
||||||
|
} from "@/components/CardContainers/CardContainers";
|
||||||
|
import Datetime from "@/components/Datetime/Datetime";
|
||||||
|
import Flatastic from "@/components/Flatastic/Flatastic";
|
||||||
|
import Footer from "@/components/Footer/Footer";
|
||||||
|
import Terminal from "@/components/Terminal/Terminal";
|
||||||
|
import Timetable from "@/components/Timetable/Timetable";
|
||||||
|
import Weather from "@/components/Weather/Weather";
|
||||||
|
|
||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
|
export default function Dashboard() {
|
||||||
|
const schemes = [style.day, style.evening, style.night];
|
||||||
|
const [schemeIndex, setSchemeIndex] = useState(0);
|
||||||
|
const scheme = schemes[schemeIndex];
|
||||||
|
|
||||||
|
// change background color based on time of day
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(
|
||||||
|
() => {
|
||||||
|
const d = new Date();
|
||||||
|
const hour = d.getHours();
|
||||||
|
if (hour >= 7 && hour < 16) {
|
||||||
|
setSchemeIndex(0);
|
||||||
|
} else if (hour >= 16 && hour < 23) {
|
||||||
|
setSchemeIndex(1);
|
||||||
|
} else {
|
||||||
|
setSchemeIndex(2);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
20 * 60 * 1000,
|
||||||
|
);
|
||||||
|
return () => {
|
||||||
|
clearInterval(timer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`${style.dashboard} ${scheme}`}>
|
||||||
|
<Amogus />
|
||||||
|
<div className={style.body}>
|
||||||
|
<CardColumn>
|
||||||
|
<Card icon="🚊" name="Timetable">
|
||||||
|
<Timetable />
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<CardRow>
|
||||||
|
<Card icon="🕐" name="Clock">
|
||||||
|
<Datetime />
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card icon="🌤" name="Weather">
|
||||||
|
<Weather />
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card icon="🍁" name="420">
|
||||||
|
<FourTwenty />
|
||||||
|
</Card>
|
||||||
|
</CardRow>
|
||||||
|
|
||||||
|
<Card icon="🔔" name="Terminal" active={true}>
|
||||||
|
<Terminal />
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card icon="🧹" name="Flatastic">
|
||||||
|
<Flatastic />
|
||||||
|
</Card>
|
||||||
|
</CardColumn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
27
src/components/Dashboard/style.module.css
Normal file
27
src/components/Dashboard/style.module.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
.dashboard {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
height: 100%;
|
||||||
|
padding: 30px;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 7 to 16 */
|
||||||
|
.day {
|
||||||
|
background-color: #007c7d;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 16 to 23 */
|
||||||
|
.evening {
|
||||||
|
background-color: #3b5773;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 23 to 8 */
|
||||||
|
.night {
|
||||||
|
background-color: #2a3f55;
|
||||||
|
}
|
||||||
44
src/components/Datetime/Datetime.tsx
Normal file
44
src/components/Datetime/Datetime.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
|
import clockImage from "/img/clock.png";
|
||||||
|
|
||||||
|
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={clockImage} 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
src/components/Datetime/style.module.css
Normal file
29
src/components/Datetime/style.module.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,11 +58,13 @@ export default function DepartureList(props: {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={style.container}>
|
<div className={style.container}>
|
||||||
<h2>{name} Departures</h2>
|
<div className={style.heading}>
|
||||||
|
<h2>{name}</h2>
|
||||||
|
</div>
|
||||||
<div className={style.departureLists}>
|
<div className={style.departureLists}>
|
||||||
{departureTables(left)}
|
{departureTables(left)}
|
||||||
{departureTables(right)}
|
{departureTables(right)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
.container {
|
.container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
|
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 {
|
.departureLists {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-around;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 30px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,37 +1,83 @@
|
|||||||
import { useFlatasticStore } from "@/store/flatastic";
|
import classNames from "classnames";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { useFlatasticStore } from "@/store/flatastic";
|
||||||
|
import type { FlatasticChore, FlatasticUser } from "@/types/flatasticChore";
|
||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
import type { FlatasticChore } from "@/types/flatasticChore";
|
function choreItem(chore: FlatasticChore, idToNameMap: Record<number, string>) {
|
||||||
|
let className = "";
|
||||||
|
let timeLeftInDays = null;
|
||||||
|
|
||||||
const idToNameMap: Record<number, string> = {
|
if (chore.rotationTime === -1) {
|
||||||
1836104: "Gruber",
|
className = "irregular";
|
||||||
1836101: "Darius",
|
} else {
|
||||||
1593610: "Arif",
|
className = chore.timeLeftNext <= 0 ? "due" : "notDue";
|
||||||
1860060: "Rishab",
|
timeLeftInDays = (
|
||||||
};
|
<span className={style.timeLeft}>
|
||||||
|
{Math.abs(Math.floor(chore.timeLeftNext / (60 * 60 * 24)))}d
|
||||||
export default function Flatastic() {
|
</span>
|
||||||
const fetchChores = useFlatasticStore((state) => state.fetch);
|
);
|
||||||
const chores = useFlatasticStore((state) => state.chores);
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchChores();
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
fetchChores();
|
|
||||||
}, 60000);
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [fetchChores]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<li
|
||||||
<h1>Flatastic Chores</h1>
|
key={chore.id}
|
||||||
<ul>
|
className={classNames(style.chore, style[className])}
|
||||||
{chores.map((chore: FlatasticChore) => (
|
>
|
||||||
<li key={chore.id} style={{ textAlign: "left" }}>
|
<span className={style.userName}>
|
||||||
{idToNameMap[chore.currentUser]}: {chore.title} -
|
{`${idToNameMap[chore.currentUser]}`}
|
||||||
Points: {chore.points}
|
</span>
|
||||||
</li>
|
: {chore.title} {timeLeftInDays} {"🪙".repeat(chore.points)}
|
||||||
))}
|
</li>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Flatastic() {
|
||||||
|
const fetchFlatasticData = useFlatasticStore((state) => state.fetch);
|
||||||
|
const flatasticData = useFlatasticStore((state) => state.flatasticData);
|
||||||
|
const chores = (flatasticData?.chores as FlatasticChore[]) || [];
|
||||||
|
const regularChores: FlatasticChore[] = [];
|
||||||
|
const irregularChores: FlatasticChore[] = [];
|
||||||
|
|
||||||
|
chores.forEach((chore) => {
|
||||||
|
if (chore.rotationTime === -1) {
|
||||||
|
irregularChores.push(chore);
|
||||||
|
} else {
|
||||||
|
regularChores.push(chore);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
regularChores.sort((a, b) => a.lastDoneDate - b.lastDoneDate);
|
||||||
|
|
||||||
|
const users = flatasticData?.users;
|
||||||
|
const idToNameMap: Record<number, string> = {};
|
||||||
|
users.forEach((user: FlatasticUser) => {
|
||||||
|
idToNameMap[user.id] = user.firstName;
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchFlatasticData();
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
fetchFlatasticData();
|
||||||
|
}, 60000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [fetchFlatasticData]);
|
||||||
|
|
||||||
|
const regularChoresRender = regularChores.map((chore) => {
|
||||||
|
return choreItem(chore, idToNameMap);
|
||||||
|
});
|
||||||
|
|
||||||
|
const irregularChoresRender = irregularChores.map((chore) => {
|
||||||
|
return choreItem(chore, idToNameMap);
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={style.container}>
|
||||||
|
<h1>Chores</h1>
|
||||||
|
<ul className={style.choreList}>
|
||||||
|
{[...regularChoresRender, ...irregularChoresRender]}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
.container {
|
||||||
|
padding: 1px 100px 30px 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.choreList {
|
||||||
|
list-style-type: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chore {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.irregular {
|
||||||
|
background-color: #aaaaaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.due {
|
||||||
|
background-color: #e4877e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notDue {
|
||||||
|
background-color: #a6cfa6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeLeft {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|||||||
36
src/components/Footer/Footer.tsx
Normal file
36
src/components/Footer/Footer.tsx
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import weedImage from "/img/weed.png";
|
||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<div className={style.container}>
|
||||||
|
<div className={style.taskbar}>
|
||||||
|
<div className={style.startButton}>
|
||||||
|
<img
|
||||||
|
className={style.startIcon}
|
||||||
|
src={weedImage}
|
||||||
|
alt="4:20"
|
||||||
|
/>
|
||||||
|
Start
|
||||||
|
</div>
|
||||||
|
<div className={style.windows}>
|
||||||
|
<span className={style.window}>
|
||||||
|
<span className={style.windowIcon}>🚊</span>Timetable
|
||||||
|
</span>
|
||||||
|
<span className={style.window}>
|
||||||
|
<span className={style.windowIcon}>🕐</span>Clock
|
||||||
|
</span>
|
||||||
|
<span className={style.windowActive}>
|
||||||
|
<span className={style.windowIcon}>🔔</span>Terminal
|
||||||
|
</span>
|
||||||
|
<span className={style.window}>
|
||||||
|
<span className={style.windowIcon}>🧹</span>Flatastic
|
||||||
|
</span>
|
||||||
|
<span className={style.window}>
|
||||||
|
<span className={style.windowIcon}>⛅</span>Weather
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/components/Footer/pasta.ts
Normal file
9
src/components/Footer/pasta.ts
Normal 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 bean’s 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;
|
||||||
68
src/components/Footer/style.module.css
Normal file
68
src/components/Footer/style.module.css
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
.container {
|
||||||
|
background-color: #c0c0c0;
|
||||||
|
height: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.taskbar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.startButton {
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
align-items: center;
|
||||||
|
width: 80px;
|
||||||
|
border-top: 2px solid white;
|
||||||
|
border-left: 2px solid white;
|
||||||
|
border-bottom: 2px solid #828282;
|
||||||
|
border-right: 2px solid #828282;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.startIcon {
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window {
|
||||||
|
height: 25px;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windowIcon {
|
||||||
|
font-size: 11pt;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
37
src/components/FourTwenty/FourTwenty.tsx
Normal file
37
src/components/FourTwenty/FourTwenty.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
|
// function Timer({ hours, minutes }: { hours: number; minutes: number }) {
|
||||||
|
// const x = hours > 0 ? `${hours}h ` : "";
|
||||||
|
// const y = minutes > 0 ? `${minutes % 60}m` : "";
|
||||||
|
|
||||||
|
// return (
|
||||||
|
// <div>
|
||||||
|
// <p>
|
||||||
|
// {x}
|
||||||
|
// {y}
|
||||||
|
// </p>
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// function FourTwentyLoading() {}
|
||||||
|
|
||||||
|
function FourTwentySmoking() {
|
||||||
|
return (
|
||||||
|
<div className={style.container}>
|
||||||
|
<img
|
||||||
|
className={style.image}
|
||||||
|
src="https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExMjFlYnhzYzJnc3ZzejBnOGZzcHFpNGpxOHNrN2dwcnZ3NmZ2eWJ0dyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/5xaOcLRnsWIB0CkfE3u/giphy.gif"
|
||||||
|
alt="Smoking"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function FourTwenty() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<FourTwentySmoking />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
5
src/components/FourTwenty/style.module.css
Normal file
5
src/components/FourTwenty/style.module.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.container img {
|
||||||
|
all: unset;
|
||||||
|
object-fit: contain;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useHomeAssistantStore } from "@/store/homeAssistant";
|
import { useHomeAssistantStore } from "@/store/homeAssistant";
|
||||||
|
|
||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
export default function Timetable() {
|
export default function Timetable() {
|
||||||
const fetchHomeAssistantData = useHomeAssistantStore((state) => state.fetch);
|
const fetchHomeAssistantData = useHomeAssistantStore(
|
||||||
const tentTemperature = useHomeAssistantStore((state) => state.tentTemperature);
|
(state) => state.fetch,
|
||||||
|
);
|
||||||
|
const tentTemperature = useHomeAssistantStore(
|
||||||
|
(state) => state.tentTemperature,
|
||||||
|
);
|
||||||
const tentHumidity = useHomeAssistantStore((state) => state.tentHumidity);
|
const tentHumidity = useHomeAssistantStore((state) => state.tentHumidity);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -16,9 +22,17 @@ export default function Timetable() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1>Tent</h1>
|
<div className={style.cardContainer}>
|
||||||
<p>Temperature: {tentTemperature}°C</p>
|
<div className={style.card}>
|
||||||
<p>Humidity: {tentHumidity}%</p>
|
<h4>Temperature</h4>
|
||||||
|
<p>{tentTemperature}°C</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={style.card}>
|
||||||
|
<h4>Humidity</h4>
|
||||||
|
<p>{tentHumidity}%</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
21
src/components/HomeAssistant/style.module.css
Normal file
21
src/components/HomeAssistant/style.module.css
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
.cardContainer {
|
||||||
|
padding-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
height: 150px;
|
||||||
|
width: 150px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
text-align: center;
|
||||||
|
border-top: 2px solid white;
|
||||||
|
border-left: 2px solid white;
|
||||||
|
border-bottom: 2px solid #828282;
|
||||||
|
border-right: 2px solid #828282;
|
||||||
|
}
|
||||||
91
src/components/Terminal/Terminal.tsx
Normal file
91
src/components/Terminal/Terminal.tsx
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { useHomeAssistantStore } from "@/store/homeAssistant";
|
||||||
|
import pasta from "./pasta.ts";
|
||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/components/Terminal/pasta.ts
Normal file
9
src/components/Terminal/pasta.ts
Normal 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 bean’s 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;
|
||||||
47
src/components/Terminal/style.module.css
Normal file
47
src/components/Terminal/style.module.css
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 10pt;
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
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;
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ import { useEffect } from "react";
|
|||||||
import DepartureList from "@/components/DepartureList/DepartureList";
|
import DepartureList from "@/components/DepartureList/DepartureList";
|
||||||
import { useKVVStore } from "@/store/kvv";
|
import { useKVVStore } from "@/store/kvv";
|
||||||
|
|
||||||
|
import style from "./style.module.css";
|
||||||
|
|
||||||
export default function Timetable() {
|
export default function Timetable() {
|
||||||
const fetchTimetable = useKVVStore((state) => state.fetch);
|
const fetchTimetable = useKVVStore((state) => state.fetch);
|
||||||
const pStreet = useKVVStore((state) => state.pStreet);
|
const pStreet = useKVVStore((state) => state.pStreet);
|
||||||
@@ -16,10 +18,16 @@ export default function Timetable() {
|
|||||||
}, [fetchTimetable]);
|
}, [fetchTimetable]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className={style.container}>
|
||||||
<h1>Timetable</h1>
|
<h1>Departures</h1>
|
||||||
<DepartureList departures={pStreet.departureList} name="P-Street" />
|
<DepartureList
|
||||||
<DepartureList departures={hStreet.departureList} name="H-Street" />
|
departures={pStreet.departureList}
|
||||||
|
name="Philippstraße"
|
||||||
|
/>
|
||||||
|
<DepartureList
|
||||||
|
departures={hStreet.departureList}
|
||||||
|
name="Händelstraße"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
.container {
|
||||||
|
padding: 1px 100px 30px 100px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,12 +10,32 @@ export default function TimetableRow({
|
|||||||
const hour = String(departure.dateTime.hour).padStart(2, "0");
|
const hour = String(departure.dateTime.hour).padStart(2, "0");
|
||||||
const minute = String(departure.dateTime.minute).padStart(2, "0");
|
const minute = String(departure.dateTime.minute).padStart(2, "0");
|
||||||
const dateTimeString = `${hour}:${minute}`;
|
const dateTimeString = `${hour}:${minute}`;
|
||||||
|
const lineNumber = departure.servingLine.number;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr className={styles.timetableRow}>
|
<tr className={styles.timetableRow}>
|
||||||
<td>{dateTimeString}</td>
|
<td className={styles.departureTime}>{dateTimeString}</td>
|
||||||
<td>{departure.servingLine.number}</td>
|
<td
|
||||||
<td>({departure.servingLine.direction})</td>
|
className={`${lineNumberToStyle(lineNumber)} ${styles.lineNumber}`}
|
||||||
|
>
|
||||||
|
{lineNumber}
|
||||||
|
</td>
|
||||||
|
<td>{departure.servingLine.direction}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lineNumberToStyle = (number) => {
|
||||||
|
switch (number) {
|
||||||
|
case "S2":
|
||||||
|
return styles.S2;
|
||||||
|
case "S5":
|
||||||
|
case "S51":
|
||||||
|
return styles.S5;
|
||||||
|
case "S1":
|
||||||
|
case "S11":
|
||||||
|
return styles.S1;
|
||||||
|
default:
|
||||||
|
return styles.lineNumberDefault;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,3 +1,31 @@
|
|||||||
|
td {
|
||||||
|
padding: 4px 7px;
|
||||||
|
}
|
||||||
|
|
||||||
.timetableRow {
|
.timetableRow {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.departureTime {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineNumber {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.S2 {
|
||||||
|
background-color: rgba(200, 100, 200, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.S5 {
|
||||||
|
background-color: rgba(230, 20, 20, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.S1 {
|
||||||
|
background-color: rgba(20, 180, 90, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.lineNumberDefault {
|
||||||
|
background-color: rgba(100, 100, 100, 0.2);
|
||||||
|
}
|
||||||
|
|||||||
41
src/components/Weather/Weather.tsx
Normal file
41
src/components/Weather/Weather.tsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useWeatherStore } from "@/store/weather";
|
||||||
|
|
||||||
|
import styles from "./style.module.css";
|
||||||
|
|
||||||
|
export default function Weather() {
|
||||||
|
const weatherData = useWeatherStore((state) => state.weatherData);
|
||||||
|
const fetchWeatherData = useWeatherStore((state) => state.fetchWeatherData);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchWeatherData();
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
fetchWeatherData();
|
||||||
|
}, 10 * 60000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [fetchWeatherData]);
|
||||||
|
|
||||||
|
if (!weatherData.current) {
|
||||||
|
return <div>Loading...</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.weatherContainer}>
|
||||||
|
<div className={styles.currentTemperature}>
|
||||||
|
<img
|
||||||
|
src={weatherData.current.icon}
|
||||||
|
alt={weatherData.current.weather_description}
|
||||||
|
/>
|
||||||
|
<span>{weatherData.current.temperature_2m.toFixed(1)}°C</span>
|
||||||
|
</div>
|
||||||
|
<div className={styles.dailyTemperatures}>
|
||||||
|
<span className={styles.minTemperature}>
|
||||||
|
{weatherData.daily.temperature_2m_min[0].toFixed(1)}°C
|
||||||
|
</span>
|
||||||
|
<span className={styles.maxTemperature}>
|
||||||
|
{weatherData.daily.temperature_2m_max[0].toFixed(1)}°C
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
30
src/components/Weather/style.module.css
Normal file
30
src/components/Weather/style.module.css
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
.weatherContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
padding: 3px;
|
||||||
|
padding-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.currentTemperature {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dailyTemperatures {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.min-temperature {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.max-temperature {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
:root {
|
:root {
|
||||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family:
|
||||||
|
Noto Sans Condensed,
|
||||||
|
sans-serif;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
color: rgba(255, 255, 255, 0.87);
|
color: #213547;
|
||||||
background-color: #242424;
|
background-color: #ffffff;
|
||||||
|
|
||||||
font-synthesis: none;
|
font-synthesis: none;
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
@@ -18,9 +20,6 @@ a {
|
|||||||
color: #646cff;
|
color: #646cff;
|
||||||
text-decoration: inherit;
|
text-decoration: inherit;
|
||||||
}
|
}
|
||||||
a:hover {
|
|
||||||
color: #535bf2;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -42,7 +41,7 @@ button {
|
|||||||
font-size: 1em;
|
font-size: 1em;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
background-color: #1a1a1a;
|
background-color: #f9f9f9;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.25s;
|
transition: border-color 0.25s;
|
||||||
}
|
}
|
||||||
@@ -53,16 +52,3 @@ button:focus,
|
|||||||
button:focus-visible {
|
button:focus-visible {
|
||||||
outline: 4px auto -webkit-focus-ring-color;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { devtools } from "zustand/middleware";
|
import { devtools } from "zustand/middleware";
|
||||||
import Flatastic from "@/api/flatastic";
|
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(
|
const useFlatasticStore = create(
|
||||||
devtools(
|
devtools(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
chores: [],
|
flatasticData: {
|
||||||
|
chores: [] as FlatasticChore[],
|
||||||
|
users: [] as FlatasticUser[],
|
||||||
|
},
|
||||||
fetch: async () => {
|
fetch: async () => {
|
||||||
if (!import.meta.env.VITE_FLATTASTIC_API_KEY) {
|
if (!import.meta.env.VITE_FLATTASTIC_API_KEY) {
|
||||||
throw new Error("Flatastic API Key is not set");
|
throw new Error("Flatastic API Key is not set");
|
||||||
@@ -15,9 +26,14 @@ const useFlatasticStore = create(
|
|||||||
import.meta.env.VITE_FLATTASTIC_API_KEY,
|
import.meta.env.VITE_FLATTASTIC_API_KEY,
|
||||||
);
|
);
|
||||||
const data = await flatastic.getTaskList();
|
const data = await flatastic.getTaskList();
|
||||||
|
const dataB = await flatastic.getInformation();
|
||||||
|
|
||||||
console.log("Flatastic chores fetched:", data);
|
set({
|
||||||
set({ chores: data as FlatasticChore[] });
|
flatasticData: {
|
||||||
|
chores: data as FlatasticChore[],
|
||||||
|
users: parseInformationData(dataB),
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,11 +16,6 @@ const useKVVStore = create(
|
|||||||
const hStreetJson = await hStreetData.json();
|
const hStreetJson = await hStreetData.json();
|
||||||
const pStreetJson = await pStreetData.json();
|
const pStreetJson = await pStreetData.json();
|
||||||
|
|
||||||
console.log("KVV departures fetched:", {
|
|
||||||
hStreet: hStreetJson,
|
|
||||||
pStreet: pStreetJson,
|
|
||||||
});
|
|
||||||
|
|
||||||
set({
|
set({
|
||||||
hStreet: hStreetJson as DepartureType[],
|
hStreet: hStreetJson as DepartureType[],
|
||||||
pStreet: pStreetJson as DepartureType[],
|
pStreet: pStreetJson as DepartureType[],
|
||||||
|
|||||||
389
src/store/weather.ts
Normal file
389
src/store/weather.ts
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
import { create } from "zustand";
|
||||||
|
import { devtools } from "zustand/middleware";
|
||||||
|
import fetchWeatherData from "@/api/weather";
|
||||||
|
|
||||||
|
const iconNumberToPng = {
|
||||||
|
"0": {
|
||||||
|
day: {
|
||||||
|
description: "Sunny",
|
||||||
|
image: "http://openweathermap.org/img/wn/01d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Clear",
|
||||||
|
image: "http://openweathermap.org/img/wn/01n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
day: {
|
||||||
|
description: "Mainly Sunny",
|
||||||
|
image: "http://openweathermap.org/img/wn/01d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Mainly Clear",
|
||||||
|
image: "http://openweathermap.org/img/wn/01n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
day: {
|
||||||
|
description: "Partly Cloudy",
|
||||||
|
image: "http://openweathermap.org/img/wn/02d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Partly Cloudy",
|
||||||
|
image: "http://openweathermap.org/img/wn/02n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
day: {
|
||||||
|
description: "Cloudy",
|
||||||
|
image: "http://openweathermap.org/img/wn/03d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Cloudy",
|
||||||
|
image: "http://openweathermap.org/img/wn/03n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"45": {
|
||||||
|
day: {
|
||||||
|
description: "Foggy",
|
||||||
|
image: "http://openweathermap.org/img/wn/50d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Foggy",
|
||||||
|
image: "http://openweathermap.org/img/wn/50n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"48": {
|
||||||
|
day: {
|
||||||
|
description: "Rime Fog",
|
||||||
|
image: "http://openweathermap.org/img/wn/50d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Rime Fog",
|
||||||
|
image: "http://openweathermap.org/img/wn/50n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"51": {
|
||||||
|
day: {
|
||||||
|
description: "Light Drizzle",
|
||||||
|
image: "http://openweathermap.org/img/wn/09d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Light Drizzle",
|
||||||
|
image: "http://openweathermap.org/img/wn/09n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"53": {
|
||||||
|
day: {
|
||||||
|
description: "Drizzle",
|
||||||
|
image: "http://openweathermap.org/img/wn/09d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Drizzle",
|
||||||
|
image: "http://openweathermap.org/img/wn/09n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"55": {
|
||||||
|
day: {
|
||||||
|
description: "Heavy Drizzle",
|
||||||
|
image: "http://openweathermap.org/img/wn/09d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Heavy Drizzle",
|
||||||
|
image: "http://openweathermap.org/img/wn/09n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"56": {
|
||||||
|
day: {
|
||||||
|
description: "Light Freezing Drizzle",
|
||||||
|
image: "http://openweathermap.org/img/wn/09d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Light Freezing Drizzle",
|
||||||
|
image: "http://openweathermap.org/img/wn/09n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"57": {
|
||||||
|
day: {
|
||||||
|
description: "Freezing Drizzle",
|
||||||
|
image: "http://openweathermap.org/img/wn/09d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Freezing Drizzle",
|
||||||
|
image: "http://openweathermap.org/img/wn/09n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"61": {
|
||||||
|
day: {
|
||||||
|
description: "Light Rain",
|
||||||
|
image: "http://openweathermap.org/img/wn/10d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Light Rain",
|
||||||
|
image: "http://openweathermap.org/img/wn/10n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"63": {
|
||||||
|
day: {
|
||||||
|
description: "Rain",
|
||||||
|
image: "http://openweathermap.org/img/wn/10d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Rain",
|
||||||
|
image: "http://openweathermap.org/img/wn/10n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"65": {
|
||||||
|
day: {
|
||||||
|
description: "Heavy Rain",
|
||||||
|
image: "http://openweathermap.org/img/wn/10d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Heavy Rain",
|
||||||
|
image: "http://openweathermap.org/img/wn/10n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"66": {
|
||||||
|
day: {
|
||||||
|
description: "Light Freezing Rain",
|
||||||
|
image: "http://openweathermap.org/img/wn/10d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Light Freezing Rain",
|
||||||
|
image: "http://openweathermap.org/img/wn/10n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"67": {
|
||||||
|
day: {
|
||||||
|
description: "Freezing Rain",
|
||||||
|
image: "http://openweathermap.org/img/wn/10d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Freezing Rain",
|
||||||
|
image: "http://openweathermap.org/img/wn/10n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"71": {
|
||||||
|
day: {
|
||||||
|
description: "Light Snow",
|
||||||
|
image: "http://openweathermap.org/img/wn/13d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Light Snow",
|
||||||
|
image: "http://openweathermap.org/img/wn/13n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"73": {
|
||||||
|
day: {
|
||||||
|
description: "Snow",
|
||||||
|
image: "http://openweathermap.org/img/wn/13d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Snow",
|
||||||
|
image: "http://openweathermap.org/img/wn/13n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"75": {
|
||||||
|
day: {
|
||||||
|
description: "Heavy Snow",
|
||||||
|
image: "http://openweathermap.org/img/wn/13d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Heavy Snow",
|
||||||
|
image: "http://openweathermap.org/img/wn/13n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"77": {
|
||||||
|
day: {
|
||||||
|
description: "Snow Grains",
|
||||||
|
image: "http://openweathermap.org/img/wn/13d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Snow Grains",
|
||||||
|
image: "http://openweathermap.org/img/wn/13n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"80": {
|
||||||
|
day: {
|
||||||
|
description: "Light Showers",
|
||||||
|
image: "http://openweathermap.org/img/wn/09d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Light Showers",
|
||||||
|
image: "http://openweathermap.org/img/wn/09n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"81": {
|
||||||
|
day: {
|
||||||
|
description: "Showers",
|
||||||
|
image: "http://openweathermap.org/img/wn/09d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Showers",
|
||||||
|
image: "http://openweathermap.org/img/wn/09n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"82": {
|
||||||
|
day: {
|
||||||
|
description: "Heavy Showers",
|
||||||
|
image: "http://openweathermap.org/img/wn/09d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Heavy Showers",
|
||||||
|
image: "http://openweathermap.org/img/wn/09n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"85": {
|
||||||
|
day: {
|
||||||
|
description: "Light Snow Showers",
|
||||||
|
image: "http://openweathermap.org/img/wn/13d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Light Snow Showers",
|
||||||
|
image: "http://openweathermap.org/img/wn/13n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"86": {
|
||||||
|
day: {
|
||||||
|
description: "Snow Showers",
|
||||||
|
image: "http://openweathermap.org/img/wn/13d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Snow Showers",
|
||||||
|
image: "http://openweathermap.org/img/wn/13n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"95": {
|
||||||
|
day: {
|
||||||
|
description: "Thunderstorm",
|
||||||
|
image: "http://openweathermap.org/img/wn/11d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Thunderstorm",
|
||||||
|
image: "http://openweathermap.org/img/wn/11n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"96": {
|
||||||
|
day: {
|
||||||
|
description: "Light Thunderstorms With Hail",
|
||||||
|
image: "http://openweathermap.org/img/wn/11d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Light Thunderstorms With Hail",
|
||||||
|
image: "http://openweathermap.org/img/wn/11n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"99": {
|
||||||
|
day: {
|
||||||
|
description: "Thunderstorm With Hail",
|
||||||
|
image: "http://openweathermap.org/img/wn/11d@2x.png",
|
||||||
|
},
|
||||||
|
night: {
|
||||||
|
description: "Thunderstorm With Hail",
|
||||||
|
image: "http://openweathermap.org/img/wn/11n@2x.png",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const useWeatherStore = create(
|
||||||
|
devtools(
|
||||||
|
(set) => ({
|
||||||
|
weatherData: {},
|
||||||
|
fetchWeatherData: async () => {
|
||||||
|
const data = await fetchWeatherData();
|
||||||
|
|
||||||
|
// Process first location. Add a for-loop for multiple locations or weather models
|
||||||
|
const response = data[0];
|
||||||
|
|
||||||
|
if (response === null) {
|
||||||
|
console.error("Failed to fetch weather data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attributes for timezone and location
|
||||||
|
const utcOffsetSeconds = response.utcOffsetSeconds();
|
||||||
|
const current = response.current();
|
||||||
|
const hourly = response.hourly();
|
||||||
|
const daily = response.daily();
|
||||||
|
|
||||||
|
if (!current || !hourly || !daily) {
|
||||||
|
console.error("Failed to fetch weather data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: The order of weather variables in the URL query and the indices below need to match!
|
||||||
|
const weatherData = {
|
||||||
|
current: {
|
||||||
|
time: current.time(),
|
||||||
|
temperature_2m: current.variables(0)?.value(),
|
||||||
|
precipitation: current.variables(1)?.value(),
|
||||||
|
rain: current.variables(2)?.value(),
|
||||||
|
showers: current.variables(3)?.value(),
|
||||||
|
snowfall: current.variables(4)?.value(),
|
||||||
|
relative_humidity_2m: current.variables(5)?.value(),
|
||||||
|
apparent_temperature: current.variables(6)?.value(),
|
||||||
|
weather_code: current.variables(7)?.value(),
|
||||||
|
cloud_cover: current.variables(8)?.value(),
|
||||||
|
is_day: current.variables(9)?.value(),
|
||||||
|
},
|
||||||
|
hourly: {
|
||||||
|
time: [
|
||||||
|
...Array(
|
||||||
|
(Number(hourly.timeEnd()) -
|
||||||
|
Number(hourly.time())) /
|
||||||
|
hourly.interval(),
|
||||||
|
),
|
||||||
|
].map(
|
||||||
|
(_, i) =>
|
||||||
|
new Date(
|
||||||
|
(Number(hourly.time()) +
|
||||||
|
i * hourly.interval() +
|
||||||
|
utcOffsetSeconds) *
|
||||||
|
1000,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
temperature_2m: hourly.variables(0)?.valuesArray(),
|
||||||
|
precipitation: hourly.variables(1)?.valuesArray(),
|
||||||
|
rain: hourly.variables(2)?.valuesArray(),
|
||||||
|
precipitation_probability: hourly
|
||||||
|
.variables(3)
|
||||||
|
?.valuesArray(),
|
||||||
|
},
|
||||||
|
daily: {
|
||||||
|
time: [
|
||||||
|
...Array(
|
||||||
|
(Number(daily.timeEnd()) -
|
||||||
|
Number(daily.time())) /
|
||||||
|
daily.interval(),
|
||||||
|
),
|
||||||
|
].map(
|
||||||
|
(_, i) =>
|
||||||
|
new Date(
|
||||||
|
(Number(daily.time()) +
|
||||||
|
i * daily.interval() +
|
||||||
|
utcOffsetSeconds) *
|
||||||
|
1000,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
temperature_2m_max: daily.variables(0)?.valuesArray(),
|
||||||
|
temperature_2m_min: daily.variables(1)?.valuesArray(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDay = weatherData.current.is_day === 1;
|
||||||
|
const weatherCode = weatherData.current.weather_code;
|
||||||
|
const url =
|
||||||
|
iconNumberToPng[weatherCode][isDay ? "day" : "night"].image;
|
||||||
|
|
||||||
|
weatherData.current = { ...weatherData.current, icon: url };
|
||||||
|
|
||||||
|
set({ weatherData });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "weather-store",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
export { useWeatherStore };
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
|
interface Flatastic {
|
||||||
|
users: Array<FlatasticUser>;
|
||||||
|
chores: Array<FlatasticChore>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FlatasticUser {
|
||||||
|
id: number;
|
||||||
|
firstName: string;
|
||||||
|
}
|
||||||
|
|
||||||
interface FlatasticChore {
|
interface FlatasticChore {
|
||||||
id: number;
|
id: number;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -6,8 +16,8 @@ interface FlatasticChore {
|
|||||||
points: number;
|
points: number;
|
||||||
rotationTime: number;
|
rotationTime: number;
|
||||||
currentUser: number;
|
currentUser: number;
|
||||||
lastDoneDate: string;
|
lastDoneDate: number;
|
||||||
timeLeftNext: number;
|
timeLeftNext: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type { FlatasticChore };
|
export type { Flatastic, FlatasticChore, FlatasticUser };
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import react from "@vitejs/plugin-react-swc";
|
import react from "@vitejs/plugin-react-swc";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
import mkcert from "vite-plugin-mkcert";
|
||||||
|
|
||||||
// https://vite.dev/config/
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [react(), mkcert()],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "src"),
|
"@": path.resolve(__dirname, "src"),
|
||||||
@@ -16,14 +16,4 @@ export default defineConfig({
|
|||||||
"@slices": path.resolve(__dirname, "src/store/slices"),
|
"@slices": path.resolve(__dirname, "src/store/slices"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
|
||||||
proxy: {
|
|
||||||
"/api": {
|
|
||||||
target: "https://home.rivercry.com",
|
|
||||||
changeOrigin: true,
|
|
||||||
secure: false,
|
|
||||||
rewrite: (path) => path.replace(/^\/api/, "/api"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user