diff --git a/public/config/desktop.xml b/public/config/desktop.xml
index 25db787..8baa742 100644
--- a/public/config/desktop.xml
+++ b/public/config/desktop.xml
@@ -1,5 +1,3 @@
-
-
/media/wallpapers/mesh-gradient-wallpaper-red-purple.png
\ No newline at end of file
diff --git a/public/config/taskbar.xml b/public/config/taskbar.xml
new file mode 100644
index 0000000..c8f1bce
--- /dev/null
+++ b/public/config/taskbar.xml
@@ -0,0 +1,3 @@
+
+ terminal,settings,media-viewer,text-editor,file-explorer,wordle,balls,minesweeper
+
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
index c91b140..96ea759 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,5 +1,5 @@
import styles from "./App.module.css";
-import { Taskbar } from "./components/task-bar/TaskBar.jsx";
+import { Taskbar } from "./components/taskbar/Taskbar.jsx";
import { WindowsManagerProvider } from "./hooks/windows/windowsManagerContext.js";
import { WindowsView } from "./components/windows/WindowsView.jsx";
import { VirtualRootProvider } from "./hooks/virtual-drive/virtualRootContext.js";
diff --git a/src/components/actions/Actions.jsx b/src/components/actions/Actions.jsx
index a0e2121..e351724 100644
--- a/src/components/actions/Actions.jsx
+++ b/src/components/actions/Actions.jsx
@@ -1,12 +1,12 @@
import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from "react";
import { useShortcuts } from "../../hooks/utils/keyboard.js";
-import style from "./Actions.module.css";
+import styles from "./Actions.module.css";
import { useScreenDimensions } from "../../hooks/utils/screen.js";
-import { TASK_BAR_HEIGHT } from "../../constants/taskBar.js";
+import { TASKBAR_HEIGHT } from "../../constants/taskBar.js";
export const STYLES = {
- CONTEXT_MENU: style["Context-menu"],
- SHORTCUTS_LISTENER: style["Shortcuts-listener"],
+ CONTEXT_MENU: styles["Context-menu"],
+ SHORTCUTS_LISTENER: styles["Shortcuts-listener"],
};
/**
@@ -16,7 +16,7 @@ export const STYLES = {
* @param {Function} props.onAnyTrigger
* @param {import("react").ElementType} props.children
* @param {*} props.triggerParams
- * @param {boolean} props.avoidTaskBar
+ * @param {boolean} props.avoidTaskbar
*/
/**
@@ -31,7 +31,7 @@ export const STYLES = {
* }}
* />
*/
-export function Actions({ children, className, onAnyTrigger, triggerParams, avoidTaskBar = true }) {
+export function Actions({ children, className, onAnyTrigger, triggerParams, avoidTaskbar = true }) {
const isListener = (className === STYLES.SHORTCUTS_LISTENER);
const ref = useRef(null);
@@ -88,8 +88,8 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
const maxX = screenWidth;
let maxY = screenHeight;
- if (avoidTaskBar)
- maxY -= TASK_BAR_HEIGHT;
+ if (avoidTaskbar)
+ maxY -= TASKBAR_HEIGHT;
const isOverflowingRight = (rect.x + rect.width > maxX);
const isOverflowingBottom = (rect.y + rect.height > maxY);
@@ -100,20 +100,20 @@ export function Actions({ children, className, onAnyTrigger, triggerParams, avoi
setAlignTop(true);
setInitiated(true);
- }, [alignLeft, avoidTaskBar, screenHeight, screenWidth]);
+ }, [alignLeft, avoidTaskbar, screenHeight, screenWidth]);
if (isListener)
return iterateOverChildren(children);
- const classNames = [style.Container];
+ const classNames = [styles.Container];
if (className != null)
classNames.push(className);
if (alignLeft)
- classNames.push(style["Align-left"]);
+ classNames.push(styles["Align-left"]);
if (alignTop)
- classNames.push(style["Align-top"]);
+ classNames.push(styles["Align-top"]);
if (!initiated)
- classNames.push(style.Uninitiated);
+ classNames.push(styles.Uninitiated);
return (
{iterateOverChildren(children)}
diff --git a/src/components/task-bar/AppButton.jsx b/src/components/task-bar/AppButton.jsx
deleted file mode 100644
index 6d1da2b..0000000
--- a/src/components/task-bar/AppButton.jsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { memo, useEffect, useState } from "react";
-import Application from "../../features/applications/application.js";
-import { useWindows } from "../../hooks/windows/windowsContext.js";
-import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
-import styles from "./TaskBar.module.css";
-import { ReactSVG } from "react-svg";
-
-/**
- * @param {object} props
- * @param {Application} props.app
- */
-export const AppButton = memo(({ app }) => {
- const [active, setActive] = useState(false);
- const windows = useWindows();
- const windowsManager = useWindowsManager();
-
- useEffect(() => {
- setActive(windowsManager.isAppActive(app.id));
- }, [app.id, windows, windowsManager]);
-
- const classNames = [styles["App-icon"]];
- if (active)
- classNames.push(styles.Active);
-
- return (
-
- );
-});
\ No newline at end of file
diff --git a/src/components/task-bar/TaskBar.jsx b/src/components/taskbar/Taskbar.jsx
similarity index 71%
rename from src/components/task-bar/TaskBar.jsx
rename to src/components/taskbar/Taskbar.jsx
index 3d5ae90..7d3a2a3 100644
--- a/src/components/task-bar/TaskBar.jsx
+++ b/src/components/taskbar/Taskbar.jsx
@@ -1,5 +1,5 @@
-import { memo, useRef, useState } from "react";
-import styles from "./TaskBar.module.css";
+import { memo, useEffect, useMemo, useRef, useState } from "react";
+import styles from "./Taskbar.module.css";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faCog, faSearch } from "@fortawesome/free-solid-svg-icons";
import AppsManager from "../../features/applications/applications.js";
@@ -12,7 +12,7 @@ import { Volume } from "./indicators/Volume.jsx";
import { SearchMenu } from "./menus/SearchMenu.jsx";
import { Calendar } from "./indicators/Calendar.jsx";
import { useScrollWithShadow } from "../../hooks/utils/scrollWithShadows.js";
-import { AppButton } from "./AppButton.jsx";
+import { AppButton } from "./app-icon/AppIcon.jsx";
import { useContextMenu } from "../../hooks/modals/contextMenu.js";
import { Actions } from "../actions/Actions.jsx";
import { useModals } from "../../hooks/modals/modals.js";
@@ -20,13 +20,17 @@ import { ClickAction } from "../actions/actions/ClickAction.jsx";
import { APPS, APP_NAMES } from "../../constants/applications.js";
import { useWindowsManager } from "../../hooks/windows/windowsManagerContext.js";
import { ModalsView } from "../modals/ModalsView.jsx";
-import { TASK_BAR_HEIGHT } from "../../constants/taskBar.js";
+import { TASKBAR_HEIGHT } from "../../constants/taskBar.js";
+import { useSettingsManager } from "../../hooks/settings/settingsManagerContext.js";
+import { SettingsManager } from "../../features/settings/settingsManager.js";
+import { useWindows } from "../../hooks/windows/windowsContext.js";
export const Taskbar = memo(() => {
+ const ref = useRef(null);
+ const settingsManager = useSettingsManager();
const [showHome, setShowHome] = useState(false);
const [showSearch, setShowSearch] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
- const ref = useRef(null);
const { boxShadow, onUpdate } = useScrollWithShadow({ ref, shadow: {
offset: 20,
blurRadius: 10,
@@ -36,13 +40,49 @@ export const Taskbar = memo(() => {
const inputRef = useRef(null);
const [modalsManager, modals] = useModals();
const windowsManager = useWindowsManager();
+ const windows = useWindows();
const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
-
+
{
windowsManager.open(APPS.SETTINGS);
}}/>
});
+ const [pins, setPins] = useState([]);
+
+ const apps = useMemo(() => AppsManager.APPLICATIONS.sort((appA, appB) => {
+ const indexA = pins.indexOf(appA.id);
+ const indexB = pins.indexOf(appB.id);
+ if (indexA < 0 && indexB > 0) {
+ return 1;
+ } else if (indexA > 0 && indexB < 0) {
+ return -1;
+ } else if (indexA < 0 && indexB < 0) {
+ return 0;
+ } else {
+ return indexA - indexB;
+ }
+ }).map((app) => {
+ const isActive = windows.map((window) => window.app.id).includes(app.id);
+ const shouldBeShown = (pins.includes(app.id) || isActive);
+ return ();
+ }), [modalsManager, pins, windows]);
+
+ useEffect(() => {
+ (async () => {
+ const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
+ settings.get("pins", (pins) => {
+ setPins(pins.split(","));
+ });
+ })();
+ }, [settingsManager]);
const updateShowHome = (value) => {
setShowHome(value);
@@ -82,8 +122,8 @@ export const Taskbar = memo(() => {
return (<>
{
if (event.target.getAttribute("data-allow-context-menu"))
@@ -132,9 +172,7 @@ export const Taskbar = memo(() => {
onResize={onUpdate}
ref={ref}
>
- {AppsManager.APPLICATIONS.map((app) =>
-
- )}
+ {apps}
@@ -142,7 +180,7 @@ export const Taskbar = memo(() => {
-
+
>);
diff --git a/src/components/task-bar/TaskBar.module.css b/src/components/taskbar/Taskbar.module.css
similarity index 65%
rename from src/components/task-bar/TaskBar.module.css
rename to src/components/taskbar/Taskbar.module.css
index 4519c6c..2229971 100644
--- a/src/components/task-bar/TaskBar.module.css
+++ b/src/components/taskbar/Taskbar.module.css
@@ -1,22 +1,22 @@
:root {
- --task-bar-height: 3rem;
- --task-bar-color: rgba(0, 0, 0, 75%);
- --task-bar-button-hover-color: rgba(255, 255, 255, 5%);
+ --taskbar-height: 3rem;
+ --taskbar-color: rgba(0, 0, 0, 75%);
+ --taskbar-button-hover-color: rgba(255, 255, 255, 5%);
}
-.Task-bar {
+.Taskbar {
position: fixed;
display: flex;
bottom: 0;
left: 0;
width: 100%;
- height: var(--task-bar-height);
- background-color: var(--task-bar-color);
+ height: var(--taskbar-height);
+ background-color: var(--taskbar-color);
backdrop-filter: blur(15px);
z-index: 10;
}
-.Task-bar button {
+.Taskbar button {
display: flex;
align-items: center;
color: var(--foreground-color-a);
@@ -28,9 +28,9 @@
z-index: -1;
}
-.Task-bar button:hover,
-.Task-bar button:focus-visible {
- background-color: var(--task-bar-button-hover-color);
+.Taskbar button:hover,
+.Taskbar button:focus-visible {
+ background-color: var(--taskbar-button-hover-color);
}
.Home-container,
@@ -94,35 +94,6 @@
width: auto;
}
-.App-icon {
- position: relative;
- width: var(--task-bar-height);
- height: var(--task-bar-height);
- aspect-ratio: 1;
-}
-
-.App-icon::after {
- content: "";
- position: absolute;
- left: 50%;
- bottom: 0;
- width: 90%;
- height: 0.15rem;
- background-color: var(--grey-a);
- transition: height 200ms ease-in-out, width 200ms ease-in-out;
- transform: translateX(-50%);
- -webkit-transform: translateX(-50%);
-}
-
-.App-icon:hover::after,
-.App-icon:focus-visible::after {
- width: 100%;
-}
-
-.App-icon:not(.Active)::after {
- height: 0;
-}
-
.Util-icons {
display: flex;
gap: 0;
diff --git a/src/components/taskbar/app-icon/AppIcon.jsx b/src/components/taskbar/app-icon/AppIcon.jsx
new file mode 100644
index 0000000..d197e8c
--- /dev/null
+++ b/src/components/taskbar/app-icon/AppIcon.jsx
@@ -0,0 +1,66 @@
+import { memo } from "react";
+import Application from "../../../features/applications/application.js";
+import { useWindowsManager } from "../../../hooks/windows/windowsManagerContext.js";
+import styles from "./AppIcon.module.css";
+import { ReactSVG } from "react-svg";
+import { useSettingsManager } from "../../../hooks/settings/settingsManagerContext.js";
+import { useContextMenu } from "../../../hooks/modals/contextMenu.js";
+import ModalsManager from "../../../features/modals/modals.js";
+import { Actions } from "../../actions/Actions.jsx";
+import { ClickAction } from "../../actions/actions/ClickAction.jsx";
+import { faThumbTack, faTimes } from "@fortawesome/free-solid-svg-icons";
+import { SettingsManager } from "../../../features/settings/settingsManager.js";
+import { removeFromArray } from "../../../features/utils/array.js";
+
+/**
+ * @param {object} props
+ * @param {Application} props.app
+ * @param {ModalsManager} props.modalsManager
+ * @param {string[]} props.pins
+ */
+export const AppButton = memo(({ app, modalsManager, pins, active, visible }) => {
+ const isPinned = pins.includes(app.id);
+
+ const windowsManager = useWindowsManager();
+ const settingsManager = useSettingsManager();
+ const { onContextMenu } = useContextMenu({ modalsManager, Actions: (props) =>
+
+ {
+ const newPins = [...pins];
+ if (isPinned) {
+ removeFromArray(app.id, pins);
+ } else {
+ newPins.push(app.id);
+ }
+
+ const settings = settingsManager.get(SettingsManager.VIRTUAL_PATHS.taskbar);
+ settings.set("pins", newPins.join(","));
+ }}/>
+ {active && {
+ windowsManager.close(windowsManager.getAppWindowId(app.id));
+ }}/>}
+
+ });
+
+ const classNames = [styles["App-icon"]];
+ if (active)
+ classNames.push(styles.Active);
+ if (!visible)
+ classNames.push(styles.Hidden);
+
+ return (
+
+ );
+});
\ No newline at end of file
diff --git a/src/components/taskbar/app-icon/AppIcon.module.css b/src/components/taskbar/app-icon/AppIcon.module.css
new file mode 100644
index 0000000..66b8ffa
--- /dev/null
+++ b/src/components/taskbar/app-icon/AppIcon.module.css
@@ -0,0 +1,58 @@
+.App-icon {
+ opacity: 1;
+ position: relative;
+ width: var(--taskbar-height);
+ height: var(--taskbar-height);
+ aspect-ratio: 1;
+ animation: appear 100ms ease-out forwards;
+}
+
+.App-icon.Hidden {
+ animation: disappear 400ms ease-in-out forwards;
+ pointer-events: none;
+}
+
+@keyframes appear {
+ 0% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+@keyframes disappear {
+ 0% {
+ opacity: 1;
+ }
+ 25% {
+ opacity: 0;
+ margin-right: 0;
+ }
+ 100% {
+ opacity: 0;
+ margin-right: calc(var(--taskbar-height) * -1);
+ }
+}
+
+.App-icon::after {
+ content: "";
+ position: absolute;
+ left: 50%;
+ bottom: 0;
+ width: 90%;
+ height: 0.15rem;
+ background-color: var(--grey-a);
+ transition: height 200ms ease-in-out, width 200ms ease-in-out;
+ transform: translateX(-50%);
+ -webkit-transform: translateX(-50%);
+}
+
+.App-icon:hover::after,
+.App-icon:focus-visible::after {
+ width: 100%;
+}
+
+.App-icon:not(.Active)::after {
+ height: 0;
+}
\ No newline at end of file
diff --git a/src/components/task-bar/indicators/Battery.jsx b/src/components/taskbar/indicators/Battery.jsx
similarity index 100%
rename from src/components/task-bar/indicators/Battery.jsx
rename to src/components/taskbar/indicators/Battery.jsx
diff --git a/src/components/task-bar/indicators/Battery.module.css b/src/components/taskbar/indicators/Battery.module.css
similarity index 100%
rename from src/components/task-bar/indicators/Battery.module.css
rename to src/components/taskbar/indicators/Battery.module.css
diff --git a/src/components/task-bar/indicators/Calendar.jsx b/src/components/taskbar/indicators/Calendar.jsx
similarity index 100%
rename from src/components/task-bar/indicators/Calendar.jsx
rename to src/components/taskbar/indicators/Calendar.jsx
diff --git a/src/components/task-bar/indicators/Calendar.module.css b/src/components/taskbar/indicators/Calendar.module.css
similarity index 100%
rename from src/components/task-bar/indicators/Calendar.module.css
rename to src/components/taskbar/indicators/Calendar.module.css
diff --git a/src/components/task-bar/indicators/Network.jsx b/src/components/taskbar/indicators/Network.jsx
similarity index 100%
rename from src/components/task-bar/indicators/Network.jsx
rename to src/components/taskbar/indicators/Network.jsx
diff --git a/src/components/task-bar/indicators/Volume.jsx b/src/components/taskbar/indicators/Volume.jsx
similarity index 100%
rename from src/components/task-bar/indicators/Volume.jsx
rename to src/components/taskbar/indicators/Volume.jsx
diff --git a/src/components/task-bar/menus/AppList.module.css b/src/components/taskbar/menus/AppList.module.css
similarity index 100%
rename from src/components/task-bar/menus/AppList.module.css
rename to src/components/taskbar/menus/AppList.module.css
diff --git a/src/components/task-bar/menus/HomeMenu.jsx b/src/components/taskbar/menus/HomeMenu.jsx
similarity index 100%
rename from src/components/task-bar/menus/HomeMenu.jsx
rename to src/components/taskbar/menus/HomeMenu.jsx
diff --git a/src/components/task-bar/menus/HomeMenu.module.css b/src/components/taskbar/menus/HomeMenu.module.css
similarity index 100%
rename from src/components/task-bar/menus/HomeMenu.module.css
rename to src/components/taskbar/menus/HomeMenu.module.css
diff --git a/src/components/task-bar/menus/SearchMenu.jsx b/src/components/taskbar/menus/SearchMenu.jsx
similarity index 100%
rename from src/components/task-bar/menus/SearchMenu.jsx
rename to src/components/taskbar/menus/SearchMenu.jsx
diff --git a/src/components/task-bar/menus/SearchMenu.module.css b/src/components/taskbar/menus/SearchMenu.module.css
similarity index 100%
rename from src/components/task-bar/menus/SearchMenu.module.css
rename to src/components/taskbar/menus/SearchMenu.module.css
diff --git a/src/components/windows/WindowView.module.css b/src/components/windows/WindowView.module.css
index 646e04e..4e65545 100644
--- a/src/components/windows/WindowView.module.css
+++ b/src/components/windows/WindowView.module.css
@@ -25,7 +25,7 @@
.Window-container.Maximized {
width: 100%;
- height: calc(100% - var(--task-bar-height));
+ height: calc(100% - var(--taskbar-height));
border-radius: 0;
transform: none !important;
resize: none;
diff --git a/src/constants/taskBar.js b/src/constants/taskBar.js
index f2adc65..a42f494 100644
--- a/src/constants/taskBar.js
+++ b/src/constants/taskBar.js
@@ -1 +1 @@
-export const TASK_BAR_HEIGHT = 3 * 16;
\ No newline at end of file
+export const TASKBAR_HEIGHT = 3 * 16;
\ No newline at end of file
diff --git a/src/features/settings/settingsManager.js b/src/features/settings/settingsManager.js
index 12f335d..1576348 100644
--- a/src/features/settings/settingsManager.js
+++ b/src/features/settings/settingsManager.js
@@ -6,7 +6,8 @@ export class SettingsManager {
* @type {Object}
*/
static VIRTUAL_PATHS = {
- "desktop": "~/.config/desktop.xml"
+ desktop: "~/.config/desktop.xml",
+ taskbar: "~/.config/taskbar.xml"
};
/**
diff --git a/src/features/virtual-drive/virtualRoot.js b/src/features/virtual-drive/virtualRoot.js
index a9f40d8..7c2e7c3 100644
--- a/src/features/virtual-drive/virtualRoot.js
+++ b/src/features/virtual-drive/virtualRoot.js
@@ -55,6 +55,8 @@ export class VirtualRoot extends VirtualFolder {
.createFolder(".config", (folder) => {
folder.createFile("desktop", "xml", (file) => {
file.setSource("/config/desktop.xml");
+ }).createFile("taskbar", "xml", (file) => {
+ file.setSource("/config/taskbar.xml");
});
})
.createFolder("Images", (folder) => {
diff --git a/src/features/windows/windows.js b/src/features/windows/windows.js
index 10a39b1..b90845c 100644
--- a/src/features/windows/windows.js
+++ b/src/features/windows/windows.js
@@ -1,4 +1,4 @@
-import { TASK_BAR_HEIGHT } from "../../constants/taskBar.js";
+import { TASKBAR_HEIGHT } from "../../constants/taskBar.js";
import { SCREEN_MARGIN } from "../../constants/windows.js";
import AppsManager from "../applications/applications.js";
import { randomRange } from "../math/random.js";
@@ -26,7 +26,7 @@ export default class WindowsManager {
const size = options?.size ?? app.windowOptions?.size ?? new Vector2(700, 400);
const position = new Vector2(randomRange(SCREEN_MARGIN, window.innerWidth - size.x - SCREEN_MARGIN),
- randomRange(SCREEN_MARGIN, window.innerHeight - size.y - SCREEN_MARGIN - TASK_BAR_HEIGHT));
+ randomRange(SCREEN_MARGIN, window.innerHeight - size.y - SCREEN_MARGIN - TASKBAR_HEIGHT));
let id = 0;
while (this.windowIds.includes(id.toString())) {
@@ -46,6 +46,8 @@ export default class WindowsManager {
lastInteraction: new Date().valueOf()
};
+ app.isActive = true;
+
this.updateWindows(this.windows);
return this.windows[id];
// console.log(this);
@@ -72,6 +74,9 @@ export default class WindowsManager {
console.warn(`Failed to close window ${windowId}: window not found`);
return;
}
+
+ const { app } = this.windows[windowId];
+ app.isActive = this.isAppActive(app.id);
console.info(`Closing window ${windowId}`);
delete this.windows[windowId];
@@ -114,6 +119,23 @@ export default class WindowsManager {
return active;
}
+ /**
+ * @param {string} appId
+ * @returns {string}
+ */
+ getAppWindowId(appId) {
+ let windowId = null;
+
+ Object.values(this.windows).forEach((window) => {
+ if (window.app.id === appId) {
+ windowId = window.id;
+ return;
+ }
+ });
+
+ return windowId;
+ }
+
/**
* @param {Function} updateWindows
*/