Skip to content
This repository was archived by the owner on Aug 2, 2025. It is now read-only.

Commit f75de13

Browse files
committed
feat: pip countdown
Uses the documentPictureInPicture API.
1 parent df18b53 commit f75de13

File tree

4 files changed

+108
-21
lines changed

4 files changed

+108
-21
lines changed

apps/client/index.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,19 @@
99
<link rel="apple-touch-icon" href="/logos/apple-touch-icon.png" />
1010
<meta name="theme_color" content="#97cdf5" />
1111
<title>Timetabl</title>
12+
<meta
13+
http-equiv="origin-trial"
14+
content="AsR/FeDUAk8drYXGvoX//0Bf1D7Isx9R1giP7SIPK8qwfJm3MD3UdRmZwfBGkS8HPEQG1tpc3a41+PJUNEnJZAUAAAB0eyJvcmlnaW4iOiJodHRwczovL3RpbWV0YWJsLmFwcDo0NDMiLCJmZWF0dXJlIjoiRG9jdW1lbnRQaWN0dXJlSW5QaWN0dXJlQVBJIiwiZXhwaXJ5IjoxNjk0MTMxMTk5LCJpc1N1YmRvbWFpbiI6dHJ1ZX0="
15+
/>
1216
<meta
1317
name="description"
1418
content="Timetabl is a blazing fast, offline-enabled, installable timetable app for SBHS. It allows you to access your periods for the day, scan your student barcode, and view your Daily Notices."
1519
/>
16-
<script defer data-domain="timetabl.app" src="https://plausible.io/js/script.js"></script>
20+
<script
21+
defer
22+
data-domain="timetabl.app"
23+
src="https://plausible.io/js/script.js"
24+
></script>
1725
</head>
1826
<body>
1927
<div id="root"></div>

apps/client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "client",
3-
"version": "1.7.3-beta",
3+
"version": "1.8.0-beta",
44
"scripts": {
55
"dev": "npm-run-all --parallel dev:*",
66
"dev:run": "FORCE_COLOR=1 vite",

apps/client/src/routes/Main/Home/Countdown.tsx

Lines changed: 97 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,16 @@ import {
44
useColorModeValue,
55
Heading,
66
Skeleton,
7+
IconButton,
8+
Spacer,
9+
Box,
710
} from "@chakra-ui/react";
811
import { useDtt } from "../../../services/sbhsApi/useDtt";
912
import { DateTime } from "luxon";
10-
import { useEffect } from "react";
13+
import { useEffect, useRef, useState } from "react";
14+
import { PictureInPicture } from "phosphor-react";
15+
16+
declare const documentPictureInPicture: any;
1117

1218
export default function Countdown({
1319
countdown,
@@ -18,8 +24,10 @@ export default function Countdown({
1824
}) {
1925
const { data: dtt } = useDtt();
2026
const isLoaded = !!dtt;
27+
const pipColor = useColorModeValue("gray.100", "gray.900");
2128
const bgColor =
2229
useToken("colors", useColorModeValue("gray.300", "gray.700")) + "55";
30+
const textColor = useColorModeValue("gray.900", "gray.100");
2331

2432
const activeIndex = dtt?.periods.findIndex(
2533
({ startTime, endTime }) =>
@@ -44,28 +52,99 @@ export default function Countdown({
4452
return () => clearInterval(timer);
4553
});
4654

55+
const countdownRef = useRef<HTMLDivElement>(null);
56+
const skeletonRef = useRef<HTMLDivElement>(null);
57+
const [fullScreen, setFullScreen] = useState(false);
58+
59+
const handlePictureInPicture = async () => {
60+
if (!countdownRef.current) return;
61+
62+
setFullScreen(true);
63+
64+
const pipWindow = await documentPictureInPicture.requestWindow();
65+
66+
[...document.styleSheets].forEach((styleSheet) => {
67+
try {
68+
const cssRules = [...styleSheet.cssRules]
69+
.map((rule) => rule.cssText)
70+
.join("");
71+
const style = document.createElement("style");
72+
73+
style.textContent = cssRules.toString();
74+
pipWindow.document.head.appendChild(style);
75+
} catch (e) {
76+
const link = document.createElement("link");
77+
78+
link.rel = "stylesheet";
79+
link.type = (styleSheet.type as string) ?? "";
80+
link.media = styleSheet.media?.toString() ?? "";
81+
link.href = styleSheet.href ?? "";
82+
pipWindow.document.head.appendChild(link);
83+
}
84+
});
85+
86+
pipWindow.document.body.append(countdownRef.current);
87+
88+
pipWindow.addEventListener("pagehide", (event: any) => {
89+
const pipPlayer = event.target.body.firstChild;
90+
skeletonRef.current?.append(pipPlayer);
91+
setFullScreen(false);
92+
});
93+
};
94+
4795
if (!nextPeriod) return null;
4896

4997
return (
50-
<Skeleton isLoaded={isLoaded}>
51-
<Flex
52-
direction={"column"}
53-
p={3}
54-
bg={bgColor}
55-
rounded={"lg"}
56-
align={"center"}
98+
<Skeleton ref={skeletonRef} isLoaded={isLoaded}>
99+
<Box
100+
ref={countdownRef}
101+
h={fullScreen ? "full" : "auto"}
102+
w="full"
103+
bg={fullScreen ? pipColor : "transparent"}
57104
>
58-
<Heading size={"xs"} fontFamily={"Poppins, sans-serif"}>
59-
{nextPeriod.name} in
60-
</Heading>
61-
<Heading
62-
size={"lg"}
63-
fontFamily={"Poppins, sans-serif"}
64-
fontWeight={"normal"}
105+
<Flex
106+
direction={"column"}
107+
p={3}
108+
bg={bgColor}
109+
rounded={fullScreen ? "none" : "lg"}
110+
align={"center"}
111+
h="full"
112+
w="full"
113+
justify={"center"}
65114
>
66-
{countdown}
67-
</Heading>
68-
</Flex>
115+
<Flex w="full">
116+
<Spacer />
117+
<Flex direction={"column"} align={"center"}>
118+
<Heading
119+
size={fullScreen ? "xl" : "xs"}
120+
fontFamily={"Poppins, sans-serif"}
121+
color={textColor}
122+
>
123+
{nextPeriod?.name ?? "Nothing"} in
124+
</Heading>
125+
<Heading
126+
size={fullScreen ? "2xl" : "lg"}
127+
fontFamily={"Poppins, sans-serif"}
128+
fontWeight={"normal"}
129+
color={textColor}
130+
>
131+
{countdown}
132+
</Heading>
133+
</Flex>
134+
<Spacer />
135+
{"documentPictureInPicture" in window && !fullScreen && (
136+
<IconButton
137+
icon={<PictureInPicture weight="fill" />}
138+
aria-label="Open Picture in Picture"
139+
alignSelf={"start"}
140+
variant={"ghost"}
141+
colorScheme="gray"
142+
onClick={handlePictureInPicture}
143+
/>
144+
)}
145+
</Flex>
146+
</Flex>
147+
</Box>
69148
</Skeleton>
70149
);
71150
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "timetabl",
3-
"version": "1.7.3-beta",
3+
"version": "1.8.0-beta",
44
"author": "Hamzah Ahmed",
55
"license": "MIT",
66
"bugs": {

0 commit comments

Comments
 (0)