heartwood every commit a ring
3.5 KB raw
import React, { useState, useEffect, useContext, useRef, useMemo } from "react";
import { toast } from "react-toastify";

import { Context } from "./context";
import TagNoteInput from "./tagNoteInput";
import strings from "../l10n/timer";
import contextStrings from "../l10n/context";
import { timeString, timeDiff } from "../utils/time";

import styles from "../styles/components/timer.module.css";

const Timer = () => {
  const { state, dispatch } = useContext(Context);
  const isPaused = !!state.timerPausedAt;
  const initialElapsed = isPaused
    ? +new Date(state.timerPausedAt) - +new Date(state.timer)
    : timeDiff(state.timer);
  const [time, setTime] = useState(timeString(initialElapsed));
  const refToMain = useRef(null);

  strings.setLanguage(state.language);

  useEffect(() => {
    if (isPaused) {
      const frozen = timeString(
        +new Date(state.timerPausedAt) - +new Date(state.timer)
      );
      setTime(frozen);
      document.title = `${frozen} — Timelite`;
      return;
    }
    setTime(timeString(timeDiff(state.timer)));
    const timerInterval = setInterval(() => {
      const timeCurrent = timeString(timeDiff(state.timer));
      setTime(timeCurrent);
      document.title = `${timeCurrent} — Timelite`;
    }, 1000);
    return () => {
      clearInterval(timerInterval);
    };
  }, [state.timer, state.timerPausedAt, isPaused]);

  useEffect(() => {
    if (refToMain.current) {
      refToMain.current.focus();
    }
  }, []);

  const allTags = useMemo(() => {
    const s = new Set();
    for (const e of state.log) for (const t of e.tags || []) s.add(t);
    return [...s].sort();
  }, [state.log]);

  const submitForm = (e) => {
    e.preventDefault();
    if (state.note.trim()) {
      dispatch({ type: "ADD_LOG", note: state.note });
      dispatch({ type: "NOTE_UPDATED", note: "" });
      contextStrings.setLanguage(state.language);
      toast.success(contextStrings.addedEntry);
    }
  };

  return (
    <>
      <div
        className={`${styles.time} ${isPaused ? styles.timePaused : ""}`.trim()}
        suppressHydrationWarning
      >
        {time}
        {isPaused && <span className={styles.pausedBadge}>{strings.paused}</span>}
      </div>
      <form onSubmit={submitForm}>
        <div className={styles.inputs}>
          <TagNoteInput
            ref={refToMain}
            className={styles.note}
            wrapperClassName={styles.noteWrap}
            aria-label={strings.note}
            placeholder={strings.note}
            value={state.note || ""}
            allTags={allTags}
            onChange={(val) => dispatch({ type: "NOTE_UPDATED", note: val })}
          />
        </div>
        <div className={styles.buttons}>
          <button
            className={`${styles.button} ${styles.resetButton} timer__button`}
            type="reset"
            onClick={() => dispatch({ type: "NEW_TIMER" })}
            title="⎇+R"
          >
            - {strings.reset}
          </button>
          <button
            className={`${styles.button} ${styles.pauseButton} timer__button`}
            type="button"
            onClick={() =>
              dispatch({ type: isPaused ? "RESUME_TIMER" : "PAUSE_TIMER" })
            }
            title="⎇+P"
          >
            {isPaused ? strings.resume : strings.pause}
          </button>
          <button
            className={`${styles.button} ${styles.addButton} timer__button`}
            type="submit"
            title="⎇+A"
          >
            {strings.add} +
          </button>
        </div>
      </form>
    </>
  );
};

export default Timer;