Merge pull request #489 from mz8i/feature/generate-prop-types
Auto-generate propTypes
This commit is contained in:
commit
6a42961eaf
13
app/.babelrc
Normal file
13
app/.babelrc
Normal 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
28
app/package-lock.json
generated
@ -425,6 +425,15 @@
|
||||
"@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": {
|
||||
"version": "7.2.0",
|
||||
"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==",
|
||||
"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": {
|
||||
"version": "23.2.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz",
|
||||
@ -9509,7 +9529,7 @@
|
||||
},
|
||||
"minimist": {
|
||||
"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="
|
||||
},
|
||||
"minipass": {
|
||||
@ -13639,7 +13659,7 @@
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"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="
|
||||
}
|
||||
}
|
||||
@ -17045,7 +17065,7 @@
|
||||
"dependencies": {
|
||||
"minimist": {
|
||||
"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=",
|
||||
"dev": true
|
||||
}
|
||||
@ -17886,7 +17906,7 @@
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"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=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -37,6 +37,8 @@
|
||||
"sharp": "^0.22.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-syntax-jsx": "^7.2.0",
|
||||
"@babel/plugin-syntax-typescript": "^7.3.3",
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/express-session": "^1.15.13",
|
||||
"@types/jest": "^24.0.17",
|
||||
@ -51,6 +53,7 @@
|
||||
"@types/sharp": "^0.22.2",
|
||||
"@types/webpack-env": "^1.14.0",
|
||||
"babel-eslint": "^10.0.2",
|
||||
"babel-plugin-typescript-to-proptypes": "^0.17.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-jest": "^22.15.0",
|
||||
"eslint-plugin-react": "^7.14.3",
|
||||
|
@ -9,6 +9,17 @@ module.exports = {
|
||||
})
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Route, Switch, Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import '../../node_modules/bootstrap/dist/css/bootstrap.min.css';
|
||||
import './app.css';
|
||||
@ -22,15 +21,21 @@ import ContactPage from './pages/contact';
|
||||
import DataAccuracyPage from './pages/data-accuracy';
|
||||
import OrdnanceSurveyLicencePage from './pages/ordnance-survey-licence';
|
||||
import OrdnanceSurveyUprnPage from './pages/ordnance-survey-uprn';
|
||||
import { Building } from './models/building';
|
||||
import { User } from './models/user';
|
||||
|
||||
|
||||
interface AppProps {
|
||||
user?: any;
|
||||
building?: any;
|
||||
user?: User;
|
||||
building?: Building;
|
||||
building_like?: boolean;
|
||||
revisionId: number;
|
||||
}
|
||||
|
||||
interface AppState {
|
||||
user?: User;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* child components to navigate without a full page reload.
|
||||
*/
|
||||
class App extends React.Component<AppProps, any> { // TODO: add proper types
|
||||
static propTypes = { // TODO: generate propTypes from TS
|
||||
user: PropTypes.object,
|
||||
building: PropTypes.object,
|
||||
building_like: PropTypes.bool
|
||||
};
|
||||
|
||||
class App extends React.Component<AppProps, AppState> {
|
||||
static mapAppPaths = ['/', '/:mode(view|edit|multi-edit)/:category/:building(\\d+)?/(history)?'];
|
||||
|
||||
constructor(props: Readonly<AppProps>) {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import InfoBox from '../components/info-box';
|
||||
@ -18,8 +17,4 @@ const BuildingNotFound: React.FunctionComponent<BuildingNotFoundProps> = (props)
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
BuildingNotFound.propTypes = {
|
||||
mode: PropTypes.string
|
||||
}
|
||||
|
||||
export default BuildingNotFound;
|
||||
|
@ -20,9 +20,9 @@ import { Building } from '../models/building';
|
||||
interface BuildingViewProps {
|
||||
cat: string;
|
||||
mode: 'view' | 'edit';
|
||||
building: Building;
|
||||
building_like: boolean;
|
||||
user: any;
|
||||
building?: Building;
|
||||
building_like?: boolean;
|
||||
user?: any;
|
||||
selectBuilding: (building: Building) => void
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,14 @@
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
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">
|
||||
<Category
|
||||
title="Location"
|
||||
@ -117,12 +121,17 @@ const Categories = (props) => (
|
||||
</ol>
|
||||
)
|
||||
|
||||
Categories.propTypes = {
|
||||
mode: PropTypes.string,
|
||||
building_id: PropTypes.number
|
||||
interface CategoryProps {
|
||||
mode: 'view' | 'edit' | 'multi-edit';
|
||||
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}`;
|
||||
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;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Link, NavLink } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { BackIcon, EditIcon, ViewIcon }from '../components/icons';
|
||||
import { BackIcon }from '../components/icons';
|
||||
|
||||
interface ContainerHeaderProps {
|
||||
cat?: string;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { DataTitleCopyable } from './data-title';
|
||||
import { BaseDataEntryProps } from './data-entry';
|
||||
@ -8,7 +7,6 @@ interface CheckboxDataEntryProps extends BaseDataEntryProps {
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
|
||||
const CheckboxDataEntry: React.FunctionComponent<CheckboxDataEntryProps> = (props) => {
|
||||
return (
|
||||
<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;
|
||||
|
@ -1,20 +1,20 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { DataTitleCopyable } from './data-title';
|
||||
import { CopyProps } from '../data-containers/category-view-props';
|
||||
|
||||
interface BaseDataEntryProps {
|
||||
slug: string;
|
||||
title: string;
|
||||
tooltip?: string;
|
||||
disabled?: boolean;
|
||||
copy?: any; // CopyProps clashes with propTypes
|
||||
copy?: CopyProps; // CopyProps clashes with propTypes
|
||||
mode?: 'view' | 'edit' | 'multi-edit';
|
||||
onChange?: (key: string, value: any) => void;
|
||||
}
|
||||
|
||||
interface DataEntryProps extends BaseDataEntryProps {
|
||||
value: string;
|
||||
value?: string;
|
||||
maxLength?: number;
|
||||
placeholder?: 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 {
|
||||
BaseDataEntryProps
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Tooltip from '../../components/tooltip';
|
||||
import { CopyProps } from '../data-containers/category-view-props';
|
||||
|
||||
|
||||
interface DataTitleProps {
|
||||
@ -18,18 +18,13 @@ const DataTitle: React.FunctionComponent<DataTitleProps> = (props) => {
|
||||
)
|
||||
}
|
||||
|
||||
DataTitle.propTypes = {
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string
|
||||
}
|
||||
|
||||
|
||||
interface DataTitleCopyableProps {
|
||||
title: string;
|
||||
tooltip: string;
|
||||
tooltip?: string;
|
||||
slug: string;
|
||||
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) => {
|
||||
@ -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 { DataTitleCopyable }
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
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;
|
||||
|
@ -1,23 +1,16 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { sanitiseURL } from '../../helpers';
|
||||
import { DataTitleCopyable } from './data-title';
|
||||
import { BaseDataEntryProps } from './data-entry';
|
||||
|
||||
|
||||
class MultiDataEntry extends Component<any, any> { // TODO: add proper types
|
||||
static propTypes = { // TODO: generate propTypes from TS
|
||||
slug: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
tooltip: PropTypes.string,
|
||||
value: PropTypes.arrayOf(PropTypes.string),
|
||||
placeholder: PropTypes.string,
|
||||
disabled: PropTypes.bool,
|
||||
onChange: PropTypes.func,
|
||||
copy: PropTypes.bool,
|
||||
toggleCopyAttribute: PropTypes.func,
|
||||
copying: PropTypes.bool
|
||||
};
|
||||
interface MultiDataEntryProps extends BaseDataEntryProps {
|
||||
value: string[];
|
||||
placeholder: string;
|
||||
}
|
||||
|
||||
class MultiDataEntry extends Component<MultiDataEntryProps> {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { DataTitleCopyable } from './data-title';
|
||||
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;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { DataTitleCopyable } from './data-title';
|
||||
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;
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { DataTitleCopyable } from './data-title';
|
||||
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;
|
||||
|
@ -1,10 +1,18 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Tooltip from '../../components/tooltip';
|
||||
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 noParent = uprns.filter(uprn => uprn.parent_uprn == null);
|
||||
const withParent = uprns.filter(uprn => uprn.parent_uprn != null);
|
||||
@ -46,15 +54,6 @@ const UPRNsDataEntry = (props) => {
|
||||
</dd>
|
||||
</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;
|
||||
|
@ -1,23 +1,19 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import NumericDataEntry from './numeric-data-entry';
|
||||
import { dataFields } from '../../data_fields';
|
||||
import { CopyProps } from '../data-containers/category-view-props';
|
||||
|
||||
class YearDataEntry extends Component<any, any> { // TODO: add proper types
|
||||
static propTypes = { // TODO: generate propTypes from TS
|
||||
year: PropTypes.number,
|
||||
upper: PropTypes.number,
|
||||
lower: PropTypes.number,
|
||||
mode: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
copy: PropTypes.shape({
|
||||
copying: PropTypes.bool,
|
||||
copyingKey: PropTypes.func,
|
||||
toggleCopyAttribute: PropTypes.func
|
||||
})
|
||||
};
|
||||
interface YearDataEntryProps {
|
||||
year: number;
|
||||
upper: number;
|
||||
lower: number;
|
||||
copy?: CopyProps;
|
||||
mode?: 'view' | 'edit' | 'multi-edit';
|
||||
onChange?: (key: string, value: any) => void;
|
||||
}
|
||||
|
||||
class YearDataEntry extends Component<YearDataEntryProps, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Redirect, NavLink } from 'react-router-dom';
|
||||
|
||||
import ContainerHeader from './container-header';
|
||||
@ -19,10 +18,10 @@ interface DataContainerProps {
|
||||
help: string;
|
||||
inactive?: boolean;
|
||||
|
||||
user: User;
|
||||
user?: User;
|
||||
mode: 'view' | 'edit';
|
||||
building: Building;
|
||||
building_like: boolean;
|
||||
building?: Building;
|
||||
building_like?: boolean;
|
||||
selectBuilding: (building: Building) => void
|
||||
}
|
||||
|
||||
@ -45,15 +44,6 @@ interface DataContainerState {
|
||||
*/
|
||||
const withCopyEdit = (WrappedComponent: React.ComponentType<CategoryViewProps>) => {
|
||||
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) {
|
||||
super(props);
|
||||
|
||||
|
@ -1,16 +1,23 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
import { Link, Redirect, RouteComponentProps } from 'react-router-dom';
|
||||
import { parse } from 'query-string';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Sidebar from './sidebar';
|
||||
import InfoBox from '../components/info-box';
|
||||
import { BackIcon }from '../components/icons';
|
||||
import DataEntry from './data-components/data-entry';
|
||||
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){
|
||||
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;
|
||||
|
@ -1,16 +1,11 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './sidebar.css';
|
||||
|
||||
const Sidebar = (props) => (
|
||||
const Sidebar: React.FC<{}> = (props) => (
|
||||
<div id="sidebar" className="info-container">
|
||||
{ props.children }
|
||||
</div>
|
||||
);
|
||||
|
||||
Sidebar.propTypes = {
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
export default Sidebar;
|
||||
|
@ -1,7 +1,10 @@
|
||||
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) {
|
||||
console.error(props.msg);
|
||||
}
|
||||
@ -12,7 +15,7 @@ function ErrorBox(props){
|
||||
(
|
||||
<div className="alert alert-danger" role="alert">
|
||||
{
|
||||
(typeof props.msg === 'string' || props.msg instanceof String)?
|
||||
typeof props.msg === 'string' ?
|
||||
props.msg
|
||||
: 'Unexpected error'
|
||||
}
|
||||
@ -21,10 +24,6 @@ function ErrorBox(props){
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
ErrorBox.propTypes = {
|
||||
msg: PropTypes.string
|
||||
}
|
||||
};
|
||||
|
||||
export default ErrorBox;
|
||||
|
@ -1,14 +1,17 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const InfoBox = (props) => (
|
||||
interface InfoBoxProps {
|
||||
msg: string;
|
||||
}
|
||||
|
||||
const InfoBox: React.FC<InfoBoxProps> = (props) => (
|
||||
<Fragment>
|
||||
{
|
||||
(props.msg || props.children)?
|
||||
(
|
||||
<div className="alert alert-info" role="alert">
|
||||
{
|
||||
(typeof props.msg === 'string' || props.msg instanceof String)?
|
||||
typeof props.msg === 'string' ?
|
||||
props.msg
|
||||
: 'Enjoy the colouring! Usual service should resume shortly.'
|
||||
}
|
||||
@ -21,9 +24,4 @@ const InfoBox = (props) => (
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
InfoBox.propTypes = {
|
||||
msg: PropTypes.string,
|
||||
children: PropTypes.node
|
||||
}
|
||||
|
||||
export default InfoBox;
|
||||
|
@ -1,14 +1,17 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './tooltip.css';
|
||||
import { InfoIcon } from './icons';
|
||||
|
||||
class Tooltip extends Component<any, any> { // TODO: add proper types
|
||||
static propTypes = { // TODO: generate propTypes from TS
|
||||
text: PropTypes.string
|
||||
};
|
||||
interface TooltipProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface TooltipState {
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
class Tooltip extends Component<TooltipProps, TooltipState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Logo } from './components/logo';
|
||||
import { User } from './models/user';
|
||||
|
||||
import './header.css';
|
||||
|
||||
|
||||
interface HeaderProps {
|
||||
user: any;
|
||||
user: User;
|
||||
animateLogo: boolean;
|
||||
}
|
||||
|
||||
@ -18,14 +19,7 @@ interface HeaderState {
|
||||
/**
|
||||
* Render the main header using a responsive design
|
||||
*/
|
||||
class Header extends React.Component<HeaderProps, HeaderState> { // TODO: add proper types
|
||||
static propTypes = { // TODO: generate propTypes from TS
|
||||
user: PropTypes.shape({
|
||||
username: PropTypes.string
|
||||
}),
|
||||
animateLogo: PropTypes.bool
|
||||
};
|
||||
|
||||
class Header extends React.Component<HeaderProps, HeaderState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {collapseMenu: true};
|
||||
|
@ -1,6 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Switch, Route, RouteComponentProps, Redirect } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Welcome from './pages/welcome';
|
||||
import Sidebar from './building/sidebar';
|
||||
@ -19,10 +18,10 @@ interface MapAppRouteParams {
|
||||
}
|
||||
|
||||
interface MapAppProps extends RouteComponentProps<MapAppRouteParams> {
|
||||
building: Building;
|
||||
building_like: boolean;
|
||||
user: any;
|
||||
revisionId: number;
|
||||
building?: Building;
|
||||
building_like?: boolean;
|
||||
user?: any;
|
||||
revisionId?: number;
|
||||
}
|
||||
|
||||
interface MapAppState {
|
||||
@ -33,13 +32,6 @@ interface 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>) {
|
||||
super(props);
|
||||
|
||||
@ -235,7 +227,7 @@ class MapApp extends React.Component<MapAppProps, MapAppState> {
|
||||
</Route>
|
||||
<Route exact path="/:mode/categories/:building?">
|
||||
<Sidebar>
|
||||
<Categories mode={mode} building_id={building_id} />
|
||||
<Categories mode={mode || 'view'} building_id={building_id} />
|
||||
</Sidebar>
|
||||
</Route>
|
||||
<Route exact path="/multi-edit/:cat" render={(props) => (
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './legend.css';
|
||||
import { Logo } from '../components/logo';
|
||||
@ -114,13 +113,15 @@ const LEGEND_CONFIG = {
|
||||
};
|
||||
|
||||
|
||||
class Legend extends React.Component<any, any> { // TODO: add proper types
|
||||
static propTypes = { // TODO: generate propTypes from TS
|
||||
slug: PropTypes.string,
|
||||
color: PropTypes.string,
|
||||
text: PropTypes.string
|
||||
};
|
||||
interface LegendProps {
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface LegendState {
|
||||
collapseList: boolean;
|
||||
}
|
||||
|
||||
class Legend extends React.Component<LegendProps, LegendState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {collapseList: false};
|
||||
|
@ -1,4 +1,3 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Map, TileLayer, ZoomControl, AttributionControl, GeoJSON } from 'react-leaflet-universal';
|
||||
import { GeoJsonObject } from 'geojson';
|
||||
@ -15,12 +14,12 @@ import { Building } from '../models/building';
|
||||
const OS_API_KEY = 'NVUxtY5r8eA6eIfwrPTAGKrAAsoeI9E9';
|
||||
|
||||
interface ColouringMapProps {
|
||||
building: Building;
|
||||
building?: Building;
|
||||
mode: 'basic' | 'view' | 'edit' | 'multi-edit';
|
||||
category: string;
|
||||
revision_id: number;
|
||||
selectBuilding: any;
|
||||
colourBuilding: any;
|
||||
selectBuilding: (building: Building) => void;
|
||||
colourBuilding: (building: Building) => void;
|
||||
}
|
||||
|
||||
interface ColouringMapState {
|
||||
@ -34,15 +33,6 @@ interface ColouringMapState {
|
||||
* Map area
|
||||
*/
|
||||
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) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -1,16 +1,35 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './search-box.css';
|
||||
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
|
||||
*/
|
||||
class SearchBox extends Component<any, any> { // TODO: add proper types
|
||||
static propTypes = { // TODO: generate propTypes from TS
|
||||
onLocate: PropTypes.func
|
||||
};
|
||||
|
||||
class SearchBox extends Component<SearchBoxProps, SearchBoxState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -1,9 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
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}>
|
||||
<button className="btn btn-outline btn-outline-dark"
|
||||
type="submit">
|
||||
@ -12,9 +16,4 @@ const ThemeSwitcher = (props) => (
|
||||
</form>
|
||||
);
|
||||
|
||||
ThemeSwitcher.propTypes = {
|
||||
currentTheme: PropTypes.string,
|
||||
onSubmit: PropTypes.func.isRequired
|
||||
}
|
||||
|
||||
export default ThemeSwitcher;
|
||||
|
@ -1,5 +1,9 @@
|
||||
interface User {
|
||||
username: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
registered?: Date;
|
||||
api_key?: string;
|
||||
error?: string;
|
||||
// TODO: add other fields as needed
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Redirect, Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ErrorBox from '../components/error-box';
|
||||
import InfoBox from '../components/info-box';
|
||||
import SupporterLogos from '../components/supporter-logos';
|
||||
import { User } from '../models/user';
|
||||
|
||||
class Login extends Component<any, any> { // TODO: add proper types
|
||||
static propTypes = { // TODO: generate propTypes from TS
|
||||
login: PropTypes.func,
|
||||
user: PropTypes.object
|
||||
};
|
||||
interface LoginProps {
|
||||
user: User,
|
||||
login: (user: User) => void;
|
||||
}
|
||||
|
||||
class Login extends Component<LoginProps, any> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -1,23 +1,23 @@
|
||||
import React, { Component, FormEvent } from 'react';
|
||||
import { Link, Redirect } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ConfirmationModal from '../components/confirmation-modal';
|
||||
import ErrorBox from '../components/error-box';
|
||||
import { User } from '../models/user';
|
||||
|
||||
class MyAccountPage extends Component<any, any> { // TODO: add proper types
|
||||
static propTypes = { // TODO: generate propTypes from TS
|
||||
user: PropTypes.shape({
|
||||
username: PropTypes.string,
|
||||
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 MyAccountPageProps {
|
||||
user: User;
|
||||
updateUser: (user: User) => void;
|
||||
logout: () => void;
|
||||
}
|
||||
|
||||
interface MyAccountPageState {
|
||||
showDeleteConfirm: boolean;
|
||||
error: string;
|
||||
}
|
||||
|
||||
|
||||
class MyAccountPage extends Component<MyAccountPageProps, MyAccountPageState> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ChangeEvent, FormEvent } from 'react';
|
||||
import React, { FormEvent } from 'react';
|
||||
import { RouteComponentProps, Redirect } from 'react-router';
|
||||
|
||||
import ErrorBox from '../components/error-box';
|
||||
|
@ -1,17 +1,27 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Redirect, Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import ErrorBox from '../components/error-box';
|
||||
import InfoBox from '../components/info-box';
|
||||
import SupporterLogos from '../components/supporter-logos';
|
||||
import { User } from '../models/user';
|
||||
|
||||
class SignUp extends Component<any, any> { // TODO: add proper types
|
||||
static propTypes = { // TODO: generate propTypes from TS
|
||||
login: PropTypes.func.isRequired,
|
||||
user: PropTypes.object
|
||||
};
|
||||
interface SignUpProps {
|
||||
login: (user: User) => void;
|
||||
user: User;
|
||||
}
|
||||
|
||||
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) {
|
||||
super(props);
|
||||
this.state = {
|
||||
@ -19,7 +29,7 @@ class SignUp extends Component<any, any> { // TODO: add proper types
|
||||
email: '',
|
||||
confirm_email: '',
|
||||
password: '',
|
||||
show_password: '',
|
||||
show_password: false,
|
||||
confirm_conditions: false,
|
||||
error: undefined
|
||||
};
|
||||
@ -31,11 +41,11 @@ class SignUp extends Component<any, any> { // TODO: add proper types
|
||||
handleChange(event) {
|
||||
const target = event.target;
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value;
|
||||
const name = target.name;
|
||||
const name: keyof SignUpState = target.name;
|
||||
|
||||
this.setState({
|
||||
[name]: value
|
||||
});
|
||||
} as Pick<SignUpState, keyof SignUpState>);
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
|
Loading…
Reference in New Issue
Block a user