Added themes

This commit is contained in:
Prozilla 2024-05-10 17:39:24 +02:00
parent 2ede410bec
commit e74765e2c8
No known key found for this signature in database
GPG key ID: 5858DFE71CAF31EE
21 changed files with 1193 additions and 380 deletions

View file

@ -53,7 +53,7 @@ export default tseslint.config(
}
],
"comma-spacing": "off",
"@typescript-eslint/comma-spacing": "error",
"@typescript-eslint/comma-spacing": "warn",
"@typescript-eslint/no-unused-vars": [
"error",
{

1339
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,7 +10,6 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"predeploy": "npm run build",
"deploy": "sh deploy.sh"
},
@ -43,6 +42,7 @@
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@eslint/js": "^9.2.0",
"@types/jest": "^29.5.12",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/webpack-env": "^1.18.4",
"@typescript-eslint/eslint-plugin": "^7.8.0",
@ -51,6 +51,8 @@
"eslint-plugin-jsdoc": "^46.4.6",
"eslint-plugin-react": "^7.34.1",
"gh-pages": "^5.0.0",
"jest": "^27.5.1",
"ts-jest": "^29.1.2",
"typescript": "^4.9.5",
"typescript-eslint": "^7.8.0"
},

3
public/config/theme.xml Normal file
View file

@ -0,0 +1,3 @@
<options>
<theme>0</theme>
</options>

View file

@ -11,4 +11,4 @@
.App > * {
pointer-events: auto;
}
}

View file

@ -6,11 +6,12 @@ import { VirtualRootProvider } from "./hooks/virtual-drive/virtualRootContext";
import { Desktop } from "./components/desktop/Desktop";
import { SettingsManagerProvider } from "./hooks/settings/settingsManagerContext";
import { ModalsView } from "./components/modals/ModalsView";
import { FC, useEffect } from "react";
import { ReactElement, useEffect } from "react";
import { ZIndexManagerProvider } from "./hooks/z-index/zIndexManagerContext";
import { ModalsManagerProvider } from "./hooks/modals/modalsManagerContext";
import { ThemeProvider } from "./hooks/themes/themes";
const App: FC = () => {
export default function App(): ReactElement {
useEffect(() => {
const onContextMenu = (event: Event) => {
event.preventDefault();
@ -29,18 +30,18 @@ const App: FC = () => {
<WindowsManagerProvider>
<ModalsManagerProvider>
<SettingsManagerProvider>
<div className={styles.App}>
<Taskbar/>
<WindowsView/>
<ModalsView/>
<Desktop/>
</div>
<ThemeProvider>
<div className={styles.App}>
<Taskbar/>
<WindowsView/>
<ModalsView/>
<Desktop/>
</div>
</ThemeProvider>
</SettingsManagerProvider>
</ModalsManagerProvider>
</WindowsManagerProvider>
</ZIndexManagerProvider>
</VirtualRootProvider>
);
};
export default App;
};

View file

@ -154,7 +154,7 @@
}
.Button-red {
--text-color: var(--background-color-a) !important;
--text-color: var(--dark-grey-b) !important;
--normal-color: var(--red-a) !important;
--hover-color: var(--red-b) !important;
}
@ -241,4 +241,26 @@
width: inherit;
height: inherit;
object-fit: contain;
}
.Dropdown {
width: auto;
max-width: calc(60% - var(--gap));
padding: 0.5rem 1rem;
color: var(--text-color);
background-color: var(--background-color-a);
border: none;
border-radius: 0.5rem;
outline: none;
font-size: 0.875em;
}
.Dropdown > * {
color: inherit;
background-color: inherit;
border: none;
border-radius: 0.5rem;
outline: none;
font-family: inherit;
font-size: inherit;
}

View file

@ -9,6 +9,13 @@ import { StorageTab } from "./tabs/StorageSettings";
import { AppsSettings } from "./tabs/AppsSettings";
import { WindowProps } from "../../windows/WindowView";
export const TABS = {
APPS: 0,
APPEARANCE: 1,
STORAGE: 2,
ABOUT: 3,
};
interface SettingsProps extends WindowProps {
tab?: number;
}

View file

@ -13,24 +13,43 @@ import { DEFAULT_FILE_SELECTOR_SIZE } from "../../../../config/modals.config";
import { IMAGE_FORMATS } from "../../../../config/apps/mediaViewer.config";
import { VirtualFile } from "../../../../features/virtual-drive/file/virtualFile";
import { VirtualFolder } from "../../../../features/virtual-drive/folder/virtualFolder";
import { THEMES } from "../../../../hooks/themes/themes";
export function AppearanceSettings() {
const virtualRoot = useVirtualRoot();
const settingsManager = useSettingsManager();
const [theme, setTheme] = useState(0);
const [wallpaper, setWallpaper] = useState<string>(null);
const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
const desktopSettings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.desktop);
const themeSettings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.theme);
const { openWindowedModal } = useWindowedModal();
useEffect(() => {
void settings.get("wallpaper", setWallpaper);
}, [settings]);
void desktopSettings.get("wallpaper", setWallpaper);
void themeSettings.get("theme", (value: string) => { setTheme(parseInt(value)); });
}, [desktopSettings, themeSettings]);
const onChange = (event: Event) => {
const onWallpaperChange = (event: Event) => {
const value = (event.target as HTMLInputElement).value;
void settings.set("wallpaper", value);
void desktopSettings.set("wallpaper", value);
};
const onThemeChange = (event: Event) => {
const value = (event.target as HTMLInputElement).value;
void themeSettings.set("theme", value);
};
return (<>
<div className={styles["Option"]}>
<p className={styles["Label"]}>Theme</p>
<div className={styles["Input"]}>
<select className={styles["Dropdown"]} aria-label="theme" value={theme} onChange={onThemeChange as unknown as ChangeEventHandler}>
{Object.entries(THEMES).map(([key, value]) =>
<option key={key} value={key}>{value}</option>
)}
</select>
</div>
</div>
<div className={styles["Option"]}>
<p className={styles["Label"]}>Wallpaper</p>
<Button
@ -42,7 +61,7 @@ export function AppearanceSettings() {
type={SELECTOR_MODE.SINGLE}
allowedFormats={IMAGE_FORMATS}
onFinish={(file: VirtualFile) => {
void settings.set("wallpaper", file.source);
void desktopSettings.set("wallpaper", file.source);
}}
{...props}
/>
@ -59,7 +78,7 @@ export function AppearanceSettings() {
value={source}
aria-label="Wallpaper image"
checked={source === wallpaper}
onChange={onChange as unknown as ChangeEventHandler}
onChange={onWallpaperChange as unknown as ChangeEventHandler}
tabIndex={0}
/>
<img src={source} alt={id} draggable="false"/>

View file

@ -38,6 +38,10 @@
text-shadow: 0.1rem 0.1rem 0.2rem hsla(var(--background-color-a-hsl), 75%);
}
.Item p {
color: var(--white-a);
}
.Item svg {
filter: drop-shadow(0.1rem 0.1rem 0.2rem hsla(var(--background-color-a-hsl), 50%));
}

View file

@ -25,6 +25,7 @@ import ModalsManager from "../../features/modals/modalsManager";
import { VirtualFolder } from "../../features/virtual-drive/folder/virtualFolder";
import { VirtualFolderLink } from "../../features/virtual-drive/folder/virtualFolderLink";
import { VirtualFile } from "../../features/virtual-drive/file/virtualFile";
import { TABS } from "../apps/settings/Settings";
export const Desktop = memo(() => {
const settingsManager = useSettingsManager();
@ -58,7 +59,7 @@ export const Desktop = memo(() => {
reloadViewport();
}}/>
<ClickAction label="Change appearance" icon={faPaintBrush} onTrigger={() => {
windowsManager.open("settings", { tab: 0 });
windowsManager.open("settings", { tab: TABS.APPEARANCE });
}}/>
<Divider/>
<ClickAction label={`Open in ${APP_NAMES.FILE_EXPLORER}`} icon={APP_ICONS.FILE_EXPLORER} onTrigger={() => {

View file

@ -11,7 +11,7 @@
display: flex;
min-width: 16rem;
max-width: 32rem;
background-color: rgba(25, 25, 25, 75%);
background-color: hsla(var(--background-color-c-hsl), 75%);
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;

View file

@ -17,7 +17,7 @@
min-width: 13rem;
max-width: 19rem;
padding: 0.5rem;
background-color: rgba(25, 25, 25, 75%);
background-color: hsla(var(--background-color-c-hsl), 75%);
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
border-bottom-right-radius: 0.5rem;

View file

@ -9,7 +9,7 @@
.Container-inner {
opacity: 1;
display: flex;
background-color: rgba(25, 25, 25, 75%);
background-color: hsla(var(--background-color-c-hsl), 75%);
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;

View file

@ -62,7 +62,7 @@ export function generateUrl(options: { appId: string; fullscreen: boolean; }) {
return baseUrl;
const { appId, fullscreen, ...extraOptions } = options;
const params = new URLSearchParams();
const params: URLSearchParams & { size?: number } = new URLSearchParams();
if (appId)
params.set("app", appId);

View file

@ -6,6 +6,7 @@ export class SettingsManager {
desktop: "~/.config/desktop.xml",
taskbar: "~/.config/taskbar.xml",
apps: "~/.config/apps.xml",
theme: "~/.config/theme.xml"
};
#pathToSettings: { [s: string]: Settings; } = {};

View file

@ -21,6 +21,8 @@ export function loadDefaultData(virtualRoot: VirtualRoot) {
file.setSource("/config/taskbar.xml");
}).createFile("apps", "xml", (file) => {
file.setSource("/config/apps.xml");
}).createFile("theme", "xml", (file) => {
file.setSource("/config/apps.xml");
});
})
.createFolder("Pictures", (folder) => {
@ -131,6 +133,8 @@ export function loadDefaultData(virtualRoot: VirtualRoot) {
file.setSource(`${process.env.PUBLIC_URL}/config/desktop.xml`);
}).createFile("taskbar", "xml", (file) => {
file.setSource(`${process.env.PUBLIC_URL}/config/taskbar.xml`);
}).createFile("theme", "xml", (file) => {
file.setSource(`${process.env.PUBLIC_URL}/config/theme.xml`);
});
}).createFolder("documents", (folder) => {
folder.createFile("info", "md", (file) => {

View file

@ -0,0 +1,31 @@
import { ReactElement, useEffect, useState } from "react";
import { useSettingsManager } from "../settings/settingsManagerContext";
import { SettingsManager } from "../../features/settings/settingsManager";
export const THEMES = {
0: "Dark",
1: "Light",
4: "Muted",
2: "Cherry",
3: "Mango",
5: "Aqua",
6: "Grape"
};
interface ThemeProviderProps {
children: ReactElement;
}
export function ThemeProvider({ children }: ThemeProviderProps): ReactElement {
const [theme, setTheme] = useState(0);
const settingsManager = useSettingsManager();
const themeSettings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.theme);
useEffect(() => {
void themeSettings.get("theme", (value: string) => { setTheme(parseInt(value)); });
}, [themeSettings]);
return <div className={`${THEMES[theme]}-theme`}>
{children}
</div>;
}

View file

@ -1,5 +1,6 @@
@import "./global/variables.css";
@import "./global/fonts.css";
@import "./global/themes.css";
@import "./global/scrollbar.css";
html, body, #root {

View file

@ -0,0 +1,70 @@
.Light-theme {
--foreground-color-a: var(--dark-grey-ca);
--foreground-color-b: var(--dark-grey-c);
--foreground-color-c: var(--dark-grey-b);
--background-color-a: var(--grey-aa);
--background-color-b: var(--grey-a);
--background-color-ba: var(--white-b);
--background-color-c: var(--white-a);
--background-color-d: var(--white-b);
--taskbar-color: rgba(255, 255, 255, 25%);
--background-color-c-hsl: 210, 45%, 90%;
}
.Cherry-theme, .Mango-theme, .Muted-theme, .Aqua-theme, .Grape-theme {
--hue: 0;
--saturation: 1;
--lightness: 1;
--white-a: hsl(var(--hue), calc(54% * var(--saturation)), calc(95% * var(--lightness)));
--white-b: hsl(var(--hue), calc(45% * var(--saturation)), calc(90% * var(--lightness)));
--grey-a: hsl(var(--hue), calc(36% * var(--saturation)), calc(85% * var(--lightness)));
--grey-aa: hsl(var(--hue), calc(27% * var(--saturation)), calc(72.5% * var(--lightness)));
--grey-b: hsl(var(--hue), calc(18% * var(--saturation)), calc(60% * var(--lightness)));
--dark-grey-a: hsl(var(--hue), calc(14% * var(--saturation)), calc(40% * var(--lightness)));
--dark-grey-b: hsl(var(--hue), calc(29% * var(--saturation)), calc(19% * var(--lightness)));
--dark-grey-c: hsl(var(--hue), calc(29% * var(--saturation)), calc(15% * var(--lightness)));
--dark-grey-ca: hsl(var(--hue), calc(27% * var(--saturation)), calc(12% * var(--lightness)));
--dark-grey-d: hsl(var(--hue), calc(14% * var(--saturation)), calc(10% * var(--lightness)));
--dark-grey-e: hsl(var(--hue), calc(12% * var(--saturation)), calc(8% * var(--lightness)));
--foreground-color-a: var(--white-a);
--foreground-color-b: var(--grey-a);
--foreground-color-c: var(--grey-b);
--background-color-a: var(--dark-grey-b);
--background-color-b: var(--dark-grey-c);
--background-color-ba: var(--dark-grey-ca);
--background-color-c: var(--dark-grey-d);
--background-color-d: var(--dark-grey-e);
}
.Cherry-theme {
--hue: 0;
--saturation: 5;
--lightness: 1.5;
}
.Mango-theme {
--hue: 40;
--saturation: 5;
--lightness: 2.5;
}
.Muted-theme {
--saturation: 0;
}
.Aqua-theme {
--hue: 180;
--saturation: 4;
--lightness: 2;
}
.Grape-theme {
--hue: 310;
--saturation: 5;
--lightness: 1.5;
}

View file

@ -1,5 +1,6 @@
:root {
--white-a: hsl(210, 54%, 95%);
--white-b: hsl(210, 45%, 90%);
--pink-a: #ff9ff3;
--pink-b: #f368e0;
--yellow-a: #feca57;
@ -17,10 +18,11 @@
--purple-a: #5f27cd;
--purple-b: #341f97;
--grey-a: hsl(210, 36%, 85%);
--grey-aa: hsl(210, 27%, 72.5%);
--grey-b: hsl(210, 18%, 60%);
--dark-grey-a: hsl(211, 14%, 40%);
--dark-grey-b: hsl(var(--background-color-a-hsl));
--dark-grey-c: hsl(var(--background-color-c-hsl));
--dark-grey-a: hsl(212, 14%, 40%);
--dark-grey-b: hsl(212, 29%, 19%);
--dark-grey-c: hsl(212, 29%, 15%);
--dark-grey-ca: hsl(212, 27%, 12%);
--dark-grey-d: hsl(212, 14%, 10%);
--dark-grey-e: hsl(212, 12%, 8%);
@ -29,13 +31,13 @@
--foreground-color-b: var(--grey-a);
--foreground-color-c: var(--grey-b);
--background-color-a: var(--dark-grey-b);
--background-color-b: var(--dark-grey-c);
--background-color-a: hsl(var(--background-color-a-hsl));
--background-color-b: hsl(var(--background-color-c-hsl));
--background-color-ba: var(--dark-grey-ca);
--background-color-c: var(--dark-grey-d);
--background-color-d: var(--dark-grey-e);
--background-color-a-hsl: 212, 29%, 19%;
--background-color-a-hsl: 212, 29%, 19%;
--background-color-c-hsl: 212, 29%, 15%;
--scrollbar-color: hsla(211, 29%, 40%, 25%);