Skip to content

Commit ade1dfe

Browse files
committed
board: implement a stopwatch
1 parent 29cc1ba commit ade1dfe

File tree

8 files changed

+179
-76
lines changed

8 files changed

+179
-76
lines changed

src/packages/frontend/editors/stopwatch/stopwatch.tsx

Lines changed: 79 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
The stopwatch component
88
*/
99

10-
import { Button, Row, Col } from "antd";
10+
import { CSSProperties } from "react";
11+
import { Button, Row, Col, Tooltip } from "antd";
1112
import {
1213
DeleteTwoTone,
1314
PauseCircleTwoTone,
@@ -27,10 +28,15 @@ interface StopwatchProps {
2728
state: TimerState; // 'paused' or 'running' or 'stopped'
2829
time: number; // when entered this state
2930
click_button: (str: string) => void;
30-
set_label: (str: string) => void;
31+
set_label?: (str: string) => void;
3132
compact?: boolean;
3233
label?: string; // a text label
34+
noLabel?: boolean; // show no label at all
35+
noDelete?: boolean; // do not show delete button
36+
noButtons?: boolean; // hide ALL buttons
3337
total?: number; // total time accumulated before entering current state
38+
style?: CSSProperties;
39+
timeStyle?: CSSProperties;
3440
}
3541

3642
interface StopwatchState {
@@ -59,51 +65,56 @@ export class Stopwatch extends Component<StopwatchProps, StopwatchState> {
5965

6066
private render_start_button(): Rendered {
6167
return (
62-
<Button
63-
icon={<PlayCircleTwoTone />}
64-
onClick={() => this.props.click_button("start")}
65-
style={!this.props.compact ? { width: "8em" } : undefined}
66-
size={this.props.compact ? "small" : undefined}
67-
>
68-
{!this.props.compact ? "Start" : undefined}
69-
</Button>
68+
<Tooltip title="Start the stopwatch">
69+
<Button
70+
icon={<PlayCircleTwoTone />}
71+
onClick={() => this.props.click_button("start")}
72+
style={!this.props.compact ? { width: "8em" } : undefined}
73+
>
74+
{!this.props.compact ? "Start" : undefined}
75+
</Button>
76+
</Tooltip>
7077
);
7178
}
7279

7380
private render_reset_button(): Rendered {
7481
return (
75-
<Button
76-
icon={<StopTwoTone />}
77-
onClick={() => this.props.click_button("reset")}
78-
size={this.props.compact ? "small" : undefined}
79-
>
80-
{!this.props.compact ? "Reset" : undefined}
81-
</Button>
82+
<Tooltip title="Reset the stopwatch to 0">
83+
<Button
84+
icon={<StopTwoTone />}
85+
onClick={() => this.props.click_button("reset")}
86+
>
87+
{!this.props.compact ? "Reset" : undefined}
88+
</Button>
89+
</Tooltip>
8290
);
8391
}
8492

8593
private render_delete_button(): Rendered {
86-
if (this.props.compact) return;
94+
if (this.props.compact || this.props.noDelete) return;
8795
return (
88-
<Button
89-
icon={<DeleteTwoTone />}
90-
onClick={() => this.props.click_button("delete")}
91-
>
92-
{!this.props.compact ? "Delete" : undefined}
93-
</Button>
96+
<Tooltip title="Delete this stopwatch">
97+
<Button
98+
icon={<DeleteTwoTone />}
99+
onClick={() => this.props.click_button("delete")}
100+
>
101+
{!this.props.compact ? "Delete" : undefined}
102+
</Button>
103+
</Tooltip>
94104
);
95105
}
96106

97107
private render_pause_button(): Rendered {
98108
return (
99-
<Button
100-
icon={<PauseCircleTwoTone />}
101-
onClick={() => this.props.click_button("pause")}
102-
style={!this.props.compact ? { width: "8em" } : undefined}
103-
size={this.props.compact ? "small" : undefined}
104-
>
105-
{!this.props.compact ? "Pause" : undefined}
106-
</Button>
109+
<Tooltip title="Pause the stopwatch">
110+
<Button
111+
icon={<PauseCircleTwoTone />}
112+
onClick={() => this.props.click_button("pause")}
113+
style={!this.props.compact ? { width: "8em" } : undefined}
114+
>
115+
{!this.props.compact ? "Pause" : undefined}
116+
</Button>
117+
</Tooltip>
107118
);
108119
}
109120

@@ -125,7 +136,12 @@ export class Stopwatch extends Component<StopwatchProps, StopwatchState> {
125136
}
126137

127138
return (
128-
<TimeAmount key={"time"} amount={amount} compact={this.props.compact} />
139+
<TimeAmount
140+
key={"time"}
141+
amount={amount}
142+
compact={this.props.compact}
143+
style={this.props.timeStyle}
144+
/>
129145
);
130146
}
131147

@@ -168,7 +184,7 @@ export class Stopwatch extends Component<StopwatchProps, StopwatchState> {
168184
<TextInput
169185
text={this.props.label ? this.props.label : ""}
170186
on_change={(value) => {
171-
this.props.set_label(value);
187+
this.props.set_label?.(value);
172188
this.setState({ editing_label: false });
173189
}}
174190
autoFocus={true}
@@ -210,6 +226,21 @@ export class Stopwatch extends Component<StopwatchProps, StopwatchState> {
210226
}
211227

212228
private render_full_size(): Rendered {
229+
if (this.props.noLabel) {
230+
return (
231+
<div
232+
style={{
233+
borderBottom: "1px solid #666",
234+
background: "#efefef",
235+
padding: "15px",
236+
...this.props.style,
237+
}}
238+
>
239+
<div>{this.render_time()}</div>
240+
<div>{this.render_buttons()}</div>
241+
</div>
242+
);
243+
}
213244
return (
214245
<div
215246
style={{
@@ -226,18 +257,25 @@ export class Stopwatch extends Component<StopwatchProps, StopwatchState> {
226257
{this.render_label()}
227258
</Col>
228259
</Row>
229-
<Row>
230-
<Col md={24}>{this.render_buttons()}</Col>
231-
</Row>
260+
{!this.props.noButtons && (
261+
<Row>
262+
<Col md={24}>{this.render_buttons()}</Col>
263+
</Row>
264+
)}
232265
</div>
233266
);
234267
}
235268

236269
public render(): Rendered {
237270
if (this.props.compact) {
238271
return (
239-
<div>
240-
{this.render_time()} {this.render_buttons()}
272+
<div style={{ display: "flex" }}>
273+
{this.render_time()}
274+
{!this.props.noButtons && (
275+
<div style={{ marginTop: "3px", marginLeft: "5px" }}>
276+
{this.render_buttons()}
277+
</div>
278+
)}
241279
</div>
242280
);
243281
} else {
@@ -257,6 +295,7 @@ const zpad = function (n) {
257295
interface TimeProps {
258296
amount: number;
259297
compact?: boolean;
298+
style?: CSSProperties;
260299
}
261300

262301
//const TimeAmount = function(props: TimeProps) {
@@ -272,6 +311,7 @@ function TimeAmount(props: TimeProps) {
272311
style={{
273312
fontSize: !props.compact ? "50pt" : undefined,
274313
fontFamily: "courier",
314+
...props.style,
275315
}}
276316
>
277317
{zpad(hours)}:{zpad(minutes)}:{zpad(seconds)}

src/packages/frontend/frame-editors/whiteboard-editor/canvas.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -368,31 +368,34 @@ export default function Canvas({
368368
} else {
369369
// clicked on an element on the canvas; either stay selected or let
370370
// it handle selecting it.
371-
return;
372371
}
372+
return;
373373
}
374-
const data = evtToData(e);
374+
const data = { ...evtToData(e), z: transforms.zMax + 1 };
375375
let params: any = undefined;
376376

377377
if (selectedTool == "note") {
378378
params = { data: getNoteParams() };
379+
} else if (selectedTool == "stopwatch") {
380+
params = { data: { fontSize: 24 } };
379381
}
380382

381-
// this code needs to move to tool panel spec stuff...
383+
const element = {
384+
...data,
385+
type: selectedTool,
386+
...params,
387+
};
388+
389+
// create element
390+
const { id } = frame.actions.createElement(element, true);
391+
392+
// in some cases, select it
382393
if (
383394
selectedTool == "text" ||
384395
selectedTool == "note" ||
385-
selectedTool == "code"
396+
selectedTool == "code" ||
397+
selectedTool == "stopwatch"
386398
) {
387-
const element = {
388-
...data,
389-
type: selectedTool,
390-
str: "",
391-
...params,
392-
z: transforms.zMax + 1,
393-
};
394-
395-
const { id } = frame.actions.createElement(element, true);
396399
frame.actions.setSelectedTool(frame.id, "select");
397400
frame.actions.setSelection(frame.id, id);
398401
}
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import { Markdown } from "@cocalc/frontend/components";
2+
13
export default function Generic({ element, focused }) {
24
focused = focused;
3-
const { str, data } = element;
45
return (
5-
<>
6-
{str != null && str}
7-
{data != null && <span>{JSON.stringify(data, undefined, 2)}</span>}
8-
</>
6+
<Markdown
7+
value={"```js\n" + JSON.stringify(element, undefined, 2) + "\n```"}
8+
style={{
9+
width: "100%",
10+
height: "100%",
11+
}}
12+
/>
913
);
1014
}

src/packages/frontend/frame-editors/whiteboard-editor/elements/render.tsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,31 @@ import Code from "./code";
99
import Frame from "./frame";
1010
import Generic from "./generic";
1111
import Pen from "./pen";
12+
import Stopwatch from "./stopwatch";
1213

1314
interface Props {
1415
element: Element;
1516
focused: boolean;
1617
canvasScale: number;
1718
}
1819

19-
export default function Render({ element, focused, canvasScale }: Props) {
20+
export default function Render(props: Props) {
2021
/* dumb for now, but will be a cool plugin system like we used for our slate wysiwyg editor....*/
2122

22-
switch (element.type) {
23+
switch (props.element.type) {
2324
case "text":
24-
return <Text element={element} focused={focused} />;
25+
return <Text {...props} />;
2526
case "note":
26-
return <Note element={element} focused={focused} />;
27+
return <Note {...props} />;
2728
case "code":
28-
return <Code element={element} focused={focused} />;
29+
return <Code {...props} />;
2930
case "frame":
30-
return (
31-
<Frame element={element} focused={focused} canvasScale={canvasScale} />
32-
);
31+
return <Frame {...props} />;
3332
case "pen":
34-
return <Pen element={element} focused={focused} />;
33+
return <Pen {...props} />;
34+
case "stopwatch":
35+
return <Stopwatch {...props} />;
3536
default:
36-
return <Generic element={element} focused={focused} />;
37+
return <Generic {...props} />;
3738
}
3839
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Stopwatch as StopwatchEditor } from "@cocalc/frontend/editors/stopwatch/stopwatch";
2+
import { getStyle } from "./text";
3+
import { useFrameContext } from "../hooks";
4+
import { webapp_client } from "@cocalc/frontend/webapp-client";
5+
6+
export default function Stopwatch({ element, focused }) {
7+
const { actions } = useFrameContext();
8+
const { data } = element;
9+
function set(obj) {
10+
actions.setElement({
11+
id: element.id,
12+
data: { ...data, ...obj, time: webapp_client.server_time() - 0 },
13+
});
14+
}
15+
16+
return (
17+
<StopwatchEditor
18+
noLabel
19+
noDelete
20+
compact
21+
noButtons={!focused}
22+
state={data?.state ?? "stopped"}
23+
time={data?.time ?? 0}
24+
total={data?.total ?? 0}
25+
click_button={(button: string) => {
26+
if (button == "start") {
27+
set({ state: "running" });
28+
} else if (button == "reset") {
29+
set({ state: "stopped", total: 0 });
30+
} else if (button == "pause") {
31+
set({
32+
total:
33+
(data?.total ?? 0) +
34+
(webapp_client.server_time() - (data?.time ?? 0)),
35+
state: "paused",
36+
});
37+
}
38+
}}
39+
timeStyle={getStyle(element, { fontSize: 20 })}
40+
style={{
41+
overflow: "scroll",
42+
border: "1px solid lightgrey",
43+
borderRadius: "5px",
44+
boxShadow: "0 0 5px grey",
45+
}}
46+
/>
47+
);
48+
}

src/packages/frontend/frame-editors/whiteboard-editor/elements/text.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ export default function Text({ element, focused }: Props) {
1919
setValue(element.str ?? "");
2020
}, [element.str]);
2121

22-
2322
if (!focused) {
2423
return (
2524
<Markdown
@@ -69,10 +68,19 @@ export default function Text({ element, focused }: Props) {
6968
);
7069
}
7170

72-
function getStyle(element) {
71+
export function getStyle(
72+
element,
73+
defaults?: {
74+
color?: string;
75+
fontSize?: number;
76+
fontFamily?: string;
77+
background?: string;
78+
}
79+
) {
7380
return {
74-
color: element.data?.color,
75-
fontSize: element.data?.fontSize,
76-
fontFamily: element.data?.fontFamily,
81+
color: element.data?.color ?? defaults?.color,
82+
fontSize: element.data?.fontSize ?? defaults?.fontSize,
83+
fontFamily: element.data?.fontFamily ?? defaults?.fontFamily,
84+
background: element.data?.background ?? defaults?.background,
7785
};
7886
}

0 commit comments

Comments
 (0)