forked from source-academy/modules
-
Notifications
You must be signed in to change notification settings - Fork 0
/
canvas_holder.tsx
165 lines (146 loc) · 4.97 KB
/
canvas_holder.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/* [Imports] */
import { Spinner, SpinnerSize } from '@blueprintjs/core';
import { IconNames } from '@blueprintjs/icons';
import React from 'react';
import { Core } from '../../bundles/csg/core.js';
import StatefulRenderer from '../../bundles/csg/stateful_renderer.js';
import type { RenderGroup } from '../../bundles/csg/utilities.js';
import HoverControlHint from './hover_control_hint';
import type { CanvasHolderProps, CanvasHolderState } from './types';
import { BP_CARD_BORDER_RADIUS, BP_TAB_BUTTON_MARGIN, BP_TAB_PANEL_MARGIN, BP_TEXT_MARGIN, CANVAS_MAX_WIDTH } from '../common/css_constants.js';
/* [Main] */
export default class CanvasHolder extends React.Component<
CanvasHolderProps,
CanvasHolderState
> {
private readonly canvasReference: React.RefObject<HTMLCanvasElement> = React.createRef();
private statefulRenderer: StatefulRenderer | null = null;
constructor(props: CanvasHolderProps) {
super(props);
this.state = {
isContextLost: false,
};
}
componentDidMount() {
console.debug(`>>> MOUNT #${this.props.componentNumber}`);
let { current: canvas } = this.canvasReference;
if (canvas === null) return;
let renderGroups: RenderGroup[] = Core
.getRenderGroupManager()
.getGroupsToRender();
let lastRenderGroup: RenderGroup = renderGroups.at(-1) as RenderGroup;
this.statefulRenderer = new StatefulRenderer(
canvas,
lastRenderGroup,
this.props.componentNumber,
() => this.setState({ isContextLost: true }),
() => this.setState({ isContextLost: false }),
);
this.statefulRenderer.start(true);
}
componentWillUnmount() {
console.debug(`>>> UNMOUNT #${this.props.componentNumber}`);
this.statefulRenderer?.stop(true);
}
// Only required method of a React Component. Returns a React Element created
// via JSX to instruct React to render a DOM node. Also attaches the
// canvasReference via the ref attribute, for imperatively modifying the
// canvas
render() {
return <>
<div
style={{
display: this.state.isContextLost ? 'none' : 'flex',
// Centre content when sidebar is wider than it
justifyContent: 'center',
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
gap: BP_TAB_BUTTON_MARGIN,
marginRight: BP_TAB_PANEL_MARGIN,
}}
>
<HoverControlHint
tooltipText="Zoom in: Scroll up"
iconName={IconNames.ZOOM_IN}
/>
<HoverControlHint
tooltipText="Zoom out: Scroll down"
iconName={IconNames.ZOOM_OUT}
/>
<HoverControlHint
tooltipText="Zoom to fit: Double left-click"
iconName={IconNames.ZOOM_TO_FIT}
/>
<HoverControlHint
tooltipText="Rotate: Left-click"
iconName={IconNames.REPEAT}
/>
<HoverControlHint
tooltipText="Pan: Middle-click OR shift + left-click"
iconName={IconNames.MOVE}
/>
</div>
<div
style={{
// Expand to take as much space as possible, otherwise this will
// have no height
width: '100%',
// Prevent canvas from becoming too large when the sidebar is
// wide, which would require lots of scrolling or never fit
// entirely on screen. Tall but skinny sidebar maxes width at 70vh
// (eg portrait mobile view). Short but wide maxes width at 30vw
// (eg wide desktop view)
maxWidth: CANVAS_MAX_WIDTH,
// Force square aspect ratio, otherwise this will have no height
aspectRatio: '1',
}}
>
<canvas
ref={this.canvasReference}
style={{
// Inline element would try to align to text baseline, with
// space below for descender. This prevents that
display: 'block',
width: '100%',
height: '100%',
borderRadius: BP_CARD_BORDER_RADIUS,
}}
// These get set on the fly by the dynamic resizer in
// StatefulRenderer's InputTracker
width="0"
height="0"
/>
</div>
</div>
<div
// Explicit dark theme as mobile view switches to dark text with light
// spinner
className="bp3-dark"
style={{
display: this.state.isContextLost ? 'block' : 'none',
textAlign: 'center',
}}
>
<h2
style={{
margin: `0px 0px ${BP_TEXT_MARGIN} 0px`,
}}
>
WebGL Context Lost
</h2>
<Spinner intent="warning" size={SpinnerSize.LARGE} />
<p
style={{
margin: `${BP_TEXT_MARGIN} 0px 0px 0px`,
}}
>
Your GPU is probably busy. Waiting for browser to re-establish connection...
</p>
</div>
</>;
}
}