|
@@ -1,4 +1,11 @@
|
|
|
+import ClockFace from "./assets/images/ClockFace.png";
|
|
|
+import ClockFace_H from "./assets/images/ClockFace_H.png";
|
|
|
+import ClockFace_M from "./assets/images/ClockFace_M.png";
|
|
|
+import ClockFace_S from "./assets/images/ClockFace_S.png";
|
|
|
import './assets/scss/_page-styles/rickandmortyapi.scss';
|
|
|
+import './assets/scss/_page-styles/common.scss';
|
|
|
+import './assets/scss/_page-styles/clock-face.scss';
|
|
|
+import React, {useState, useEffect, useRef} from 'react';
|
|
|
|
|
|
// hw19 (22.10.2022)
|
|
|
|
|
@@ -24,7 +31,9 @@ const EpisodeItem = ({name, air_date, children}) =>
|
|
|
|
|
|
const Episode = () =>
|
|
|
dataEpisodes.map(item => (
|
|
|
- <div className="episode" key={Math.random()}>
|
|
|
+ <div className="episode"
|
|
|
+ key={Math.random()}
|
|
|
+ >
|
|
|
<EpisodeItem name={item.name}
|
|
|
air_date={item.air_date}
|
|
|
>
|
|
@@ -41,11 +50,322 @@ const Episode = () =>
|
|
|
</div>
|
|
|
))
|
|
|
|
|
|
-const App = () =>
|
|
|
- <div className="App">
|
|
|
- <section className="episodes">
|
|
|
- <Episode />
|
|
|
+
|
|
|
+// React JSX Homework - hw20 (22.10.2022)
|
|
|
+
|
|
|
+// Spoiler
|
|
|
+const Spoiler = ({header="+", open, children}) => {
|
|
|
+ const [isOpen, setIsOpen] = useState(open)
|
|
|
+ return (
|
|
|
+ <section>
|
|
|
+ <div onClick={() => setIsOpen(!isOpen)}>{header}</div>
|
|
|
+ {isOpen && children}
|
|
|
</section>
|
|
|
- </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+// RangeInput
|
|
|
+const RangeInput = ({min, max}) => {
|
|
|
+ const [text, setText] = useState('');
|
|
|
+ return (
|
|
|
+ <input type="text"
|
|
|
+ value={text}
|
|
|
+ style={{borderColor: (text.length > max || text.length < min) ? 'red' : 'rgb(118, 118, 118)'}}
|
|
|
+ onChange={event => setText(event.target.value)}
|
|
|
+ />
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+// LoginForm
|
|
|
+const LoginForm = ({onLogin}) => {
|
|
|
+ const [login, setLogin] = useState('');
|
|
|
+ const [password, setPassword] = useState('');
|
|
|
+
|
|
|
+ return(
|
|
|
+ <fieldset>
|
|
|
+ <input placeholder="login"
|
|
|
+ type="text"
|
|
|
+ name="login"
|
|
|
+ value={login}
|
|
|
+ onChange={e => setLogin(e.target.value)}
|
|
|
+ /><br/>
|
|
|
+ <input placeholder="password"
|
|
|
+ type="password"
|
|
|
+ name="password"
|
|
|
+ value={password}
|
|
|
+ onChange={e => setPassword(e.target.value)}
|
|
|
+ /><br/>
|
|
|
+ <button className="login-button"
|
|
|
+ onClick={() => onLogin(login, password)}
|
|
|
+ type="button"
|
|
|
+ disabled={!(login.length > 0 && password.length > 0)}
|
|
|
+ >Login</button>
|
|
|
+ </fieldset>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+// PasswordConfirm
|
|
|
+
|
|
|
+const PasswordConfirm = ({min}) => {
|
|
|
+ const [password, setPassword] = useState('');
|
|
|
+ const [confirmPassword, setConfirmPassword] = useState('');
|
|
|
+
|
|
|
+ return(
|
|
|
+ <fieldset>
|
|
|
+ <input placeholder="password"
|
|
|
+ type="password"
|
|
|
+ name="password"
|
|
|
+ value={password}
|
|
|
+ style={{borderColor: (password.length >= min && password === confirmPassword) ? 'gray' : 'red'}}
|
|
|
+ onChange={e => setPassword(e.target.value)}
|
|
|
+ /><br/>
|
|
|
+ <input placeholder="confirm password"
|
|
|
+ type="password"
|
|
|
+ name="confirmPassword"
|
|
|
+ value={confirmPassword}
|
|
|
+ style={{borderColor: (confirmPassword.length >= min && confirmPassword === password) ? 'gray' : 'red'}}
|
|
|
+ onChange={e => setConfirmPassword(e.target.value)}
|
|
|
+ />
|
|
|
+ </fieldset>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+// Timer
|
|
|
+const Timer = ({seconds}) => {
|
|
|
+ const [count, setCount] = useState(seconds);
|
|
|
+ const intervalRef = useRef(null)
|
|
|
+ useEffect(() => {
|
|
|
+ intervalRef.current = setInterval(() => {
|
|
|
+ setCount(count => count - 1)
|
|
|
+ }, 1000);
|
|
|
+ return () => {
|
|
|
+ clearInterval(intervalRef.current);
|
|
|
+ }
|
|
|
+ }, [seconds]);
|
|
|
+ const pause = () => {
|
|
|
+ clearInterval(intervalRef.current);
|
|
|
+ }
|
|
|
+ if(count === 0) pause();
|
|
|
+ const ISODate = (start, end) => new Date(count * 1000).toISOString().slice(start, end);
|
|
|
+ return(
|
|
|
+ <div>
|
|
|
+ <span>{ISODate(11, 13)}</span>:
|
|
|
+ <span>{ISODate(14, 16)}</span>:
|
|
|
+ <span>{ISODate(17, 19)}</span>
|
|
|
+ <button className="default-button"
|
|
|
+ type="button"
|
|
|
+ onClick={pause}
|
|
|
+ >Pause</button>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+// TimerControl
|
|
|
+const TimerControl = () => {
|
|
|
+ const [hoursVal, setHoursVal] = useState(10);
|
|
|
+ const [minutesVal, setMinutesVal] = useState(0);
|
|
|
+ const [secondsVal, setSecondsVal] = useState(0);
|
|
|
+ const [isStart, setStart] = useState(false);
|
|
|
+ const totalSeconds = (hoursVal * 3600) + (minutesVal * 60) + secondsVal;
|
|
|
+
|
|
|
+ return(
|
|
|
+ <fieldset>
|
|
|
+ <label>
|
|
|
+ часы
|
|
|
+ <input type="text"
|
|
|
+ name="hours"
|
|
|
+ value={hoursVal}
|
|
|
+ onChange={event => setHoursVal(+event.target.value)}
|
|
|
+ />
|
|
|
+ </label>
|
|
|
+ <label>
|
|
|
+ минуты
|
|
|
+ <input type="text"
|
|
|
+ name="minutes"
|
|
|
+ value={minutesVal}
|
|
|
+ onChange={event => setMinutesVal(+event.target.value)}
|
|
|
+ />
|
|
|
+ </label>
|
|
|
+ <label>
|
|
|
+ секунды
|
|
|
+ <input type="text"
|
|
|
+ name="seconds"
|
|
|
+ value={secondsVal}
|
|
|
+ onChange={event => setSecondsVal(+event.target.value)}
|
|
|
+ />
|
|
|
+ </label>
|
|
|
+ <button type="button"
|
|
|
+ className="default-button"
|
|
|
+ onClick={() => setStart(!isStart)}
|
|
|
+ >Start Timer</button>
|
|
|
+ {isStart && <Timer seconds={totalSeconds}/>}
|
|
|
+ </fieldset>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+// TimerContainer
|
|
|
+const SecondsTimer = ({seconds}) => <h2>{seconds}</h2>
|
|
|
+
|
|
|
+const TimerContainer = ({seconds, refresh, render}) => {
|
|
|
+ const [count, setCount] = useState(seconds);
|
|
|
+ useEffect(() => {
|
|
|
+ const intervalID = setInterval(() => {
|
|
|
+ setCount(count => count - 1)
|
|
|
+ }, refresh);
|
|
|
+ return () => {
|
|
|
+ clearInterval(intervalID);
|
|
|
+ }
|
|
|
+ }, [seconds]);
|
|
|
+ const RenderProps = render;
|
|
|
+ return(
|
|
|
+ <RenderProps seconds={count}/>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// LCD
|
|
|
+
|
|
|
+const TimerPresentation = ({seconds}) => {
|
|
|
+ const ISODate = (start, end) => new Date(seconds * 1000).toISOString().slice(start, end);
|
|
|
+ return(
|
|
|
+ <div>
|
|
|
+ <span>{ISODate(11, 13)}</span>:
|
|
|
+ <span>{ISODate(14, 16)}</span>:
|
|
|
+ <span>{ISODate(17, 19)}</span>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// Watch
|
|
|
+
|
|
|
+const Watch = ({seconds}) => {
|
|
|
+ const ISODate = (start, end) => new Date(seconds * 1000).toISOString().slice(start, end);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <figure className="clock-face">
|
|
|
+ <img className="clock-face__bg"
|
|
|
+ src={ClockFace}
|
|
|
+ width="400"
|
|
|
+ height="400"
|
|
|
+ alt="циферблат"
|
|
|
+ />
|
|
|
+ <img className="clock-face__element"
|
|
|
+ src={ClockFace_H}
|
|
|
+ width="400"
|
|
|
+ height="400"
|
|
|
+ alt="стрелка часовая"
|
|
|
+ style={{rotate: `${ISODate(11, 13)*30}deg`}}
|
|
|
+ />
|
|
|
+ <img className="clock-face__element"
|
|
|
+ src={ClockFace_M}
|
|
|
+ width="400"
|
|
|
+ height="400"
|
|
|
+ alt="стрелка минутная"
|
|
|
+ style={{rotate: `${ISODate(14, 16)*6}deg`}}
|
|
|
+ />
|
|
|
+ <img className="clock-face__element"
|
|
|
+ src={ClockFace_S}
|
|
|
+ width="400"
|
|
|
+ height="400"
|
|
|
+ alt="стрелка секундная"
|
|
|
+ style={{rotate: `${ISODate(17, 19)*6}deg`}}
|
|
|
+ />
|
|
|
+ </figure>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+// TimerControl + TimerContainer
|
|
|
+const TimerControlContainer = ({render}) => {
|
|
|
+ const [hoursVal, setHoursVal] = useState(0);
|
|
|
+ const [minutesVal, setMinutesVal] = useState(0);
|
|
|
+ const [secondsVal, setSecondsVal] = useState(0);
|
|
|
+ const [isStart, setStart] = useState(false);
|
|
|
+ const totalSeconds = (hoursVal * 3600) + (minutesVal * 60) + secondsVal;
|
|
|
+
|
|
|
+ return(
|
|
|
+ <fieldset>
|
|
|
+ <label>
|
|
|
+ часы
|
|
|
+ <input type="text"
|
|
|
+ name="hours"
|
|
|
+ value={hoursVal}
|
|
|
+ onChange={event => setHoursVal(+event.target.value)}
|
|
|
+ />
|
|
|
+ </label>
|
|
|
+ <label>
|
|
|
+ минуты
|
|
|
+ <input type="text"
|
|
|
+ name="minutes"
|
|
|
+ value={minutesVal}
|
|
|
+ onChange={event => setMinutesVal(+event.target.value)}
|
|
|
+ />
|
|
|
+ </label>
|
|
|
+ <label>
|
|
|
+ секунды
|
|
|
+ <input type="text"
|
|
|
+ name="seconds"
|
|
|
+ value={secondsVal}
|
|
|
+ onChange={event => setSecondsVal(+event.target.value)}
|
|
|
+ />
|
|
|
+ </label>
|
|
|
+ <button type="button"
|
|
|
+ className="default-button"
|
|
|
+ onClick={() => setStart(!isStart)}
|
|
|
+ >Start Timer</button>
|
|
|
+ {isStart && <TimerContainer seconds={totalSeconds} render={render} refresh={150}/>}
|
|
|
+ </fieldset>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+const App = () => {
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="App">
|
|
|
+ {/*<section className="episodes">
|
|
|
+ <Episode />
|
|
|
+ </section>*/}
|
|
|
+
|
|
|
+ <Spoiler header={<h1>Заголовок</h1>} open>
|
|
|
+ Контент 1
|
|
|
+ <p>
|
|
|
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Commodi, eaque?
|
|
|
+ </p>
|
|
|
+ </Spoiler>
|
|
|
+ <br/>
|
|
|
+ <Spoiler>
|
|
|
+ <h2>Контент 2</h2>
|
|
|
+ <p>
|
|
|
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aperiam consectetur consequatur corporis dolores, laborum minus repellat sequi! Aliquam, aspernatur, ipsam?
|
|
|
+ </p>
|
|
|
+ </Spoiler>
|
|
|
+ <br/>
|
|
|
+ <RangeInput min={5} max={20} />
|
|
|
+ <br/><br/>
|
|
|
+ <LoginForm onLogin={(login, password) => console.log({login, password})}/>
|
|
|
+ <br/>
|
|
|
+ <PasswordConfirm min={2} />
|
|
|
+ <br/>
|
|
|
+ <Timer seconds={50}/>
|
|
|
+ <TimerControl />
|
|
|
+ <TimerContainer seconds={1800}
|
|
|
+ refresh={1000}
|
|
|
+ render={SecondsTimer}
|
|
|
+ />
|
|
|
+ <TimerContainer seconds={500}
|
|
|
+ refresh={500}
|
|
|
+ render={TimerPresentation}
|
|
|
+ />
|
|
|
+ <TimerContainer seconds={30800}
|
|
|
+ refresh={1000}
|
|
|
+ render={Watch}
|
|
|
+ />
|
|
|
+ <TimerControlContainer render={Watch} />
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
export default App;
|