diff --git a/README.md b/README.md index 58925987..0b538104 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ My portfolio website simulating macOS's GUI: https://portfolio.zxh.io -Powered by [React](https://reactjs.org/) + [React Redux](https://react-redux.js.org/) + [UnoCSS](https://uno.antfu.me/) + [TypeScript](https://www.typescriptlang.org/) + [Vite](https://vitejs.dev/). +Powered by [React](https://reactjs.org/) + [Zustand](https://zustand-demo.pmnd.rs/) + [UnoCSS](https://uno.antfu.me/) + [TypeScript](https://www.typescriptlang.org/) + [Vite](https://vitejs.dev/). ![light mode](./public/screenshots/light.png) ![dark mode](./public/screenshots/dark.png) diff --git a/package.json b/package.json index 26ca576b..e512c472 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,10 @@ "private": true, "scripts": { "build": "vite build", - "dev": "vite", + "dev": "vite --host", "lint": "eslint --ext .js,.ts,.tsx ./", "prepare": "husky install", - "serve": "vite preview", + "serve": "vite preview --host", "up": "taze major -I" }, "lint-staged": { @@ -14,7 +14,6 @@ "package.json": "sort-package-json" }, "dependencies": { - "@reduxjs/toolkit": "^1.9.3", "@rooks/use-raf": "^4.11.2", "date-fns": "^2.29.3", "framer-motion": "^10.3.0", @@ -23,12 +22,11 @@ "react-dom": "^18.2.0", "react-markdown": "^8.0.5", "react-rangeslider": "2.2.0", - "react-redux": "^8.0.5", "react-rnd": "^10.4.1", "react-syntax-highlighter": "^15.5.0", "react-webcam": "7.0.1", - "redux": "^4.2.1", - "web-vitals": "^3.3.0" + "web-vitals": "^3.3.0", + "zustand": "^4.3.6" }, "devDependencies": { "@iconify/json": "^2.2.34", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ec6fc3c7..1d2b4b5d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2,7 +2,6 @@ lockfileVersion: 5.4 specifiers: '@iconify/json': ^2.2.34 - '@reduxjs/toolkit': ^1.9.3 '@rooks/use-raf': ^4.11.2 '@types/node': ^18.15.3 '@types/react': ^18.0.28 @@ -28,11 +27,9 @@ specifiers: react-dom: ^18.2.0 react-markdown: ^8.0.5 react-rangeslider: 2.2.0 - react-redux: ^8.0.5 react-rnd: ^10.4.1 react-syntax-highlighter: ^15.5.0 react-webcam: 7.0.1 - redux: ^4.2.1 rehype-katex: ^6.0.2 remark-gfm: ^3.0.1 remark-math: ^5.1.1 @@ -42,9 +39,9 @@ specifiers: unocss: ^0.50.4 vite: ^4.1.4 web-vitals: ^3.3.0 + zustand: ^4.3.6 dependencies: - '@reduxjs/toolkit': 1.9.3_k4ae6lp43ej6mezo3ztvx6pykq '@rooks/use-raf': 4.11.2 date-fns: 2.29.3 framer-motion: 10.3.0_biqbaboplfbrettd7655fr4n2y @@ -53,12 +50,11 @@ dependencies: react-dom: 18.2.0_react@18.2.0 react-markdown: 8.0.5_pmekkgnqduwlme35zpnqhenc34 react-rangeslider: 2.2.0_react@18.2.0 - react-redux: 8.0.5_ctrls2ti7t7iutxbwkm5ipogyy react-rnd: 10.4.1_biqbaboplfbrettd7655fr4n2y react-syntax-highlighter: 15.5.0_react@18.2.0 react-webcam: 7.0.1_biqbaboplfbrettd7655fr4n2y - redux: 4.2.1 web-vitals: 3.3.0 + zustand: 4.3.6_react@18.2.0 devDependencies: '@iconify/json': 2.2.34 @@ -801,25 +797,6 @@ packages: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true - /@reduxjs/toolkit/1.9.3_k4ae6lp43ej6mezo3ztvx6pykq: - resolution: {integrity: sha512-GU2TNBQVofL09VGmuSioNPQIu6Ml0YLf4EJhgj0AvBadRlCGzUWet8372LjvO4fqKZF2vH1xU0htAa7BrK9pZg==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.0.2 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - dependencies: - immer: 9.0.19 - react: 18.2.0 - react-redux: 8.0.5_ctrls2ti7t7iutxbwkm5ipogyy - redux: 4.2.1 - redux-thunk: 2.4.2_redux@4.2.1 - reselect: 4.1.7 - dev: false - /@rollup/pluginutils/5.0.2: resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} engines: {node: '>=14.0.0'} @@ -859,13 +836,6 @@ packages: dependencies: '@types/unist': 2.0.6 - /@types/hoist-non-react-statics/3.3.1: - resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==} - dependencies: - '@types/react': 18.0.28 - hoist-non-react-statics: 3.3.2 - dev: false - /@types/json-schema/7.0.11: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true @@ -893,6 +863,7 @@ packages: resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} dependencies: '@types/react': 18.0.28 + dev: true /@types/react-rangeslider/2.2.4: resolution: {integrity: sha512-WUgIfk7pCvat5QbTpJDh/0m3e8NZvqzVy3RXTaU8UaDliNM+SBz8hFsIfVXhwN/ruOvILOvCdh2+D6sKvzr+Ng==} @@ -923,10 +894,6 @@ packages: /@types/unist/2.0.6: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} - /@types/use-sync-external-store/0.0.3: - resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} - dev: false - /@typescript-eslint/eslint-plugin/5.55.0_342y7v4tc7ytrrysmit6jo4wri: resolution: {integrity: sha512-IZGc50rtbjk+xp5YQoJvmMPmJEYoC53SiKPXyqWfv15XoD2Y5Kju6zN0DwlmaGJp1Iw33JsWJcQ7nw0lGCGjVg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2545,12 +2512,6 @@ packages: resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} dev: false - /hoist-non-react-statics/3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - dependencies: - react-is: 16.13.1 - dev: false - /hosted-git-info/5.2.1: resolution: {integrity: sha512-xIcQYMnhcx2Nr4JTjsFmwwnr9vldugPy9uVm0o87bjqqWMv9GaqsTeT+i99wTl0mk1uLxJtHxLb8kymqTENQsw==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -2625,10 +2586,6 @@ packages: engines: {node: '>= 4'} dev: true - /immer/9.0.19: - resolution: {integrity: sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==} - dev: false - /import-fresh/3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -4232,40 +4189,6 @@ packages: resize-observer-polyfill: 1.5.1 dev: false - /react-redux/8.0.5_ctrls2ti7t7iutxbwkm5ipogyy: - resolution: {integrity: sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==} - peerDependencies: - '@types/react': ^16.8 || ^17.0 || ^18.0 - '@types/react-dom': ^16.8 || ^17.0 || ^18.0 - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - react-native: '>=0.59' - redux: ^4 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - react-dom: - optional: true - react-native: - optional: true - redux: - optional: true - dependencies: - '@babel/runtime': 7.21.0 - '@types/hoist-non-react-statics': 3.3.1 - '@types/react': 18.0.28 - '@types/react-dom': 18.0.11 - '@types/use-sync-external-store': 0.0.3 - hoist-non-react-statics: 3.3.2 - react: 18.2.0 - react-dom: 18.2.0_react@18.2.0 - react-is: 18.2.0 - redux: 4.2.1 - use-sync-external-store: 1.2.0_react@18.2.0 - dev: false - /react-refresh/0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} @@ -4356,20 +4279,6 @@ packages: picomatch: 2.3.1 dev: true - /redux-thunk/2.4.2_redux@4.2.1: - resolution: {integrity: sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==} - peerDependencies: - redux: ^4 - dependencies: - redux: 4.2.1 - dev: false - - /redux/4.2.1: - resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} - dependencies: - '@babel/runtime': 7.21.0 - dev: false - /refractor/3.6.0: resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==} dependencies: @@ -4457,10 +4366,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /reselect/4.1.7: - resolution: {integrity: sha512-Zu1xbUt3/OPwsXL46hvOOoQrap2azE7ZQbokq61BQfiXvhewsKDwhMeZjTX9sX0nvw1t/U5Audyn1I9P/m9z0A==} - dev: false - /resize-observer-polyfill/1.5.1: resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} dev: false @@ -5326,6 +5231,22 @@ packages: engines: {node: '>=10'} dev: true + /zustand/4.3.6_react@18.2.0: + resolution: {integrity: sha512-6J5zDxjxLE+yukC2XZWf/IyWVKnXT9b9HUv09VJ/bwGCpKNcaTqp7Ws28Xr8jnbvnZcdRaidztAPsXFBIqufiw==} + engines: {node: '>=12.7.0'} + peerDependencies: + immer: '>=9.0' + react: '>=16.8' + peerDependenciesMeta: + immer: + optional: true + react: + optional: true + dependencies: + react: 18.2.0 + use-sync-external-store: 1.2.0_react@18.2.0 + dev: false + /zwitch/2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} dev: true diff --git a/public/markdown/about-site.md b/public/markdown/about-site.md index 43107b09..3ed35fb0 100644 --- a/public/markdown/about-site.md +++ b/public/markdown/about-site.md @@ -1,5 +1,5 @@ # About This Site -This site is inspired by macOS [Big Sur](https://www.apple.com/in/macos/big-sur/) and [Catalina](https://www.apple.com/bw/macos/catalina/), developed using [React](https://reactjs.org/), [React Redux](https://react-redux.js.org/) and [UnoCSS](https://uno.antfu.me/), and hosted on [Github Pages](https://pages.github.com/). Some of the icons are generated using [sindresorhus/file-icon-cli](https://github.com/sindresorhus/file-icon-cli). +This site is inspired by macOS [Big Sur](https://www.apple.com/in/macos/big-sur/) and [Catalina](https://www.apple.com/bw/macos/catalina/), developed using [React](https://reactjs.org/), [Zustand](https://zustand-demo.pmnd.rs/) and [UnoCSS](https://uno.antfu.me/), and hosted on [Github Pages](https://pages.github.com/). Some of the icons are generated using [sindresorhus/file-icon-cli](https://github.com/sindresorhus/file-icon-cli). The source code is hosted [here](https://github.com/Renovamen/playground-macos). diff --git a/src/components/Launchpad.tsx b/src/components/Launchpad.tsx index 2a835de0..c2c20347 100644 --- a/src/components/Launchpad.tsx +++ b/src/components/Launchpad.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { wallpapers, launchpadApps } from "~/configs"; -import { useAppSelector } from "~/redux/hooks"; +import { useStore } from "~/stores"; interface LaunchpadProps { show: boolean; @@ -10,7 +10,7 @@ interface LaunchpadProps { const placeholderText = "Search"; export default function Launchpad({ show, toggleLaunchpad }: LaunchpadProps) { - const dark = useAppSelector((state) => state.system.dark); + const dark = useStore((state) => state.dark); const [searchText, setSearchText] = useState(""); const [focus, setFocus] = useState(false); diff --git a/src/components/Window.tsx b/src/components/Window.tsx index 296bd883..02ed915d 100644 --- a/src/components/Window.tsx +++ b/src/components/Window.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from "react"; import { Rnd } from "react-rnd"; import { useWindowSize } from "~/hooks"; -import { useAppSelector } from "~/redux/hooks"; +import { useStore } from "~/stores"; const FullIcon = ({ size }: { size: number }) => { return ( @@ -108,7 +108,7 @@ const TrafficLights = ({ id, close, max, setMax, setMin }: TrafficProps) => { }; const Window = (props: WindowProps) => { - const dockSize = useAppSelector((state) => state.dock.size); + const dockSize = useStore((state) => state.dockSize); const { winWidth, winHeight } = useWindowSize(); const initWidth = Math.min(winWidth, props.width ? props.width : 640); @@ -141,7 +141,9 @@ const Window = (props: WindowProps) => { const children = React.cloneElement( props.children as React.ReactElement, - { width: width } + { + width: width + } ); return ( diff --git a/src/components/apps/Bear.tsx b/src/components/apps/Bear.tsx index 1a953b9c..22aed9c0 100644 --- a/src/components/apps/Bear.tsx +++ b/src/components/apps/Bear.tsx @@ -5,8 +5,8 @@ import remarkMath from "remark-math"; import rehypeKatex from "rehype-katex"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { dracula, prism } from "react-syntax-highlighter/dist/esm/styles/prism"; +import { useStore } from "~/stores"; import bear from "~/configs/bear"; -import { useAppSelector } from "~/redux/hooks"; import type { BearMdData } from "~/types"; interface ContentProps { @@ -156,7 +156,7 @@ const fixImageURL = (text: string, contentURL: string): string => { const Content = ({ contentID, contentURL }: ContentProps) => { const [storeMd, setStoreMd] = useState<{ [key: string]: string }>({}); - const dark = useAppSelector((state) => state.system.dark); + const dark = useStore((state) => state.dark); const fetchMarkdown = useCallback( (id: string, url: string) => { diff --git a/src/components/apps/Safari.tsx b/src/components/apps/Safari.tsx index 2a8d0648..39a7f321 100644 --- a/src/components/apps/Safari.tsx +++ b/src/components/apps/Safari.tsx @@ -1,7 +1,7 @@ import React, { useState } from "react"; import { websites, wallpapers } from "~/configs"; import { checkURL } from "~/utils"; -import { useAppSelector } from "~/redux/hooks"; +import { useStore } from "~/stores"; import type { SiteSectionData, SiteData } from "~/types"; interface SafariState { @@ -77,7 +77,7 @@ const NavSection = ({ width, section, setGoURL }: NavSectionProps) => { const numTracker = Math.floor(Math.random() * 99 + 1); const NavPage = ({ width, setGoURL }: NavProps) => { - const dark = useAppSelector((state) => state.system.dark); + const dark = useStore((state) => state.dark); const grid = width < 640 ? "grid-cols-4" : "grid-cols-8"; const span = width < 640 ? "col-span-3" : "col-span-7"; @@ -125,7 +125,7 @@ const NavPage = ({ width, setGoURL }: NavProps) => { }; const NoInternetPage = () => { - const dark = useAppSelector((state) => state.system.dark); + const dark = useStore((state) => state.dark); return (
{ }; const Safari = ({ width }: SafariProps) => { - const wifi = useAppSelector((state) => state.system.wifi); + const wifi = useStore((state) => state.wifi); const [state, setState] = useState({ goURL: "", currentURL: "" diff --git a/src/components/dock/Dock.tsx b/src/components/dock/Dock.tsx index 7b01e5e6..efa60cbb 100644 --- a/src/components/dock/Dock.tsx +++ b/src/components/dock/Dock.tsx @@ -1,6 +1,6 @@ import { useMotionValue } from "framer-motion"; import { apps } from "~/configs"; -import { useAppSelector } from "~/redux/hooks"; +import { useStore } from "~/stores"; import DockItem from "./DockItem"; interface DockProps { @@ -20,9 +20,9 @@ export default function Dock({ toggleLaunchpad, hide }: DockProps) { - const { dockSize, dockMag } = useAppSelector((state) => ({ - dockSize: state.dock.size, - dockMag: state.dock.mag + const { dockSize, dockMag } = useStore((state) => ({ + dockSize: state.dockSize, + dockMag: state.dockMag })); const openApp = (id: string) => { diff --git a/src/components/menus/ControlCenterMenu.tsx b/src/components/menus/ControlCenterMenu.tsx index d84539b7..329ce167 100644 --- a/src/components/menus/ControlCenterMenu.tsx +++ b/src/components/menus/ControlCenterMenu.tsx @@ -2,14 +2,7 @@ import { useRef } from "react"; import type { RefObject } from "react"; import Slider from "react-rangeslider"; import "react-rangeslider/lib/index.css"; -import { - toggleDark, - toggleWIFI, - toggleAirdrop, - toggleBleutooth, - toggleFullScreen -} from "~/redux/slices"; -import { useAppDispatch, useAppSelector } from "~/redux/hooks"; +import { useStore } from "~/stores"; import { music } from "~/configs"; import { useClickOutside } from "~/hooks"; @@ -59,16 +52,28 @@ export default function ControlCenterMenu({ }: CCMProps) { const controlCenterRef = useRef(null); const { dark, wifi, brightness, bluetooth, airdrop, fullscreen, volume } = - useAppSelector((state) => ({ - dark: state.system.dark, - wifi: state.system.wifi, - brightness: state.system.brightness, - bluetooth: state.system.bluetooth, - airdrop: state.system.airdrop, - fullscreen: state.system.fullscreen, - volume: state.system.volume + useStore((state) => ({ + dark: state.dark, + wifi: state.wifi, + brightness: state.brightness, + bluetooth: state.bluetooth, + airdrop: state.airdrop, + fullscreen: state.fullscreen, + volume: state.volume })); - const dispatch = useAppDispatch(); + const { + toggleWIFI, + toggleBluetooth, + toggleAirdrop, + toggleDark, + toggleFullScreen + } = useStore((state) => ({ + toggleWIFI: state.toggleWIFI, + toggleBluetooth: state.toggleBluetooth, + toggleAirdrop: state.toggleAirdrop, + toggleDark: state.toggleDark, + toggleFullScreen: state.toggleFullScreen + })); useClickOutside(controlCenterRef, toggleControlCenter, [btnRef]); @@ -82,7 +87,7 @@ export default function ControlCenterMenu({
dispatch(toggleWIFI(!wifi))} + onClick={toggleWIFI} >
@@ -94,7 +99,7 @@ export default function ControlCenterMenu({
dispatch(toggleBleutooth(!bluetooth))} + onClick={toggleBluetooth} >
@@ -106,7 +111,7 @@ export default function ControlCenterMenu({
dispatch(toggleAirdrop(!airdrop))} + onClick={toggleAirdrop} >
@@ -119,7 +124,7 @@ export default function ControlCenterMenu({
dispatch(toggleDark(!dark))} + onClick={toggleDark} > {dark ? ( @@ -139,7 +144,7 @@ export default function ControlCenterMenu({
dispatch(toggleFullScreen(!fullscreen))} + onClick={() => toggleFullScreen(!fullscreen)} > {fullscreen ? ( diff --git a/src/components/menus/TopBar.tsx b/src/components/menus/TopBar.tsx index 21c9b147..d58bfe35 100644 --- a/src/components/menus/TopBar.tsx +++ b/src/components/menus/TopBar.tsx @@ -7,8 +7,7 @@ import WifiMenu from "./WifiMenu"; import Battery from "./Battery"; import ControlCenterMenu from "./ControlCenterMenu"; import { isFullScreen } from "~/utils"; -import { setVolume, setBrightness, toggleFullScreen } from "~/redux/slices"; -import { useAppDispatch, useAppSelector } from "~/redux/hooks"; +import { useStore } from "~/stores"; import { music } from "~/configs"; import { useAudio, useWindowSize, useInterval } from "~/hooks"; import type { MacActions } from "~/types"; @@ -91,11 +90,15 @@ const TopBar = (props: TopBarProps) => { }); const { winWidth, winHeight } = useWindowSize(); - const { volume, wifi } = useAppSelector((state) => ({ - volume: state.system.volume, - wifi: state.system.wifi + const { volume, wifi } = useStore((state) => ({ + volume: state.volume, + wifi: state.wifi + })); + const { toggleFullScreen, setVolume, setBrightness } = useStore((state) => ({ + toggleFullScreen: state.toggleFullScreen, + setVolume: state.setVolume, + setBrightness: state.setBrightness })); - const dispatch = useAppDispatch(); useInterval(() => { setState({ @@ -111,16 +114,16 @@ const TopBar = (props: TopBarProps) => { useEffect(() => { const isFull = isFullScreen(); - dispatch(toggleFullScreen(isFull)); + toggleFullScreen(isFull); }, [winWidth, winHeight]); const setAudioVolume = (value: number): void => { - dispatch(setVolume(value)); + setVolume(value); controls.volume(value / 100); }; const setSiteBrightness = (value: number): void => { - dispatch(setBrightness(value)); + setBrightness(value); }; const toggleControlCenter = (): void => { diff --git a/src/components/menus/WifiMenu.tsx b/src/components/menus/WifiMenu.tsx index f57ae20c..368b7022 100644 --- a/src/components/menus/WifiMenu.tsx +++ b/src/components/menus/WifiMenu.tsx @@ -1,8 +1,7 @@ import { useRef } from "react"; import type { RefObject } from "react"; import "react-rangeslider/lib/index.css"; -import { toggleWIFI } from "~/redux/slices"; -import { useAppDispatch, useAppSelector } from "~/redux/hooks"; +import { useStore } from "~/stores"; import { useClickOutside } from "~/hooks"; interface WifiMenuProps { @@ -11,9 +10,11 @@ interface WifiMenuProps { } export default function WifiMenu({ toggleWifiMenu, btnRef }: WifiMenuProps) { - const wifi = useAppSelector((state) => state.system.wifi); - const dispatch = useAppDispatch(); const wifiRef = useRef(null); + const { wifi, toggleWIFI } = useStore((state) => ({ + wifi: state.wifi, + toggleWIFI: state.toggleWIFI + })); useClickOutside(wifiRef, toggleWifiMenu, [btnRef]); @@ -25,11 +26,7 @@ export default function WifiMenu({ toggleWifiMenu, btnRef }: WifiMenuProps) {
Wi-Fi
diff --git a/src/index.tsx b/src/index.tsx index 5794adf8..9c6bf09d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,8 +1,6 @@ import React, { useState } from "react"; import { createRoot } from "react-dom/client"; -import { Provider } from "react-redux"; -import { store } from "~/redux/store"; import Desktop from "~/pages/Desktop"; import Login from "~/pages/Login"; import Boot from "~/pages/Boot"; @@ -69,9 +67,7 @@ const rootElement = document.getElementById("root") as HTMLElement; const root = createRoot(rootElement); root.render( - - - - - + + + ); diff --git a/src/pages/Desktop.tsx b/src/pages/Desktop.tsx index 81881fae..425a3f92 100644 --- a/src/pages/Desktop.tsx +++ b/src/pages/Desktop.tsx @@ -7,7 +7,7 @@ import Launchpad from "~/components/Launchpad"; import Window from "~/components/Window"; import Spotlight from "~/components/Spotlight"; import { apps, wallpapers } from "~/configs"; -import { useAppSelector } from "~/redux/hooks"; +import { useStore } from "~/stores"; import type { MacActions } from "~/types"; interface DesktopState { @@ -48,9 +48,9 @@ export default function Desktop(props: MacActions) { const [spotlightBtnRef, setSpotlightBtnRef] = useState | null>(null); - const { dark, brightness } = useAppSelector((state) => ({ - dark: state.system.dark, - brightness: state.system.brightness + const { dark, brightness } = useStore((state) => ({ + dark: state.dark, + brightness: state.brightness })); const getAppsData = (): void => { diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index d6cdd545..71466737 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -1,12 +1,12 @@ import React, { useState } from "react"; -import { useAppSelector } from "~/redux/hooks"; +import { useStore } from "~/stores"; import { wallpapers, user } from "~/configs"; import type { MacActions } from "~/types"; export default function Login(props: MacActions) { const [password, setPassword] = useState(""); const [sign, setSign] = useState("Click to enter"); - const dark = useAppSelector((state) => state.system.dark); + const dark = useStore((state) => state.dark); const keyPress = (e: React.KeyboardEvent) => { const keyCode = e.key; diff --git a/src/redux/hooks.ts b/src/redux/hooks.ts deleted file mode 100644 index 72d1476d..00000000 --- a/src/redux/hooks.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; -import { RootState, AppDispatch } from "./store"; - -export const useAppDispatch = () => useDispatch(); -export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/src/redux/slices/dock.ts b/src/redux/slices/dock.ts deleted file mode 100644 index c7bbfb56..00000000 --- a/src/redux/slices/dock.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; - -export const dockSlice = createSlice({ - name: "dock", - initialState: { - size: 50, - mag: 2 - }, - reducers: { - setDockSize: (state, action: PayloadAction) => { - state.size = action.payload; - }, - setDockMag: (state, action: PayloadAction) => { - state.mag = action.payload; - } - } -}); - -export const { setDockSize, setDockMag } = dockSlice.actions; - -export default dockSlice.reducer; diff --git a/src/redux/slices/index.ts b/src/redux/slices/index.ts deleted file mode 100644 index 1a5e68d6..00000000 --- a/src/redux/slices/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from "./system"; -export * from "./dock"; diff --git a/src/redux/slices/system.ts b/src/redux/slices/system.ts deleted file mode 100644 index 98204254..00000000 --- a/src/redux/slices/system.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { enterFullScreen, exitFullScreen } from "~/utils"; - -export const systemSlice = createSlice({ - name: "system", - initialState: { - dark: false, - volume: 100, - brightness: 80, - wifi: true, - bluetooth: true, - airdrop: true, - fullscreen: false - }, - reducers: { - toggleDark: (state, action: PayloadAction) => { - state.dark = action.payload; - - if (action.payload) document.documentElement.classList.add("dark"); - else document.documentElement.classList.remove("dark"); - }, - setVolume: (state, action: PayloadAction) => { - state.volume = action.payload; - }, - setBrightness: (state, action: PayloadAction) => { - state.brightness = action.payload; - }, - toggleWIFI: (state, action: PayloadAction) => { - state.wifi = action.payload; - }, - toggleBleutooth: (state, action: PayloadAction) => { - state.bluetooth = action.payload; - }, - toggleAirdrop: (state, action: PayloadAction) => { - state.airdrop = action.payload; - }, - toggleFullScreen: (state, action: PayloadAction) => { - action.payload ? enterFullScreen() : exitFullScreen(); - state.fullscreen = action.payload; - } - } -}); - -export const { - toggleDark, - setVolume, - setBrightness, - toggleWIFI, - toggleBleutooth, - toggleAirdrop, - toggleFullScreen -} = systemSlice.actions; - -export default systemSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts deleted file mode 100644 index 856ba1b1..00000000 --- a/src/redux/store.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { configureStore } from "@reduxjs/toolkit"; -import systemReducer from "./slices/system"; -import dockReducer from "./slices/dock"; - -export const store = configureStore({ - reducer: { - system: systemReducer, - dock: dockReducer - } -}); - -export type AppDispatch = typeof store.dispatch; -export type RootState = ReturnType; diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 00000000..1b449cb1 --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,8 @@ +import { create } from "zustand"; +import { createDockSlice, type DockSlice } from "./slices/dock"; +import { createSystemSlice, type SystemSlice } from "./slices/system"; + +export const useStore = create((...a) => ({ + ...createDockSlice(...a), + ...createSystemSlice(...a) +})); diff --git a/src/stores/slices/dock.ts b/src/stores/slices/dock.ts new file mode 100644 index 00000000..53ea50b7 --- /dev/null +++ b/src/stores/slices/dock.ts @@ -0,0 +1,15 @@ +import { StateCreator } from "zustand"; + +export interface DockSlice { + dockSize: number; + dockMag: number; + setDockSize: (v: number) => void; + setDockMag: (v: number) => void; +} + +export const createDockSlice: StateCreator = (set) => ({ + dockSize: 50, + dockMag: 2, + setDockSize: (v) => set(() => ({ dockSize: v })), + setDockMag: (v) => set(() => ({ dockMag: v })) +}); diff --git a/src/stores/slices/system.ts b/src/stores/slices/system.ts new file mode 100644 index 00000000..c9afe5aa --- /dev/null +++ b/src/stores/slices/system.ts @@ -0,0 +1,45 @@ +import { StateCreator } from "zustand"; +import { enterFullScreen, exitFullScreen } from "~/utils"; + +export interface SystemSlice { + dark: boolean; + volume: number; + brightness: number; + wifi: boolean; + bluetooth: boolean; + airdrop: boolean; + fullscreen: boolean; + toggleDark: () => void; + toggleWIFI: () => void; + toggleBluetooth: () => void; + toggleAirdrop: () => void; + toggleFullScreen: (v: boolean) => void; + setVolume: (v: number) => void; + setBrightness: (v: number) => void; +} + +export const createSystemSlice: StateCreator = (set) => ({ + dark: false, + volume: 100, + brightness: 80, + wifi: true, + bluetooth: true, + airdrop: true, + fullscreen: false, + toggleDark: () => + set((state) => { + if (!state.dark) document.documentElement.classList.add("dark"); + else document.documentElement.classList.remove("dark"); + return { dark: !state.dark }; + }), + toggleWIFI: () => set((state) => ({ wifi: !state.wifi })), + toggleBluetooth: () => set((state) => ({ bluetooth: !state.bluetooth })), + toggleAirdrop: () => set((state) => ({ airdrop: !state.airdrop })), + toggleFullScreen: (v) => + set(() => { + v ? enterFullScreen() : exitFullScreen(); + return { fullscreen: v }; + }), + setVolume: (v) => set(() => ({ volume: v })), + setBrightness: (v) => set(() => ({ brightness: v })) +});