Skip to content

Commit 7b105b7

Browse files
committed
port hoc.js to typescript
this turned out to be really messy. first of all, typing HOCs is not really easy. but once I started to get going with it, I hit a TypeScript 3.2 bug that prevents the HOC typing trick from working. This would've worked in earlier TSC versions. See: microsoft/TypeScript#28938
1 parent 128df01 commit 7b105b7

File tree

4 files changed

+135
-53
lines changed

4 files changed

+135
-53
lines changed

src/containers/CharSelect.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ interface CharSelectProps {
9595
}
9696

9797
interface CharSelectState {
98-
charPos: Coord2;
98+
charPos: Coord2 | null;
9999
isActive: boolean;
100100
}
101101

@@ -133,7 +133,7 @@ class CharSelect extends Component<CharSelectProps, CharSelectState> {
133133
this.props.Toolbar.setCurrentChar(this.state.charPos)
134134
}
135135

136-
handleCharPosChanged = (charPos: Coord2) => {
136+
handleCharPosChanged = (charPos: Coord2 | null) => {
137137
this.setState({ charPos })
138138
}
139139

@@ -161,7 +161,7 @@ class CharSelect extends Component<CharSelectProps, CharSelectState> {
161161
}
162162
let screencode = this.props.curScreencode
163163
if (this.state.isActive) {
164-
screencode = utils.charScreencodeFromRowCol(font, this.state.charPos)
164+
screencode = utils.charScreencodeFromRowCol(font, this.state.charPos!)
165165
}
166166
if (!this.fb) {
167167
throw new Error('FB cannot be null here');
@@ -201,7 +201,7 @@ class CharSelect extends Component<CharSelectProps, CharSelectState> {
201201
framebufHeight={H}
202202
grid={true}
203203
opacity={0.5}
204-
charPos={this.state.charPos}
204+
charPos={this.state.charPos!}
205205
/>
206206
: null}
207207
{this.props.selected ?

src/containers/Toolbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ class ToolbarView extends Component<
314314
pickerId='border'
315315
containerClassName={styles.tooltip}
316316
active={this.state.pickerActive.border}
317-
color={this.props.borderColor}
317+
color={this.props.borderColor!}
318318
onSetActive={this.setPickerActive}
319319
onSelectColor={this.handleSelectBorderColor}
320320
paletteRemap={this.props.paletteRemap}

src/containers/hoc.js renamed to src/containers/hoc.tsx

Lines changed: 129 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,37 @@
11

2-
import React, { Component } from 'react';
2+
import React, { Component, CSSProperties } from 'react';
33
import ReactCursorPosition from 'react-cursor-position'
4-
import PropTypes from 'prop-types'
4+
import { Coord2 } from '../redux/types';
55

6-
export class CharPosition extends Component {
6+
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
7+
type Subtract<T, K> = Omit<T, keyof K>;
78

8-
constructor (props) {
9+
type DragStartFunc = (charPos: Coord2) => void;
10+
type DragMoveFunc = (charPos: Coord2) => void;
11+
type DragEndFunc = () => void;
12+
type AltClickFunc = (charPos: Coord2) => void;
13+
14+
type Position = {
15+
position: { x: number, y: number };
16+
elementDimensions: { width: number, height: number };
17+
}
18+
19+
type IsActive = { isActive: boolean };
20+
interface CharPositionProps {
21+
onActivationChanged(args: IsActive): void;
22+
onCharPosChanged: (pos: Coord2|null) => void;
23+
}
24+
25+
export class CharPosition extends Component<CharPositionProps> {
26+
27+
prevCharPos: Coord2|null = null;
28+
29+
constructor (props: CharPositionProps) {
930
super(props)
1031
this.prevCharPos = null
1132
}
1233

13-
toCharPos = ({position, elementDimensions}) => {
34+
toCharPos = ({position, elementDimensions}: Position): Coord2|null => {
1435
if (elementDimensions === null) {
1536
return null
1637
}
@@ -24,17 +45,17 @@ export class CharPosition extends Component {
2445
// The parent component needs to know if the cursor is active (inside the
2546
// child div) to conditionally render sibling components like cursor pos,
2647
// char under cursor, etc.
27-
handleActivationChanged = ({isActive}) => {
48+
handleActivationChanged = ({isActive}: {isActive: boolean}) => {
2849
this.props.onActivationChanged({isActive})
2950
}
3051

3152
// The parent component needs to know what the current charpos is inside the
3253
// child div).
33-
handlePositionChanged = (vals) => {
54+
handlePositionChanged = (vals: Position) => {
3455
if (vals.elementDimensions === undefined) {
3556
return
3657
}
37-
const { row, col } = this.toCharPos(vals)
58+
const { row, col } = this.toCharPos(vals)!
3859
if (this.prevCharPos === null ||
3960
row !== this.prevCharPos.row ||
4061
col !== this.prevCharPos.col) {
@@ -56,21 +77,54 @@ export class CharPosition extends Component {
5677
}
5778
}
5879

59-
export const withMouseCharPositionShiftLockAxis = C => {
60-
class ToCharRowCol extends Component {
61-
static propTypes = {
62-
altKey: PropTypes.bool.isRequired,
63-
shiftKey: PropTypes.bool.isRequired
64-
}
65-
constructor (props) {
66-
super(props)
80+
// The component wrapped by withMouseCharPositionShiftLockAxis must
81+
// have these props in its component type.
82+
interface WithMouseCharPosWrappeeProps {
83+
charPos: Coord2|null;
84+
onMouseDown: (e: any, dragStart: DragStartFunc, altClick: AltClickFunc) => void;
85+
onMouseMove: (e: any, dragMove: DragMoveFunc) => void;
86+
onMouseUp: (e: any, dragEnd: DragEndFunc) => void;
87+
}
6788

68-
this.prevCharPos = null
69-
this.dragging = false
70-
this.prevCoord = null
71-
this.lockStartCoord = null
72-
this.lockedCharPos = null
73-
this.shiftLockAxis = null
89+
interface WithMouseCharPositionShiftLockAxisProps {
90+
altKey: boolean;
91+
shiftKey: boolean;
92+
isActive: boolean;
93+
onCharPosChange: (args:{
94+
isActive: boolean;
95+
charPos: Coord2
96+
}) => void;
97+
}
98+
99+
interface CursorPositionProps {
100+
containerSize: CSSProperties;
101+
onActivationChanged(args: IsActive): void;
102+
}
103+
104+
interface ToCharRowColProps extends Position {
105+
framebufWidth: number;
106+
framebufHeight: number;
107+
}
108+
109+
export const withMouseCharPositionShiftLockAxis = <P extends object>(C: React.ComponentType<WithMouseCharPosWrappeeProps>) => {
110+
class ToCharRowCol extends Component<P & CursorPositionProps & WithMouseCharPositionShiftLockAxisProps & ToCharRowColProps> {
111+
112+
prevCharPos: Coord2|null = null;
113+
prevCoord: Coord2|null = null;
114+
lockStartCoord: Coord2|null = null;
115+
lockedCharPos: Coord2|null = null;
116+
shiftLockAxis: 'shift'|'row'|'col'|null = null;
117+
dragging = false;
118+
119+
constructor (props: P & CursorPositionProps & WithMouseCharPositionShiftLockAxisProps & ToCharRowColProps) {
120+
super(props);
121+
122+
this.prevCharPos = null;
123+
this.dragging = false;
124+
this.prevCoord = null;
125+
this.lockStartCoord = null;
126+
this.lockedCharPos = null;
127+
this.shiftLockAxis = null;
74128
}
75129

76130
currentCharPos = () => {
@@ -87,13 +141,13 @@ export const withMouseCharPositionShiftLockAxis = C => {
87141
}
88142
}
89143

90-
handleMouseDown = (e, dragStart, altClick) => {
144+
handleMouseDown = (e: any, dragStart: DragStartFunc, altClick: AltClickFunc) => {
91145
const charPos = this.currentCharPos()
92146
// alt-left click doesn't start dragging
93147
if (this.props.altKey) {
94-
this.dragging = false
95-
altClick(charPos)
96-
return
148+
this.dragging = false;
149+
altClick(charPos);
150+
return;
97151
}
98152

99153
this.dragging = true
@@ -110,7 +164,7 @@ export const withMouseCharPositionShiftLockAxis = C => {
110164
}
111165
}
112166

113-
handleMouseUp = (e, dragEnd) => {
167+
handleMouseUp = (_e: React.PointerEvent, dragEnd: DragEndFunc) => {
114168
if (this.dragging) {
115169
dragEnd()
116170
}
@@ -120,7 +174,7 @@ export const withMouseCharPositionShiftLockAxis = C => {
120174
this.shiftLockAxis = null
121175
}
122176

123-
handleMouseMove = (e, dragMove) => {
177+
handleMouseMove = (_e:any, dragMove: DragMoveFunc) => {
124178
const charPos = this.currentCharPos()
125179

126180
if (this.prevCharPos === null ||
@@ -134,21 +188,22 @@ export const withMouseCharPositionShiftLockAxis = C => {
134188
return
135189
}
136190

137-
const coord = charPos
138-
if (this.prevCoord.row !== coord.row ||
139-
this.prevCoord.col !== coord.col) {
191+
// Note: prevCoord is known to be not null here as it's been set
192+
// in mouse down
193+
const coord = charPos;
194+
if (this.prevCoord!.row !== coord.row || this.prevCoord!.col !== coord.col) {
140195

141196
if (this.shiftLockAxis === 'shift') {
142-
if (this.prevCoord.row === coord.row) {
197+
if (this.prevCoord!.row === coord.row) {
143198
this.shiftLockAxis = 'row'
144-
} else if (this.prevCoord.col === coord.col) {
199+
} else if (this.prevCoord!.col === coord.col) {
145200
this.shiftLockAxis = 'col'
146201
}
147202
}
148203

149204
if (this.shiftLockAxis !== null) {
150205
let lockedCharPos = {
151-
...this.lockStartCoord
206+
...this.lockStartCoord!
152207
}
153208

154209
if (this.shiftLockAxis === 'row') {
@@ -184,30 +239,44 @@ export const withMouseCharPositionShiftLockAxis = C => {
184239
)
185240
}
186241
}
187-
return class extends Component {
242+
return class extends Component<P & WithMouseCharPositionShiftLockAxisProps & CursorPositionProps & ToCharRowColProps> {
188243
render () {
189244
return (
190245
<ReactCursorPosition
191246
style={{
192247
...this.props.containerSize
193248
}}
194249
onActivationChanged={this.props.onActivationChanged}>
195-
<ToCharRowCol {...this.props}/>
250+
<ToCharRowCol {...this.props} />
196251
</ReactCursorPosition>
197252
)
198253
}
199254
}
200255
}
201256

202-
export const withHoverFade = (C, options) => {
203-
return class extends Component {
204-
constructor (props) {
205-
super(props)
206-
this.timerId = null
207-
this.state = {
208-
fadeOut: false
209-
}
210-
}
257+
interface WithHoverInjectedProps {
258+
onToggleActive: () => void;
259+
fadeOut: boolean;
260+
}
261+
262+
interface WithHoverFadeProps {
263+
pickerId: any;
264+
active: boolean;
265+
containerClassName: string;
266+
onSetActive: (pickerId: any, active: boolean) => void;
267+
}
268+
269+
interface WithHoverFadeState {
270+
fadeOut: boolean;
271+
}
272+
273+
// See https://medium.com/@jrwebdev/react-higher-order-component-patterns-in-typescript-42278f7590fb
274+
export const withHoverFade = <P extends WithHoverInjectedProps>(C: React.ComponentType<P>) => {
275+
return class extends Component<Subtract<P, WithHoverInjectedProps> & WithHoverFadeProps, WithHoverFadeState> {
276+
state: WithHoverFadeState = {
277+
fadeOut: false
278+
};
279+
timerId: any = null;
211280

212281
componentWillUnmount () {
213282
if (this.timerId !== null) {
@@ -244,16 +313,28 @@ export const withHoverFade = (C, options) => {
244313
}
245314

246315
render () {
316+
// const { pickerId, active, containerClassName, onSetActive, ...rest} = this.props as WithHoverFadeProps;
317+
// See: https://github.com/Microsoft/TypeScript/issues/28938
318+
const ts32workAround: any = {
319+
...this.props,
320+
onToggleActive: this.handleToggleActive,
321+
fadeOut: this.state.fadeOut
322+
}
323+
/*
324+
<C
325+
{...rest}
326+
onToggleActive={this.handleToggleActive}
327+
fadeOut={this.state.fadeOut}
328+
/>
329+
*/
247330
return (
248331
<div
249332
className={this.props.containerClassName}
250333
onMouseLeave={this.handleMouseLeave}
251334
onMouseEnter={this.handleMouseEnter}
252335
>
253336
<C
254-
onToggleActive={this.handleToggleActive}
255-
fadeOut={this.state.fadeOut}
256-
{...this.props}
337+
{...ts32workAround}
257338
/>
258339
</div>
259340
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
declare module 'react-cursor-position'

0 commit comments

Comments
 (0)