[#1] Remove office phone and add email app
All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 1m1s

This commit is contained in:
2025-05-01 22:32:48 -04:00
parent 257d56c327
commit 84b1c36073
6 changed files with 31 additions and 194 deletions

View File

@@ -15,7 +15,6 @@ import {
BackSide, BackSide,
} from "three"; } from "three";
import { Billboard, Text } from "@react-three/drei"; import { Billboard, Text } from "@react-three/drei";
import { useConfig } from "../hooks/useConfig";
interface ButtonsProps { interface ButtonsProps {
menuTraversal: Stack<View>; menuTraversal: Stack<View>;
@@ -23,7 +22,6 @@ interface ButtonsProps {
pendingView: View | null; pendingView: View | null;
setPendingView: Dispatch<SetStateAction<View | null>>; setPendingView: Dispatch<SetStateAction<View | null>>;
goPreviousView(): void; goPreviousView(): void;
phoneHovered: boolean;
currentView: View; currentView: View;
} }
@@ -35,11 +33,8 @@ export const Buttons = ({
pendingView, pendingView,
setPendingView, setPendingView,
goPreviousView, goPreviousView,
phoneHovered,
currentView, currentView,
}: ButtonsProps) => { }: ButtonsProps) => {
const config = useConfig();
const [hovered, setHovered] = useState<View | null>(null); const [hovered, setHovered] = useState<View | null>(null);
const desktopRef = useDisableRaycast(currentView !== View.MainView); const desktopRef = useDisableRaycast(currentView !== View.MainView);
@@ -47,8 +42,6 @@ export const Buttons = ({
const sideRef = useDisableRaycast(currentView !== View.MainView); const sideRef = useDisableRaycast(currentView !== View.MainView);
const cellphoneRef = useDisableRaycast(currentView !== View.DesktopView); const cellphoneRef = useDisableRaycast(currentView !== View.DesktopView);
const pcRef = useDisableRaycast(currentView !== View.DesktopView); const pcRef = useDisableRaycast(currentView !== View.DesktopView);
const resumeRef = useDisableRaycast(currentView !== View.SideView);
const phoneRef = useDisableRaycast(currentView !== View.SideView);
const goToView = useCallback( const goToView = useCallback(
(view: View) => { (view: View) => {
@@ -89,12 +82,12 @@ export const Buttons = ({
}, [menuTraversal, pendingView, setPendingView]); }, [menuTraversal, pendingView, setPendingView]);
useEffect(() => { useEffect(() => {
if (hovered || phoneHovered) { if (hovered) {
document.body.style.cursor = "pointer"; document.body.style.cursor = "pointer";
} else { } else {
document.body.style.cursor = "auto"; document.body.style.cursor = "auto";
} }
}, [hovered, phoneHovered]); }, [hovered]);
const mainMenuSpring = useThreeSpring<MeshStandardMaterial>({ const mainMenuSpring = useThreeSpring<MeshStandardMaterial>({
opacity: currentView === View.MainView ? 1 : 0, opacity: currentView === View.MainView ? 1 : 0,
@@ -116,14 +109,11 @@ export const Buttons = ({
const printerMenuSpring = useThreeSpring<MeshStandardMaterial>({ const printerMenuSpring = useThreeSpring<MeshStandardMaterial>({
opacity: currentView === View.PrinterView ? 1 : 0, opacity: currentView === View.PrinterView ? 1 : 0,
}); });
const sideMenuSpring = useThreeSpring<MeshStandardMaterial>({
opacity: currentView === View.SideView ? 1 : 0,
});
const desktopViewSpring = useThreeSpring<BufferGeometry>({ const desktopViewSpring = useThreeSpring<BufferGeometry>({
scale: hovered === View.DesktopView ? 1.25 : 1, scale: hovered === View.DesktopView ? 1.25 : 1,
}); });
const sideViewSpring = useThreeSpring<BufferGeometry>({ const resumeViewSpring = useThreeSpring<BufferGeometry>({
scale: hovered === View.SideView ? 1.25 : 1, scale: hovered === View.ResumeView ? 1.25 : 1,
}); });
const cellphoneViewSpring = useThreeSpring<BufferGeometry>({ const cellphoneViewSpring = useThreeSpring<BufferGeometry>({
scale: hovered === View.CellphoneView ? 1.25 : 1, scale: hovered === View.CellphoneView ? 1.25 : 1,
@@ -134,15 +124,9 @@ export const Buttons = ({
const creditsViewSpring = useThreeSpring<BufferGeometry>({ const creditsViewSpring = useThreeSpring<BufferGeometry>({
scale: hovered === View.CreditsView ? 1.25 : 1, scale: hovered === View.CreditsView ? 1.25 : 1,
}); });
const resumeViewSpring = useThreeSpring<BufferGeometry>({
scale: hovered === View.ResumeView ? 1.25 : 1,
});
const printerViewSpring = useThreeSpring<BufferGeometry>({ const printerViewSpring = useThreeSpring<BufferGeometry>({
scale: hovered === View.PrinterView ? 1.25 : 1, scale: hovered === View.PrinterView ? 1.25 : 1,
}); });
const phoneViewSpring = useThreeSpring<BufferGeometry>({
scale: hovered === View.PhoneView ? 1.25 : 1,
});
return ( return (
<> <>
@@ -242,16 +226,16 @@ export const Buttons = ({
position={[2.525, 6, 6.75]} position={[2.525, 6, 6.75]}
onClick={ onClick={
currentView === View.MainView currentView === View.MainView
? () => goToView(View.SideView) ? () => goToView(View.ResumeView)
: undefined : undefined
} }
onPointerOver={ onPointerOver={
currentView === View.MainView currentView === View.MainView
? () => setHovered(View.SideView) ? () => setHovered(View.ResumeView)
: undefined : undefined
} }
onPointerOut={() => setHovered(null)} onPointerOut={() => setHovered(null)}
scale={sideViewSpring.scale} scale={resumeViewSpring.scale}
> >
<sphereGeometry args={[0.6, 32, 32]} /> <sphereGeometry args={[0.6, 32, 32]} />
<animated.meshStandardMaterial <animated.meshStandardMaterial
@@ -260,7 +244,10 @@ export const Buttons = ({
opacity={mainMenuSpring.opacity} opacity={mainMenuSpring.opacity}
/> />
</animated.mesh> </animated.mesh>
<animated.mesh position={[2.525, 6, 6.75]} scale={sideViewSpring.scale}> <animated.mesh
position={[2.525, 6, 6.75]}
scale={resumeViewSpring.scale}
>
<sphereGeometry args={[0.65, 32, 32]} /> <sphereGeometry args={[0.65, 32, 32]} />
<animated.meshStandardMaterial <animated.meshStandardMaterial
transparent transparent
@@ -277,7 +264,7 @@ export const Buttons = ({
fillOpacity={mainMenuSpring.opacity} fillOpacity={mainMenuSpring.opacity}
outlineOpacity={mainMenuSpring.opacity} outlineOpacity={mainMenuSpring.opacity}
> >
Resume & Contact Resume
</AnimatedText> </AnimatedText>
</Billboard> </Billboard>
</group> </group>
@@ -450,105 +437,6 @@ export const Buttons = ({
</AnimatedText> </AnimatedText>
</Billboard> </Billboard>
</group> </group>
{/* Side Menu */}
<group>
<animated.mesh
ref={resumeRef}
position={[2.845, 4.75, 6.454]}
onClick={
currentView === View.SideView
? () => goToView(View.ResumeView)
: undefined
}
onPointerOver={
currentView === View.SideView
? () => setHovered(View.ResumeView)
: undefined
}
onPointerOut={() => setHovered(null)}
scale={resumeViewSpring.scale}
>
<sphereGeometry args={[0.175, 32, 32]} />
<animated.meshStandardMaterial
transparent
color="white"
opacity={sideMenuSpring.opacity}
/>
</animated.mesh>
<animated.mesh
position={[2.845, 4.75, 6.454]}
scale={resumeViewSpring.scale}
>
<sphereGeometry args={[0.175 + 7 / 480, 32, 32]} />
<animated.meshStandardMaterial
transparent
color="black"
opacity={sideMenuSpring.opacity}
side={BackSide}
/>
</animated.mesh>
<Billboard position={[2.845, 5.1875, 6.454]}>
<AnimatedText
font="/assets/inter.ttf"
fontSize={0.21875}
outlineWidth={7 / 480}
fillOpacity={sideMenuSpring.opacity}
outlineOpacity={sideMenuSpring.opacity}
>
Resume
</AnimatedText>
</Billboard>
{config.phoneNumber && (
<>
<animated.mesh
ref={phoneRef}
position={[3.75, 5.125, 7]}
onClick={
currentView === View.SideView
? () => goToView(View.PhoneView)
: undefined
}
onPointerOver={
currentView === View.SideView
? () => setHovered(View.PhoneView)
: undefined
}
onPointerOut={() => setHovered(null)}
scale={phoneViewSpring.scale}
>
<sphereGeometry args={[0.175, 32, 32]} />
<animated.meshStandardMaterial
transparent
color="white"
opacity={sideMenuSpring.opacity}
/>
</animated.mesh>
<animated.mesh
position={[3.75, 5.125, 7]}
scale={phoneViewSpring.scale}
>
<sphereGeometry args={[0.175 + 7 / 480, 32, 32]} />
<animated.meshStandardMaterial
transparent
color="black"
opacity={sideMenuSpring.opacity}
side={BackSide}
/>
</animated.mesh>
<Billboard position={[3.75, 5.515, 7]}>
<AnimatedText
font="/assets/inter.ttf"
fontSize={0.21875}
outlineWidth={7 / 480}
fillOpacity={sideMenuSpring.opacity}
outlineOpacity={sideMenuSpring.opacity}
>
Contact
</AnimatedText>
</Billboard>
</>
)}
</group>
</> </>
); );
}; };

View File

@@ -56,7 +56,6 @@ export function Scene(props: JSX.IntrinsicElements["group"]) {
// States // States
const [pendingView, setPendingView] = useState<View | null>(null); const [pendingView, setPendingView] = useState<View | null>(null);
const [phoneHovered, setPhoneHovered] = useState(false);
const [backHovered, setBackHovered] = useState(false); const [backHovered, setBackHovered] = useState(false);
const [backClicked, setBackClicked] = useState(false); const [backClicked, setBackClicked] = useState(false);
const [menuTraversal, setMenuTraversal] = useState<Stack<View>>(() => { const [menuTraversal, setMenuTraversal] = useState<Stack<View>>(() => {
@@ -182,7 +181,6 @@ export function Scene(props: JSX.IntrinsicElements["group"]) {
pendingView={pendingView} pendingView={pendingView}
setPendingView={setPendingView} setPendingView={setPendingView}
goPreviousView={goPreviousView} goPreviousView={goPreviousView}
phoneHovered={phoneHovered}
currentView={currentView} currentView={currentView}
/> />
<group> <group>
@@ -321,67 +319,6 @@ export function Scene(props: JSX.IntrinsicElements["group"]) {
</Html> </Html>
</mesh> </mesh>
</group> </group>
<group
position={[4, 4.74056, 7.25]}
rotation={[-Math.PI, -Math.PI / 4, -Math.PI]}
scale={0.3}
>
<group
position={[-0.45957, -1.57735, -0.97302]}
rotation={[-Math.PI / 2, 0, 0]}
scale={100}
onClick={
config.phoneNumber && currentView === View.PhoneView
? () => {
window.open(
`tel:${config.phoneNumber}`,
"_blank",
"noopener,noreferrer"
);
}
: undefined
}
onPointerOver={
config.phoneNumber && currentView === View.PhoneView
? () => setPhoneHovered(true)
: undefined
}
onPointerOut={() => setPhoneHovered(false)}
>
<mesh
castShadow
receiveShadow
geometry={nodes.Top.geometry}
material={materials.TopMaterial}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.OfficePhoneBody.geometry}
material={materials.OfficePhoneBodyMaterial}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.Screen.geometry}
material={materials.ScreenMaterial}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.Buttons.geometry}
material={materials.HandleAndButtonsMaterial}
/>
<mesh
castShadow
receiveShadow
geometry={nodes.Handle.geometry}
material={materials.HandleAndButtonsMaterial}
position={[-0.01639, -0.0078, 0.02006]}
rotation={[-0.37437, 0, Math.PI / 2]}
/>
</group>
</group>
<Text <Text
position={[-2.415, 12.5, -6]} position={[-2.415, 12.5, -6]}
font="/assets/inter-bold.ttf" font="/assets/inter-bold.ttf"

View File

@@ -36,11 +36,6 @@ export const views: PerspectiveCameraProps[] = [
position: [3.0143, 5.5, 6.0997], position: [3.0143, 5.5, 6.0997],
rotation: [-2.55743, -0.69621, -2.74104], rotation: [-2.55743, -0.69621, -2.74104],
}, },
{
// SideView
position: [0.5, 7.75, 4.15],
rotation: [-2.1005, -0.68155, -2.35619],
},
{ {
// CreditsView // CreditsView
position: [1.25, 5.9822, -2.075], position: [1.25, 5.9822, -2.075],
@@ -56,8 +51,7 @@ export enum View {
CellphoneView = 4, CellphoneView = 4,
DesktopView = 5, DesktopView = 5,
PhoneView = 6, PhoneView = 6,
SideView = 7, CreditsView = 7,
CreditsView = 8,
} }
export const hashtoView: Record<string, View> = { export const hashtoView: Record<string, View> = {

View File

@@ -27,6 +27,7 @@ import {
mdiWifi, mdiWifi,
mdiBattery80, mdiBattery80,
mdiHelp, mdiHelp,
mdiEmail,
} from "@mdi/js"; } from "@mdi/js";
import Icon from "@mdi/react"; import Icon from "@mdi/react";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
@@ -88,6 +89,17 @@ export const CellphoneUI = () => {
<div className={`${appClass} ${fillerClass}`}> <div className={`${appClass} ${fillerClass}`}>
<Icon path={mdiCalculator} className="text-neutral-300" /> <Icon path={mdiCalculator} className="text-neutral-300" />
</div> </div>
{config.email && (
<a
href={`mailto:${config.email}`}
target="_blank"
className={appClass}
onMouseOver={handleEnter}
onMouseOut={handleLeave}
>
<Icon path={mdiEmail} className="text-neutral-300" />
</a>
)}
{config.socials?.matrix && ( {config.socials?.matrix && (
<a <a
href={`https://matrix.to/#/${config.socials.matrix}`} href={`https://matrix.to/#/${config.socials.matrix}`}

View File

@@ -66,6 +66,11 @@ export async function register() {
format: "phone", format: "phone",
nullable: true, nullable: true,
}, },
email: {
type: "string",
format: "email",
nullable: true,
},
fallbackUrl: { fallbackUrl: {
type: "string", type: "string",
format: "uri", format: "uri",

View File

@@ -16,6 +16,7 @@ export interface Config {
tags?: string[]; tags?: string[];
}[]; }[];
phoneNumber?: string; phoneNumber?: string;
email?: string;
fallbackUrl?: string; fallbackUrl?: string;
name: [string, string]; name: [string, string];
status?: string; status?: string;