2026-04-12 19:20:37 +00:00
|
|
|
import CoreViewModel, { Context } from "./coreViewModel";
|
2026-09-23 00:09:09 +02:00
|
|
|
import * as React from "../../react";
|
2026-04-12 19:20:37 +00:00
|
|
|
import { CommonKeys } from "../../View/keystrokes";
|
2026-09-28 15:21:50 +02:00
|
|
|
import SettingsModel, {
|
|
|
|
|
Languages,
|
|
|
|
|
ThemeSettings,
|
|
|
|
|
} from "../../Model/Global/settingsModel";
|
2026-04-12 19:20:37 +00:00
|
|
|
|
|
|
|
|
export default class SettingsViewModel extends Context {
|
2026-04-01 08:29:39 +00:00
|
|
|
// state
|
|
|
|
|
username: React.State<string> = new React.State("");
|
|
|
|
|
usernameInput: React.State<string> = new React.State("");
|
2026-04-08 20:39:12 +00:00
|
|
|
isShowingSettingsModal: React.State<boolean> = new React.State(false);
|
2026-04-10 19:00:59 +00:00
|
|
|
selectedModalPage: React.State<SettingsModalPages | undefined> =
|
2026-09-09 00:04:27 +02:00
|
|
|
new React.State<SettingsModalPages | undefined>(undefined);
|
2026-04-09 17:14:33 +00:00
|
|
|
requiresReload = new React.State<boolean>(false);
|
2026-04-01 08:29:39 +00:00
|
|
|
|
2026-04-09 16:48:22 +00:00
|
|
|
firstDayOfWeek: React.State<string> = new React.State("0");
|
2026-09-23 00:09:09 +02:00
|
|
|
language: React.State<Languages | string> = new React.State<
|
|
|
|
|
Languages | string
|
|
|
|
|
>(Languages.English);
|
|
|
|
|
theme: React.State<ThemeSettings | string> = new React.State<
|
|
|
|
|
ThemeSettings | string
|
|
|
|
|
>(ThemeSettings.System);
|
2026-04-09 16:48:22 +00:00
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
// guards
|
|
|
|
|
cannotSetName: React.State<boolean> = React.createProxyState(
|
|
|
|
|
[this.usernameInput],
|
|
|
|
|
() =>
|
|
|
|
|
this.usernameInput.value == "" ||
|
2026-04-09 16:48:22 +00:00
|
|
|
this.usernameInput.value ==
|
|
|
|
|
this.coreViewModel.settingsModel.username,
|
2026-04-01 08:29:39 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// methods
|
2026-09-28 15:07:02 +02:00
|
|
|
setName = (name?: string): void => {
|
2026-09-28 15:21:50 +02:00
|
|
|
if (typeof name == "string") {
|
|
|
|
|
this.usernameInput.value = name;
|
|
|
|
|
}
|
|
|
|
|
this.coreViewModel.settingsModel.setName(this.usernameInput.value);
|
|
|
|
|
this.username.value = this.coreViewModel.settingsModel.username;
|
|
|
|
|
this.usernameInput.callSubscriptions();
|
2026-04-01 08:29:39 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setFirstDayofWeek = (): void => {
|
2026-09-28 15:21:50 +02:00
|
|
|
this.coreViewModel.settingsModel.setFirstDayOfWeek(
|
|
|
|
|
this.firstDayOfWeek.value,
|
|
|
|
|
);
|
2026-04-01 08:29:39 +00:00
|
|
|
};
|
|
|
|
|
|
2026-04-08 20:39:12 +00:00
|
|
|
showSettingsModal = (): void => {
|
2026-09-28 15:21:50 +02:00
|
|
|
this.coreViewModel.context = this;
|
|
|
|
|
this.isShowingSettingsModal.value = true;
|
2026-04-08 21:03:36 +00:00
|
|
|
};
|
2026-04-08 20:39:12 +00:00
|
|
|
|
2026-04-08 21:03:36 +00:00
|
|
|
showModalPage = (page: SettingsModalPages): void => {
|
2026-09-28 15:21:50 +02:00
|
|
|
this.selectedModalPage.value = page;
|
2026-04-08 21:03:36 +00:00
|
|
|
};
|
2026-04-08 20:39:12 +00:00
|
|
|
|
2026-04-09 18:09:26 +00:00
|
|
|
// view
|
|
|
|
|
applyTheme = (): void => {
|
2026-09-28 15:21:50 +02:00
|
|
|
let theme: string = this.theme.value;
|
|
|
|
|
|
|
|
|
|
if (theme == ThemeSettings.System)
|
|
|
|
|
theme = SettingsViewModel.getSystemTheme();
|
|
|
|
|
|
|
|
|
|
switch (theme) {
|
|
|
|
|
case ThemeSettings.Dynamic:
|
|
|
|
|
const scene = this.selectDynamicScene();
|
|
|
|
|
this.setScene(scene);
|
|
|
|
|
break;
|
|
|
|
|
case ThemeSettings.Dark:
|
|
|
|
|
this.setScene(DynamicSceneNight);
|
|
|
|
|
break;
|
|
|
|
|
case ThemeSettings.Black:
|
|
|
|
|
this.setScene(DynamicSceneBlack);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
this.setScene(DynamicSceneDay);
|
|
|
|
|
}
|
2026-04-09 18:09:26 +00:00
|
|
|
};
|
|
|
|
|
|
2026-09-17 20:03:55 +02:00
|
|
|
selectDynamicScene = (): DynamicScene => {
|
2026-09-28 15:21:50 +02:00
|
|
|
let hour = new Date().getHours();
|
|
|
|
|
|
|
|
|
|
if (hour >= 21 || hour <= 6) {
|
|
|
|
|
return DynamicSceneNight;
|
|
|
|
|
} else if (hour > 6 && hour < 10) {
|
|
|
|
|
return DynamicSceneSunrise;
|
|
|
|
|
} else if (hour < 14) {
|
|
|
|
|
return DynamicSceneDay;
|
|
|
|
|
} else if (hour < 17) {
|
|
|
|
|
return DynamicSceneAfternoon;
|
|
|
|
|
} else {
|
|
|
|
|
return DynamicSceneSunset;
|
|
|
|
|
}
|
2026-09-23 00:09:09 +02:00
|
|
|
};
|
2026-09-17 20:03:55 +02:00
|
|
|
|
|
|
|
|
setScene = (scene: DynamicScene): void => {
|
2026-09-28 15:21:50 +02:00
|
|
|
document.body.style.setProperty(
|
|
|
|
|
"--backdrop-grass-filter",
|
|
|
|
|
`brightness(${scene.brightness})`,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
function setSkyColor(
|
|
|
|
|
tone: 1 | 2,
|
|
|
|
|
hue: number,
|
|
|
|
|
saturation: number,
|
|
|
|
|
luma: number,
|
|
|
|
|
) {
|
|
|
|
|
document.body.style.setProperty(
|
|
|
|
|
`--sky-${tone}`,
|
|
|
|
|
`hsl(${hue}, ${saturation}%, ${luma}%)`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
setSkyColor(1, scene.hue1, scene.saturation, scene.luma);
|
|
|
|
|
setSkyColor(2, scene.hue2, scene.saturation - 10, scene.luma - 10);
|
|
|
|
|
|
|
|
|
|
document.body.setAttribute("theme", scene.baseTheme);
|
2026-09-23 00:09:09 +02:00
|
|
|
};
|
2026-09-17 20:03:55 +02:00
|
|
|
|
2026-04-14 17:19:35 +00:00
|
|
|
// exit
|
|
|
|
|
close = (): void => {
|
2026-09-28 15:21:50 +02:00
|
|
|
this.coreViewModel.closeContext(this.contextId);
|
2026-04-14 17:19:35 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
handleContextClose = (): void => {
|
2026-09-28 15:21:50 +02:00
|
|
|
this.isShowingSettingsModal.value = false;
|
|
|
|
|
if (this.requiresReload.value == true) {
|
|
|
|
|
window.location.reload();
|
|
|
|
|
}
|
2026-04-15 12:47:10 +00:00
|
|
|
};
|
2026-04-14 17:19:35 +00:00
|
|
|
|
2026-04-01 08:29:39 +00:00
|
|
|
// init
|
2026-09-28 15:21:50 +02:00
|
|
|
constructor(
|
|
|
|
|
public readonly coreViewModel: CoreViewModel,
|
|
|
|
|
public readonly settingsModel: SettingsModel,
|
|
|
|
|
) {
|
|
|
|
|
super("settings");
|
|
|
|
|
|
|
|
|
|
this.username.value = coreViewModel.settingsModel.username;
|
|
|
|
|
this.usernameInput.value = coreViewModel.settingsModel.username;
|
|
|
|
|
this.firstDayOfWeek.value = coreViewModel.settingsModel.firstDayOfWeek;
|
|
|
|
|
this.language.value = coreViewModel.settingsModel.language;
|
|
|
|
|
this.theme.value = coreViewModel.settingsModel.theme;
|
|
|
|
|
|
|
|
|
|
// subscriptions
|
|
|
|
|
this.firstDayOfWeek.subscribe(this.setFirstDayofWeek);
|
|
|
|
|
this.language.subscribeSilent((newValue) => {
|
|
|
|
|
this.coreViewModel.settingsModel.setLanguage(newValue);
|
|
|
|
|
this.requiresReload.value = true;
|
|
|
|
|
});
|
|
|
|
|
this.theme.subscribeSilent((newValue) => {
|
|
|
|
|
this.coreViewModel.settingsModel.setTheme(newValue);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// set theme
|
|
|
|
|
this.theme.subscribe(() => {
|
|
|
|
|
this.applyTheme();
|
|
|
|
|
});
|
|
|
|
|
SettingsViewModel.generateThemeMedia().addEventListener("change", () =>
|
|
|
|
|
this.applyTheme(),
|
|
|
|
|
);
|
|
|
|
|
const seconds = new Date().getSeconds();
|
|
|
|
|
const secondsUntilNewMinute = 60 - seconds;
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
setInterval(this.applyTheme, 1000 * 60);
|
|
|
|
|
}, secondsUntilNewMinute);
|
|
|
|
|
|
|
|
|
|
// keystrokes
|
|
|
|
|
this.registerKeyStroke(CommonKeys.CloseOrCancel, this.close);
|
2026-04-09 18:09:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static generateThemeMedia(): MediaQueryList {
|
2026-09-28 15:21:50 +02:00
|
|
|
return window.matchMedia("(prefers-color-scheme: dark)");
|
2026-04-09 18:09:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static getSystemTheme(): string {
|
2026-09-28 15:21:50 +02:00
|
|
|
const media = SettingsViewModel.generateThemeMedia();
|
|
|
|
|
return media.matches == true ? ThemeSettings.Dark : ThemeSettings.Light;
|
2026-04-01 08:29:39 +00:00
|
|
|
}
|
2024-07-20 02:00:00 +02:00
|
|
|
}
|
2026-04-08 21:03:36 +00:00
|
|
|
|
|
|
|
|
export enum SettingsModalPages {
|
|
|
|
|
Appearance,
|
2026-09-28 15:21:50 +02:00
|
|
|
Account,
|
|
|
|
|
Regional,
|
|
|
|
|
Info,
|
2026-09-17 20:03:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface DynamicScene {
|
|
|
|
|
hue1: number;
|
|
|
|
|
hue2: number;
|
|
|
|
|
saturation: number;
|
|
|
|
|
luma: number;
|
|
|
|
|
brightness: number;
|
|
|
|
|
baseTheme: ThemeSettings;
|
|
|
|
|
}
|
|
|
|
|
export const DynamicSceneNight: DynamicScene = {
|
|
|
|
|
hue1: 215,
|
|
|
|
|
hue2: 220,
|
2026-09-23 00:09:09 +02:00
|
|
|
saturation: 50,
|
2026-09-17 20:03:55 +02:00
|
|
|
luma: 25,
|
|
|
|
|
brightness: 0.4,
|
|
|
|
|
baseTheme: ThemeSettings.Dark,
|
2026-09-23 00:09:09 +02:00
|
|
|
};
|
2026-09-17 20:03:55 +02:00
|
|
|
export const DynamicSceneSunrise: DynamicScene = {
|
|
|
|
|
hue1: 45,
|
|
|
|
|
hue2: 190,
|
2026-09-23 00:09:09 +02:00
|
|
|
saturation: 100,
|
2026-09-17 20:03:55 +02:00
|
|
|
luma: 50,
|
|
|
|
|
brightness: 1.2,
|
|
|
|
|
baseTheme: ThemeSettings.Light,
|
2026-09-23 00:09:09 +02:00
|
|
|
};
|
2026-09-17 20:03:55 +02:00
|
|
|
export const DynamicSceneDay: DynamicScene = {
|
|
|
|
|
hue1: 190,
|
2026-09-25 17:22:49 +02:00
|
|
|
hue2: 230,
|
2026-09-23 00:09:09 +02:00
|
|
|
saturation: 100,
|
2026-09-17 20:03:55 +02:00
|
|
|
luma: 70,
|
2026-09-25 17:22:49 +02:00
|
|
|
brightness: 1.1,
|
2026-09-17 20:03:55 +02:00
|
|
|
baseTheme: ThemeSettings.Light,
|
2026-09-23 00:09:09 +02:00
|
|
|
};
|
2026-09-17 20:03:55 +02:00
|
|
|
export const DynamicSceneAfternoon: DynamicScene = {
|
2026-09-25 17:22:49 +02:00
|
|
|
hue1: 220,
|
|
|
|
|
hue2: 250,
|
2026-09-23 00:09:09 +02:00
|
|
|
saturation: 100,
|
2026-09-17 20:03:55 +02:00
|
|
|
luma: 50,
|
|
|
|
|
brightness: 0.8,
|
|
|
|
|
baseTheme: ThemeSettings.Light,
|
2026-09-23 00:09:09 +02:00
|
|
|
};
|
2026-09-17 20:03:55 +02:00
|
|
|
export const DynamicSceneSunset: DynamicScene = {
|
|
|
|
|
hue1: 20,
|
|
|
|
|
hue2: 260,
|
2026-09-23 00:09:09 +02:00
|
|
|
saturation: 100,
|
2026-09-17 20:03:55 +02:00
|
|
|
luma: 50,
|
|
|
|
|
brightness: 0.4,
|
|
|
|
|
baseTheme: ThemeSettings.Dark,
|
2026-09-23 00:09:09 +02:00
|
|
|
};
|
2026-09-25 14:18:39 +02:00
|
|
|
export const DynamicSceneBlack: DynamicScene = {
|
|
|
|
|
hue1: 0,
|
|
|
|
|
hue2: 0,
|
|
|
|
|
saturation: 0,
|
|
|
|
|
luma: 0,
|
|
|
|
|
brightness: 0,
|
2026-09-25 14:32:45 +02:00
|
|
|
baseTheme: ThemeSettings.Black,
|
2026-09-25 14:18:39 +02:00
|
|
|
};
|