From 4199980fbff51a957e8ad1717d85a4980daf98aa Mon Sep 17 00:00:00 2001 From: Cat Johnson Date: Mon, 16 Sep 2024 12:40:04 -0700 Subject: [PATCH 01/20] Beginning of removing createReactClass. --- .../perseus/src/widgets/measurer/measurer.tsx | 149 +++++++++++------- 1 file changed, 92 insertions(+), 57 deletions(-) diff --git a/packages/perseus/src/widgets/measurer/measurer.tsx b/packages/perseus/src/widgets/measurer/measurer.tsx index 0c81bba357..a74276c8a2 100644 --- a/packages/perseus/src/widgets/measurer/measurer.tsx +++ b/packages/perseus/src/widgets/measurer/measurer.tsx @@ -1,5 +1,4 @@ /* eslint-disable @babel/no-invalid-this */ -import createReactClass from "create-react-class"; import $ from "jquery"; import PropTypes from "prop-types"; import * as React from "react"; @@ -11,8 +10,12 @@ import {ApiOptions} from "../../perseus-api"; import GraphUtils from "../../util/graph-utils"; import type {Coord} from "../../interactive2/types"; -import type {WidgetExports} from "../../types"; +import type {WidgetExports, WidgetProps} from "../../types"; import type {Interval} from "../../util/interval"; +import { + PerseusImageWidgetOptions, + PerseusMeasurerWidgetOptions, +} from "../../perseus-types"; const defaultImage = { url: null, @@ -20,51 +23,69 @@ const defaultImage = { left: 0, } as const; -const Measurer: any = createReactClass({ - displayName: "Measurer", +type RenderProps = PerseusMeasurerWidgetOptions; // there is no transform as part of exports +type Rubric = { + protractorX: number; + protractorY: number; +}; - propTypes: { - apiOptions: ApiOptions.propTypes, - box: PropTypes.arrayOf(PropTypes.number), - image: PropTypes.shape({ - url: PropTypes.string, - top: PropTypes.number, - left: PropTypes.number, - }), - showProtractor: PropTypes.bool, - protractorX: PropTypes.number, - protractorY: PropTypes.number, - showRuler: PropTypes.bool, - rulerLabel: PropTypes.string, - rulerTicks: PropTypes.number, - rulerPixels: PropTypes.number, - rulerLength: PropTypes.number, - }, +type ExternalProps = WidgetProps; - getDefaultProps: function () { - return { - box: [480, 480], - image: {}, - showProtractor: true, - protractorX: 7.5, - protractorY: 0.5, - showRuler: false, - rulerLabel: "", - rulerTicks: 10, - rulerPixels: 40, - rulerLength: 10, - }; - }, +type Props = ExternalProps & { + apiOptions: NonNullable; + box: NonNullable; + image: ExternalProps["image"]; + showProtractor: NonNullable; + protractorX: NonNullable; + protractorY: NonNullable; + showRuler: NonNullable; + rulerLabel: NonNullable; + rulerTicks: NonNullable; + rulerPixels: NonNullable; + rulerLength: NonNullable; +}; + +type DefaultProps = { + apiOptions: Props["apiOptions"]; + box: Props["box"]; + image: Props["image"]; + showProtractor: Props["showProtractor"]; + protractorX: Props["protractorX"]; + protractorY: Props["protractorY"]; + showRuler: Props["showRuler"]; + rulerLabel: Props["rulerLabel"]; + rulerTicks: Props["rulerTicks"]; + rulerPixels: Props["rulerPixels"]; + rulerLength: Props["rulerLength"]; +}; + +export class Measurer extends React.Component { + displayName: string = "Measurer"; - getInitialState: function () { + static defaultProps: DefaultProps = { + box: [480, 480], + image: {}, + showProtractor: true, + protractorX: 7.5, + protractorY: 0.5, + showRuler: false, + rulerLabel: "", + rulerTicks: 10, + rulerPixels: 40, + rulerLength: 10, + }; + + focus = $.noop; + + getInitialState() { return {}; - }, + } - componentDidMount: function () { + componentDidMount() { this.setupGraphie(); - }, + } - componentDidUpdate: function (prevProps) { + componentDidUpdate(prevProps) { const shouldSetupGraphie = _.any( [ "box", @@ -85,9 +106,9 @@ const Measurer: any = createReactClass({ if (shouldSetupGraphie) { this.setupGraphie(); } - }, + } - setupGraphie: function () { + setupGraphie() { // eslint-disable-next-line react/no-string-refs const graphieDiv = ReactDOM.findDOMNode(this.refs.graphieDiv); // @ts-expect-error - TS2769 - No overload matches this call. | TS2339 - Property 'empty' does not exist on type 'JQueryStatic'. @@ -110,8 +131,8 @@ const Measurer: any = createReactClass({ this.props.apiOptions.setDrawingAreaAvailable, }); - if (this.protractor) { - this.protractor.remove(); + if (this.props.protractor) { + this.props.protractor.remove(); } if (this.props.showProtractor) { @@ -122,8 +143,8 @@ const Measurer: any = createReactClass({ ]); } - if (this.ruler) { - this.ruler.remove(); + if (this.props.ruler) { + this.props.ruler.remove(); } if (this.props.showRuler) { @@ -139,20 +160,27 @@ const Measurer: any = createReactClass({ units: this.props.rulerLength, }); } - }, + } - getUserInput: function () { + getUserInput() { return {}; - }, + } - simpleValidate: function (rubric) { - // TODO(joel) - I don't understand how this is useful! - return Measurer.validate(this.getUserInput(), rubric); - }, + validate(state, rubric) { + return { + type: "points", + earned: 1, + total: 1, + message: null, + }; + } - focus: $.noop, + simpleValidate(rubric) { + // TODO(joel) - I don't understand how this is useful! + return this.validate(this.getUserInput(), rubric); + } - render: function () { + render() { const image = _.extend({}, defaultImage, this.props.image); // TODO(scottgrant): This isn't a11y-friendly! We should insist on @@ -181,8 +209,8 @@ const Measurer: any = createReactClass({
); - }, -}); + } +} _.extend(Measurer, { validate: function (state, rubric) { @@ -219,3 +247,10 @@ export default { version: {major: 1, minor: 0}, propUpgrades: propUpgrades, } as WidgetExports; +function getUserInput() { + throw new Error("Function not implemented."); +} + +function simpleValidate(rubric: any) { + throw new Error("Function not implemented."); +} From 0987c84051ab4f999914363041dbdfbe081dfed1 Mon Sep 17 00:00:00 2001 From: Cat Johnson Date: Mon, 23 Sep 2024 16:02:24 -0700 Subject: [PATCH 02/20] Migrated measure to class structure. --- .../perseus/src/widgets/measurer/measurer.tsx | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/perseus/src/widgets/measurer/measurer.tsx b/packages/perseus/src/widgets/measurer/measurer.tsx index cf31ceb7c2..1b3cb9ead1 100644 --- a/packages/perseus/src/widgets/measurer/measurer.tsx +++ b/packages/perseus/src/widgets/measurer/measurer.tsx @@ -1,6 +1,5 @@ /* eslint-disable @babel/no-invalid-this */ import $ from "jquery"; -import PropTypes from "prop-types"; import * as React from "react"; import ReactDOM from "react-dom"; import _ from "underscore"; @@ -13,10 +12,7 @@ import noopValidator from "../__shared__/noop-validator"; import type {Coord} from "../../interactive2/types"; import type {WidgetExports, WidgetProps} from "../../types"; import type {Interval} from "../../util/interval"; -import { - PerseusImageWidgetOptions, - PerseusMeasurerWidgetOptions, -} from "../../perseus-types"; +import {PerseusMeasurerWidgetOptions} from "../../perseus-types"; const defaultImage = { url: null, @@ -25,20 +21,15 @@ const defaultImage = { } as const; type RenderProps = PerseusMeasurerWidgetOptions; // there is no transform as part of exports -type Rubric = { - protractorX: number; - protractorY: number; -}; +type Rubric = {}; type ExternalProps = WidgetProps; type Props = ExternalProps & { - apiOptions: NonNullable; + apiOptions: NonNullable; box: NonNullable; image: ExternalProps["image"]; showProtractor: NonNullable; - protractorX: NonNullable; - protractorY: NonNullable; showRuler: NonNullable; rulerLabel: NonNullable; rulerTicks: NonNullable; @@ -51,8 +42,6 @@ type DefaultProps = { box: Props["box"]; image: Props["image"]; showProtractor: Props["showProtractor"]; - protractorX: Props["protractorX"]; - protractorY: Props["protractorY"]; showRuler: Props["showRuler"]; rulerLabel: Props["rulerLabel"]; rulerTicks: Props["rulerTicks"]; @@ -62,13 +51,16 @@ type DefaultProps = { export class Measurer extends React.Component { displayName: string = "Measurer"; + protractorX: number = 7.5; + protractorY: number = 0.5; static defaultProps: DefaultProps = { + apiOptions: ApiOptions.defaults, box: [480, 480], - image: {}, + image: { + url: null, + }, showProtractor: true, - protractorX: 7.5, - protractorY: 0.5, showRuler: false, rulerLabel: "", rulerTicks: 10, @@ -77,6 +69,8 @@ export class Measurer extends React.Component { }; focus = $.noop; + protractor: any; + ruler: any; getInitialState() { return {}; @@ -132,25 +126,25 @@ export class Measurer extends React.Component { this.props.apiOptions.setDrawingAreaAvailable, }); - if (this.props.protractor) { - this.props.protractor.remove(); + if (this.protractor) { + this.protractor.remove(); } if (this.props.showProtractor) { // @ts-expect-error - Property 'protractor' does not exist on type 'Graphie'. this.protractor = graphie.protractor([ - this.props.protractorX, - this.props.protractorY, + this.protractorX, + this.protractorY, ]); } - if (this.props.ruler) { - this.props.ruler.remove(); + if (this.ruler) { + this.ruler.remove(); } if (this.props.showRuler) { // @ts-expect-error - Property 'ruler' does not exist on type 'Graphie'. - this.ruler = graphie.ruler({ + this._ruler = graphie.ruler({ center: [ (range[0][0] + range[0][1]) / 2, (range[1][0] + range[1][1]) / 2, @@ -167,7 +161,12 @@ export class Measurer extends React.Component { return {}; } - validate(state, rubric) { + validate( + state: { + currentValue: string; + }, + rubric: Rubric, + ) { return { type: "points", earned: 1, @@ -176,9 +175,9 @@ export class Measurer extends React.Component { }; } - simpleValidate: function () { + simpleValidate() { return noopValidator(1); - }, + } render() { const image = _.extend({}, defaultImage, this.props.image); @@ -241,6 +240,7 @@ export default { version: {major: 1, minor: 0}, propUpgrades: propUpgrades, } as WidgetExports; + function getUserInput() { throw new Error("Function not implemented."); } From 28517ff8aee55aaaefe2e35e73203afcb88520d0 Mon Sep 17 00:00:00 2001 From: Cat Johnson Date: Mon, 23 Sep 2024 17:33:25 -0700 Subject: [PATCH 03/20] Removing createReactClass --- .../src/components/drag-target.tsx | 79 ++++---- .../src/components/json-editor.tsx | 60 +++--- .../src/components/sortable.tsx | 171 +++++++++++------- .../src/stateful-editor-page.tsx | 70 +++---- 4 files changed, 225 insertions(+), 155 deletions(-) diff --git a/packages/perseus-editor/src/components/drag-target.tsx b/packages/perseus-editor/src/components/drag-target.tsx index 4e78e7378f..887d0165f1 100644 --- a/packages/perseus-editor/src/components/drag-target.tsx +++ b/packages/perseus-editor/src/components/drag-target.tsx @@ -22,48 +22,61 @@ // * custom styles for global drag and dragOver // * only respond to certain types of drags (only images for instance)! -import createReactClass from "create-react-class"; -import PropTypes from "prop-types"; import * as React from "react"; -const DragTarget = createReactClass({ - propTypes: { - // All props not listed here are forwarded to the root element without - // modification. - onDrop: PropTypes.func.isRequired, - component: PropTypes.any, // component type - shouldDragHighlight: PropTypes.func, - style: PropTypes.any, - }, - getDefaultProps: function () { - return { - component: "div", - shouldDragHighlight: () => true, - }; - }, - getInitialState: function () { +type Props = { + onDrop: (any) => boolean; + component: any; + shouldDragHighlight: (any) => boolean; + style: any; +}; + +type DefaultProps = { + onDrop: Props["onDrop"]; + component: Props["component"]; + shouldDragHighlight: Props["shouldDragHighlight"]; + style: Props["style"]; +}; + +export class DragTarget extends React.Component { + dragHover: any; + + static defaultProps: DefaultProps = { + onDrop: () => true, + component: "div", + shouldDragHighlight: () => true, + style: {}, + }; + + getInitialState() { return {dragHover: false}; - }, - handleDrop: function (e) { + } + + handleDrop(e) { e.stopPropagation(); e.preventDefault(); this.setState({dragHover: false}); this.props.onDrop(e); - }, - handleDragEnd: function () { + } + + handleDragEnd() { this.setState({dragHover: false}); - }, - handleDragOver: function (e) { + } + + handleDragOver(e) { e.preventDefault(); - }, - handleDragLeave: function () { + } + + handleDragLeave() { this.setState({dragHover: false}); - }, - handleDragEnter: function (e) { + } + + handleDragEnter(e) { this.setState({dragHover: this.props.shouldDragHighlight(e)}); - }, - render: function () { - const opacity = this.state.dragHover ? {opacity: 0.3} : {}; + } + + render() { + const opacity = this.dragHover ? {opacity: 0.3} : {}; const Component = this.props.component; const forwardProps = Object.assign({}, this.props); @@ -81,7 +94,7 @@ const DragTarget = createReactClass({ onDragLeave={this.handleDragLeave} /> ); - }, -}); + } +} export default DragTarget; diff --git a/packages/perseus-editor/src/components/json-editor.tsx b/packages/perseus-editor/src/components/json-editor.tsx index 3c1f3186b9..98dd5827f1 100644 --- a/packages/perseus-editor/src/components/json-editor.tsx +++ b/packages/perseus-editor/src/components/json-editor.tsx @@ -1,30 +1,42 @@ /* eslint-disable @babel/no-invalid-this */ /* eslint-disable react/no-unsafe */ -import createReactClass from "create-react-class"; import * as React from "react"; import _ from "underscore"; -const JsonEditor: any = createReactClass({ - displayName: "JsonEditor", +type Props = { + value: any; +}; - getInitialState: function () { - return { - currentValue: JSON.stringify(this.props.value, null, 4), - valid: true, - }; - }, +type DefaultProps = { + value: Props["value"]; +}; - UNSAFE_componentWillReceiveProps: function (nextProps) { +export class JsonEditor extends React.Component { + static displayName: "JsonEditor"; + currentValue: string + valid: boolean; + + static defaultProps: DefaultProps = { + value: 0 + }; + + static getInitialState() { + currentValue: JSON.stringify(this.props.value, null, 4), + valid: true, + }; + + + UNSAFE_componentWillReceiveProps(nextProps) { const shouldReplaceContent = - !this.state.valid || - !_.isEqual(nextProps.value, JSON.parse(this.state.currentValue)); + !this.valid || + !_.isEqual(nextProps.value, JSON.parse(this.currentValue)); if (shouldReplaceContent) { this.setState(this.getInitialState()); } - }, + }; - handleKeyDown: function (e) { + handleKeyDown(e) { // This handler allows the tab character to be entered by pressing // tab, instead of jumping to the next (non-existant) field if (e.key === "Tab") { @@ -39,9 +51,9 @@ const JsonEditor: any = createReactClass({ e.preventDefault(); this.handleChange(e); } - }, + }; - handleChange: function (e) { + handleChange(e) { const nextString = e.target.value; try { let json = JSON.parse(nextString); @@ -68,11 +80,11 @@ const JsonEditor: any = createReactClass({ valid: false, }); } - }, + }; // You can type whatever you want as you're typing, but if it's not valid // when you blur, it will revert to the last valid value. - handleBlur: function (e) { + handleBlur(e) { const nextString = e.target.value; try { let json = JSON.parse(nextString); @@ -99,22 +111,22 @@ const JsonEditor: any = createReactClass({ valid: true, }); } - }, + }; - render: function () { + render() { const classes = - "perseus-json-editor " + (this.state.valid ? "valid" : "invalid"); + "perseus-json-editor " + (this.valid ? "valid" : "invalid"); return (