diff --git a/README.md b/README.md index df5830ca..707aa5c9 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,9 @@ pnpm build ## Changelog -- **Update 2023.06.25**: Built a simple [Typora](https://typora.io/) clone on top of [Milkdown](https://milkdown.dev/). +- **Update 2023.06.26**: Improve [FaceTime](https://support.apple.com/en-us/HT208176). + +- **Update 2023.06.25**: Add [Typora](https://typora.io/), built on top of [Milkdown](https://milkdown.dev/). - **Update 2021.12.05**: Simulated the device's actual battery state using [Battery API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API), displaying 100% charge on [unsupported browsers](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API#browser_compatibility). diff --git a/src/components/Window.tsx b/src/components/Window.tsx index 541e841b..1f358992 100644 --- a/src/components/Window.tsx +++ b/src/components/Window.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect } from "react"; import { Rnd } from "react-rnd"; import { useWindowSize } from "~/hooks"; import { useStore } from "~/stores"; +import { minMarginX, minMarginY, appBarHeight } from "~/utils"; const FullIcon = ({ size }: { size: number }) => { return ( @@ -39,24 +40,22 @@ const ExitFullIcon = ({ size }: { size: number }) => { ); }; -const minMarginY = 32; -const minMarginX = 100; - interface TrafficProps { id: string; max: boolean; + aspectRatio?: number; setMax: (id: string, target?: boolean) => void; setMin: (id: string) => void; close: (id: string) => void; } interface WindowProps extends TrafficProps { + title: string; min: boolean; width?: number; height?: number; minWidth?: number; minHeight?: number; - title: string; x?: number; y?: number; z: number; @@ -71,7 +70,9 @@ interface WindowState { y: number; } -const TrafficLights = ({ id, close, max, setMax, setMin }: TrafficProps) => { +const TrafficLights = ({ id, close, aspectRatio, max, setMax, setMin }: TrafficProps) => { + const disableMax = aspectRatio !== undefined; + const closeWindow = (e: React.MouseEvent | React.TouchEvent): void => { e.stopPropagation(); close(id); @@ -95,11 +96,14 @@ const TrafficLights = ({ id, close, max, setMax, setMin }: TrafficProps) => { ); @@ -136,6 +140,7 @@ const Window = (props: WindowProps) => { const border = props.max ? "" : "border border-gray-500/30"; const width = props.max ? winWidth : state.width; const height = props.max ? winHeight : state.height; + const disableMax = props.aspectRatio !== undefined; const children = React.cloneElement(props.children as React.ReactElement, { width: width @@ -184,6 +189,8 @@ const Window = (props: WindowProps) => { dragHandleClassName="window-bar" disableDragging={props.max} enableResizing={!props.max} + lockAspectRatio={props.aspectRatio} + lockAspectRatioExtraHeight={props.aspectRatio ? appBarHeight : undefined} style={{ zIndex: props.z }} onMouseDown={() => props.focus(props.id)} className={`absolute ${round} overflow-hidden bg-transparent w-full h-full ${border} shadow-lg shadow-black/30 ${minimized}`} @@ -191,14 +198,15 @@ const Window = (props: WindowProps) => { >
props.setMax(props.id)} + onDoubleClick={() => !disableMax && props.setMax(props.id)} > {props.title}
diff --git a/src/components/apps/FaceTime.tsx b/src/components/apps/FaceTime.tsx index 80115076..3b19208d 100644 --- a/src/components/apps/FaceTime.tsx +++ b/src/components/apps/FaceTime.tsx @@ -1,60 +1,162 @@ import { useRef, useState } from "react"; import Webcam from "react-webcam"; +import format from "date-fns/format"; +import { useStore } from "~/stores"; -const videoConstraints = { - facingMode: "user" +interface SidebarProps { + state: FaceTimeState; + onTake: () => void; + onSave: () => void; + onSelect: (src: string) => void; +} + +interface SidebarItemProps { + date: string; + active: boolean; +} + +interface FaceTimeState { + canSave: boolean; + curImage: string | null; +} + +const SidebarItem = ({ date, active }: SidebarItemProps) => { + const [hover, setHover] = useState(false); + const { deleteImage } = useStore((state) => ({ + deleteImage: state.delFaceTimeImage + })); + + return ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + > +
+ +
+ +
+
+ FaceTime Link +
+
+ + FaceTime ยท {format(Number(date), "hh:mm:ss")} +
+
+ + { + e.stopPropagation(); + deleteImage(date); + }} + /> +
+ ); }; -const FaceTime = () => { - const [click, setClick] = useState(false); - const [img, setImg] = useState(""); - const webcamRef = useRef(null); +const Sidebar = ({ state, onTake, onSave, onSelect }: SidebarProps) => { + const { images } = useStore((state) => ({ + images: state.faceTimeImages + })); - const capture = () => { - if (!webcamRef.current) return; - const imageSrc = webcamRef.current.getScreenshot() as string; - setImg(imageSrc); - }; - - if (click) - return ( -
- {img && ( - yourimage - )} + return ( +
+
-
- ); - else - return ( -
- +
+ +
+
Recent
+ {Object.keys(images) + .reverse() + .map((date) => ( + + ))} +
+
+ ); +}; + +const FaceTime = () => { + const webcamRef = useRef(null); + const { addImage } = useStore((state) => ({ + addImage: state.addFaceTimeImage + })); + const [state, setState] = useState({ + canSave: false, + curImage: null + }); + + return ( +
+ { + if (!state.curImage) { + const src = webcamRef.current?.getScreenshot() || ""; + setState({ curImage: src, canSave: true }); + } else setState({ curImage: null, canSave: false }); + }} + onSave={() => { + addImage(state.curImage!); + setState({ curImage: null, canSave: false }); + }} + onSelect={(src) => { + setState({ curImage: src, canSave: false }); + }} + /> + +
+ {!state.curImage ? ( + + ) : ( + state.curImage && ( + your-image + ) + )}
- ); +
+ ); }; export default FaceTime; diff --git a/src/configs/apps.tsx b/src/configs/apps.tsx index 518dbe79..1bf29329 100644 --- a/src/configs/apps.tsx +++ b/src/configs/apps.tsx @@ -4,7 +4,7 @@ import Safari from "~/components/apps/Safari"; import Bear from "~/components/apps/Bear"; import Typora from "~/components/apps/Typora"; import VSCode from "~/components/apps/VSCode"; - +import { appBarHeight } from "~/utils"; import type { AppsData } from "~/types"; const apps: AppsData[] = [ @@ -18,9 +18,9 @@ const apps: AppsData[] = [ id: "bear", title: "Bear", desktop: true, - show: true, width: 860, height: 500, + show: true, y: -40, img: "img/icons/bear.png", content: @@ -29,7 +29,6 @@ const apps: AppsData[] = [ id: "typora", title: "Typora", desktop: true, - show: false, width: 600, height: 580, y: -20, @@ -40,7 +39,6 @@ const apps: AppsData[] = [ id: "safari", title: "Safari", desktop: true, - show: false, width: 1024, minWidth: 375, minHeight: 200, @@ -52,10 +50,9 @@ const apps: AppsData[] = [ id: "vscode", title: "VSCode", desktop: true, - show: false, width: 900, height: 600, - x: -80, + x: 80, y: -30, img: "img/icons/vscode.png", content: @@ -64,16 +61,20 @@ const apps: AppsData[] = [ id: "facetime", title: "FaceTime", desktop: true, - show: false, img: "img/icons/facetime.png", - height: 530, + width: 500 * 1.7, + height: 500 + appBarHeight, + minWidth: 350 * 1.7, + minHeight: 350 + appBarHeight, + aspectRatio: 1.7, + x: -80, + y: 20, content: }, { id: "terminal", title: "Terminal", desktop: true, - show: false, img: "img/icons/terminal.png", content: }, diff --git a/src/pages/Desktop.tsx b/src/pages/Desktop.tsx index 99ce6665..0e77ed1e 100644 --- a/src/pages/Desktop.tsx +++ b/src/pages/Desktop.tsx @@ -8,6 +8,7 @@ import Window from "~/components/Window"; import Spotlight from "~/components/Spotlight"; import { apps, wallpapers } from "~/configs"; import { useStore } from "~/stores"; +import { minMarginY } from "~/utils"; import type { MacActions } from "~/types"; interface DesktopState { @@ -30,8 +31,6 @@ interface DesktopState { spotlight: boolean; } -const minMarginY = 32; - export default function Desktop(props: MacActions) { const [state, setState] = useState({ showApps: {}, @@ -62,7 +61,7 @@ export default function Desktop(props: MacActions) { apps.forEach((app) => { showApps = { ...showApps, - [app.id]: app.show + [app.id]: !!app.show }; appsZ = { ...appsZ, @@ -215,12 +214,13 @@ export default function Desktop(props: MacActions) { return apps.map((app) => { if (app.desktop && state.showApps[app.id]) { const props = { - title: app.title, id: app.id, + title: app.title, width: app.width, height: app.height, minWidth: app.minWidth, minHeight: app.minHeight, + aspectRatio: app.aspectRatio, x: app.x, y: app.y, z: state.appsZ[app.id], diff --git a/src/stores/slices/user.ts b/src/stores/slices/user.ts index d1ac2bef..4722aceb 100644 --- a/src/stores/slices/user.ts +++ b/src/stores/slices/user.ts @@ -3,9 +3,27 @@ import { StateCreator } from "zustand"; export interface UserSlice { typoraMd: string; setTyporaMd: (v: string) => void; + faceTimeImages: { + [date: string]: string; + }; + addFaceTimeImage: (v: string) => void; + delFaceTimeImage: (k: string) => void; } export const createUserSlice: StateCreator = (set) => ({ typoraMd: `# Hi ๐Ÿ‘‹\nThis is a simple clone of [Typora](https://typora.io/). Built on top of [Milkdown](https://milkdown.dev/), an open-source WYSIWYG markdown editor.`, - setTyporaMd: (v) => set(() => ({ typoraMd: v })) + setTyporaMd: (v) => set(() => ({ typoraMd: v })), + faceTimeImages: {}, + addFaceTimeImage: (v) => + set((state) => { + const images = state.faceTimeImages; + images[+new Date()] = v; + return { faceTimeImages: images }; + }), + delFaceTimeImage: (k) => + set((state) => { + const images = state.faceTimeImages; + delete images[k]; + return { faceTimeImages: images }; + }) }); diff --git a/src/types/configs/apps.d.ts b/src/types/configs/apps.d.ts index ffc53c93..b8ca9c54 100644 --- a/src/types/configs/apps.d.ts +++ b/src/types/configs/apps.d.ts @@ -8,6 +8,7 @@ export interface AppsData { height?: number; minWidth?: number; minHeight?: number; + aspectRatio?: number; x?: number; y?: number; content?: JSX.Element; diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 00000000..3208a791 --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,3 @@ +export const minMarginY = 32; // top bar height +export const minMarginX = 100; +export const appBarHeight = 24; // app window's title bar height diff --git a/src/utils/index.ts b/src/utils/index.ts index 65516e1a..65165616 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,2 +1,3 @@ export * from "./screen"; export * from "./url"; +export * from "./constants";