implement theme setting
This commit is contained in:
Vendored
+13
-23
@@ -57,22 +57,20 @@ main {
|
|||||||
var(--color-2) 30%,
|
var(--color-2) 30%,
|
||||||
transparent 35%);
|
transparent 35%);
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme=dark] #background-wrapper {
|
||||||
#background-wrapper {
|
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
#background-wrapper #sky {
|
body[theme=dark] #background-wrapper #sky {
|
||||||
--color-1: hsl(188, 100%, 30%);
|
--color-1: hsl(188, 100%, 30%);
|
||||||
--color-2: hsl(194, 72%, 24%);
|
--color-2: hsl(194, 72%, 24%);
|
||||||
}
|
}
|
||||||
#background-wrapper #grass-1 {
|
body[theme=dark] #background-wrapper #grass-1 {
|
||||||
--color-1: hsl(120, 30%, 30%);
|
--color-1: hsl(120, 30%, 30%);
|
||||||
--color-2: hsl(120, 30%, 24%);
|
--color-2: hsl(120, 30%, 24%);
|
||||||
}
|
}
|
||||||
#background-wrapper #grass-2 {
|
body[theme=dark] #background-wrapper #grass-2 {
|
||||||
--color-1: hsl(110, 60%, 30%);
|
--color-1: hsl(110, 60%, 30%);
|
||||||
--color-2: hsl(110, 50%, 24%);
|
--color-2: hsl(110, 50%, 24%);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* src/colors.css */
|
/* src/colors.css */
|
||||||
@@ -86,14 +84,12 @@ main {
|
|||||||
--surface-hover: hsla(var(--hue), 100%, 5%, 10%);
|
--surface-hover: hsla(var(--hue), 100%, 5%, 10%);
|
||||||
--surface-active: hsla(var(--hue), 100%, 5%, 15%);
|
--surface-active: hsla(var(--hue), 100%, 5%, 15%);
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme=dark] [color]:not([color=standard]) {
|
||||||
[color]:not([color=standard]) {
|
|
||||||
--accent-luma: var(--accent-luma-dark);
|
--accent-luma: var(--accent-luma-dark);
|
||||||
--accent-text-luma: var(--accent-text-luma-dark);
|
--accent-text-luma: var(--accent-text-luma-dark);
|
||||||
--surface: hsla(var(--hue), 100%, 95%, 5%);
|
--surface: hsla(var(--hue), 100%, 95%, 5%);
|
||||||
--surface-hover: hsla(var(--hue), 100%, 95%, 10%);
|
--surface-hover: hsla(var(--hue), 100%, 95%, 10%);
|
||||||
--surface-active: hsla(var(--hue), 100%, 95%, 15%);
|
--surface-active: hsla(var(--hue), 100%, 95%, 15%);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[color]:not([color=standard]).tile {
|
[color]:not([color=standard]).tile {
|
||||||
--gradient-luma-1: 70%;
|
--gradient-luma-1: 70%;
|
||||||
@@ -111,14 +107,12 @@ main {
|
|||||||
--surface-active: hsl(var(--hue), 100%, var(--active-luma));
|
--surface-active: hsl(var(--hue), 100%, var(--active-luma));
|
||||||
--text-normal: hsl(var(--hue), 100%, var(--text-luma));
|
--text-normal: hsl(var(--hue), 100%, var(--text-luma));
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme=dark] [color]:not([color=standard]).tile {
|
||||||
[color]:not([color=standard]).tile {
|
|
||||||
--gradient-luma-1: 20%;
|
--gradient-luma-1: 20%;
|
||||||
--gradient-luma-2: 15%;
|
--gradient-luma-2: 15%;
|
||||||
--hover-luma: 25%;
|
--hover-luma: 25%;
|
||||||
--active-luma: 30%;
|
--active-luma: 30%;
|
||||||
--text-luma: 90%;
|
--text-luma: 90%;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[color]:not([color=standard]).fill-color {
|
[color]:not([color=standard]).fill-color {
|
||||||
color: white;
|
color: white;
|
||||||
@@ -133,12 +127,10 @@ main {
|
|||||||
--sat: 100%;
|
--sat: 100%;
|
||||||
background: hsl(var(--hue), var(--sat), var(--luma), 40%);
|
background: hsl(var(--hue), var(--sat), var(--luma), 40%);
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme=dark] [color]:not([color=standard]).background {
|
||||||
[color]:not([color=standard]).background {
|
|
||||||
--luma: var(--background-luma-dark);
|
--luma: var(--background-luma-dark);
|
||||||
--sat: 100%;
|
--sat: 100%;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
[color=standard].background {
|
[color=standard].background {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
}
|
}
|
||||||
@@ -147,15 +139,13 @@ main {
|
|||||||
--luma: var(--subtle-luma);
|
--luma: var(--subtle-luma);
|
||||||
background: hsl(var(--hue), 100%, var(--luma), 50%);
|
background: hsl(var(--hue), 100%, var(--luma), 50%);
|
||||||
}
|
}
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme=dark] [color]:not([color=standard]).subtle-background {
|
||||||
[color]:not([color=standard]).subtle-background {
|
|
||||||
--luma: var(--subtle-luma-dark);
|
--luma: var(--subtle-luma-dark);
|
||||||
}
|
}
|
||||||
@media (display-mode: standalone) {
|
@media (display-mode: standalone) {
|
||||||
[color]:not([color=standard]).subtle-background {
|
[color]:not([color=standard]).subtle-background {
|
||||||
--luma: 0%;
|
--luma: 0%;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
[color=coral] {
|
[color=coral] {
|
||||||
--hue: 20;
|
--hue: 20;
|
||||||
|
|||||||
Vendored
+1
-4
@@ -9,10 +9,7 @@
|
|||||||
content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no"
|
content="width=device-width, initial-scale=1.0, viewport-fit=cover, user-scalable=no"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<meta
|
<meta name="theme-color" content="black" />
|
||||||
name="theme-color"
|
|
||||||
content="black"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="styles.css" />
|
<link rel="stylesheet" href="styles.css" />
|
||||||
<link rel="stylesheet" href="index.css" />
|
<link rel="stylesheet" href="index.css" />
|
||||||
|
|||||||
Vendored
+132
-37
@@ -620,7 +620,8 @@
|
|||||||
settingsModel: {
|
settingsModel: {
|
||||||
username: ["user-name"],
|
username: ["user-name"],
|
||||||
firstDayOfWeek: ["first-day-of-week"],
|
firstDayOfWeek: ["first-day-of-week"],
|
||||||
language: ["language"]
|
language: ["language"],
|
||||||
|
theme: ["theme"]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -5060,6 +5061,11 @@
|
|||||||
regional: "Language & Region",
|
regional: "Language & Region",
|
||||||
info: "About Comms"
|
info: "About Comms"
|
||||||
},
|
},
|
||||||
|
themes: {
|
||||||
|
dark: "Dark",
|
||||||
|
light: "Light",
|
||||||
|
system: "Device theme"
|
||||||
|
},
|
||||||
version: "Version",
|
version: "Version",
|
||||||
language: "Language",
|
language: "Language",
|
||||||
firstDayOfWeekLabel: "First day of week"
|
firstDayOfWeekLabel: "First day of week"
|
||||||
@@ -5265,6 +5271,11 @@
|
|||||||
regional: "Sprache & Region",
|
regional: "Sprache & Region",
|
||||||
info: "\xDCber Comms"
|
info: "\xDCber Comms"
|
||||||
},
|
},
|
||||||
|
themes: {
|
||||||
|
dark: "Dunkel",
|
||||||
|
light: "Hell",
|
||||||
|
system: "Ger\xE4teeinstellung"
|
||||||
|
},
|
||||||
version: "Version",
|
version: "Version",
|
||||||
language: "Sprache",
|
language: "Sprache",
|
||||||
firstDayOfWeekLabel: "Erster Wochentag"
|
firstDayOfWeekLabel: "Erster Wochentag"
|
||||||
@@ -5453,6 +5464,11 @@
|
|||||||
regional: "Idioma y Regi\xF3n",
|
regional: "Idioma y Regi\xF3n",
|
||||||
info: "Sobre Comms"
|
info: "Sobre Comms"
|
||||||
},
|
},
|
||||||
|
themes: {
|
||||||
|
dark: "Oscuro",
|
||||||
|
light: "Claro",
|
||||||
|
system: "Seg\xFAn dispositivo"
|
||||||
|
},
|
||||||
version: "Versi\xF3n",
|
version: "Versi\xF3n",
|
||||||
language: "Idioma",
|
language: "Idioma",
|
||||||
firstDayOfWeekLabel: "Primer d\xEDa de la semana"
|
firstDayOfWeekLabel: "Primer d\xEDa de la semana"
|
||||||
@@ -5585,12 +5601,6 @@
|
|||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
es: "Espa\xF1ol"
|
es: "Espa\xF1ol"
|
||||||
};
|
};
|
||||||
var Languages = /* @__PURE__ */ ((Languages2) => {
|
|
||||||
Languages2["English"] = "en";
|
|
||||||
Languages2["German"] = "de";
|
|
||||||
Languages2["Spanish"] = "es";
|
|
||||||
return Languages2;
|
|
||||||
})(Languages || {});
|
|
||||||
|
|
||||||
// src/ViewModel/Global/coreViewModel.ts
|
// src/ViewModel/Global/coreViewModel.ts
|
||||||
var CoreViewModel = class {
|
var CoreViewModel = class {
|
||||||
@@ -6249,61 +6259,60 @@
|
|||||||
// src/Model/Global/settingsModel.ts
|
// src/Model/Global/settingsModel.ts
|
||||||
var SettingsModel = class _SettingsModel {
|
var SettingsModel = class _SettingsModel {
|
||||||
// storage
|
// storage
|
||||||
setName(newValue) {
|
storeSetting(pathName, value) {
|
||||||
this.username = newValue;
|
|
||||||
const path = StorageModel.getPath(
|
const path = StorageModel.getPath(
|
||||||
"settings" /* SettingsModel */,
|
"settings" /* SettingsModel */,
|
||||||
filePaths.settingsModel.username
|
filePaths.settingsModel[pathName]
|
||||||
);
|
);
|
||||||
this.storageModel.write(path, newValue);
|
this.storageModel.write(path, value);
|
||||||
|
}
|
||||||
|
setName(newValue) {
|
||||||
|
this.username = newValue;
|
||||||
|
this.storeSetting("username", newValue);
|
||||||
}
|
}
|
||||||
setFirstDayOfWeek(newValue) {
|
setFirstDayOfWeek(newValue) {
|
||||||
this.firstDayOfWeek = newValue;
|
this.firstDayOfWeek = newValue;
|
||||||
const path = StorageModel.getPath(
|
this.storeSetting("firstDayOfWeek", newValue);
|
||||||
"settings" /* SettingsModel */,
|
|
||||||
filePaths.settingsModel.firstDayOfWeek
|
|
||||||
);
|
|
||||||
this.storageModel.write(path, newValue);
|
|
||||||
}
|
}
|
||||||
setLanguage(newValue) {
|
setLanguage(newValue) {
|
||||||
this.language = newValue;
|
this.language = newValue;
|
||||||
const path = StorageModel.getPath(
|
this.storeSetting("language", newValue);
|
||||||
"settings" /* SettingsModel */,
|
}
|
||||||
filePaths.settingsModel.language
|
setTheme(newValue) {
|
||||||
);
|
this.theme = newValue;
|
||||||
this.storageModel.write(path, newValue);
|
this.storeSetting("theme", newValue);
|
||||||
}
|
}
|
||||||
// load
|
// load
|
||||||
loadUsername() {
|
readSetting(pathName) {
|
||||||
const path = StorageModel.getPath(
|
const path = StorageModel.getPath(
|
||||||
"settings" /* SettingsModel */,
|
"settings" /* SettingsModel */,
|
||||||
filePaths.settingsModel.username
|
filePaths.settingsModel[pathName]
|
||||||
);
|
);
|
||||||
const content = this.storageModel.read(path);
|
return this.storageModel.read(path);
|
||||||
|
}
|
||||||
|
loadUsername() {
|
||||||
|
const content = this.readSetting("username");
|
||||||
this.username = content ?? "";
|
this.username = content ?? "";
|
||||||
}
|
}
|
||||||
loadFirstDayofWeek() {
|
loadFirstDayofWeek() {
|
||||||
const path = StorageModel.getPath(
|
const content = this.readSetting("firstDayOfWeek");
|
||||||
"settings" /* SettingsModel */,
|
|
||||||
filePaths.settingsModel.firstDayOfWeek
|
|
||||||
);
|
|
||||||
const content = this.storageModel.read(path);
|
|
||||||
this.firstDayOfWeek = content ?? "0";
|
this.firstDayOfWeek = content ?? "0";
|
||||||
}
|
}
|
||||||
loadLanguage() {
|
loadLanguage() {
|
||||||
const path = StorageModel.getPath(
|
const content = this.readSetting("language");
|
||||||
"settings" /* SettingsModel */,
|
|
||||||
filePaths.settingsModel.language
|
|
||||||
);
|
|
||||||
const content = this.storageModel.read(path);
|
|
||||||
this.language = content ?? _SettingsModel.getSystemLanguage();
|
this.language = content ?? _SettingsModel.getSystemLanguage();
|
||||||
}
|
}
|
||||||
|
loadTheme() {
|
||||||
|
const content = this.readSetting("theme");
|
||||||
|
this.theme = content ?? "system" /* System */;
|
||||||
|
}
|
||||||
// init
|
// init
|
||||||
constructor(storageModel2) {
|
constructor(storageModel2) {
|
||||||
this.storageModel = storageModel2;
|
this.storageModel = storageModel2;
|
||||||
this.loadUsername();
|
this.loadUsername();
|
||||||
this.loadFirstDayofWeek();
|
this.loadFirstDayofWeek();
|
||||||
this.loadLanguage();
|
this.loadLanguage();
|
||||||
|
this.loadTheme();
|
||||||
}
|
}
|
||||||
static getSystemLanguage() {
|
static getSystemLanguage() {
|
||||||
switch (navigator.language.substring(0, 2)) {
|
switch (navigator.language.substring(0, 2)) {
|
||||||
@@ -6316,9 +6325,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var Languages = /* @__PURE__ */ ((Languages2) => {
|
||||||
|
Languages2["English"] = "en";
|
||||||
|
Languages2["German"] = "de";
|
||||||
|
Languages2["Spanish"] = "es";
|
||||||
|
return Languages2;
|
||||||
|
})(Languages || {});
|
||||||
|
|
||||||
// src/ViewModel/Global/settingsViewModel.ts
|
// src/ViewModel/Global/settingsViewModel.ts
|
||||||
var SettingsViewModel = class {
|
var SettingsViewModel = class _SettingsViewModel {
|
||||||
// init
|
// init
|
||||||
constructor(coreViewModel2) {
|
constructor(coreViewModel2) {
|
||||||
this.coreViewModel = coreViewModel2;
|
this.coreViewModel = coreViewModel2;
|
||||||
@@ -6331,7 +6346,12 @@
|
|||||||
);
|
);
|
||||||
this.requiresReload = new State(false);
|
this.requiresReload = new State(false);
|
||||||
this.firstDayOfWeek = new State("0");
|
this.firstDayOfWeek = new State("0");
|
||||||
this.language = new State("en" /* English */);
|
this.language = new State(
|
||||||
|
"en" /* English */
|
||||||
|
);
|
||||||
|
this.theme = new State(
|
||||||
|
"system" /* System */
|
||||||
|
);
|
||||||
// guards
|
// guards
|
||||||
this.cannotSetName = createProxyState(
|
this.cannotSetName = createProxyState(
|
||||||
[this.usernameInput],
|
[this.usernameInput],
|
||||||
@@ -6360,15 +6380,41 @@
|
|||||||
this.showModalPage = (page) => {
|
this.showModalPage = (page) => {
|
||||||
this.selectedModalPage.value = page;
|
this.selectedModalPage.value = page;
|
||||||
};
|
};
|
||||||
|
// view
|
||||||
|
this.applyTheme = () => {
|
||||||
|
let theme = this.theme.value;
|
||||||
|
if (theme == "system" /* System */) {
|
||||||
|
theme = _SettingsViewModel.getSystemTheme();
|
||||||
|
}
|
||||||
|
document.body.setAttribute("theme", theme);
|
||||||
|
};
|
||||||
this.username.value = coreViewModel2.settingsModel.username;
|
this.username.value = coreViewModel2.settingsModel.username;
|
||||||
this.usernameInput.value = coreViewModel2.settingsModel.username;
|
this.usernameInput.value = coreViewModel2.settingsModel.username;
|
||||||
this.firstDayOfWeek.value = coreViewModel2.settingsModel.firstDayOfWeek;
|
this.firstDayOfWeek.value = coreViewModel2.settingsModel.firstDayOfWeek;
|
||||||
this.language.value = coreViewModel2.settingsModel.language;
|
this.language.value = coreViewModel2.settingsModel.language;
|
||||||
|
this.theme.value = coreViewModel2.settingsModel.theme;
|
||||||
this.firstDayOfWeek.subscribe(this.setFirstDayofWeek);
|
this.firstDayOfWeek.subscribe(this.setFirstDayofWeek);
|
||||||
this.language.subscribeSilent((newValue) => {
|
this.language.subscribeSilent((newValue) => {
|
||||||
this.coreViewModel.settingsModel.setLanguage(newValue);
|
this.coreViewModel.settingsModel.setLanguage(newValue);
|
||||||
this.requiresReload.value = true;
|
this.requiresReload.value = true;
|
||||||
});
|
});
|
||||||
|
this.theme.subscribeSilent((newValue) => {
|
||||||
|
this.coreViewModel.settingsModel.setTheme(newValue);
|
||||||
|
});
|
||||||
|
this.theme.subscribe(() => {
|
||||||
|
this.applyTheme();
|
||||||
|
});
|
||||||
|
_SettingsViewModel.generateThemeMedia().addEventListener(
|
||||||
|
"change",
|
||||||
|
() => this.applyTheme()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
static generateThemeMedia() {
|
||||||
|
return window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
}
|
||||||
|
static getSystemTheme() {
|
||||||
|
const media = _SettingsViewModel.generateThemeMedia();
|
||||||
|
return media.matches == true ? "dark" /* Dark */ : "light" /* Light */;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -6736,6 +6782,33 @@
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// src/View/Components/optionButton.tsx
|
||||||
|
function OptionButton(text, value, selection) {
|
||||||
|
function select() {
|
||||||
|
selection.value = value;
|
||||||
|
}
|
||||||
|
const isSelected = createProxyState(
|
||||||
|
[selection],
|
||||||
|
() => selection.value == value
|
||||||
|
);
|
||||||
|
return /* @__PURE__ */ createElement("button", { class: "standard", "toggle:selected": isSelected, "on:click": select }, text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// src/View/Components/optionButtonList.tsx
|
||||||
|
function OptionButtonList(options, selection) {
|
||||||
|
function OptionToView(option) {
|
||||||
|
const [text, value] = option;
|
||||||
|
return OptionButton(text, value, selection);
|
||||||
|
}
|
||||||
|
return /* @__PURE__ */ createElement(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
class: "flex-column gap",
|
||||||
|
"children:append": [options, OptionToView]
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// src/View/Modals/settingsModal.tsx
|
// src/View/Modals/settingsModal.tsx
|
||||||
function SettingsModal(coreViewModel2, settingsViewModel2) {
|
function SettingsModal(coreViewModel2, settingsViewModel2) {
|
||||||
const detailView = createProxyState(
|
const detailView = createProxyState(
|
||||||
@@ -6743,7 +6816,10 @@
|
|||||||
() => {
|
() => {
|
||||||
switch (settingsViewModel2.selectedModalPage.value) {
|
switch (settingsViewModel2.selectedModalPage.value) {
|
||||||
case 0 /* Appearance */:
|
case 0 /* Appearance */:
|
||||||
return /* @__PURE__ */ createElement("div", null, "Appearance");
|
return SettingsAppearancePane(
|
||||||
|
coreViewModel2,
|
||||||
|
settingsViewModel2
|
||||||
|
);
|
||||||
case 1 /* Regional */:
|
case 1 /* Regional */:
|
||||||
return SettingsRegionalPane(
|
return SettingsRegionalPane(
|
||||||
coreViewModel2,
|
coreViewModel2,
|
||||||
@@ -6822,6 +6898,25 @@
|
|||||||
)
|
)
|
||||||
)), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))));
|
)), /* @__PURE__ */ createElement("span", { class: "icon" }, "arrow_drop_down"))));
|
||||||
}
|
}
|
||||||
|
function SettingsAppearancePane(coreViewModel2, settingsViewModel2) {
|
||||||
|
return /* @__PURE__ */ createElement("div", { class: "slide-up" }, /* @__PURE__ */ createElement("h2", null, coreViewModel2.translations.settings.pages.appearance), /* @__PURE__ */ createElement("hr", null), OptionButtonList(
|
||||||
|
new ListState([
|
||||||
|
[
|
||||||
|
coreViewModel2.translations.settings.themes.dark,
|
||||||
|
"dark" /* Dark */
|
||||||
|
],
|
||||||
|
[
|
||||||
|
coreViewModel2.translations.settings.themes.light,
|
||||||
|
"light" /* Light */
|
||||||
|
],
|
||||||
|
[
|
||||||
|
coreViewModel2.translations.settings.themes.system,
|
||||||
|
"system" /* System */
|
||||||
|
]
|
||||||
|
]),
|
||||||
|
settingsViewModel2.theme
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// src/index.tsx
|
// src/index.tsx
|
||||||
var storageModel = new StorageModel();
|
var storageModel = new StorageModel();
|
||||||
|
|||||||
Vendored
+5
-7
@@ -74,8 +74,7 @@ COLORS
|
|||||||
--danger-active: #750e13;
|
--danger-active: #750e13;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme="dark"] {
|
||||||
:root {
|
|
||||||
--background: hsla(0, 0%, 9%, 100%);
|
--background: hsla(0, 0%, 9%, 100%);
|
||||||
--background-glass: hsla(0, 0%, 9%, 20%);
|
--background-glass: hsla(0, 0%, 9%, 20%);
|
||||||
--background-transparent: hsla(0, 0%, 100%, 5%);
|
--background-transparent: hsla(0, 0%, 100%, 5%);
|
||||||
@@ -101,25 +100,24 @@ COLORS
|
|||||||
|
|
||||||
--link: #78a9ff;
|
--link: #78a9ff;
|
||||||
--link-hover: #a6c8ff;
|
--link-hover: #a6c8ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (display-mode: standalone) {
|
@media (display-mode: standalone) {
|
||||||
:root {
|
:root {
|
||||||
--background: hsla(0, 0%, 0%, 100%);
|
--background: hsla(0, 0%, 0%, 100%);
|
||||||
--background-transparent: hsla(0, 0%, 0%, 60%);
|
--background-transparent: hsla(0, 0%, 0%, 60%);
|
||||||
--layer: hsla(0, 0%, 0%, 60%);
|
--layer: hsla(0, 0%, 0%, 60%);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* basic */
|
/* basic */
|
||||||
html {
|
html {
|
||||||
background: black;
|
background: black;
|
||||||
color: var(--text-normal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--background);
|
background: var(--background);
|
||||||
|
color: var(--text-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile {
|
.tile {
|
||||||
@@ -284,7 +282,7 @@ SIZES
|
|||||||
|
|
||||||
/* padding */
|
/* padding */
|
||||||
--control-padding: 1rem;
|
--control-padding: 1rem;
|
||||||
--control-padding-compact: .4rem .8rem;
|
--control-padding-compact: 0.4rem 0.8rem;
|
||||||
--control-marker-padding: 0.7rem;
|
--control-marker-padding: 0.7rem;
|
||||||
--content-padding: 1rem;
|
--content-padding: 1rem;
|
||||||
--modal-padding: 1.25rem;
|
--modal-padding: 1.25rem;
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ export default class SettingsModel {
|
|||||||
theme: string;
|
theme: string;
|
||||||
|
|
||||||
// storage
|
// storage
|
||||||
private storeSetting(pathName: keyof typeof filePaths.settingsModel, value: string): void {
|
private storeSetting(
|
||||||
|
pathName: keyof typeof filePaths.settingsModel,
|
||||||
|
value: string,
|
||||||
|
): void {
|
||||||
const path = StorageModel.getPath(
|
const path = StorageModel.getPath(
|
||||||
StorageModelSubPaths.SettingsModel,
|
StorageModelSubPaths.SettingsModel,
|
||||||
filePaths.settingsModel[pathName],
|
filePaths.settingsModel[pathName],
|
||||||
@@ -41,7 +44,9 @@ export default class SettingsModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load
|
// load
|
||||||
private readSetting(pathName: keyof typeof filePaths.settingsModel): string {
|
private readSetting(
|
||||||
|
pathName: keyof typeof filePaths.settingsModel,
|
||||||
|
): string {
|
||||||
const path = StorageModel.getPath(
|
const path = StorageModel.getPath(
|
||||||
StorageModelSubPaths.SettingsModel,
|
StorageModelSubPaths.SettingsModel,
|
||||||
filePaths.settingsModel[pathName],
|
filePaths.settingsModel[pathName],
|
||||||
@@ -76,6 +81,7 @@ export default class SettingsModel {
|
|||||||
this.loadUsername();
|
this.loadUsername();
|
||||||
this.loadFirstDayofWeek();
|
this.loadFirstDayofWeek();
|
||||||
this.loadLanguage();
|
this.loadLanguage();
|
||||||
|
this.loadTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getSystemLanguage(): Languages {
|
static getSystemLanguage(): Languages {
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
|
export function OptionButton<T>(
|
||||||
|
text: string,
|
||||||
|
value: T,
|
||||||
|
selection: React.State<T>,
|
||||||
|
) {
|
||||||
|
function select() {
|
||||||
|
selection.value = value;
|
||||||
|
}
|
||||||
|
const isSelected = React.createProxyState(
|
||||||
|
[selection],
|
||||||
|
() => selection.value == value,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button class="standard" toggle:selected={isSelected} on:click={select}>
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import * as React from "bloatless-react";
|
||||||
|
import { OptionButton } from "./optionButton";
|
||||||
|
|
||||||
|
export function OptionButtonList<T>(
|
||||||
|
options: React.ListState<[string, T]>,
|
||||||
|
selection: React.State<T>,
|
||||||
|
) {
|
||||||
|
function OptionToView(option: [string, T]) {
|
||||||
|
const [text, value] = option;
|
||||||
|
return OptionButton(text, value, selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
class="flex-column gap"
|
||||||
|
children:append={[options, OptionToView]}
|
||||||
|
></div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -8,7 +8,9 @@ import { SplitModal } from "../Components/splitModal";
|
|||||||
import { InfoTile } from "../Components/infoTile";
|
import { InfoTile } from "../Components/infoTile";
|
||||||
import { Option } from "../Components/option";
|
import { Option } from "../Components/option";
|
||||||
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
import CoreViewModel from "../../ViewModel/Global/coreViewModel";
|
||||||
import { Languages } from "../../Model/Global/settingsModel";
|
import { Languages, ThemeSettings } from "../../Model/Global/settingsModel";
|
||||||
|
import { OptionButtonList } from "../Components/optionButtonList";
|
||||||
|
import { stringify } from "../../Model/Utility/utility";
|
||||||
|
|
||||||
export function SettingsModal(
|
export function SettingsModal(
|
||||||
coreViewModel: CoreViewModel,
|
coreViewModel: CoreViewModel,
|
||||||
@@ -19,7 +21,10 @@ export function SettingsModal(
|
|||||||
() => {
|
() => {
|
||||||
switch (settingsViewModel.selectedModalPage.value) {
|
switch (settingsViewModel.selectedModalPage.value) {
|
||||||
case SettingsModalPages.Appearance:
|
case SettingsModalPages.Appearance:
|
||||||
return <div>Appearance</div>;
|
return SettingsAppearancePane(
|
||||||
|
coreViewModel,
|
||||||
|
settingsViewModel,
|
||||||
|
);
|
||||||
case SettingsModalPages.Regional:
|
case SettingsModalPages.Regional:
|
||||||
return SettingsRegionalPane(
|
return SettingsRegionalPane(
|
||||||
coreViewModel,
|
coreViewModel,
|
||||||
@@ -161,8 +166,7 @@ function SettingsRegionalPane(
|
|||||||
Option(
|
Option(
|
||||||
languageNames[language],
|
languageNames[language],
|
||||||
language,
|
language,
|
||||||
language ==
|
language == settingsViewModel.language.value,
|
||||||
settingsViewModel.language.value,
|
|
||||||
),
|
),
|
||||||
)}
|
)}
|
||||||
</select>
|
</select>
|
||||||
@@ -172,3 +176,32 @@ function SettingsRegionalPane(
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function SettingsAppearancePane(
|
||||||
|
coreViewModel: CoreViewModel,
|
||||||
|
settingsViewModel: SettingsViewModel,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div class="slide-up">
|
||||||
|
<h2>{coreViewModel.translations.settings.pages.appearance}</h2>
|
||||||
|
<hr></hr>
|
||||||
|
{OptionButtonList(
|
||||||
|
new React.ListState<[string, ThemeSettings]>([
|
||||||
|
[
|
||||||
|
coreViewModel.translations.settings.themes.dark,
|
||||||
|
ThemeSettings.Dark,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
coreViewModel.translations.settings.themes.light,
|
||||||
|
ThemeSettings.Light,
|
||||||
|
],
|
||||||
|
[
|
||||||
|
coreViewModel.translations.settings.themes.system,
|
||||||
|
ThemeSettings.System,
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
settingsViewModel.theme,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -80,6 +80,11 @@ const englishTranslations = {
|
|||||||
regional: "Language & Region",
|
regional: "Language & Region",
|
||||||
info: "About Comms",
|
info: "About Comms",
|
||||||
},
|
},
|
||||||
|
themes: {
|
||||||
|
dark: "Dark",
|
||||||
|
light: "Light",
|
||||||
|
system: "Device theme",
|
||||||
|
},
|
||||||
|
|
||||||
version: "Version",
|
version: "Version",
|
||||||
language: "Language",
|
language: "Language",
|
||||||
@@ -375,6 +380,11 @@ export const allTranslations: {
|
|||||||
regional: "Sprache & Region",
|
regional: "Sprache & Region",
|
||||||
info: "Über Comms",
|
info: "Über Comms",
|
||||||
},
|
},
|
||||||
|
themes: {
|
||||||
|
dark: "Dunkel",
|
||||||
|
light: "Hell",
|
||||||
|
system: "Geräteeinstellung",
|
||||||
|
},
|
||||||
|
|
||||||
version: "Version",
|
version: "Version",
|
||||||
language: "Sprache",
|
language: "Sprache",
|
||||||
@@ -639,6 +649,11 @@ export const allTranslations: {
|
|||||||
regional: "Idioma y Región",
|
regional: "Idioma y Región",
|
||||||
info: "Sobre Comms",
|
info: "Sobre Comms",
|
||||||
},
|
},
|
||||||
|
themes: {
|
||||||
|
dark: "Oscuro",
|
||||||
|
light: "Claro",
|
||||||
|
system: "Según dispositivo",
|
||||||
|
},
|
||||||
|
|
||||||
version: "Versión",
|
version: "Versión",
|
||||||
language: "Idioma",
|
language: "Idioma",
|
||||||
@@ -832,6 +847,6 @@ export const languageNames = {
|
|||||||
en: "English",
|
en: "English",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
es: "Español",
|
es: "Español",
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Translations = typeof englishTranslations;
|
export type Translations = typeof englishTranslations;
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
import * as React from "bloatless-react";
|
import * as React from "bloatless-react";
|
||||||
|
|
||||||
import CoreViewModel from "./coreViewModel";
|
import CoreViewModel from "./coreViewModel";
|
||||||
import SettingsModel from "../../Model/Global/settingsModel";
|
import { Languages, ThemeSettings } from "../../Model/Global/settingsModel";
|
||||||
import { Languages } from "../../View/translations";
|
|
||||||
|
|
||||||
export default class SettingsViewModel {
|
export default class SettingsViewModel {
|
||||||
// state
|
// state
|
||||||
@@ -15,7 +14,12 @@ export default class SettingsViewModel {
|
|||||||
requiresReload = new React.State<boolean>(false);
|
requiresReload = new React.State<boolean>(false);
|
||||||
|
|
||||||
firstDayOfWeek: React.State<string> = new React.State("0");
|
firstDayOfWeek: React.State<string> = new React.State("0");
|
||||||
language: React.State<Languages|string> = new React.State(Languages.English);
|
language: React.State<Languages | string> = new React.State(
|
||||||
|
Languages.English,
|
||||||
|
);
|
||||||
|
theme: React.State<ThemeSettings | string> = new React.State(
|
||||||
|
ThemeSettings.System,
|
||||||
|
);
|
||||||
|
|
||||||
// guards
|
// guards
|
||||||
cannotSetName: React.State<boolean> = React.createProxyState(
|
cannotSetName: React.State<boolean> = React.createProxyState(
|
||||||
@@ -54,19 +58,49 @@ export default class SettingsViewModel {
|
|||||||
this.selectedModalPage.value = page;
|
this.selectedModalPage.value = page;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// view
|
||||||
|
applyTheme = (): void => {
|
||||||
|
let theme = this.theme.value;
|
||||||
|
if (theme == ThemeSettings.System) {
|
||||||
|
theme = SettingsViewModel.getSystemTheme();
|
||||||
|
}
|
||||||
|
document.body.setAttribute("theme", theme);
|
||||||
|
};
|
||||||
|
|
||||||
// init
|
// init
|
||||||
constructor(public coreViewModel: CoreViewModel) {
|
constructor(public coreViewModel: CoreViewModel) {
|
||||||
this.username.value = coreViewModel.settingsModel.username;
|
this.username.value = coreViewModel.settingsModel.username;
|
||||||
this.usernameInput.value = coreViewModel.settingsModel.username;
|
this.usernameInput.value = coreViewModel.settingsModel.username;
|
||||||
this.firstDayOfWeek.value = coreViewModel.settingsModel.firstDayOfWeek;
|
this.firstDayOfWeek.value = coreViewModel.settingsModel.firstDayOfWeek;
|
||||||
this.language.value = coreViewModel.settingsModel.language;
|
this.language.value = coreViewModel.settingsModel.language;
|
||||||
|
this.theme.value = coreViewModel.settingsModel.theme;
|
||||||
|
|
||||||
// subscriptions
|
// subscriptions
|
||||||
this.firstDayOfWeek.subscribe(this.setFirstDayofWeek);
|
this.firstDayOfWeek.subscribe(this.setFirstDayofWeek);
|
||||||
this.language.subscribeSilent((newValue) => {
|
this.language.subscribeSilent((newValue) => {
|
||||||
this.coreViewModel.settingsModel.setLanguage(newValue);
|
this.coreViewModel.settingsModel.setLanguage(newValue);
|
||||||
this.requiresReload.value = true;
|
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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static generateThemeMedia(): MediaQueryList {
|
||||||
|
return window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
}
|
||||||
|
|
||||||
|
static getSystemTheme(): string {
|
||||||
|
const media = SettingsViewModel.generateThemeMedia();
|
||||||
|
return media.matches == true ? ThemeSettings.Dark : ThemeSettings.Light;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-9
@@ -76,23 +76,21 @@ main {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme="dark"] #background-wrapper {
|
||||||
#background-wrapper {
|
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
#background-wrapper #sky {
|
body[theme="dark"] #background-wrapper #sky {
|
||||||
--color-1: hsl(188, 100%, 30%);
|
--color-1: hsl(188, 100%, 30%);
|
||||||
--color-2: hsl(194, 72%, 24%);
|
--color-2: hsl(194, 72%, 24%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#background-wrapper #grass-1 {
|
body[theme="dark"] #background-wrapper #grass-1 {
|
||||||
--color-1: hsl(120, 30%, 30%);
|
--color-1: hsl(120, 30%, 30%);
|
||||||
--color-2: hsl(120, 30%, 24%);
|
--color-2: hsl(120, 30%, 24%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#background-wrapper #grass-2 {
|
body[theme="dark"] #background-wrapper #grass-2 {
|
||||||
--color-1: hsl(110, 60%, 30%);
|
--color-1: hsl(110, 60%, 30%);
|
||||||
--color-2: hsl(110, 50%, 24%);
|
--color-2: hsl(110, 50%, 24%);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-14
@@ -12,15 +12,13 @@
|
|||||||
--surface-active: hsla(var(--hue), 100%, 5%, 15%);
|
--surface-active: hsla(var(--hue), 100%, 5%, 15%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme="dark"] [color]:not([color="standard"]) {
|
||||||
[color]:not([color="standard"]) {
|
|
||||||
--accent-luma: var(--accent-luma-dark);
|
--accent-luma: var(--accent-luma-dark);
|
||||||
--accent-text-luma: var(--accent-text-luma-dark);
|
--accent-text-luma: var(--accent-text-luma-dark);
|
||||||
|
|
||||||
--surface: hsla(var(--hue), 100%, 95%, 5%);
|
--surface: hsla(var(--hue), 100%, 95%, 5%);
|
||||||
--surface-hover: hsla(var(--hue), 100%, 95%, 10%);
|
--surface-hover: hsla(var(--hue), 100%, 95%, 10%);
|
||||||
--surface-active: hsla(var(--hue), 100%, 95%, 15%);
|
--surface-active: hsla(var(--hue), 100%, 95%, 15%);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tile */
|
/* tile */
|
||||||
@@ -44,8 +42,7 @@
|
|||||||
--text-normal: hsl(var(--hue), 100%, var(--text-luma));
|
--text-normal: hsl(var(--hue), 100%, var(--text-luma));
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme="dark"] [color]:not([color="standard"]).tile {
|
||||||
[color]:not([color="standard"]).tile {
|
|
||||||
--gradient-luma-1: 20%;
|
--gradient-luma-1: 20%;
|
||||||
--gradient-luma-2: 15%;
|
--gradient-luma-2: 15%;
|
||||||
|
|
||||||
@@ -53,7 +50,6 @@
|
|||||||
--active-luma: 30%;
|
--active-luma: 30%;
|
||||||
|
|
||||||
--text-luma: 90%;
|
--text-luma: 90%;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fill */
|
/* fill */
|
||||||
@@ -74,12 +70,10 @@
|
|||||||
background: hsl(var(--hue), var(--sat), var(--luma), 40%);
|
background: hsl(var(--hue), var(--sat), var(--luma), 40%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme="dark"] [color]:not([color="standard"]).background {
|
||||||
[color]:not([color="standard"]).background {
|
|
||||||
--luma: var(--background-luma-dark);
|
--luma: var(--background-luma-dark);
|
||||||
--sat: 100%;
|
--sat: 100%;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[color="standard"].background {
|
[color="standard"].background {
|
||||||
background: var(--surface);
|
background: var(--surface);
|
||||||
@@ -92,16 +86,14 @@
|
|||||||
background: hsl(var(--hue), 100%, var(--luma), 50%);
|
background: hsl(var(--hue), 100%, var(--luma), 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
body[theme="dark"] [color]:not([color="standard"]).subtle-background {
|
||||||
[color]:not([color="standard"]).subtle-background {
|
|
||||||
--luma: var(--subtle-luma-dark);
|
--luma: var(--subtle-luma-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (display-mode: standalone) {
|
@media (display-mode: standalone) {
|
||||||
[color]:not([color="standard"]).subtle-background {
|
[color]:not([color="standard"]).subtle-background {
|
||||||
--luma: 0%;
|
--luma: 0%;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* definitions */
|
/* definitions */
|
||||||
|
|||||||
Reference in New Issue
Block a user