Merge pull request #489 from mz8i/feature/generate-prop-types

Auto-generate propTypes
This commit is contained in:
mz8i 2019-11-06 20:01:05 +00:00 committed by GitHub
commit 6a42961eaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 249 additions and 337 deletions

13
app/.babelrc Normal file
View File

@ -0,0 +1,13 @@
{
"plugins": [
"@babel/plugin-syntax-jsx",
"@babel/plugin-syntax-typescript",
[
"babel-plugin-typescript-to-proptypes",
{
"implicitChildren": true,
"typeCheck": true
}
]
]
}

28
app/package-lock.json generated
View File

@ -425,6 +425,15 @@
"@babel/helper-plugin-utils": "^7.0.0" "@babel/helper-plugin-utils": "^7.0.0"
} }
}, },
"@babel/plugin-syntax-typescript": {
"version": "7.3.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.3.3.tgz",
"integrity": "sha512-dGwbSMA1YhVS8+31CnPR7LB4pcbrzcV99wQzby4uAfrkZPYZlQ7ImwdpzLqi6Z6IL02b8IAL379CaMwo0x5Lag==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.0.0"
}
},
"@babel/plugin-transform-arrow-functions": { "@babel/plugin-transform-arrow-functions": {
"version": "7.2.0", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz",
@ -2324,6 +2333,17 @@
"integrity": "sha512-f49NsaohQ1ByY20nUrpc30QFdbeT4ntV4PAL2vSZe6uCB5nqAcqXS/qzU+aI6ZfYhWASx5eIsTFvFrs1B2ffGg==", "integrity": "sha512-f49NsaohQ1ByY20nUrpc30QFdbeT4ntV4PAL2vSZe6uCB5nqAcqXS/qzU+aI6ZfYhWASx5eIsTFvFrs1B2ffGg==",
"dev": true "dev": true
}, },
"babel-plugin-typescript-to-proptypes": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/babel-plugin-typescript-to-proptypes/-/babel-plugin-typescript-to-proptypes-0.17.1.tgz",
"integrity": "sha512-yREUfvDlmn6QjM0QbywXUkXBQMD/iFfLVTl+jig4X7ZLUg9lq8ZLuex8HIM2SQ4X3vcjGnWPFowodlMcXhwxdQ==",
"dev": true,
"requires": {
"@babel/helper-module-imports": "^7.0.0",
"@babel/helper-plugin-utils": "^7.0.0",
"@babel/plugin-syntax-typescript": "^7.2.0"
}
},
"babel-preset-jest": { "babel-preset-jest": {
"version": "23.2.0", "version": "23.2.0",
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz",
@ -9509,7 +9529,7 @@
}, },
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
}, },
"minipass": { "minipass": {
@ -13639,7 +13659,7 @@
"dependencies": { "dependencies": {
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
} }
} }
@ -17045,7 +17065,7 @@
"dependencies": { "dependencies": {
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true "dev": true
} }
@ -17886,7 +17906,7 @@
}, },
"wrap-ansi": { "wrap-ansi": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"dev": true, "dev": true,
"requires": { "requires": {

View File

@ -37,6 +37,8 @@
"sharp": "^0.22.1" "sharp": "^0.22.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-syntax-jsx": "^7.2.0",
"@babel/plugin-syntax-typescript": "^7.3.3",
"@types/express": "^4.17.0", "@types/express": "^4.17.0",
"@types/express-session": "^1.15.13", "@types/express-session": "^1.15.13",
"@types/jest": "^24.0.17", "@types/jest": "^24.0.17",
@ -51,6 +53,7 @@
"@types/sharp": "^0.22.2", "@types/sharp": "^0.22.2",
"@types/webpack-env": "^1.14.0", "@types/webpack-env": "^1.14.0",
"babel-eslint": "^10.0.2", "babel-eslint": "^10.0.2",
"babel-plugin-typescript-to-proptypes": "^0.17.1",
"eslint": "^5.16.0", "eslint": "^5.16.0",
"eslint-plugin-jest": "^22.15.0", "eslint-plugin-jest": "^22.15.0",
"eslint-plugin-react": "^7.14.3", "eslint-plugin-react": "^7.14.3",

View File

@ -9,6 +9,17 @@ module.exports = {
}) })
config.module.rules = rules; config.module.rules = rules;
// find module rule that runs ts-loader for TS(X) files
const tsRule = config.module.rules.find(r =>
new RegExp(r.test).test('test.tsx') && Array.isArray(r.use) && r.use.some(u => u.loader.includes('ts-loader')));
// run babel-loader before ts-loader to generate propTypes
tsRule.use.push({
loader: 'babel-loader',
options: {
babelrc: true
}
})
return config; return config;
}, },
}; };

View File

@ -1,6 +1,5 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { Route, Switch, Link } from 'react-router-dom'; import { Route, Switch, Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import '../../node_modules/bootstrap/dist/css/bootstrap.min.css'; import '../../node_modules/bootstrap/dist/css/bootstrap.min.css';
import './app.css'; import './app.css';
@ -22,15 +21,21 @@ import ContactPage from './pages/contact';
import DataAccuracyPage from './pages/data-accuracy'; import DataAccuracyPage from './pages/data-accuracy';
import OrdnanceSurveyLicencePage from './pages/ordnance-survey-licence'; import OrdnanceSurveyLicencePage from './pages/ordnance-survey-licence';
import OrdnanceSurveyUprnPage from './pages/ordnance-survey-uprn'; import OrdnanceSurveyUprnPage from './pages/ordnance-survey-uprn';
import { Building } from './models/building';
import { User } from './models/user';
interface AppProps { interface AppProps {
user?: any; user?: User;
building?: any; building?: Building;
building_like?: boolean; building_like?: boolean;
revisionId: number; revisionId: number;
} }
interface AppState {
user?: User;
}
/** /**
* App component * App component
* *
@ -43,13 +48,7 @@ interface AppProps {
* map or other pages are rendered, based on the URL. Use a react-router-dom <Link /> in * map or other pages are rendered, based on the URL. Use a react-router-dom <Link /> in
* child components to navigate without a full page reload. * child components to navigate without a full page reload.
*/ */
class App extends React.Component<AppProps, any> { // TODO: add proper types class App extends React.Component<AppProps, AppState> {
static propTypes = { // TODO: generate propTypes from TS
user: PropTypes.object,
building: PropTypes.object,
building_like: PropTypes.bool
};
static mapAppPaths = ['/', '/:mode(view|edit|multi-edit)/:category/:building(\\d+)?/(history)?']; static mapAppPaths = ['/', '/:mode(view|edit|multi-edit)/:category/:building(\\d+)?/(history)?'];
constructor(props: Readonly<AppProps>) { constructor(props: Readonly<AppProps>) {

View File

@ -1,5 +1,4 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import InfoBox from '../components/info-box'; import InfoBox from '../components/info-box';
@ -18,8 +17,4 @@ const BuildingNotFound: React.FunctionComponent<BuildingNotFoundProps> = (props)
</Fragment> </Fragment>
); );
BuildingNotFound.propTypes = {
mode: PropTypes.string
}
export default BuildingNotFound; export default BuildingNotFound;

View File

@ -20,9 +20,9 @@ import { Building } from '../models/building';
interface BuildingViewProps { interface BuildingViewProps {
cat: string; cat: string;
mode: 'view' | 'edit'; mode: 'view' | 'edit';
building: Building; building?: Building;
building_like: boolean; building_like?: boolean;
user: any; user?: any;
selectBuilding: (building: Building) => void selectBuilding: (building: Building) => void
} }

View File

@ -1,10 +1,14 @@
import React from 'react'; import React from 'react';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
import './categories.css' import './categories.css'
const Categories = (props) => ( interface CategoriesProps {
mode: 'view' | 'edit' | 'multi-edit';
building_id?: number;
}
const Categories: React.FC<CategoriesProps> = (props) => (
<ol className="data-category-list"> <ol className="data-category-list">
<Category <Category
title="Location" title="Location"
@ -117,12 +121,17 @@ const Categories = (props) => (
</ol> </ol>
) )
Categories.propTypes = { interface CategoryProps {
mode: PropTypes.string, mode: 'view' | 'edit' | 'multi-edit';
building_id: PropTypes.number building_id?: number;
slug: string;
title: string;
desc: string;
help: string;
inactive: boolean;
} }
const Category = (props) => { const Category: React.FC<CategoryProps> = (props) => {
let categoryLink = `/${props.mode}/${props.slug}`; let categoryLink = `/${props.mode}/${props.slug}`;
if (props.building_id != undefined) categoryLink += `/${props.building_id}`; if (props.building_id != undefined) categoryLink += `/${props.building_id}`;
@ -145,14 +154,4 @@ const Category = (props) => {
); );
} }
Category.propTypes = {
title: PropTypes.string,
desc: PropTypes.string,
slug: PropTypes.string,
help: PropTypes.string,
inactive: PropTypes.bool,
mode: PropTypes.string,
building_id: PropTypes.number
}
export default Categories; export default Categories;

View File

@ -1,7 +1,7 @@
import React, { Fragment } from 'react'; import React from 'react';
import { Link, NavLink } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { BackIcon, EditIcon, ViewIcon }from '../components/icons'; import { BackIcon }from '../components/icons';
interface ContainerHeaderProps { interface ContainerHeaderProps {
cat?: string; cat?: string;

View File

@ -1,5 +1,4 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { DataTitleCopyable } from './data-title'; import { DataTitleCopyable } from './data-title';
import { BaseDataEntryProps } from './data-entry'; import { BaseDataEntryProps } from './data-entry';
@ -8,7 +7,6 @@ interface CheckboxDataEntryProps extends BaseDataEntryProps {
value: boolean; value: boolean;
} }
const CheckboxDataEntry: React.FunctionComponent<CheckboxDataEntryProps> = (props) => { const CheckboxDataEntry: React.FunctionComponent<CheckboxDataEntryProps> = (props) => {
return ( return (
<Fragment> <Fragment>
@ -37,18 +35,4 @@ const CheckboxDataEntry: React.FunctionComponent<CheckboxDataEntryProps> = (prop
); );
} }
CheckboxDataEntry.propTypes = {
title: PropTypes.string,
slug: PropTypes.string,
tooltip: PropTypes.string,
disabled: PropTypes.bool,
value: PropTypes.any,
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
}
export default CheckboxDataEntry; export default CheckboxDataEntry;

View File

@ -1,20 +1,20 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { DataTitleCopyable } from './data-title'; import { DataTitleCopyable } from './data-title';
import { CopyProps } from '../data-containers/category-view-props';
interface BaseDataEntryProps { interface BaseDataEntryProps {
slug: string; slug: string;
title: string; title: string;
tooltip?: string; tooltip?: string;
disabled?: boolean; disabled?: boolean;
copy?: any; // CopyProps clashes with propTypes copy?: CopyProps; // CopyProps clashes with propTypes
mode?: 'view' | 'edit' | 'multi-edit'; mode?: 'view' | 'edit' | 'multi-edit';
onChange?: (key: string, value: any) => void; onChange?: (key: string, value: any) => void;
} }
interface DataEntryProps extends BaseDataEntryProps { interface DataEntryProps extends BaseDataEntryProps {
value: string; value?: string;
maxLength?: number; maxLength?: number;
placeholder?: string; placeholder?: string;
valueTransform?: (string) => string valueTransform?: (string) => string
@ -49,22 +49,6 @@ const DataEntry: React.FunctionComponent<DataEntryProps> = (props) => {
); );
} }
DataEntry.propTypes = {
title: PropTypes.string,
slug: PropTypes.string,
tooltip: PropTypes.string,
disabled: PropTypes.bool,
value: PropTypes.any,
placeholder: PropTypes.string,
maxLength: PropTypes.number,
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
}
export default DataEntry; export default DataEntry;
export { export {
BaseDataEntryProps BaseDataEntryProps

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import Tooltip from '../../components/tooltip'; import Tooltip from '../../components/tooltip';
import { CopyProps } from '../data-containers/category-view-props';
interface DataTitleProps { interface DataTitleProps {
@ -18,18 +18,13 @@ const DataTitle: React.FunctionComponent<DataTitleProps> = (props) => {
) )
} }
DataTitle.propTypes = {
title: PropTypes.string,
tooltip: PropTypes.string
}
interface DataTitleCopyableProps { interface DataTitleCopyableProps {
title: string; title: string;
tooltip: string; tooltip?: string;
slug: string; slug: string;
disabled?: boolean; disabled?: boolean;
copy?: any; // TODO: type should be CopyProps, but that clashes with propTypes in some obscure way copy?: CopyProps;
} }
const DataTitleCopyable: React.FunctionComponent<DataTitleCopyableProps> = (props) => { const DataTitleCopyable: React.FunctionComponent<DataTitleCopyableProps> = (props) => {
@ -55,18 +50,5 @@ const DataTitleCopyable: React.FunctionComponent<DataTitleCopyableProps> = (prop
); );
} }
DataTitleCopyable.propTypes = {
title: PropTypes.string,
tooltip: PropTypes.string,
slug: PropTypes.string,
disabled: PropTypes.bool,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func,
toggleCopying: PropTypes.func
})
}
export default DataTitle; export default DataTitle;
export { DataTitleCopyable } export { DataTitleCopyable }

View File

@ -1,6 +1,5 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
import Tooltip from '../../components/tooltip'; import Tooltip from '../../components/tooltip';
@ -49,11 +48,4 @@ const LikeDataEntry: React.FunctionComponent<LikeDataEntryProps> = (props) => {
); );
} }
LikeDataEntry.propTypes = {
// mode: PropTypes.string,
userLike: PropTypes.bool,
totalLikes: PropTypes.number,
onLike: PropTypes.func
};
export default LikeDataEntry; export default LikeDataEntry;

View File

@ -1,23 +1,16 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { sanitiseURL } from '../../helpers'; import { sanitiseURL } from '../../helpers';
import { DataTitleCopyable } from './data-title'; import { DataTitleCopyable } from './data-title';
import { BaseDataEntryProps } from './data-entry';
class MultiDataEntry extends Component<any, any> { // TODO: add proper types interface MultiDataEntryProps extends BaseDataEntryProps {
static propTypes = { // TODO: generate propTypes from TS value: string[];
slug: PropTypes.string, placeholder: string;
title: PropTypes.string, }
tooltip: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.string), class MultiDataEntry extends Component<MultiDataEntryProps> {
placeholder: PropTypes.string,
disabled: PropTypes.bool,
onChange: PropTypes.func,
copy: PropTypes.bool,
toggleCopyAttribute: PropTypes.func,
copying: PropTypes.bool
};
constructor(props) { constructor(props) {
super(props); super(props);

View File

@ -1,5 +1,4 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { DataTitleCopyable } from './data-title'; import { DataTitleCopyable } from './data-title';
import { BaseDataEntryProps } from './data-entry'; import { BaseDataEntryProps } from './data-entry';
@ -45,22 +44,4 @@ const NumericDataEntry: React.FunctionComponent<NumericDataEntryProps> = (props)
); );
} }
NumericDataEntry.propTypes = {
title: PropTypes.string,
slug: PropTypes.string,
tooltip: PropTypes.string,
disabled: PropTypes.bool,
value: PropTypes.any,
placeholder: PropTypes.string,
max: PropTypes.number,
min: PropTypes.number,
step: PropTypes.number,
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
}
export default NumericDataEntry; export default NumericDataEntry;

View File

@ -1,5 +1,4 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { DataTitleCopyable } from './data-title'; import { DataTitleCopyable } from './data-title';
import { BaseDataEntryProps } from './data-entry'; import { BaseDataEntryProps } from './data-entry';
@ -44,20 +43,4 @@ const SelectDataEntry: React.FunctionComponent<SelectDataEntryProps> = (props) =
); );
} }
SelectDataEntry.propTypes = {
title: PropTypes.string,
slug: PropTypes.string,
tooltip: PropTypes.string,
disabled: PropTypes.bool,
value: PropTypes.any,
placeholder: PropTypes.string,
options: PropTypes.arrayOf(PropTypes.string),
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
}
export default SelectDataEntry; export default SelectDataEntry;

View File

@ -1,5 +1,4 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { DataTitleCopyable } from './data-title'; import { DataTitleCopyable } from './data-title';
import { BaseDataEntryProps } from './data-entry'; import { BaseDataEntryProps } from './data-entry';
@ -42,20 +41,4 @@ const TextboxDataEntry: React.FunctionComponent<TextboxDataEntryProps> = (props)
); );
} }
TextboxDataEntry.propTypes = {
title: PropTypes.string,
slug: PropTypes.string,
tooltip: PropTypes.string,
disabled: PropTypes.bool,
value: PropTypes.any,
placeholder: PropTypes.string,
maxLength: PropTypes.number,
onChange: PropTypes.func,
copy: PropTypes.shape({
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
}
export default TextboxDataEntry; export default TextboxDataEntry;

View File

@ -1,10 +1,18 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import Tooltip from '../../components/tooltip';
import DataTitle from './data-title'; import DataTitle from './data-title';
const UPRNsDataEntry = (props) => {
interface UPRNsDataEntryProps {
title: string;
tooltip: string;
value: {
uprn: string;
parent_uprn?: string;
}[];
}
const UPRNsDataEntry: React.FC<UPRNsDataEntryProps> = (props) => {
const uprns = props.value || []; const uprns = props.value || [];
const noParent = uprns.filter(uprn => uprn.parent_uprn == null); const noParent = uprns.filter(uprn => uprn.parent_uprn == null);
const withParent = uprns.filter(uprn => uprn.parent_uprn != null); const withParent = uprns.filter(uprn => uprn.parent_uprn != null);
@ -46,15 +54,6 @@ const UPRNsDataEntry = (props) => {
</dd> </dd>
</Fragment> </Fragment>
) )
} };
UPRNsDataEntry.propTypes = {
title: PropTypes.string,
tooltip: PropTypes.string,
value: PropTypes.arrayOf(PropTypes.shape({
uprn: PropTypes.string.isRequired,
parent_uprn: PropTypes.string
}))
}
export default UPRNsDataEntry; export default UPRNsDataEntry;

View File

@ -1,23 +1,19 @@
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import NumericDataEntry from './numeric-data-entry'; import NumericDataEntry from './numeric-data-entry';
import { dataFields } from '../../data_fields'; import { dataFields } from '../../data_fields';
import { CopyProps } from '../data-containers/category-view-props';
class YearDataEntry extends Component<any, any> { // TODO: add proper types interface YearDataEntryProps {
static propTypes = { // TODO: generate propTypes from TS year: number;
year: PropTypes.number, upper: number;
upper: PropTypes.number, lower: number;
lower: PropTypes.number, copy?: CopyProps;
mode: PropTypes.string, mode?: 'view' | 'edit' | 'multi-edit';
onChange: PropTypes.func, onChange?: (key: string, value: any) => void;
copy: PropTypes.shape({ }
copying: PropTypes.bool,
copyingKey: PropTypes.func,
toggleCopyAttribute: PropTypes.func
})
};
class YearDataEntry extends Component<YearDataEntryProps, any> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {

View File

@ -1,5 +1,4 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Redirect, NavLink } from 'react-router-dom'; import { Redirect, NavLink } from 'react-router-dom';
import ContainerHeader from './container-header'; import ContainerHeader from './container-header';
@ -19,10 +18,10 @@ interface DataContainerProps {
help: string; help: string;
inactive?: boolean; inactive?: boolean;
user: User; user?: User;
mode: 'view' | 'edit'; mode: 'view' | 'edit';
building: Building; building?: Building;
building_like: boolean; building_like?: boolean;
selectBuilding: (building: Building) => void selectBuilding: (building: Building) => void
} }
@ -45,15 +44,6 @@ interface DataContainerState {
*/ */
const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>) => { const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>) => {
return class DataContainer extends React.Component<DataContainerProps, DataContainerState> { return class DataContainer extends React.Component<DataContainerProps, DataContainerState> {
static propTypes = { // TODO: generate propTypes from TS
title: PropTypes.string,
slug: PropTypes.string,
intro: PropTypes.string,
help: PropTypes.string,
inactive: PropTypes.bool,
children: PropTypes.node
};
constructor(props) { constructor(props) {
super(props); super(props);

View File

@ -1,16 +1,23 @@
import React, { Fragment } from 'react'; import React from 'react';
import { Link, Redirect } from 'react-router-dom'; import { Link, Redirect, RouteComponentProps } from 'react-router-dom';
import { parse } from 'query-string'; import { parse } from 'query-string';
import PropTypes from 'prop-types';
import Sidebar from './sidebar'; import Sidebar from './sidebar';
import InfoBox from '../components/info-box'; import InfoBox from '../components/info-box';
import { BackIcon }from '../components/icons'; import { BackIcon }from '../components/icons';
import DataEntry from './data-components/data-entry'; import DataEntry from './data-components/data-entry';
import { dataFields } from '../data_fields'; import { dataFields } from '../data_fields';
import { User } from '../models/user';
interface MultiEditRouteParams {
cat: string;
}
const MultiEdit = (props) => { interface MultiEditProps extends RouteComponentProps<MultiEditRouteParams> {
user?: User;
}
const MultiEdit: React.FC<MultiEditProps> = (props) => {
if (!props.user){ if (!props.user){
return <Redirect to="/sign-up.html" /> return <Redirect to="/sign-up.html" />
} }
@ -85,10 +92,4 @@ const MultiEdit = (props) => {
); );
} }
MultiEdit.propTypes = {
user: PropTypes.object,
match: PropTypes.object,
location: PropTypes.object
}
export default MultiEdit; export default MultiEdit;

View File

@ -1,16 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import './sidebar.css'; import './sidebar.css';
const Sidebar = (props) => ( const Sidebar: React.FC<{}> = (props) => (
<div id="sidebar" className="info-container"> <div id="sidebar" className="info-container">
{ props.children } { props.children }
</div> </div>
); );
Sidebar.propTypes = {
children: PropTypes.node
}
export default Sidebar; export default Sidebar;

View File

@ -1,7 +1,10 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
function ErrorBox(props){ interface ErrorBoxProps {
msg: string;
}
const ErrorBox: React.FC<ErrorBoxProps> = (props) => {
if (props.msg) { if (props.msg) {
console.error(props.msg); console.error(props.msg);
} }
@ -12,7 +15,7 @@ function ErrorBox(props){
( (
<div className="alert alert-danger" role="alert"> <div className="alert alert-danger" role="alert">
{ {
(typeof props.msg === 'string' || props.msg instanceof String)? typeof props.msg === 'string' ?
props.msg props.msg
: 'Unexpected error' : 'Unexpected error'
} }
@ -21,10 +24,6 @@ function ErrorBox(props){
} }
</Fragment> </Fragment>
); );
} };
ErrorBox.propTypes = {
msg: PropTypes.string
}
export default ErrorBox; export default ErrorBox;

View File

@ -1,14 +1,17 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
const InfoBox = (props) => ( interface InfoBoxProps {
msg: string;
}
const InfoBox: React.FC<InfoBoxProps> = (props) => (
<Fragment> <Fragment>
{ {
(props.msg || props.children)? (props.msg || props.children)?
( (
<div className="alert alert-info" role="alert"> <div className="alert alert-info" role="alert">
{ {
(typeof props.msg === 'string' || props.msg instanceof String)? typeof props.msg === 'string' ?
props.msg props.msg
: 'Enjoy the colouring! Usual service should resume shortly.' : 'Enjoy the colouring! Usual service should resume shortly.'
} }
@ -21,9 +24,4 @@ const InfoBox = (props) => (
</Fragment> </Fragment>
); );
InfoBox.propTypes = {
msg: PropTypes.string,
children: PropTypes.node
}
export default InfoBox; export default InfoBox;

View File

@ -1,14 +1,17 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './tooltip.css'; import './tooltip.css';
import { InfoIcon } from './icons'; import { InfoIcon } from './icons';
class Tooltip extends Component<any, any> { // TODO: add proper types interface TooltipProps {
static propTypes = { // TODO: generate propTypes from TS text: string;
text: PropTypes.string }
};
interface TooltipState {
active: boolean;
}
class Tooltip extends Component<TooltipProps, TooltipState> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {

View File

@ -1,13 +1,14 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
import { Logo } from './components/logo'; import { Logo } from './components/logo';
import { User } from './models/user';
import './header.css'; import './header.css';
interface HeaderProps { interface HeaderProps {
user: any; user: User;
animateLogo: boolean; animateLogo: boolean;
} }
@ -18,14 +19,7 @@ interface HeaderState {
/** /**
* Render the main header using a responsive design * Render the main header using a responsive design
*/ */
class Header extends React.Component<HeaderProps, HeaderState> { // TODO: add proper types class Header extends React.Component<HeaderProps, HeaderState> {
static propTypes = { // TODO: generate propTypes from TS
user: PropTypes.shape({
username: PropTypes.string
}),
animateLogo: PropTypes.bool
};
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {collapseMenu: true}; this.state = {collapseMenu: true};

View File

@ -1,6 +1,5 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { Switch, Route, RouteComponentProps, Redirect } from 'react-router-dom'; import { Switch, Route, RouteComponentProps, Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import Welcome from './pages/welcome'; import Welcome from './pages/welcome';
import Sidebar from './building/sidebar'; import Sidebar from './building/sidebar';
@ -19,10 +18,10 @@ interface MapAppRouteParams {
} }
interface MapAppProps extends RouteComponentProps<MapAppRouteParams> { interface MapAppProps extends RouteComponentProps<MapAppRouteParams> {
building: Building; building?: Building;
building_like: boolean; building_like?: boolean;
user: any; user?: any;
revisionId: number; revisionId?: number;
} }
interface MapAppState { interface MapAppState {
@ -33,13 +32,6 @@ interface MapAppState {
} }
class MapApp extends React.Component<MapAppProps, MapAppState> { class MapApp extends React.Component<MapAppProps, MapAppState> {
static propTypes = {
category: PropTypes.string,
revision_id: PropTypes.number,
building: PropTypes.object,
building_like: PropTypes.bool,
user: PropTypes.object
};
constructor(props: Readonly<MapAppProps>) { constructor(props: Readonly<MapAppProps>) {
super(props); super(props);
@ -235,7 +227,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
</Route> </Route>
<Route exact path="/:mode/categories/:building?"> <Route exact path="/:mode/categories/:building?">
<Sidebar> <Sidebar>
<Categories mode={mode} building_id={building_id} /> <Categories mode={mode || 'view'} building_id={building_id} />
</Sidebar> </Sidebar>
</Route> </Route>
<Route exact path="/multi-edit/:cat" render={(props) => ( <Route exact path="/multi-edit/:cat" render={(props) => (

View File

@ -1,5 +1,4 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import './legend.css'; import './legend.css';
import { Logo } from '../components/logo'; import { Logo } from '../components/logo';
@ -114,13 +113,15 @@ const LEGEND_CONFIG = {
}; };
class Legend extends React.Component<any, any> { // TODO: add proper types interface LegendProps {
static propTypes = { // TODO: generate propTypes from TS slug: string;
slug: PropTypes.string, }
color: PropTypes.string,
text: PropTypes.string
};
interface LegendState {
collapseList: boolean;
}
class Legend extends React.Component<LegendProps, LegendState> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {collapseList: false}; this.state = {collapseList: false};

View File

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react'; import React, { Component, Fragment } from 'react';
import { Map, TileLayer, ZoomControl, AttributionControl, GeoJSON } from 'react-leaflet-universal'; import { Map, TileLayer, ZoomControl, AttributionControl, GeoJSON } from 'react-leaflet-universal';
import { GeoJsonObject } from 'geojson'; import { GeoJsonObject } from 'geojson';
@ -15,12 +14,12 @@ import { Building } from '../models/building';
const OS_API_KEY = 'NVUxtY5r8eA6eIfwrPTAGKrAAsoeI9E9'; const OS_API_KEY = 'NVUxtY5r8eA6eIfwrPTAGKrAAsoeI9E9';
interface ColouringMapProps { interface ColouringMapProps {
building: Building; building?: Building;
mode: 'basic' | 'view' | 'edit' | 'multi-edit'; mode: 'basic' | 'view' | 'edit' | 'multi-edit';
category: string; category: string;
revision_id: number; revision_id: number;
selectBuilding: any; selectBuilding: (building: Building) => void;
colourBuilding: any; colourBuilding: (building: Building) => void;
} }
interface ColouringMapState { interface ColouringMapState {
@ -34,15 +33,6 @@ interface ColouringMapState {
* Map area * Map area
*/ */
class ColouringMap extends Component<ColouringMapProps, ColouringMapState> { class ColouringMap extends Component<ColouringMapProps, ColouringMapState> {
static propTypes = { // TODO: generate propTypes from TS
building: PropTypes.object,
mode: PropTypes.string,
category: PropTypes.string,
revision_id: PropTypes.number,
selectBuilding: PropTypes.func,
colourBuilding: PropTypes.func
};
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {

View File

@ -1,16 +1,35 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './search-box.css'; import './search-box.css';
import { SearchIcon } from '../components/icons'; import { SearchIcon } from '../components/icons';
import { Point } from 'geojson';
interface SearchResult {
type: string;
attributes: {
label: string;
zoom: number;
};
geometry: Point
}
interface SearchBoxProps {
onLocate: (lat: number, lng: number, zoom: number) => void
}
interface SearchBoxState {
q: string;
results: SearchResult[];
fetching: boolean;
collapsedSearch: boolean;
smallScreen: boolean;
}
/** /**
* Search for location * Search for location
*/ */
class SearchBox extends Component<any, any> { // TODO: add proper types class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
static propTypes = { // TODO: generate propTypes from TS
onLocate: PropTypes.func
};
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {

View File

@ -1,9 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import './theme-switcher.css'; import './theme-switcher.css';
const ThemeSwitcher = (props) => ( interface ThemeSwitcherProps {
currentTheme: string;
onSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
}
const ThemeSwitcher: React.FC<ThemeSwitcherProps> = (props) => (
<form className={`theme-switcher ${props.currentTheme}`} onSubmit={props.onSubmit}> <form className={`theme-switcher ${props.currentTheme}`} onSubmit={props.onSubmit}>
<button className="btn btn-outline btn-outline-dark" <button className="btn btn-outline btn-outline-dark"
type="submit"> type="submit">
@ -12,9 +16,4 @@ const ThemeSwitcher = (props) => (
</form> </form>
); );
ThemeSwitcher.propTypes = {
currentTheme: PropTypes.string,
onSubmit: PropTypes.func.isRequired
}
export default ThemeSwitcher; export default ThemeSwitcher;

View File

@ -1,5 +1,9 @@
interface User { interface User {
username: string; username?: string;
email?: string;
registered?: Date;
api_key?: string;
error?: string;
// TODO: add other fields as needed // TODO: add other fields as needed
} }

View File

@ -1,17 +1,17 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Redirect, Link } from 'react-router-dom'; import { Redirect, Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import ErrorBox from '../components/error-box'; import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box'; import InfoBox from '../components/info-box';
import SupporterLogos from '../components/supporter-logos'; import SupporterLogos from '../components/supporter-logos';
import { User } from '../models/user';
class Login extends Component<any, any> { // TODO: add proper types interface LoginProps {
static propTypes = { // TODO: generate propTypes from TS user: User,
login: PropTypes.func, login: (user: User) => void;
user: PropTypes.object }
};
class Login extends Component<LoginProps, any> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {

View File

@ -1,23 +1,23 @@
import React, { Component, FormEvent } from 'react'; import React, { Component, FormEvent } from 'react';
import { Link, Redirect } from 'react-router-dom'; import { Link, Redirect } from 'react-router-dom';
import PropTypes from 'prop-types';
import ConfirmationModal from '../components/confirmation-modal'; import ConfirmationModal from '../components/confirmation-modal';
import ErrorBox from '../components/error-box'; import ErrorBox from '../components/error-box';
import { User } from '../models/user';
class MyAccountPage extends Component<any, any> { // TODO: add proper types interface MyAccountPageProps {
static propTypes = { // TODO: generate propTypes from TS user: User;
user: PropTypes.shape({ updateUser: (user: User) => void;
username: PropTypes.string, logout: () => void;
email: PropTypes.string, }
registered: PropTypes.instanceOf(Date), // TODO: check if fix correct
api_key: PropTypes.string,
error: PropTypes.object
}),
updateUser: PropTypes.func,
logout: PropTypes.func
};
interface MyAccountPageState {
showDeleteConfirm: boolean;
error: string;
}
class MyAccountPage extends Component<MyAccountPageProps, MyAccountPageState> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {

View File

@ -1,4 +1,4 @@
import React, { ChangeEvent, FormEvent } from 'react'; import React, { FormEvent } from 'react';
import { RouteComponentProps, Redirect } from 'react-router'; import { RouteComponentProps, Redirect } from 'react-router';
import ErrorBox from '../components/error-box'; import ErrorBox from '../components/error-box';

View File

@ -1,17 +1,27 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Redirect, Link } from 'react-router-dom'; import { Redirect, Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import ErrorBox from '../components/error-box'; import ErrorBox from '../components/error-box';
import InfoBox from '../components/info-box'; import InfoBox from '../components/info-box';
import SupporterLogos from '../components/supporter-logos'; import SupporterLogos from '../components/supporter-logos';
import { User } from '../models/user';
class SignUp extends Component<any, any> { // TODO: add proper types interface SignUpProps {
static propTypes = { // TODO: generate propTypes from TS login: (user: User) => void;
login: PropTypes.func.isRequired, user: User;
user: PropTypes.object }
};
interface SignUpState {
username: string;
email: string;
confirm_email: string;
show_password: boolean;
password: string;
confirm_conditions: boolean;
error: string;
}
class SignUp extends Component<SignUpProps, SignUpState> {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
@ -19,7 +29,7 @@ class SignUp extends Component<any, any> { // TODO: add proper types
email: '', email: '',
confirm_email: '', confirm_email: '',
password: '', password: '',
show_password: '', show_password: false,
confirm_conditions: false, confirm_conditions: false,
error: undefined error: undefined
}; };
@ -31,11 +41,11 @@ class SignUp extends Component<any, any> { // TODO: add proper types
handleChange(event) { handleChange(event) {
const target = event.target; const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value; const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name; const name: keyof SignUpState = target.name;
this.setState({ this.setState({
[name]: value [name]: value
}); } as Pick<SignUpState, keyof SignUpState>);
} }
handleSubmit(event) { handleSubmit(event) {