Updated taskbar + added search menu
This commit is contained in:
parent
27a0c480ad
commit
1499e4f178
14 changed files with 530 additions and 79 deletions
25
src/components/task-bar/AppList.module.css
Normal file
25
src/components/task-bar/AppList.module.css
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
.App-list {
|
||||||
|
--scrollbar-color: rgba(0, 0, 0, 25%);
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-button {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-button > div,
|
||||||
|
.App-button > div > div,
|
||||||
|
.App-button > div > div > svg {
|
||||||
|
width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-button > p {
|
||||||
|
margin: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
69
src/components/task-bar/Battery.jsx
Normal file
69
src/components/task-bar/Battery.jsx
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { faBatteryEmpty, faBatteryFull, faBatteryHalf, faBatteryQuarter, faBatteryThreeQuarters, faMinus } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import styles from "./Battery.module.css";
|
||||||
|
|
||||||
|
export function Battery() {
|
||||||
|
const [isCharging, setIsCharging] = useState(false);
|
||||||
|
const [percentage, setPercentage] = useState(100);
|
||||||
|
// const [chargingTime, setChargingTime] = useState(0);
|
||||||
|
// const [dischargingTime, setDischargingTime] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
navigator.getBattery().then((battery) => {
|
||||||
|
const updateIsCharging = () => {
|
||||||
|
setIsCharging(battery.charging);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updatePercentage = () => {
|
||||||
|
setPercentage(battery.level * 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
// const updateChargingTime = () => {
|
||||||
|
// setChargingTime(battery.chargingTime);
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const updateDischargingTime = () => {
|
||||||
|
// setDischargingTime(battery.dischargingTime);
|
||||||
|
// };
|
||||||
|
|
||||||
|
updateIsCharging();
|
||||||
|
updatePercentage();
|
||||||
|
// updateChargingTime();
|
||||||
|
// updateDischargingTime();
|
||||||
|
|
||||||
|
battery.addEventListener("chargingchange", updateIsCharging);
|
||||||
|
battery.addEventListener("levelchange", updatePercentage);
|
||||||
|
// battery.addEventListener("chargingtimechange", updateChargingTime);
|
||||||
|
// battery.addEventListener("dischargingtimechange", updateDischargingTime);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
battery.removeEventListener("chargingchange", updateIsCharging);
|
||||||
|
battery.removeEventListener("levelchange", updatePercentage);
|
||||||
|
// battery.removeEventListener("chargingtimechange", updateChargingTime);
|
||||||
|
// battery.removeEventListener("dischargingtimechange", updateDischargingTime);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let icon = faBatteryFull;
|
||||||
|
if (percentage < 25) {
|
||||||
|
icon = faBatteryEmpty;
|
||||||
|
} else if (percentage < 50) {
|
||||||
|
icon = faBatteryQuarter;
|
||||||
|
} else if (percentage < 75) {
|
||||||
|
icon = faBatteryHalf;
|
||||||
|
} else if (percentage < 100) {
|
||||||
|
icon = faBatteryThreeQuarters;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={styles.Button} title="Battery">
|
||||||
|
{!isCharging
|
||||||
|
? <FontAwesomeIcon className={styles["Charging-indicator"]} icon={faMinus}/>
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
<FontAwesomeIcon icon={icon}/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
13
src/components/task-bar/Battery.module.css
Normal file
13
src/components/task-bar/Battery.module.css
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
.Button {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Charging-indicator {
|
||||||
|
--outline-color: var(--background-color-c);
|
||||||
|
--outline-width: 2px;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0.55rem;
|
||||||
|
right: 0.15rem;
|
||||||
|
height: 0.7rem !important;
|
||||||
|
}
|
||||||
28
src/components/task-bar/Calendar.jsx
Normal file
28
src/components/task-bar/Calendar.jsx
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import styles from "./Calendar.module.css";
|
||||||
|
|
||||||
|
export function Calendar() {
|
||||||
|
const [date, setDate] = useState(new Date());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInterval(() => {
|
||||||
|
setDate(new Date());
|
||||||
|
}, 30000);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={styles.Button} title="Date & Time" style={{ userSelect: "none" }}>
|
||||||
|
{date.toLocaleString("en-US", {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
hour12: false,
|
||||||
|
})}
|
||||||
|
<br/>
|
||||||
|
{date.toLocaleDateString("en-GB", {
|
||||||
|
day: "numeric",
|
||||||
|
month: "short",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
3
src/components/task-bar/Calendar.module.css
Normal file
3
src/components/task-bar/Calendar.module.css
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.Button {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import styles from "./HomeMenu.module.css";
|
import styles from "./HomeMenu.module.css";
|
||||||
|
import appStyles from "./AppList.module.css";
|
||||||
import { faCircleInfo, faFileLines, faGear, faImage, faPowerOff } from "@fortawesome/free-solid-svg-icons";
|
import { faCircleInfo, faFileLines, faGear, faImage, faPowerOff } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
|
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
|
||||||
import ApplicationsManager from "../../features/applications/applications.js";
|
import ApplicationsManager from "../../features/applications/applications.js";
|
||||||
|
|
@ -8,7 +9,7 @@ import { closeTab } from "../../features/utils/browser.js";
|
||||||
import { useKeyboardListener } from "../../hooks/utils/keyboard.js";
|
import { useKeyboardListener } from "../../hooks/utils/keyboard.js";
|
||||||
import { useVirtualRoot } from "../../hooks/virtual-drive/VirtualRootContext.js";
|
import { useVirtualRoot } from "../../hooks/virtual-drive/VirtualRootContext.js";
|
||||||
|
|
||||||
export function HomeMenu({ active, setActive }) {
|
export function HomeMenu({ active, setActive, search }) {
|
||||||
const windowsManager = useWindowsManager();
|
const windowsManager = useWindowsManager();
|
||||||
const virtualRoot = useVirtualRoot();
|
const virtualRoot = useVirtualRoot();
|
||||||
|
|
||||||
|
|
@ -23,6 +24,10 @@ export function HomeMenu({ active, setActive }) {
|
||||||
onlyAltKey = true;
|
onlyAltKey = true;
|
||||||
} else {
|
} else {
|
||||||
onlyAltKey = false;
|
onlyAltKey = false;
|
||||||
|
|
||||||
|
if (active && event.key.length === 1) {
|
||||||
|
search(event.key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +44,7 @@ export function HomeMenu({ active, setActive }) {
|
||||||
useKeyboardListener({ onKeyDown, onKeyUp });
|
useKeyboardListener({ onKeyDown, onKeyUp });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames.join(" ")} onKeyDown={onKeyDown}>
|
<div className={classNames.join(" ")}>
|
||||||
<div className={styles["Container-inner"]}>
|
<div className={styles["Container-inner"]}>
|
||||||
<div className={styles.Buttons}>
|
<div className={styles.Buttons}>
|
||||||
<button title="Power" onClick={() => { closeTab(); }}>
|
<button title="Power" onClick={() => { closeTab(); }}>
|
||||||
|
|
@ -72,11 +77,11 @@ export function HomeMenu({ active, setActive }) {
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.Apps}>
|
<div className={styles.Apps}>
|
||||||
<h2>Apps</h2>
|
<h2>Apps</h2>
|
||||||
<div className={styles["App-list"]}>
|
<div className={appStyles["App-list"]}>
|
||||||
{ApplicationsManager.APPLICATIONS.map(({ name, id }) =>
|
{ApplicationsManager.APPLICATIONS.map(({ name, id }) =>
|
||||||
<button
|
<button
|
||||||
key={id}
|
key={id}
|
||||||
className={styles["App-button"]}
|
className={appStyles["App-button"]}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActive(false);
|
setActive(false);
|
||||||
windowsManager.open(id);
|
windowsManager.open(id);
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,11 @@
|
||||||
transition: opacity 200ms ease-in-out, transform 200ms ease-in-out;
|
transition: opacity 200ms ease-in-out, transform 200ms ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.Container-outer:not(.Active) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.Container-outer:not(.Active) .Container-inner {
|
.Container-outer:not(.Active) .Container-inner {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(100px);
|
transform: translateY(100px);
|
||||||
|
|
@ -35,6 +40,8 @@
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.25rem;
|
padding: 0.25rem;
|
||||||
|
margin-top: auto;
|
||||||
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Buttons > button {
|
.Buttons > button {
|
||||||
|
|
@ -47,10 +54,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.Apps {
|
.Apps {
|
||||||
--scrollbar-color: rgba(0, 0, 0, 25%);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100% !important;
|
||||||
max-height: 20rem;
|
max-height: 20rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
padding-right: 0.25rem;
|
padding-right: 0.25rem;
|
||||||
|
|
@ -64,28 +70,3 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-list {
|
|
||||||
overflow: auto;
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-list > button {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-list > button > div,
|
|
||||||
.App-list > button > div > div,
|
|
||||||
.App-list > button > div > div > svg {
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.App-list > button > p {
|
|
||||||
margin: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
10
src/components/task-bar/Network.jsx
Normal file
10
src/components/task-bar/Network.jsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { faWifi } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
export function Network() {
|
||||||
|
return (
|
||||||
|
<button title="Wifi">
|
||||||
|
<FontAwesomeIcon icon={faWifi}/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
57
src/components/task-bar/SearchMenu.jsx
Normal file
57
src/components/task-bar/SearchMenu.jsx
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
import styles from "./SearchMenu.module.css";
|
||||||
|
import appStyles from "./AppList.module.css";
|
||||||
|
import ApplicationsManager from "../../features/applications/applications.js";
|
||||||
|
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
|
||||||
|
import { ReactSVG } from "react-svg";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export function SearchMenu({ active, setActive, searchQuery, setSearchQuery, inputRef }) {
|
||||||
|
const windowsManager = useWindowsManager();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (inputRef.current)
|
||||||
|
inputRef.current.focus();
|
||||||
|
}, [inputRef]);
|
||||||
|
|
||||||
|
const onChange = (event) => {
|
||||||
|
const value = event.target.value;
|
||||||
|
setSearchQuery(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const classNames = [styles["Container-outer"]];
|
||||||
|
if (active)
|
||||||
|
classNames.push(styles.Active);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classNames.join(" ")}>
|
||||||
|
<div className={styles["Container-inner"]}>
|
||||||
|
<div className={appStyles["App-list"]}>
|
||||||
|
{ApplicationsManager.APPLICATIONS.filter(({ name }) =>
|
||||||
|
name.toLowerCase().includes(searchQuery.toLowerCase().trim())
|
||||||
|
).map(({ name, id }) =>
|
||||||
|
<button
|
||||||
|
key={id}
|
||||||
|
className={appStyles["App-button"]}
|
||||||
|
onClick={() => {
|
||||||
|
setActive(false);
|
||||||
|
windowsManager.open(id);
|
||||||
|
}}
|
||||||
|
title={name}
|
||||||
|
>
|
||||||
|
<ReactSVG src={`${process.env.PUBLIC_URL}/media/applications/icons/${id}.svg`}/>
|
||||||
|
<p>{name}</p>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
className={styles.Input}
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={onChange}
|
||||||
|
spellCheck={false}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
50
src/components/task-bar/SearchMenu.module.css
Normal file
50
src/components/task-bar/SearchMenu.module.css
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
.Container-outer {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 100%;
|
||||||
|
height: auto !important;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Container-inner {
|
||||||
|
opacity: 1;
|
||||||
|
background-color: rgba(25, 25, 25, 75%);
|
||||||
|
backdrop-filter: blur(15px);
|
||||||
|
transform: none;
|
||||||
|
transition: opacity 200ms ease-in-out, transform 200ms ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Container-outer:not(.Active) {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Container-outer:not(.Active) .Container-inner {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.Container-inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 13rem;
|
||||||
|
max-width: 29rem;
|
||||||
|
max-height: 20rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-top-left-radius: 0.5rem;
|
||||||
|
border-top-right-radius: 0.5rem;
|
||||||
|
border-bottom-right-radius: 0.5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
resize: horizontal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
color: var(--foreground-color-a);
|
||||||
|
background-color: rgba(0, 0, 0, 25%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
outline: none;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import styles from "./TaskBar.module.css";
|
import styles from "./TaskBar.module.css";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faBatteryFull, faSearch, faVolumeHigh, faWifi } from "@fortawesome/free-solid-svg-icons";
|
import { faSearch } from "@fortawesome/free-solid-svg-icons";
|
||||||
import ApplicationsManager from "../../features/applications/applications.js";
|
import ApplicationsManager from "../../features/applications/applications.js";
|
||||||
import { useWindows } from "../../hooks/windows/WindowsContext.js";
|
import { useWindows } from "../../hooks/windows/WindowsContext.js";
|
||||||
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
|
import { useWindowsManager } from "../../hooks/windows/WindowsManagerContext.js";
|
||||||
|
|
@ -10,6 +10,12 @@ import { ReactSVG } from "react-svg";
|
||||||
import Application from "../../features/applications/application.js";
|
import Application from "../../features/applications/application.js";
|
||||||
import { HomeMenu } from "./HomeMenu.jsx";
|
import { HomeMenu } from "./HomeMenu.jsx";
|
||||||
import OutsideClickListener from "../../hooks/utils/outsideClick.js";
|
import OutsideClickListener from "../../hooks/utils/outsideClick.js";
|
||||||
|
import { Battery } from "./Battery.jsx";
|
||||||
|
import { Network } from "./Network.jsx";
|
||||||
|
import { Volume } from "./Volume.jsx";
|
||||||
|
import { SearchMenu } from "./SearchMenu.jsx";
|
||||||
|
import { Calendar } from "./Calendar.jsx";
|
||||||
|
import { useScrollWithShadow } from "../../hooks/utils/scrollWithShadows.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} props
|
* @param {Object} props
|
||||||
|
|
@ -41,56 +47,99 @@ function AppButton({ app }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Taskbar() {
|
export function Taskbar() {
|
||||||
const [date, setDate] = useState(new Date());
|
|
||||||
const [showHome, setShowHome] = useState(false);
|
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,
|
||||||
|
spreadRadius: -10,
|
||||||
|
color: {
|
||||||
|
a: 25
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const inputRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
const updateShowHome = (value) => {
|
||||||
setInterval(() => {
|
setShowHome(value);
|
||||||
setDate(new Date());
|
|
||||||
}, 30000);
|
if (value) {
|
||||||
}, []);
|
setShowSearch(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateShowSearch = (value) => {
|
||||||
|
setShowSearch(value);
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
if (searchQuery !== "") {
|
||||||
|
setSearchQuery("");
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowHome(false);
|
||||||
|
|
||||||
|
if (inputRef.current) {
|
||||||
|
inputRef.current.focus();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!showSearch) {
|
||||||
|
setSearchQuery("");
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const search = (query) => {
|
||||||
|
updateShowSearch(true);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles["Task-bar"]}>
|
<div className={styles["Task-bar"]}>
|
||||||
<div className={styles["Program-icons"]}>
|
<div className={styles["Menu-icons"]}>
|
||||||
<div className={styles["Home-container"]}>
|
<div className={styles["Home-container"]}>
|
||||||
<OutsideClickListener onOutsideClick={() => { setShowHome(false); }}>
|
<OutsideClickListener onOutsideClick={() => { updateShowHome(false); }}>
|
||||||
<button title="Home" className={styles["Home-button"]} onClick={() => { setShowHome(!showHome); }}>
|
<button title="Home"
|
||||||
|
className={`${styles["Menu-button"]} ${styles["Home-button"]}`}
|
||||||
|
onClick={() => { updateShowHome(!showHome); }}
|
||||||
|
>
|
||||||
<ReactSVG src={process.env.PUBLIC_URL + "/media/logo.svg"}/>
|
<ReactSVG src={process.env.PUBLIC_URL + "/media/logo.svg"}/>
|
||||||
</button>
|
</button>
|
||||||
<HomeMenu active={showHome} setActive={setShowHome}/>
|
<HomeMenu active={showHome} setActive={updateShowHome} search={search}/>
|
||||||
</OutsideClickListener>
|
</OutsideClickListener>
|
||||||
</div>
|
</div>
|
||||||
<button title="Search">
|
<div className={styles["Search-container"]}>
|
||||||
<FontAwesomeIcon icon={faSearch}/>
|
<OutsideClickListener onOutsideClick={() => { updateShowSearch(false); }}>
|
||||||
</button>
|
<button title="Search"
|
||||||
|
className={`${styles["Menu-button"]} ${styles["Search-button"]}`}
|
||||||
|
onClick={() => { updateShowSearch(!showSearch); }}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faSearch}/>
|
||||||
|
</button>
|
||||||
|
<SearchMenu
|
||||||
|
active={showSearch}
|
||||||
|
setActive={updateShowSearch}
|
||||||
|
searchQuery={searchQuery}
|
||||||
|
setSearchQuery={setSearchQuery}
|
||||||
|
inputRef={inputRef}
|
||||||
|
/>
|
||||||
|
</OutsideClickListener>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className={styles["App-icons"]} onScroll={onUpdate} onResize={onUpdate} ref={ref} style={{ boxShadow }}>
|
||||||
{ApplicationsManager.APPLICATIONS.map((app) =>
|
{ApplicationsManager.APPLICATIONS.map((app) =>
|
||||||
<AppButton app={app} key={app.id}/>
|
<AppButton app={app} key={app.id}/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles["Util-icons"]}>
|
<div className={styles["Util-icons"]}>
|
||||||
<button title="Battery">
|
<Battery/>
|
||||||
<FontAwesomeIcon icon={faBatteryFull}/>
|
<Network/>
|
||||||
</button>
|
<Volume/>
|
||||||
<button title="Wifi">
|
<Calendar/>
|
||||||
<FontAwesomeIcon icon={faWifi}/>
|
|
||||||
</button>
|
|
||||||
<button title="Volume">
|
|
||||||
<FontAwesomeIcon icon={faVolumeHigh}/>
|
|
||||||
</button>
|
|
||||||
<button title="Date & Time" style={{ userSelect: "none" }}>
|
|
||||||
{date.toLocaleString("en-US", {
|
|
||||||
hour: "numeric",
|
|
||||||
minute: "numeric",
|
|
||||||
hour12: false,
|
|
||||||
})}
|
|
||||||
<br/>
|
|
||||||
{date.toLocaleDateString("en-GB", {
|
|
||||||
day: "numeric",
|
|
||||||
month: "short",
|
|
||||||
year: "numeric",
|
|
||||||
})}
|
|
||||||
</button>
|
|
||||||
<button title="View Desktop" id="desktop-button"/>
|
<button title="View Desktop" id="desktop-button"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@
|
||||||
.Task-bar {
|
.Task-bar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
@ -26,13 +25,15 @@
|
||||||
border: none;
|
border: none;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: background-color 100ms ease-in-out;
|
transition: background-color 100ms ease-in-out;
|
||||||
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Task-bar button:hover {
|
.Task-bar button:hover {
|
||||||
background-color: var(--task-bar-button-hover-color);
|
background-color: var(--task-bar-button-hover-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.Home-container {
|
.Home-container,
|
||||||
|
.Search-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
@ -42,27 +43,37 @@
|
||||||
filter: none;
|
filter: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Program-icons {
|
.Menu-icons,
|
||||||
|
.App-icons {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Program-icons > *, .Home-button {
|
.App-icons {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-icons::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.App-icons > *,
|
||||||
|
.Menu-button {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Program-icons > * > svg {
|
.App-icons > * > svg,
|
||||||
|
.Menu-button > svg {
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Program-icons > * > div,
|
.Menu-icons div,
|
||||||
.Program-icons > * > div > div,
|
.Menu-icons div > svg,
|
||||||
.Program-icons > * > div > div > svg,
|
.App-icons div,
|
||||||
.Home-button > div,
|
.App-icons div > svg {
|
||||||
.Home-button > div > div,
|
|
||||||
.Home-button > div > div > svg {
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
@ -97,6 +108,9 @@
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
margin-left: auto;
|
||||||
|
padding-left: 0.5rem;
|
||||||
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Util-icons > * {
|
.Util-icons > * {
|
||||||
|
|
|
||||||
10
src/components/task-bar/Volume.jsx
Normal file
10
src/components/task-bar/Volume.jsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { faVolumeHigh } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
export function Volume() {
|
||||||
|
return (
|
||||||
|
<button title="Volume">
|
||||||
|
<FontAwesomeIcon icon={faVolumeHigh}/>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
137
src/hooks/utils/scrollWithShadows.js
Normal file
137
src/hooks/utils/scrollWithShadows.js
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
/**
|
||||||
|
* https://medium.com/dfind-consulting/react-scroll-hook-with-shadows-9ba2d47ae32
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import React, { useCallback, useEffect, useRef } from "react";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} options
|
||||||
|
* @param {React.ElementRef} options.ref
|
||||||
|
* @param {Boolean=true} options.horizontal
|
||||||
|
* @param {Boolean=true} options.dynamicOffset
|
||||||
|
* @param {Number=1} options.dynamicOffsetFactor
|
||||||
|
* @param {Object} options.shadow
|
||||||
|
* @param {Number=8} options.shadow.offset
|
||||||
|
* @param {Number=5} options.shadow.blurRadius
|
||||||
|
* @param {Number=-5} options.shadow.spreadRadius
|
||||||
|
* @param {Object} options.shadow.color
|
||||||
|
* @param {Number=0} options.shadow.color.r
|
||||||
|
* @param {Number=0} options.shadow.color.g
|
||||||
|
* @param {Number=0} options.shadow.color.b
|
||||||
|
* @param {Number=50} options.shadow.color.a
|
||||||
|
*/
|
||||||
|
export function useScrollWithShadow(options) {
|
||||||
|
const [initiated, setInitiated] = useState(false);
|
||||||
|
const [scrollStart, setScrollStart] = useState(0);
|
||||||
|
const [scrollLength, setScrollLength] = useState(0);
|
||||||
|
const [clientLength, setClientLength] = useState(0);
|
||||||
|
|
||||||
|
if (options == null)
|
||||||
|
options = {};
|
||||||
|
if (options.shadow == null)
|
||||||
|
options.shadow = {};
|
||||||
|
if (options.shadow.color == null)
|
||||||
|
options.shadow.color = {};
|
||||||
|
|
||||||
|
const {
|
||||||
|
ref,
|
||||||
|
horizontal = true,
|
||||||
|
dynamicOffset = true,
|
||||||
|
dynamicOffsetFactor = 3,
|
||||||
|
shadow: {
|
||||||
|
offset = 8,
|
||||||
|
blurRadius = 5,
|
||||||
|
spreadRadius = -5,
|
||||||
|
color: {
|
||||||
|
r = 0,
|
||||||
|
g = 0,
|
||||||
|
b = 0,
|
||||||
|
a = 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const updateValues = useCallback((element) => {
|
||||||
|
if (!element)
|
||||||
|
return;
|
||||||
|
|
||||||
|
console.log(element);
|
||||||
|
|
||||||
|
setScrollStart(horizontal ? element.scrollLeft : element.scrollTop);
|
||||||
|
setScrollLength(horizontal ? element.scrollWidth : element.scrollHeight);
|
||||||
|
setClientLength(horizontal ? element.clientWidth : element.clientHeight);
|
||||||
|
}, [horizontal]);
|
||||||
|
|
||||||
|
const onUpdate = (event) => {
|
||||||
|
console.log(event);
|
||||||
|
updateValues(event.target);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onResize = () => {
|
||||||
|
updateValues(ref.current);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ref.current && !initiated) {
|
||||||
|
console.log(ref.current);
|
||||||
|
setInitiated(true);
|
||||||
|
updateValues(ref.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", onResize);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", onResize);
|
||||||
|
}
|
||||||
|
}, [ref, updateValues, initiated]);
|
||||||
|
|
||||||
|
const getBoxShadow = () => {
|
||||||
|
const startDistance = scrollStart;
|
||||||
|
const endDistance = scrollLength - scrollStart - clientLength;
|
||||||
|
|
||||||
|
const isStart = startDistance === 0;
|
||||||
|
const isEnd = endDistance === 0;
|
||||||
|
const isBetween = scrollStart > 0 && clientLength < scrollLength - scrollStart;
|
||||||
|
|
||||||
|
let startOffset = offset;
|
||||||
|
let endOffset = offset;
|
||||||
|
|
||||||
|
if (dynamicOffset) {
|
||||||
|
startOffset = startDistance * dynamicOffsetFactor - offset;
|
||||||
|
endOffset = endDistance * dynamicOffsetFactor - offset;
|
||||||
|
|
||||||
|
if (startOffset > offset) {
|
||||||
|
startOffset = offset;
|
||||||
|
} else if (startOffset < 0) {
|
||||||
|
startOffset = 0;
|
||||||
|
}
|
||||||
|
if (endOffset > offset) {
|
||||||
|
endOffset = offset;
|
||||||
|
} else if (endOffset < 0) {
|
||||||
|
endOffset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shadowStartOffset = horizontal ? `${startOffset}px 0` : `0 ${startOffset}px`;
|
||||||
|
const shadowEndOffset = horizontal ? `-${endOffset}px 0` : `0 -${endOffset}px`;
|
||||||
|
|
||||||
|
const start = `inset ${shadowStartOffset} ${blurRadius}px ${spreadRadius}px rgba(${r}, ${g}, ${b}, ${a}%)`;
|
||||||
|
const end = `inset ${shadowEndOffset} ${blurRadius}px ${spreadRadius}px rgba(${r}, ${g}, ${b}, ${a}%)`;
|
||||||
|
|
||||||
|
let boxShadow = "none";
|
||||||
|
|
||||||
|
if (isStart) {
|
||||||
|
boxShadow = end;
|
||||||
|
} else if (isBetween) {
|
||||||
|
boxShadow = `${start}, ${end}`;
|
||||||
|
} else if (isEnd) {
|
||||||
|
boxShadow = start;
|
||||||
|
}
|
||||||
|
|
||||||
|
return boxShadow;
|
||||||
|
};
|
||||||
|
|
||||||
|
return { boxShadow: getBoxShadow(), onUpdate };
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue