Path: blob/master/src/packages/frontend/editors/stopwatch/editor.tsx
1691 views
/*1* This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.2* License: MS-RSL – see LICENSE.md for details3*/45/*6Time78Right now this is the simplest possible imaginable stopwatch, with state synchronized properly.910This is also probably a good relatively simple example of a React-based editor that11uses persistent shared state.1213Later, maybe:1415- [ ] Make the editor title tab display the current time16- [ ] Make TimeTravel rendering work (so easy undo in case accidentally hit stop)17- [x] Labels/description, which is full markdown, hence can have links18- [ ] Ability to set a specific time19- [ ] Initialize this will just be a simple stopwatch, synchronized between viewers.20- [ ] Maybe a bunch of stopwatches and countdown timers, with labels, markdown links, etc.; draggable.21- [ ] Later yet, it may hook into what other activities are going on in a project, to auto stop/start, etc.22- [ ] Time tracking23*/2425import { Alert, Button } from "antd";26import { PlusCircleTwoTone } from "@ant-design/icons";27import { Loading } from "@cocalc/frontend/components/loading";28import { ReactNode } from "react";29import Stopwatch from "./stopwatch";30import { ButtonBar } from "./button-bar";31import type { TimeActions } from "./actions";32import { List } from "immutable";33import { useRedux } from "@cocalc/frontend/app-framework";34import { useFrameContext } from "@cocalc/frontend/frame-editors/frame-tree/frame-context";3536export default function EditorTime() {37// TODO: sort of abusive...38const { project_id, path, actions } = useFrameContext() as unknown as {39project_id: string;40path: string;41actions?: TimeActions;42};43const timers: List<any> | undefined = useRedux(["timers"], project_id, path);44const error: string | undefined = useRedux(["error"], project_id, path);4546if (timers == null || actions == null) {47return <Loading />;48}4950function renderStopwatches(): ReactNode[] {51if (timers == null) {52return [];53}54return timers55.sortBy((x) => x.get("id"))56.toJS()57.map((data) => (58<Stopwatch59key={data.id}60label={data.label}61total={data.total}62state={data.state}63time={data.time}64countdown={data.countdown}65clickButton={(button) => clickButton(data.id, button)}66setLabel={(label) => setLabel(data.id, label)}67setCountdown={68data.countdown != null69? (countdown) => {70actions?.setCountdown(data.id, countdown);71}72: undefined73}74/>75));76}7778function clickButton(id: number, button: string): void {79if (actions == null) {80return;81}82switch (button) {83case "reset":84actions.resetStopwatch(id);85return;86case "start":87actions.startStopwatch(id);88return;89case "pause":90actions.pauseStopwatch(id);91return;92case "delete":93actions.deleteStopwatch(id);94return;95default:96console.warn(`unknown button '${button}'`);97return;98}99}100101function setLabel(id: number, label: string): void {102actions?.setLabel(id, label);103}104105function renderButtonBar(): ReactNode {106if (actions == null) return null;107return <ButtonBar actions={actions} />;108}109110return (111<div className="smc-vfill">112{error && <Alert type="error" message={`Error: ${error}`} />}113{renderButtonBar()}114<div className="smc-vfill" style={{ overflowY: "auto" }}>115{renderStopwatches()}116<div style={{ display: "flex" }}>117<Button118size="large"119icon={<PlusCircleTwoTone />}120style={{ maxWidth: "200px", margin: "15px" }}121key={"add-stopwatch"}122onClick={() => actions.addStopwatch()}123>124New Stopwatch125</Button>126<Button127size="large"128icon={<PlusCircleTwoTone />}129style={{ maxWidth: "200px", margin: "15px" }}130key={"add-timer"}131onClick={() => actions.addTimer()}132>133New Timer134</Button>135</div>136</div>137</div>138);139}140141142