Skip to content

Commit da1f39c

Browse files
committed
Popout feature basic
1 parent a14cdc5 commit da1f39c

3 files changed

Lines changed: 106 additions & 3 deletions

File tree

src/components/Timer/Timer.jsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ import config from '../../config.json';
2222
import TimeEntryForm from '../Timelog/TimeEntryForm';
2323
import Countdown from './Countdown';
2424
import TimerStatus from './TimerStatus';
25+
import TimerPopout from './TimerPopout';
2526

2627
function Timer({ authUser, darkMode }) {
28+
const isPopout = !!window.opener;
2729
/**
2830
* Because the websocket can not be closed when internet is cut off (lost server connection),
2931
* the readyState will be stuck at OPEN, so here we need to use a custom readyState to
@@ -402,7 +404,7 @@ function Timer({ authUser, darkMode }) {
402404
const bodyBg = darkMode ? 'bg-yinmn-blue' : '';
403405

404406
return (
405-
<div className={css.timerContainer}>
407+
<div className={cs(css.timerContainer)}>
406408
<button
407409
type="button"
408410
disabled={isButtonDisabled}
@@ -527,13 +529,18 @@ function Timer({ authUser, darkMode }) {
527529
fontSize="1.3rem"
528530
/>
529531
</button>
532+
{!isPopout && (
533+
<button type="button" aria-label="Open Timer Popout" className="popout">
534+
<TimerPopout authUser={authUser} darkMode={darkMode} TimerComponent={Timer} />
535+
</button>
536+
)}
530537
</div>
531538
)}
532539

533540
{showTimer && (
534541
<div className={css.timer}>
535542
<div className={css.timerContent}>
536-
{customReadyState === ReadyState.OPEN ? (
543+
{customReadyState === ReadyState.OPEN && !isPopout && (
537544
<Countdown
538545
message={message}
539546
timerRange={{ MAX_HOURS, MIN_MINS }}
@@ -548,7 +555,8 @@ function Timer({ authUser, darkMode }) {
548555
handleStopButton={handleStopButton}
549556
toggleTimer={toggleTimer}
550557
/>
551-
) : (
558+
)}
559+
{customReadyState !== ReadyState.OPEN && (
552560
<TimerStatus
553561
readyState={customReadyState}
554562
message={message}

src/components/Timer/Timer.module.css

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@
88
-moz-user-select: none;
99
}
1010

11+
/* .popout {
12+
position: absolute;
13+
width: 280px;
14+
height: 400px;
15+
left: 0;
16+
top: 4rem;
17+
background: #343a40;
18+
border-radius: 1rem;
19+
box-shadow: 15px 20px 25px -5px rgb(0 0 0 / 0.5), 0 8px 10px -6px rgb(0 0 0 / 0.1);
20+
overflow: hidden;
21+
} */
22+
1123
.previewContainer {
1224
display: flex;
1325
flex-direction: column;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { FaExternalLinkAlt } from 'react-icons/fa';
2+
import ReactDOM from 'react-dom';
3+
import { useRef, useEffect } from 'react';
4+
import cs from 'classnames';
5+
import styles from './Timer.module.css';
6+
7+
function TimerPopout({ authUser, darkMode, TimerComponent }) {
8+
const popupRef = useRef(null);
9+
10+
const openPopoutWindow = () => {
11+
if (popupRef.current && !popupRef.current.closed) {
12+
popupRef.current.focus();
13+
return;
14+
}
15+
16+
const features = {
17+
width: 300,
18+
height: 200,
19+
right: window.screen.width - 350,
20+
top: 30,
21+
menubar: 'no',
22+
toolbar: 'no',
23+
location: 'no',
24+
status: 'no',
25+
resizable: 'yes',
26+
};
27+
28+
const featuresStr = Object.entries(features)
29+
.map(([key, value]) => `${key}=${value}`)
30+
.join(',');
31+
32+
const popup = window.open('', 'Timer', featuresStr);
33+
34+
popupRef.current = popup;
35+
36+
popup.document.write(`
37+
<!DOCTYPE html>
38+
<html>
39+
<head>
40+
<title>Timer</title>
41+
<link rel="stylesheet" href="${window.location.origin}/Timer.module.css">
42+
${darkMode ? '<style>body { background-color: #1a1a2e; color: white; }</style>' : ''}
43+
</head>
44+
<body>
45+
<h1 style="text-align: center;">Timer</h1>
46+
<div id="timer-root"></div>
47+
</body>
48+
</html>
49+
`);
50+
51+
const root = popup.document.getElementById('timer-root');
52+
ReactDOM.render(<TimerComponent authUser={authUser} darkMode={darkMode} />, root);
53+
54+
popup.onbeforeunload = () => {
55+
ReactDOM.unmountComponentAtNode(root);
56+
};
57+
};
58+
59+
useEffect(() => {
60+
return () => {
61+
if (popupRef.current && !popupRef.current.closed) {
62+
popupRef.current.close();
63+
}
64+
};
65+
}, []);
66+
67+
return (
68+
<button
69+
type="button"
70+
onClick={openPopoutWindow}
71+
className={styles.btnDiv}
72+
aria-label="Open timer in new window"
73+
>
74+
<FaExternalLinkAlt
75+
className={cs(styles.transitionColor, styles.btn)}
76+
fontSize="1.5rem"
77+
title="Open timer in new window"
78+
/>
79+
</button>
80+
);
81+
}
82+
83+
export default TimerPopout;

0 commit comments

Comments
 (0)