From de4d71f1a36811079e41f470a49321017302dbb3 Mon Sep 17 00:00:00 2001 From: Renovamen Date: Tue, 4 May 2021 20:30:19 +0800 Subject: [PATCH] fix: fix screen and audio controller --- README.md | 2 +- public/markdown/about-me.md | 2 +- src/components/apps/Bear.js | 8 +- src/components/apps/Safari.js | 15 +- src/components/dock/Dock.js | 10 +- src/components/topbar/AppleMenu.js | 14 +- src/components/topbar/ControlCenterMenu.js | 141 +++----------- src/components/topbar/MenuBar.js | 214 ++++++++++++++++----- src/pages/Desktop.js | 9 +- src/styles/index.css | 92 ++++++--- src/utils/screen.js | 27 +++ src/utils/url.js | 13 ++ 12 files changed, 323 insertions(+), 224 deletions(-) create mode 100644 src/utils/screen.js create mode 100644 src/utils/url.js diff --git a/README.md b/README.md index 638626cc..8bb5d9e6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # playground-macos -My portfolio website simulating macOS's GUI: https://zxh.vercel.app +My portfolio website simulating macOS's GUI: https://portfolio.zxh.io Powered by [React](https://reactjs.org/) and [tailwindcss](https://tailwindcss.com/). diff --git a/public/markdown/about-me.md b/public/markdown/about-me.md index c446c277..2e02702b 100644 --- a/public/markdown/about-me.md +++ b/public/markdown/about-me.md @@ -27,4 +27,4 @@ Reach me at: ## Résumé -My résumé can be found [here](https://zxh.io/files/cv/brief/en.pdf). +My résumé can be found here: [English](https://zxh.io/files/cv/brief/en.pdf) / [中文](https://zxh.io/files/cv/brief/cn.pdf). diff --git a/src/components/apps/Bear.js b/src/components/apps/Bear.js index 145bc9de..a5a90b19 100644 --- a/src/components/apps/Bear.js +++ b/src/components/apps/Bear.js @@ -29,7 +29,7 @@ const Highlighter = (dark) => { class Sidebar extends Component { render() { return ( -
+
@@ -56,7 +56,7 @@ class Sidebar extends Component { class Middlebar extends Component { render() { return ( -
+
    {this.props.items.map((item, index) => (
  • +
    +
    diff --git a/src/components/apps/Safari.js b/src/components/apps/Safari.js index a2975c20..17273ffa 100644 --- a/src/components/apps/Safari.js +++ b/src/components/apps/Safari.js @@ -5,20 +5,7 @@ import { FaShieldAlt } from "react-icons/fa"; import { FiChevronLeft, FiChevronRight } from "react-icons/fi"; import { BsLayoutSidebar } from "react-icons/bs"; import { IoShareOutline, IoCopyOutline } from "react-icons/io5"; - -// https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url -const checkURL = (str) => { - const pattern = new RegExp( - "^(https?:\\/\\/)?" + // protocol - "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name - "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address - "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path - "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string - "(\\#[-a-z\\d_]*)?$", - "i" // fragment locator - ); - return !!pattern.test(str); -}; +import { checkURL } from "../../utils/url"; class NavSection extends Component { render() { diff --git a/src/components/dock/Dock.js b/src/components/dock/Dock.js index bd6d37a5..e3cc7321 100644 --- a/src/components/dock/Dock.js +++ b/src/components/dock/Dock.js @@ -3,9 +3,15 @@ import { useMotionValue } from "framer-motion"; import apps from "../../configs/apps"; import DockItem from "./DockItem"; -export default function Dock({ open, showApps, toggleLaunchpad, hide }) { +export default function Dock({ + open, + showApps, + showLaunchpad, + toggleLaunchpad, + hide +}) { const openApp = (id) => { - if (id === "launchpad") toggleLaunchpad(); + if (id === "launchpad") toggleLaunchpad(!showLaunchpad); else { toggleLaunchpad(false); open(id); diff --git a/src/components/topbar/AppleMenu.js b/src/components/topbar/AppleMenu.js index 7e82f054..e240ac78 100644 --- a/src/components/topbar/AppleMenu.js +++ b/src/components/topbar/AppleMenu.js @@ -1,7 +1,7 @@ import React from "react"; import { MenuItem, MenuItemGroup } from "../menu/base"; -export default function AppleMenu({ setlogon }) { +export default function AppleMenu({ logout }) { return (
    @@ -18,15 +18,13 @@ export default function AppleMenu({ setlogon }) { Force Quit... - setlogon(false)}>Sleep - setlogon(false)}>Restart... - setlogon(false)}>Shut Down... + Sleep + Restart... + Shut Down... - setlogon(false)}>Lock Screen - setlogon(false)}> - Log Out Xiaohan Zou... - + Lock Screen + Log Out Xiaohan Zou...
    ); diff --git a/src/components/topbar/ControlCenterMenu.js b/src/components/topbar/ControlCenterMenu.js index 3a7409d2..a928f2ef 100644 --- a/src/components/topbar/ControlCenterMenu.js +++ b/src/components/topbar/ControlCenterMenu.js @@ -15,21 +15,6 @@ import { import { IoSunny, IoMoon, IoVolumeHigh } from "react-icons/io5"; import { FaWifi } from "react-icons/fa"; -const enterFullScreen = () => { - const element = document.documentElement; - if (element.requestFullscreen) element.requestFullscreen(); - else if (element.msRequestFullscreen) element.msRequestFullscreen(); - else if (element.mozRequestFullScreen) element.mozRequestFullScreen(); - else if (element.webkitRequestFullscreen) element.webkitRequestFullscreen(); -}; - -const exitFullScreen = () => { - if (document.exitFullscreen) document.exitFullscreen(); - else if (document.msExitFullscreen) document.msExitFullscreen(); - else if (document.mozExitFullScreen) document.mozExitFullScreen(); - else if (document.webkitExitFullscreen) document.webkitExitFullscreen(); -}; - const SliderComponent = ({ icon, value, setValue }) => { return (
    @@ -47,85 +32,11 @@ const SliderComponent = ({ icon, value, setValue }) => { }; export default class ControlCenterMenu extends Component { - constructor(props) { - super(props); - this.state = { - playing: false, - volume: 100, - brightness: Math.floor(Math.random() * 100), - btn: { - wifi: true, - bluetooth: true, - airdrop: true - }, - fullscreen: false - }; - this.toggleAudio = this.toggleAudio.bind(this); - this.resize.bind(this); - } - - componentDidMount() { - window.addEventListener("resize", this.resize); - this.audio = new Audio("music/sunflower.mp3"); - this.audio.load(); - this.audio.addEventListener("ended", () => this.audio.play()); - this.audio.volume = 1; - } - - componentWillUnmount() { - window.removeEventListener("resize", this.resize); - this.audio.removeEventListener("ended", () => this.audio.play()); - } - - resize = () => { - const isFullScreen = !!( - document.webkitIsFullScreen || - document.mozFullScreen || - document.msFullscreenElement || - document.fullscreenElement - ); - this.setState({ - fullscreen: isFullScreen - }); - }; - - toggleAudio = () => { - this.setState({ playing: !this.state.playing }, () => { - this.state.playing ? this.audio.play() : this.audio.pause(); - }); - }; - toggleMode = () => { this.props.setDark(!this.props.dark); nightwind.toggle(); }; - toggleBtn = (name) => { - let btn = this.state.btn; - btn[name] = !btn[name]; - this.setState({ - btn: btn - }); - }; - - toggleFullScreen = () => { - this.setState({ fullscreen: !this.state.fullscreen }, () => { - this.state.fullscreen ? enterFullScreen() : exitFullScreen(); - }); - }; - - setVolume = (value) => { - this.setState({ volume: value }, () => { - this.audio.volume = value / 100; - }); - }; - - setBrightness = (value) => { - this.setState({ - brightness: value - }); - }; - render() { return (
    @@ -134,16 +45,16 @@ export default class ControlCenterMenu extends Component { this.toggleBtn("wifi")} + onClick={() => this.props.toggleBtn("wifi")} />
    Wifi - {this.state.btn.wifi ? "Home" : "Off"} + {this.props.btn.wifi ? "Home" : "Off"}
    @@ -151,16 +62,16 @@ export default class ControlCenterMenu extends Component { this.toggleBtn("bluetooth")} + onClick={() => this.props.toggleBtn("bluetooth")} />
    Bluetooth - {this.state.btn.bluetooth ? "On" : "Off"} + {this.props.btn.bluetooth ? "On" : "Off"}
    @@ -168,16 +79,16 @@ export default class ControlCenterMenu extends Component { this.toggleBtn("airdrop")} + onClick={() => this.props.toggleBtn("airdrop")} />
    AirDrop - {this.state.btn.airdrop ? "Contacts Only" : "Off"} + {this.props.btn.airdrop ? "Contacts Only" : "Off"}
    @@ -207,29 +118,35 @@ export default class ControlCenterMenu extends Component { Keyboard Brightness
    - {this.state.fullscreen ? ( - + {this.props.fullscreen ? ( + this.props.toggleFullScreen(false)} + /> ) : ( - + this.props.toggleFullScreen(true)} + /> )} - {this.state.fullscreen ? "Exit Fullscreen" : "Enter Fullscreen"} + {this.props.fullscreen ? "Exit Fullscreen" : "Enter Fullscreen"}
    Display } - value={this.state.brightness} - setValue={this.setBrightness} + value={this.props.brightness} + setValue={this.props.setBrightness} />
    Sound } - value={this.state.volume} - setValue={this.setVolume} + value={this.props.volume} + setValue={this.props.setVolume} />
    @@ -242,10 +159,16 @@ export default class ControlCenterMenu extends Component { Sunflower Post Malone / Swae Lee
    - {this.state.playing ? ( - + {this.props.playing ? ( + this.props.toggleAudio(false)} + size={24} + /> ) : ( - + this.props.toggleAudio(true)} + size={24} + /> )}
diff --git a/src/components/topbar/MenuBar.js b/src/components/topbar/MenuBar.js index 01d8750a..bc1a1c2f 100644 --- a/src/components/topbar/MenuBar.js +++ b/src/components/topbar/MenuBar.js @@ -1,7 +1,12 @@ -import React, { useEffect, useState } from "react"; +import React, { Component } from "react"; import format from "date-fns/format"; import AppleMenu from "./AppleMenu"; import ControlCenterMenu from "./ControlCenterMenu"; +import { + enterFullScreen, + exitFullScreen, + isFullScreen +} from "../../utils/screen"; // ------- import icons ------- import { BsBatteryFull } from "react-icons/bs"; @@ -29,57 +34,166 @@ const MenuItem = ({ ); }; -export default function MenuBar({ title, dark, setDark, setlogon }) { - const [date, setDate] = useState(new Date()); - const [showControlCenter, setShowControlCenter] = useState(false); - const [showAppleMenu, setShowAppleMenu] = useState(false); +// export default function MenuBar({ title, dark, setDark, setlogon }) { +export default class MenuBar extends Component { + constructor(props) { + super(props); + this.state = { + date: new Date(), + showControlCenter: false, + showAppleMen: false, + playing: false, + volume: 100, + brightness: Math.floor(Math.random() * 100), + btn: { + wifi: true, + bluetooth: true, + airdrop: true + }, + fullscreen: false + }; + this.toggleAudio = this.toggleAudio.bind(this); + this.resize.bind(this); + } - useEffect(() => { - let timer = setTimeout(() => setDate(new Date()), 60 * 1000); - return () => clearTimeout(timer); - }, [date]); + componentDidMount() { + // current date and time + setInterval(() => { + this.setState({ + data: new Date() + }); + }, 60 * 1000); - return ( -
-
- setShowAppleMenu(!showAppleMenu)} - > - - - {title} -
+ // listen to screen size change + window.addEventListener("resize", this.resize); + + // load music + this.audio = new Audio("music/sunflower.mp3"); + this.audio.load(); + // auto replay + this.audio.addEventListener("ended", () => this.audio.play()); + } + + componentWillUnmount() { + window.removeEventListener("resize", this.resize); + this.audio.removeEventListener("ended", () => this.audio.play()); + } + + resize = () => { + this.setState({ + fullscreen: isFullScreen() + }); + }; + + setVolume = (value) => { + this.setState({ volume: value }, () => { + this.audio.volume = value / 100; + }); + }; + + setBrightness = (value) => { + this.setState({ + brightness: value + }); + }; + + toggleBtn = (name) => { + let btn = this.state.btn; + btn[name] = !btn[name]; + this.setState({ + btn: btn + }); + }; + + toggleAudio = (target) => { + this.setState({ playing: target }, () => { + this.state.playing ? this.audio.play() : this.audio.pause(); + }); + }; - {/* Open this when clicking on Apple logo */} - {showAppleMenu && } - -
- - 100% - - - - - - - - - setShowControlCenter(!showControlCenter)}> - control center - - - {/* Open this when clicking on Control Center button */} - {showControlCenter && ( - - )} - - {format(date, "eee d MMM h:mm aa")} + toggleFullScreen = (target) => { + this.setState({ fullscreen: target }, () => { + this.state.fullscreen ? enterFullScreen() : exitFullScreen(); + }); + }; + + logout = () => { + Promise.all([ + this.toggleFullScreen(false), + this.toggleAudio(false), + this.setVolume(100) + ]).then(() => this.props.setlogon(false)); + }; + + render() { + return ( +
+
+ + this.setState({ + showAppleMenu: !this.state.showAppleMenu + }) + } + > + + + + {this.props.title} + +
+ + {/* Open this when clicking on Apple logo */} + {this.state.showAppleMenu && } + +
+ + 100% + + + + + + + + + + this.setState({ + showControlCenter: !this.state.showControlCenter + }) + } + > + control center + + + {/* Open this when clicking on Control Center button */} + {this.state.showControlCenter && ( + + )} + + {format(this.state.date, "eee d MMM h:mm aa")} +
-
- ); + ); + } } diff --git a/src/pages/Desktop.js b/src/pages/Desktop.js index 8efdac67..c492f5b5 100644 --- a/src/pages/Desktop.js +++ b/src/pages/Desktop.js @@ -32,6 +32,7 @@ export default class Desktop extends Component { appsZ = {}, maxApps = {}, minApps = {}; + apps.forEach((app) => { showApps = { ...showApps, @@ -50,13 +51,12 @@ export default class Desktop extends Component { [app.id]: false }; }); + this.setState({ showApps }); }; toggleLaunchpad = (target) => { - if (target === undefined) target = !this.state.showLaunchpad; - - var r = document.querySelector(`#launchpad`); + let r = document.querySelector(`#launchpad`); if (target) { r.style.transform = "scale(1)"; r.style.transition = "ease-in 0.2s"; @@ -69,7 +69,7 @@ export default class Desktop extends Component { }; setWinowsPosition = (id) => { - var r = document.querySelector(`#window-${id}`); + let r = document.querySelector(`#window-${id}`); const rect = r.getBoundingClientRect(); r.style.setProperty( "--window-transform-x", @@ -229,6 +229,7 @@ export default class Desktop extends Component { diff --git a/src/styles/index.css b/src/styles/index.css index 4f678014..c4e2967b 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -93,110 +93,140 @@ html, body { /* --------- bear markdown --------- */ -.bear a { +.bear .markdown a { @apply text-red-500; } -.bear h1, .bear h2, .bear h3, .bear h4 { +.bear .markdown h1, +.bear .markdown h2, +.bear .markdown h3, +.bear .markdown h4 { @apply font-medium relative text-gray-800; } -.bear h1, .bear h2, .bear h3, .bear h4 { +.bear .markdown h1, +.bear .markdown h2, +.bear .markdown h3, +.bear .markdown h4 { @apply mt-7; } -.bear h1::before, -.bear h2::before, -.bear h3::before, -.bear h4::before { +.bear .markdown h1::before, +.bear .markdown h2::before, +.bear .markdown h3::before, +.bear .markdown h4::before { @apply absolute text-sm text-gray-400 -left-7 bottom-1; } -.bear h1 { +.bear .markdown h1 { @apply text-3xl; } -.bear h1::before { +.bear .markdown h1::before { content: 'H1'; } -.bear h2 { +.bear .markdown h2 { @apply text-2xl; } -.bear h2::before { +.bear .markdown h2::before { content: 'H2'; } -.bear h3 { +.bear .markdown h3 { @apply text-xl; } -.bear h3::before { +.bear .markdown h3::before { content: 'H3'; } -.bear h3 { +.bear .markdown h3 { @apply text-xl; } -.bear h4 { +.bear .markdown h4 { @apply text-lg; } -.bear h4::before { +.bear .markdown h4::before { content: 'H4'; } -.bear p { +.bear .markdown p { @apply mt-6; } -.bear h2 + p, .bear h3 + p, .bear h4 + p { +.bear .markdown h2 + p, +.bear .markdown h3 + p, +.bear .markdown h4 + p { @apply mt-4; } -.bear ul { +.bear .markdown ul { @apply mt-2 list-disc pl-5; } -.bear ul > li::marker { +.bear .markdown ul > li::marker { @apply text-red-500; } -.bear li p { +.bear .markdown li p { @apply m-0 mb-2; } -.bear code { +.bear .markdown code { @apply text-sm border border-gray-300 rounded py-0.5 px-1 bg-gray-50; } -.bear pre { +.bear .markdown pre { @apply mt-2; } -.bear pre div { +.bear .markdown pre div { @apply border border-gray-300 rounded; } -.bear pre code, .dark .bear pre code { - font-size: 15px; - @apply bg-transparent border-none p-0; +.bear .markdown pre code, +.dark .bear .markdown pre code { + @apply text-base bg-transparent border-none p-0; } -.bear img { +.bear .markdown .token { + background: transparent !important; +} + +.bear .markdown img { @apply inline-block; } -.bear table { +.bear .markdown table { @apply mt-4 block w-full overflow-auto; } -.bear table td, .bear table th { +.bear .markdown table td, +.bear .markdown table th { @apply px-3 py-2 border border-gray-300; } -.bear table tr { +.bear .markdown table tr { @apply border-t border-gray-300 bg-transparent; } + +.dark .bear .markdown, +.dark .bear .midbar { + @apply bg-gray-800; +} + +.dark .bear .midbar li.bg-white { + @apply bg-gray-900; +} + +.dark .bear .midbar li:hover { + @apply bg-gray-900; +} + +.dark .bear .sidebar { + @apply bg-gray-700 text-white; +} diff --git a/src/utils/screen.js b/src/utils/screen.js new file mode 100644 index 00000000..68e26120 --- /dev/null +++ b/src/utils/screen.js @@ -0,0 +1,27 @@ +export const enterFullScreen = () => { + if (!isFullScreen()) { + const element = document.documentElement; + if (element.requestFullscreen) element.requestFullscreen(); + else if (element.msRequestFullscreen) element.msRequestFullscreen(); + else if (element.mozRequestFullScreen) element.mozRequestFullScreen(); + else if (element.webkitRequestFullscreen) element.webkitRequestFullscreen(); + } +}; + +export const exitFullScreen = () => { + if (isFullScreen()) { + if (document.exitFullscreen) document.exitFullscreen(); + else if (document.msExitFullscreen) document.msExitFullscreen(); + else if (document.mozExitFullScreen) document.mozExitFullScreen(); + else if (document.webkitExitFullscreen) document.webkitExitFullscreen(); + } +}; + +export const isFullScreen = () => { + return !!( + document.webkitIsFullScreen || + document.mozFullScreen || + document.msFullscreenElement || + document.fullscreenElement + ); +}; diff --git a/src/utils/url.js b/src/utils/url.js new file mode 100644 index 00000000..8db08e60 --- /dev/null +++ b/src/utils/url.js @@ -0,0 +1,13 @@ +// https://stackoverflow.com/questions/5717093/check-if-a-javascript-string-is-a-url +export const checkURL = (str) => { + const pattern = new RegExp( + "^(https?:\\/\\/)?" + // protocol + "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name + "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address + "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path + "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string + "(\\#[-a-z\\d_]*)?$", + "i" // fragment locator + ); + return !!pattern.test(str); +};