Skip to content

Commit

Permalink
feat: improve FaceTime
Browse files Browse the repository at this point in the history
  • Loading branch information
Renovamen committed Jun 26, 2023
1 parent 71c22b4 commit 4502af2
Show file tree
Hide file tree
Showing 9 changed files with 206 additions and 70 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
26 changes: 17 additions & 9 deletions src/components/Window.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -95,11 +96,14 @@ const TrafficLights = ({ id, close, max, setMax, setMin }: TrafficProps) => {
<span className={`icon i-fe:minus text-[10px] ${max ? "invisible" : ""}`} />
</button>
<button
className="window-btn bg-green-500 dark:bg-green-400"
className={`window-btn ${
disableMax ? "c-bg-400" : "bg-green-500 dark:bg-green-400"
}`}
onClick={() => setMax(id)}
onTouchEnd={() => setMax(id)}
disabled={disableMax}
>
{max ? <ExitFullIcon size={9} /> : <FullIcon size={6} />}
{!disableMax && (max ? <ExitFullIcon size={9} /> : <FullIcon size={6} />)}
</button>
</div>
);
Expand Down Expand Up @@ -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<any>, {
width: width
Expand Down Expand Up @@ -184,21 +189,24 @@ 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}`}
id={`window-${props.id}`}
>
<div
className="window-bar relative h-6 text-center c-bg-200"
onDoubleClick={() => props.setMax(props.id)}
onDoubleClick={() => !disableMax && props.setMax(props.id)}
>
<TrafficLights
id={props.id}
close={props.close}
max={props.max}
aspectRatio={props.aspectRatio}
setMax={props.setMax}
setMin={props.setMin}
close={props.close}
/>
<span className="font-semibold c-text-700">{props.title}</span>
</div>
Expand Down
194 changes: 148 additions & 46 deletions src/components/apps/FaceTime.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
className={`hstack h-16 px-2.5 rounded-md space-x-2 ${active && "bg-[#508041]"}`}
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<div className="h-11 w-11 rounded-full bg-zinc-600 flex-center">
<span className="i-ph:link-bold" text="2xl white/80" />
</div>

<div text="left">
<div className="font-medium leading-4" text="white sm">
FaceTime Link
</div>
<div className="hstack space-x-1 text-white/60">
<span className="i-ion:videocam" />
<span>FaceTime · {format(Number(date), "hh:mm:ss")}</span>
</div>
</div>

<span
className="i-maki:cross absolute right-2.5 duration-150"
text={`lg white/60 hover:white ${!hover && "transparent"}`}
onClick={(e) => {
e.stopPropagation();
deleteImage(date);
}}
/>
</div>
);
};

const FaceTime = () => {
const [click, setClick] = useState(false);
const [img, setImg] = useState("");
const webcamRef = useRef<Webcam>(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 (
<div id="container" className="bg-gray-800 h-full flex-center flex-col space-y-6">
{img && (
<img
className="border-8 border-white max-h-60 md:max-h-96"
src={img}
alt="yourimage"
/>
)}
return (
<div className="absolute w-74 h-full z-1 left-0 top-0 flex flex-col bg-zinc-900/85 backdrop-blur-xl">
<div className="p-5 space-y-2.5 text-sm">
<button
className="text-black mx-auto no-outline bg-white h-6 w-20"
border="1 black/50 rounded-full"
onClick={() => setClick(false)}
className="flex-center space-x-1 w-full py-1 text-white bg-green-700 rounded-md"
onClick={onTake}
>
<b>Retake</b>
<span className="i-ion:ios-videocam text-base" />
<span>{state.curImage ? "Retake" : "Take a Picture"}</span>
</button>
</div>
);
else
return (
<div id="container" className="bg-gray-800 h-full flex-center flex-col space-y-6">
<Webcam
className="border-8 border-white max-h-60 md:max-h-96"
audio={false}
ref={webcamRef}
screenshotFormat="image/jpeg"
videoConstraints={videoConstraints}
/>
<button
className="mx-auto no-outline bg-white h-12 w-12"
border="1 black/50 rounded-full"
onClick={() => {
setClick(true);
capture();
}}
/>
className={`flex-center space-x-1 w-full py-1 text-white rounded-md bg-stone-500 ${
!state.canSave && "opacity-60 cursor-not-allowed"
}`}
disabled={!state.canSave}
onClick={onSave}
>
<span
className={`${
state.canSave ? "i-mdi:content-save" : "i-mdi:content-save-off"
} text-base`}
/>
<span>Save Picture</span>
</button>
</div>

<div className="text-xs flex-1 overflow-y-scroll" p="t-5 b-2.5 x-2.5">
<div className="px-2.5 text-white/60 mb-2">Recent</div>
{Object.keys(images)
.reverse()
.map((date) => (
<button
className="relative w-full"
key={date}
onClick={() => onSelect(images[date])}
>
<SidebarItem date={date} active={state.curImage === images[date]} />
</button>
))}
</div>
</div>
);
};

const FaceTime = () => {
const webcamRef = useRef<Webcam>(null);
const { addImage } = useStore((state) => ({
addImage: state.addFaceTimeImage
}));
const [state, setState] = useState<FaceTimeState>({
canSave: false,
curImage: null
});

return (
<div className="relative h-full">
<Sidebar
state={state}
onTake={() => {
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 });
}}
/>

<div className="h-full bg-zinc-800">
{!state.curImage ? (
<Webcam
className="h-full w-full"
mirrored={true}
audio={false}
ref={webcamRef}
screenshotFormat="image/jpeg"
videoConstraints={{
facingMode: "user",
aspectRatio: 1.7
}}
/>
) : (
state.curImage && (
<img className="h-full w-full" src={state.curImage} alt="your-image" />
)
)}
</div>
);
</div>
);
};

export default FaceTime;
19 changes: 10 additions & 9 deletions src/configs/apps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [
Expand All @@ -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: <Bear />
Expand All @@ -29,7 +29,6 @@ const apps: AppsData[] = [
id: "typora",
title: "Typora",
desktop: true,
show: false,
width: 600,
height: 580,
y: -20,
Expand All @@ -40,7 +39,6 @@ const apps: AppsData[] = [
id: "safari",
title: "Safari",
desktop: true,
show: false,
width: 1024,
minWidth: 375,
minHeight: 200,
Expand All @@ -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: <VSCode />
Expand All @@ -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: <FaceTime />
},
{
id: "terminal",
title: "Terminal",
desktop: true,
show: false,
img: "img/icons/terminal.png",
content: <Terminal />
},
Expand Down
Loading

0 comments on commit 4502af2

Please sign in to comment.