Compare commits

...

4 commits

Author SHA1 Message Date
8f32aef17d ui: BulmaDrawer opening can't be cancelled now 2026-02-13 00:26:30 +00:00
12bc014ca6 ⬆️ ui: upgrade deps
`yarn upgrade-interactive --latest`
2026-02-13 00:05:28 +00:00
ff6afae0a0 🔧 model rework for "Credentials" 🐛 (missed file)
- updated ui/src/components/LoginModal.vue
2026-02-12 22:01:47 +00:00
f72d8e65a5 🧰 ui: tooling update
- new devcontainer spec "18-bookworm" -> "20-trixie"
- fixed "vue serve"
- improved vscode settings.json
- improved typescript config
- minor formatting
2026-02-12 21:59:41 +00:00
27 changed files with 2596 additions and 2368 deletions

View file

@ -2,15 +2,19 @@
// https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/javascript-node // https://github.com/microsoft/vscode-dev-containers/tree/v0.245.2/containers/javascript-node
{ {
"name": "Advent22 UI", "name": "Advent22 UI",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/vscode/devcontainers/javascript-node:1-18-bookworm", "image": "mcr.microsoft.com/devcontainers/javascript-node:4-20-trixie",
// Features to add to the dev container. More info: https://containers.dev/features. // Features to add to the dev container. More info: https://containers.dev/features.
"features": { "features": {
"ghcr.io/devcontainers/features/git-lfs:1": {},
"ghcr.io/devcontainers-extra/features/apt-get-packages:1": { "ghcr.io/devcontainers-extra/features/apt-get-packages:1": {
"packages": "git-flow, git-lfs" "packages": "git-flow"
}, },
"ghcr.io/devcontainers-extra/features/vue-cli:2": {} "ghcr.io/devcontainers-extra/features/vue-cli:2": {}
}, },
// Configure tool-specific properties. // Configure tool-specific properties.
"customizations": { "customizations": {
// Configure properties specific to VS Code. // Configure properties specific to VS Code.
@ -29,11 +33,16 @@
] ]
} }
}, },
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created. // Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install", // "postCreateCommand": "yarn install",
"postStartCommand": "yarn install --production false",
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. // Use 'postStartCommand' to run commands after the container is started.
"remoteUser": "node" "postStartCommand": "npx --yes update-browserslist-db@latest && yarn install --production false"
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
} }

View file

@ -30,6 +30,9 @@ module.exports = {
env: { env: {
mocha: true, mocha: true,
}, },
rules: {
"@typescript-eslint/no-unused-expressions": "off",
}
}, },
], ],
}; };

View file

@ -1,21 +1,23 @@
{ {
"editor.formatOnSave": true, "[scss][vue][typescript][javascript][json][jsonc][jsonl]": {
"[vue]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode" "[jsonc]": {
}, "editor.formatOnSave": false,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}, },
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.organizeImports": "explicit" "source.organizeImports": "explicit"
}, },
"git.closeDiffOnOperation": true, "git.closeDiffOnOperation": true,
"editor.formatOnSave": true,
"editor.tabSize": 2, "editor.tabSize": 2,
"sass.disableAutoIndent": true, "sass.disableAutoIndent": true,
"sass.format.convert": false, "sass.format.convert": false,
"sass.format.deleteWhitespace": true, "sass.format.deleteWhitespace": true,
"volar.inlayHints.eventArgumentInInlineHandlers": false
"prettier.trailingComma": "all",
} }

View file

@ -1,3 +1,5 @@
{ {
"presets": ["@vue/cli-plugin-babel/preset"] "presets": [
"@vue/cli-plugin-babel/preset"
]
} }

View file

@ -3,46 +3,44 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve --host 0.0.0.0 --port 8080", "serve": "vue-cli-service serve",
"build": "vue-cli-service build", "build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit", "test:unit": "vue-cli-service test:unit",
"test:unit-watch": "vue-cli-service test:unit --watch", "test:unit-watch": "vue-cli-service test:unit --watch",
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint",
"ui": "vue ui --host 0.0.0.0 --headless"
}, },
"devDependencies": { "devDependencies": {
"@fortawesome/fontawesome-svg-core": "^6.6.0", "@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^7.2.0",
"@fortawesome/vue-fontawesome": "^3.0.8", "@fortawesome/vue-fontawesome": "^3.1.3",
"@types/chai": "^4.3.17", "@types/chai": "^5.2.3",
"@types/luxon": "^3.4.2", "@types/luxon": "^3.7.1",
"@types/mocha": "^10.0.7", "@types/mocha": "^10.0.10",
"@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/eslint-plugin": "^8.55.0",
"@typescript-eslint/parser": "^7.3.1", "@typescript-eslint/parser": "^8.55.0",
"@vue/cli-plugin-babel": "~5.0.0", "@vue/cli-plugin-babel": "^5.0.9",
"@vue/cli-plugin-eslint": "~5.0.0", "@vue/cli-plugin-eslint": "^5.0.9",
"@vue/cli-plugin-typescript": "~5.0.0", "@vue/cli-plugin-typescript": "^5.0.9",
"@vue/cli-plugin-unit-mocha": "~5.0.0", "@vue/cli-plugin-unit-mocha": "^5.0.9",
"@vue/cli-service": "~5.0.0", "@vue/cli-service": "^5.0.9",
"@vue/eslint-config-typescript": "^13.0.0", "@vue/eslint-config-typescript": "^13.0.0",
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"@vue/tsconfig": "^0.8.1",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^1.7.3", "axios": "^1.13.5",
"bulma": "^1.0.2", "bulma": "^1.0.4",
"bulma-toast": "2.4.3", "bulma-toast": "2.4.3",
"chai": "^5.1.1", "chai": "^6.2.2",
"core-js": "^3.38.0", "core-js": "^3.48.0",
"eslint": "^8.57.0", "eslint": "^8.57.1",
"eslint-plugin-vue": "^9.27.0", "eslint-plugin-vue": "^9.33.0",
"luxon": "^3.5.0", "luxon": "^3.7.2",
"pinia": "^2.2.1", "pinia": "^3.0.4",
"sass": "^1.77.8", "sass": "~1.94.3",
"sass-loader": "^16.0.0", "sass-loader": "^16.0.0",
"typescript": "~5.5.4", "typescript": "^5.9.3",
"vue": "^3.5.25", "vue": "^3.5.25",
"vue-class-component": "^8.0.0-0" "vue-cli-plugin-webpack-bundle-analyzer": "^4.0.0"
},
"dependencies": {},
"prettier": {
"trailingComma": "all"
} }
} }

View file

@ -12,7 +12,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { APIError } from "@/lib/api_error"; import { APIError } from "@/lib/api_error";
import { Credentials } from "@/lib/model"; import type { Credentials } from "@/lib/model";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
import { ref } from "vue"; import { ref } from "vue";

View file

@ -49,15 +49,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { API } from "@/lib/api"; import { API } from "@/lib/api";
import { APIError } from "@/lib/api_error"; import { APIError } from "@/lib/api_error";
import { VueLike, name_door, unwrap_loading } from "@/lib/helpers"; import { type VueLike, name_door, unwrap_loading } from "@/lib/helpers";
import { ImageData } from "@/lib/model"; import type { ImageData } from "@/lib/model";
import { Door } from "@/lib/rects/door"; import { Door } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
import { onBeforeUnmount } from "vue"; import { onBeforeUnmount } from "vue";
import MultiModal, { HMultiModal } from "./MultiModal.vue"; import MultiModal, { type HMultiModal } from "./MultiModal.vue";
import BulmaButton from "./bulma/Button.vue"; import BulmaButton from "./bulma/Button.vue";
import BulmaToast, { HBulmaToast } from "./bulma/Toast.vue"; import BulmaToast, { type HBulmaToast } from "./bulma/Toast.vue";
import CalendarDoor from "./calendar/CalendarDoor.vue"; import CalendarDoor from "./calendar/CalendarDoor.vue";
import ThouCanvas from "./calendar/ThouCanvas.vue"; import ThouCanvas from "./calendar/ThouCanvas.vue";

View file

@ -16,7 +16,7 @@
ref="username_input" ref="username_input"
class="input" class="input"
type="text" type="text"
v-model="creds[0]" v-model="creds.username"
/> />
</div> </div>
</div> </div>
@ -24,7 +24,7 @@
<div class="field"> <div class="field">
<label class="label">Passwort</label> <label class="label">Passwort</label>
<div class="control"> <div class="control">
<input class="input" type="password" v-model="creds[1]" /> <input class="input" type="password" v-model="creds.password" />
</div> </div>
</div> </div>
</section> </section>
@ -49,7 +49,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { wait_for } from "@/lib/helpers"; import { wait_for } from "@/lib/helpers";
import { Credentials } from "@/lib/model"; import type { Credentials } from "@/lib/model";
import { onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue"; import { onBeforeUnmount, onMounted, ref, useTemplateRef } from "vue";
import BulmaButton from "./bulma/Button.vue"; import BulmaButton from "./bulma/Button.vue";
@ -60,7 +60,10 @@ const emit = defineEmits<{
(event: "cancel"): void; (event: "cancel"): void;
}>(); }>();
const creds = ref<Credentials>(["", ""]); const creds = ref<Credentials>({
username: "",
password: "",
});
function submit(): void { function submit(): void {
emit("submit", creds.value); emit("submit", creds.value);

View file

@ -48,10 +48,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { API } from "@/lib/api"; import { API } from "@/lib/api";
import { name_door, objForEach } from "@/lib/helpers"; import { name_door, objForEach } from "@/lib/helpers";
import { ImageData, NumStrDict } from "@/lib/model"; import type { ImageData, NumStrDict } from "@/lib/model";
import { ref } from "vue"; import { ref } from "vue";
import MultiModal, { HMultiModal } from "../MultiModal.vue"; import MultiModal, { type HMultiModal } from "../MultiModal.vue";
import BulmaButton from "../bulma/Button.vue"; import BulmaButton from "../bulma/Button.vue";
import BulmaDrawer from "../bulma/Drawer.vue"; import BulmaDrawer from "../bulma/Drawer.vue";

View file

@ -191,7 +191,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { API } from "@/lib/api"; import { API } from "@/lib/api";
import { AdminConfigModel, Credentials, DoorSaved } from "@/lib/model"; import type { AdminConfigModel, Credentials, DoorSaved } from "@/lib/model";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
import { ref } from "vue"; import { ref } from "vue";

View file

@ -71,11 +71,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { API } from "@/lib/api"; import { API } from "@/lib/api";
import { APIError } from "@/lib/api_error"; import { APIError } from "@/lib/api_error";
import { DoorSaved } from "@/lib/model"; import type { DoorSaved } from "@/lib/model";
import { Door } from "@/lib/rects/door"; import { Door } from "@/lib/rects/door";
import { toast } from "bulma-toast"; import { toast } from "bulma-toast";
import { ref } from "vue"; import { ref } from "vue";
import { BCStep } from "../bulma/Breadcrumbs.vue"; import type { BCStep } from "../bulma/Breadcrumbs.vue";
import Calendar from "../Calendar.vue"; import Calendar from "../Calendar.vue";
import BulmaBreadcrumbs from "../bulma/Breadcrumbs.vue"; import BulmaBreadcrumbs from "../bulma/Breadcrumbs.vue";

View file

@ -58,10 +58,19 @@ const state = ref<"closed" | "loading" | "ok" | "err">("closed");
const is_open = computed(() => state.value !== "closed"); const is_open = computed(() => state.value !== "closed");
async function toggle(): Promise<void> { async function toggle(): Promise<void> {
if (is_open.value) { switch (state.value) {
state.value = "closed"; case "closed":
} else { // start opening when closed
await load(); await load();
break;
case "loading":
// don't toggle when loading
break;
default:
state.value = "closed";
break;
} }
} }

View file

@ -8,7 +8,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Options as ToastOptions, toast } from "bulma-toast"; import { type Options as ToastOptions, toast } from "bulma-toast";
import { onMounted, useTemplateRef } from "vue"; import { onMounted, useTemplateRef } from "vue";
export type HBulmaToast = { export type HBulmaToast = {

View file

@ -17,7 +17,7 @@
import { Door } from "@/lib/rects/door"; import { Door } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
import { VueLike } from "@/lib/helpers"; import type { VueLike } from "@/lib/helpers";
import SVGRect from "./SVGRect.vue"; import SVGRect from "./SVGRect.vue";
const store = advent22Store(); const store = advent22Store();

View file

@ -20,7 +20,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { VueLike, unwrap_loading } from "@/lib/helpers"; import { type VueLike, unwrap_loading } from "@/lib/helpers";
import { Rectangle } from "@/lib/rects/rectangle"; import { Rectangle } from "@/lib/rects/rectangle";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";
import { computed } from "vue"; import { computed } from "vue";

View file

@ -29,7 +29,7 @@ import { Rectangle } from "@/lib/rects/rectangle";
import { Vector2D } from "@/lib/rects/vector2d"; import { Vector2D } from "@/lib/rects/vector2d";
import { computed, ref } from "vue"; import { computed, ref } from "vue";
import { VueLike } from "@/lib/helpers"; import type { VueLike } from "@/lib/helpers";
import CalendarDoor from "../calendar/CalendarDoor.vue"; import CalendarDoor from "../calendar/CalendarDoor.vue";
import SVGRect from "../calendar/SVGRect.vue"; import SVGRect from "../calendar/SVGRect.vue";
import ThouCanvas from "../calendar/ThouCanvas.vue"; import ThouCanvas from "../calendar/ThouCanvas.vue";

View file

@ -24,7 +24,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { VueLike, unwrap_loading } from "@/lib/helpers"; import { type VueLike, unwrap_loading } from "@/lib/helpers";
import { Door } from "@/lib/rects/door"; import { Door } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";

View file

@ -16,7 +16,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { VueLike, unwrap_loading } from "@/lib/helpers"; import { type VueLike, unwrap_loading } from "@/lib/helpers";
import { Door } from "@/lib/rects/door"; import { Door } from "@/lib/rects/door";
import { advent22Store } from "@/lib/store"; import { advent22Store } from "@/lib/store";

View file

@ -26,7 +26,7 @@
import { Door } from "@/lib/rects/door"; import { Door } from "@/lib/rects/door";
import { ref, useTemplateRef } from "vue"; import { ref, useTemplateRef } from "vue";
import { VueLike, unwrap_vuelike, wait_for } from "@/lib/helpers"; import { type VueLike, unwrap_vuelike, wait_for } from "@/lib/helpers";
import SVGRect from "../calendar/SVGRect.vue"; import SVGRect from "../calendar/SVGRect.vue";
const model = defineModel<VueLike<Door>>({ required: true }); const model = defineModel<VueLike<Door>>({ required: true });

View file

@ -1,9 +1,10 @@
import axios, { import type {
AxiosBasicCredentials, AxiosBasicCredentials,
type AxiosRequestConfig, AxiosRequestConfig,
type Method, Method,
type RawAxiosRequestHeaders, RawAxiosRequestHeaders,
} from "axios"; } from "axios";
import axios from "axios";
import { APIError } from "./api_error"; import { APIError } from "./api_error";
interface Params { interface Params {
@ -21,7 +22,8 @@ export class API {
return `${window.location.protocol}//${window.location.host}/api`; return `${window.location.protocol}//${window.location.host}/api`;
} else if (process.env.NODE_ENV !== "development") { } else if (process.env.NODE_ENV !== "development") {
// not in prouction or development mode // not in prouction or development mode
console.warn("Unexpected NODE_ENV value"); // eslint-disable-next-line no-console
console.warn("Unexpected NODE_ENV value: ", process.env.NODE_ENV);
} }
// in development mode, return "proto://hostname:8000/api" // in development mode, return "proto://hostname:8000/api"
@ -82,6 +84,7 @@ export class API {
const response = await this.axios.request<T>(this.get_axios_config(p)); const response = await this.axios.request<T>(this.get_axios_config(p));
return response.data; return response.data;
} catch (reason) { } catch (reason) {
// eslint-disable-next-line no-console
console.error(`Failed to query ${p.endpoint}: ${reason}`); console.error(`Failed to query ${p.endpoint}: ${reason}`);
throw new APIError(reason, p.endpoint); throw new APIError(reason, p.endpoint);
} }

View file

@ -1,4 +1,4 @@
import { nextTick, UnwrapRef } from "vue"; import { nextTick, type UnwrapRef } from "vue";
import { APIError } from "./api_error"; import { APIError } from "./api_error";
export function objForEach<T>( export function objForEach<T>(
@ -42,6 +42,7 @@ export function handle_error(error: unknown): void {
if (error instanceof APIError) { if (error instanceof APIError) {
error.alert(); error.alert();
} else { } else {
// eslint-disable-next-line no-console
console.error(error); console.error(error);
} }
} }

View file

@ -1,5 +1,5 @@
import { VueLike, unwrap_vuelike } from "../helpers"; import { type VueLike, unwrap_vuelike } from "../helpers";
import { DoorSaved } from "../model"; import type { DoorSaved } from "../model";
import { Rectangle } from "./rectangle"; import { Rectangle } from "./rectangle";
import { Vector2D } from "./vector2d"; import { Vector2D } from "./vector2d";

View file

@ -1,7 +1,7 @@
import { acceptHMRUpdate, defineStore } from "pinia"; import { acceptHMRUpdate, defineStore } from "pinia";
import { API } from "./api"; import { API } from "./api";
import { Loading } from "./helpers"; import type { Loading } from "./helpers";
import { Credentials, DoorSaved, ImageData, SiteConfigModel } from "./model"; import type { Credentials, DoorSaved, ImageData, SiteConfigModel } from "./model";
import { Door } from "./rects/door"; import { Door } from "./rects/door";
declare global { declare global {
@ -20,9 +20,7 @@ type State = {
next_door_target: number | null; next_door_target: number | null;
}; };
export const advent22Store = defineStore({ export const advent22Store = defineStore("advent22", {
id: "advent22",
state: (): State => ({ state: (): State => ({
on_initialized: [], on_initialized: [],
is_touch_device: is_touch_device:
@ -64,7 +62,7 @@ export const advent22Store = defineStore({
link.href = favicon.data_url; link.href = favicon.data_url;
if (link.parentElement === null) if (link.parentElement === null)
document.getElementsByTagName("head")[0].appendChild(link); document.getElementsByTagName("head")[0]!.appendChild(link);
} catch { } } catch { }
try { try {

View file

@ -20,4 +20,4 @@
// main imports // main imports
//============== //==============
@import "animate.css/animate"; @forward "animate.css/animate";

View file

@ -1,30 +1,33 @@
{ {
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": { "compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"experimentalDecorators": true, "experimentalDecorators": true,
"skipLibCheck": true, "lib": [
"esModuleInterop": true, "es2020",
"allowSyntheticDefaultImports": true, "dom",
"forceConsistentCasingInFileNames": true, "dom.iterable",
"useDefineForClassFields": true, "es2022.object",
"sourceMap": true, "es2023.array",
],
// "moduleResolution": "node",
// "sourceMap": true,
"baseUrl": ".", "baseUrl": ".",
"types": ["webpack-env", "mocha", "chai"], "types": [
"webpack-env",
"mocha",
"chai",
],
"paths": { "paths": {
"@/*": ["src/*"] "@/*": [
"src/*",
]
}, },
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
}, },
"include": [ "include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue", "src/**/*.vue",
"src/**/*.ts",
// "src/**/*.tsx",
"tests/**/*.ts", "tests/**/*.ts",
"tests/**/*.tsx" // "tests/**/*.tsx",
], ],
"exclude": ["node_modules"]
} }

View file

@ -1,10 +1,11 @@
/* eslint-disable @typescript-eslint/no-require-imports */
const { defineConfig } = require("@vue/cli-service"); const { defineConfig } = require("@vue/cli-service");
const webpack = require("webpack"); const webpack = require("webpack");
module.exports = defineConfig({ module.exports = defineConfig({
transpileDependencies: true, transpileDependencies: true,
devServer: { devServer: {
host: "localhost", host: "0.0.0.0",
}, },
pages: { pages: {
index: { index: {

File diff suppressed because it is too large Load diff