Impl: MVP
This commit is contained in:
parent
3905520afe
commit
28c6a03691
@ -1,6 +1,8 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
reactStrictMode: true,
|
||||
}
|
||||
output: "export",
|
||||
trailingSlash: true,
|
||||
};
|
||||
|
||||
module.exports = nextConfig
|
||||
module.exports = nextConfig;
|
||||
|
@ -3,9 +3,10 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --hostname 127.0.0.1 --port 56723",
|
||||
"dev": "next dev --hostname 127.0.0.1 --port 56724",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"export": "next export",
|
||||
"start": "next start --hostname 127.0.0.1 --port 56723",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -1,5 +1,5 @@
|
||||
import "./globals.scss";
|
||||
import type { AppProps } from "next/app";
|
||||
import "./globals.scss";
|
||||
|
||||
export default function App({ Component, pageProps }: AppProps) {
|
||||
return <Component {...pageProps} />;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Html, Head, Main, NextScript } from "next/document";
|
||||
import { Head, Html, Main, NextScript } from "next/document";
|
||||
|
||||
export default function Document() {
|
||||
return (
|
||||
@ -7,6 +7,8 @@ export default function Document() {
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
{/* eslint-disable-next-line @next/next/no-sync-scripts*/}
|
||||
{/* <script src="/tests.js"></script> */}
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
|
@ -0,0 +1,15 @@
|
||||
@font-face {
|
||||
font-family: "quicksand";
|
||||
/* license: url("/quicksand.txt"); */
|
||||
src: local("quicksand") url("/quicksand.ttf") format("tff");
|
||||
}
|
||||
|
||||
$font-family-base: quicksand, roboto !default;
|
||||
|
||||
@import "~bootstrap/scss/bootstrap";
|
||||
|
||||
.mcw {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: max-content;
|
||||
}
|
291
pages/index.tsx
291
pages/index.tsx
@ -1,9 +1,290 @@
|
||||
import styles from "./Clock.module.scss";
|
||||
import {
|
||||
faArrowDown,
|
||||
faArrowUp,
|
||||
faCode,
|
||||
faPause,
|
||||
faPlay,
|
||||
faRotateLeft,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
export default function Clock() {
|
||||
const capitalize = (str: string) => {
|
||||
if (str.length) {
|
||||
return str[0].toLocaleUpperCase() + str.slice(1).toLocaleLowerCase();
|
||||
}
|
||||
return str;
|
||||
};
|
||||
interface Timer {
|
||||
name: string;
|
||||
length: number;
|
||||
incrementLength: any; // To-DO: specify type
|
||||
decrementLength: any; // To-DO: specify type
|
||||
colorTheme: string;
|
||||
}
|
||||
|
||||
function TimerControl({
|
||||
name,
|
||||
length,
|
||||
incrementLength,
|
||||
decrementLength,
|
||||
colorTheme,
|
||||
}: Timer) {
|
||||
return (
|
||||
<>
|
||||
<p id="unique_id">Hello, World!</p>
|
||||
</>
|
||||
<div id={name} className="col">
|
||||
<h2 id={`${name}-label`} className="mcw">{`${capitalize(
|
||||
name
|
||||
)} Length`}</h2>
|
||||
<div id={`${name}_buttons_and_value`} className="mcw row py-2">
|
||||
<div className="col">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-${colorTheme}`}
|
||||
id={`${name}-decrement`}
|
||||
onClick={incrementLength}
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowUp} />
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
id={`${name}-length`}
|
||||
className={`col h4 mx-2 py-1 px-3 bg-light border border-${colorTheme}`}
|
||||
>
|
||||
{length}
|
||||
</span>
|
||||
<div className="col">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-${colorTheme}`}
|
||||
id={`${name}-increment`}
|
||||
onClick={decrementLength}
|
||||
>
|
||||
<FontAwesomeIcon icon={faArrowDown} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface State {
|
||||
session: boolean;
|
||||
running: boolean;
|
||||
sessionLength: number;
|
||||
breakLength: number;
|
||||
countdown: number;
|
||||
bgColor: string;
|
||||
}
|
||||
|
||||
enum BgColors {
|
||||
Paused = "bg-primary",
|
||||
Session = "bg-success",
|
||||
Break = "bg-warning",
|
||||
}
|
||||
|
||||
export default function Clock() {
|
||||
const SECS_IN_A_MIN = 60;
|
||||
const DEFAULT_SESSION_LENGTH = 25;
|
||||
const DEFAULT_BREAK_LENGTH = 5;
|
||||
const [state, setState] = useState<State>({
|
||||
session: true,
|
||||
running: false,
|
||||
sessionLength: DEFAULT_SESSION_LENGTH,
|
||||
breakLength: DEFAULT_BREAK_LENGTH,
|
||||
countdown: DEFAULT_SESSION_LENGTH * SECS_IN_A_MIN,
|
||||
bgColor: BgColors.Paused,
|
||||
});
|
||||
const beepRef = useRef<HTMLAudioElement>(null);
|
||||
const countdownRef = useRef<NodeJS.Timer | null>(null);
|
||||
|
||||
const resetCounter = () => {
|
||||
beepRef.current?.pause();
|
||||
beepRef.current?.fastSeek(0);
|
||||
if (countdownRef.current) clearInterval(countdownRef.current);
|
||||
setState(() => ({
|
||||
session: true,
|
||||
running: false,
|
||||
sessionLength: DEFAULT_SESSION_LENGTH,
|
||||
breakLength: DEFAULT_BREAK_LENGTH,
|
||||
countdown: DEFAULT_SESSION_LENGTH * SECS_IN_A_MIN,
|
||||
bgColor: BgColors.Paused,
|
||||
}));
|
||||
};
|
||||
|
||||
const startCounter = () => {
|
||||
if (countdownRef.current) clearInterval(countdownRef.current);
|
||||
countdownRef.current = setInterval(() => {
|
||||
setState((prevState) => {
|
||||
if (prevState.countdown <= 0) {
|
||||
const wasSession = prevState.session;
|
||||
beepRef.current?.play();
|
||||
return {
|
||||
...prevState,
|
||||
session: !wasSession,
|
||||
running: true,
|
||||
countdown:
|
||||
(wasSession ? prevState.breakLength : prevState.sessionLength) *
|
||||
SECS_IN_A_MIN,
|
||||
bgColor: wasSession ? BgColors.Break : BgColors.Session,
|
||||
};
|
||||
} else {
|
||||
return { ...prevState, countdown: prevState.countdown - 1 };
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
running: true,
|
||||
countdown: prevState.countdown - 1,
|
||||
bgColor: prevState.session ? BgColors.Session : BgColors.Break,
|
||||
}));
|
||||
};
|
||||
|
||||
const stopCounter = () => {
|
||||
if (countdownRef.current) clearInterval(countdownRef.current);
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
running: false,
|
||||
bgColor: BgColors.Paused,
|
||||
}));
|
||||
};
|
||||
|
||||
const incrementSessionLength = () => {
|
||||
setState((prevState) => {
|
||||
if (prevState.running) return prevState;
|
||||
const sessionLength = Math.min(prevState.sessionLength + 1, 60);
|
||||
return {
|
||||
...prevState,
|
||||
sessionLength: sessionLength,
|
||||
countdown: prevState.session
|
||||
? sessionLength * SECS_IN_A_MIN
|
||||
: prevState.countdown,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const decrementSessionLength = () => {
|
||||
setState((prevState) => {
|
||||
if (prevState.running) return prevState;
|
||||
const sessionLength = Math.max(prevState.sessionLength - 1, 1);
|
||||
return {
|
||||
...prevState,
|
||||
sessionLength: sessionLength,
|
||||
countdown: prevState.session
|
||||
? sessionLength * SECS_IN_A_MIN
|
||||
: prevState.countdown,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const incrementBreakLength = () => {
|
||||
setState((prevState) => {
|
||||
if (prevState.running) return prevState;
|
||||
const breakLength = Math.min(prevState.breakLength + 1, 60);
|
||||
return {
|
||||
...prevState,
|
||||
breakLength: breakLength,
|
||||
countdown: prevState.session
|
||||
? prevState.countdown
|
||||
: breakLength * SECS_IN_A_MIN,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const decrementBreakLength = () => {
|
||||
setState((prevState) => {
|
||||
if (prevState.running) return prevState;
|
||||
const breakLength = Math.max(prevState.breakLength - 1, 1);
|
||||
return {
|
||||
...prevState,
|
||||
breakLength: breakLength,
|
||||
countdown: prevState.session
|
||||
? prevState.countdown
|
||||
: breakLength * SECS_IN_A_MIN,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div id="background" className={state.bgColor}>
|
||||
<div
|
||||
id="center"
|
||||
className="d-flex align-items-center justify-content-center min-vh-100 text-center container"
|
||||
>
|
||||
<div id="content:">
|
||||
<div id="main" className="bg-white p-4 rounded-4">
|
||||
<div id="head">
|
||||
<h1 className="mcw display-1 text-primary">25 + 5 Clock</h1>
|
||||
</div>
|
||||
<div id="controls" className="row py-2 ">
|
||||
<TimerControl
|
||||
name="session"
|
||||
length={state.sessionLength}
|
||||
incrementLength={incrementSessionLength}
|
||||
decrementLength={decrementSessionLength}
|
||||
colorTheme="success"
|
||||
/>
|
||||
<TimerControl
|
||||
name="break"
|
||||
length={state.breakLength}
|
||||
incrementLength={incrementBreakLength}
|
||||
decrementLength={decrementBreakLength}
|
||||
colorTheme="warning"
|
||||
/>
|
||||
</div>
|
||||
<div id="display">
|
||||
<h2 id="timer-label" className="mcw">
|
||||
{state.session ? "Session" : "Break"}
|
||||
</h2>
|
||||
<h1 id="time-left" className={`mcw display-2`}>
|
||||
{`${String(
|
||||
Math.floor(state.countdown / SECS_IN_A_MIN)
|
||||
).padStart(2, "0")}:${String(
|
||||
state.countdown % SECS_IN_A_MIN
|
||||
).padStart(2, "0")}`}
|
||||
</h1>
|
||||
<div id="countdown_controls">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary mx-2"
|
||||
id="start_stop"
|
||||
onClick={() => {
|
||||
state.running ? stopCounter() : startCounter();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={state.running ? faPause : faPlay} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-primary mx-2"
|
||||
id="reset"
|
||||
onClick={resetCounter}
|
||||
>
|
||||
<FontAwesomeIcon icon={faRotateLeft} />
|
||||
</button>
|
||||
</div>
|
||||
<audio
|
||||
id="beep"
|
||||
ref={beepRef}
|
||||
src="/timer_beep.wav"
|
||||
preload="auto"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer">
|
||||
<p className="mcw my-3">
|
||||
<a
|
||||
className={
|
||||
state.session || !state.running ? "link-light" : "link-dark"
|
||||
}
|
||||
href="https://radii.dev/freeCodeCamp.org-Front-End-Dev-Libraries/Build-a-25-plus-5-Clock"
|
||||
>
|
||||
<FontAwesomeIcon icon={faCode} /> Source Code & License
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
BIN
public/quicksand.ttf
Normal file
BIN
public/quicksand.ttf
Normal file
Binary file not shown.
93
public/quicksandtxt
Normal file
93
public/quicksandtxt
Normal file
@ -0,0 +1,93 @@
|
||||
Copyright 2011 The Quicksand Project Authors (https://github.com/andrew-paglinawan/QuicksandFamily), with Reserved Font Name “Quicksand”.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
581
public/tests.js
Normal file
581
public/tests.js
Normal file
File diff suppressed because one or more lines are too long
BIN
public/timer_beep.wav
Normal file
BIN
public/timer_beep.wav
Normal file
Binary file not shown.
Loading…
Reference in New Issue
Block a user