From 0e7b36c2d443306325f17bb8850f5bb6176202bf Mon Sep 17 00:00:00 2001 From: Alexander Kiryukhin Date: Fri, 3 Jun 2022 19:04:53 +0300 Subject: initial --- src/components/App.js | 97 ++++++++++++++++++++++++++++++++++++++++++++ src/components/TimePicker.js | 22 ++++++++++ src/components/Timer.js | 62 ++++++++++++++++++++++++++++ src/components/Timers.js | 41 +++++++++++++++++++ 4 files changed, 222 insertions(+) create mode 100644 src/components/App.js create mode 100644 src/components/TimePicker.js create mode 100644 src/components/Timer.js create mode 100644 src/components/Timers.js (limited to 'src/components') diff --git a/src/components/App.js b/src/components/App.js new file mode 100644 index 0000000..4688bbb --- /dev/null +++ b/src/components/App.js @@ -0,0 +1,97 @@ +import { useEffect, useState } from 'react'; +import { CopyTwoTone, PlusCircleFilled, ClearOutlined } from '@ant-design/icons'; +import { Button, Card, Col, Form, Input, Layout, message, Popconfirm, Row, Switch } from 'antd'; + +import useInterval from '../hooks/interval'; +import useLocalStorage from '../hooks/local'; +import { copyTextToClipboard } from '../utils/clipboard'; +import { configFromLink } from '../utils/configFromLink'; +import { defaultTimer } from '../utils/timerTemplate'; +import { updateTimers } from '../utils/updateTimers'; + +import Timers from './Timers'; + +const { Footer } = Layout; + +function App() { + const [browserNotifications, setNotifications] = useLocalStorage("notifications", false); // доступны ли нативные уведомления + const [sound, setSound] = useLocalStorage("sound", true); // включен ли звук + const [timers, setTimers] = useLocalStorage("timers", configFromLink()); // хранилище таймеров + const [url, setUrl] = useState(""); // генерируемая ссылка на кофигурацию + + // Действия при открытии приложения: + // 1. Инициализация конфигурации из адреса + // 2. Запрос разрешений на уведомления + useEffect(() => { + if (window.location.hash !== "") { + setTimers(configFromLink()); + window.location.hash = ""; + } + Notification.requestPermission().then((result) => { + if (result === 'granted') { + setNotifications(true); + } else { + setNotifications(false); + } + }); + // eslint-disable-next-line + }, []); + + // Формирование ссылки на конфигурацию при обновлении таймеров + useEffect(() => { + const d = timers.map(t => [t.name, t.initialTime]); + setUrl(btoa(unescape(encodeURIComponent(JSON.stringify(d))))); + }, [timers]); + + // Обновление таймеров + useInterval(updateTimers.bind(this, timers, setTimers, sound, browserNotifications), 100, true); // интервал опроса таймеров - 100мс потому что если вкладка с таймерами в фоне, браузер начинает заметлять работу js + + return + + + Мультитаймер Мультитаймер} + bodyStyle={{ padding: 4 }} + style={{ padding: 4, margin: 4 }} + > +
+ + + + + setSound(x)} /> + + + { + copyTextToClipboard(`https://timer.neonxp.dev/#${url}`) + message.success("Скопировано в буфер обмена") + }} />} + /> + + + setTimers([])} + okText="Да" + cancelText="Нет"> + + + +
+
+ + +
+ +
; +} + +export default App; diff --git a/src/components/TimePicker.js b/src/components/TimePicker.js new file mode 100644 index 0000000..0419088 --- /dev/null +++ b/src/components/TimePicker.js @@ -0,0 +1,22 @@ +import { InputNumber } from "antd"; + +const TimePicker = ({ setTime, time, disabled }) => { + const seconds = parseInt(Math.floor(time) % 60); + const minutes = parseInt(Math.floor(time / 60) % 60); + const hours = parseInt(Math.floor(time / 3600)); + const updateHours = (hours) => { + setTime(parseInt(hours)*3600+parseInt(minutes)*60+parseInt(seconds)); + } + const updateMinutes = (minutes) => { + setTime(parseInt(hours)*3600+parseInt(minutes)*60+parseInt(seconds)); + } + const updateSeconds = (seconds) => { + setTime(parseInt(hours)*3600+parseInt(minutes)*60+parseInt(seconds)); + } + return <> + {updateHours(x)}} addonAfter={`ч`} /> + {updateMinutes(x)}} addonAfter={`м`} /> + {updateSeconds(x)}} addonAfter={`с`} /> + ; +} +export default TimePicker; diff --git a/src/components/Timer.js b/src/components/Timer.js new file mode 100644 index 0000000..1333040 --- /dev/null +++ b/src/components/Timer.js @@ -0,0 +1,62 @@ +import { DeleteTwoTone, PlayCircleFilled, StopFilled, LeftCircleFilled, RightCircleFilled, DownCircleFilled, UpCircleFilled, EditFilled } from "@ant-design/icons"; +import { Button, Card, Col, Input, Progress, Space, Popconfirm, Grid } from "antd"; +import ButtonGroup from "antd/lib/button/button-group"; +import { useState } from "react"; +import TimePicker from "./TimePicker"; + +const { useBreakpoint } = Grid; + +const Timer = ({ name, started, start, stop, setName, initialTime, time, setTime, remove, index, first, last, moveTimer }) => { + const [edit, setEdit] = useState(false); + const toggleEdit = () => { + setEdit(!edit); + } + const screens = useBreakpoint(); + let seconds = ("0" + (Math.floor(time) % 60)).slice(-2); + let minutes = ("0" + (Math.floor(time / 60) % 60)).slice(-2); + let hours = ("0" + Math.floor(time / 3600)).slice(-2); + return + setName(ev.target.value)} />) + : (
{name}
) + } + actions={[ + , + , + ]} + bodyStyle={{ padding: 4 }} + style={{ padding: 4, margin: 4 }} + > + + {edit + ? ( + + + + + ) + : ( + + + + )} + + + {screens.sm + ? ( `${hours}:${minutes}:${seconds}`} width={200} />) + : ( `${hours}:${minutes}:${seconds}`} width={200} style={{ width: "200px" }} />) + } + + +
+ ; +} + +export default Timer; diff --git a/src/components/Timers.js b/src/components/Timers.js new file mode 100644 index 0000000..10b2557 --- /dev/null +++ b/src/components/Timers.js @@ -0,0 +1,41 @@ +import Timer from "./Timer"; + +const Timers = ({ items, setTimers }) => { + const setTime = (idx, time) => { + setTimers(items.map((t, id) => id !== idx ? t : { ...t, time: time, initialTime: time })); + }; + const initialTimer = (idx) => { + setTimers(items.map((t, id) => id !== idx ? t : { ...t, started: true, time: t.initialTime, startedAt: (new Date()).getTime() })); + }; + const stopTimer = (idx) => { + setTimers(items.map((t, id) => id !== idx ? t : { ...t, started: false })); + }; + const setTimerName = (idx, name) => { + setTimers(items.map((t, id) => id !== idx ? t : { ...t, name })); + }; + const deleteTimer = (idx) => { + setTimers(items.filter((t, id) => id !== idx)); + }; + const moveTimer = (idx, offset) => { + [items[idx], items[idx + offset]] = [items[idx + offset], items[idx]]; + setTimers(items); + }; + return items.map((t, idx) => setTime(idx, time)} + start={() => initialTimer(idx)} + stop={() => stopTimer(idx)} + setName={(name) => setTimerName(idx, name)} + remove={() => deleteTimer(idx)} + moveTimer={(offset) => moveTimer(idx, offset)} + />); +} + +export default Timers; -- cgit v1.2.3