diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 86a1866..b72be75 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -5,7 +5,7 @@ module.exports = { }, extends: [ 'plugin:react/recommended', - 'standard' + 'standard-with-typescript' ], parser: '@typescript-eslint/parser', parserOptions: { @@ -13,7 +13,8 @@ module.exports = { jsx: true }, ecmaVersion: 'latest', - sourceType: 'module' + sourceType: 'module', + project: './tsconfig.json' }, plugins: [ 'react', @@ -21,9 +22,11 @@ module.exports = { ], rules: { 'space-before-function-paren': ['error', 'never'], + '@typescript-eslint/space-before-function-paren': ['error', 'never'], indent: ['warn', 2, { SwitchCase: 1 }], - semi: ['warn', 'always'], + semi: 'off', + '@typescript-eslint/semi': ['warn', 'always'], 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': 'error' + '@typescript-eslint/no-unused-vars': 'error', } }; diff --git a/package-lock.json b/package-lock.json index 83e5fcb..285432c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "autoprefixer": "^10.4.8", "eslint": "^8.20.0", "eslint-config-standard": "^17.0.0", + "eslint-config-standard-with-typescript": "^22.0.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-n": "^15.2.4", "eslint-plugin-promise": "^6.0.0", @@ -2697,6 +2698,24 @@ "eslint-plugin-promise": "^6.0.0" } }, + "node_modules/eslint-config-standard-with-typescript": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-22.0.0.tgz", + "integrity": "sha512-VA36U7UlFpwULvkdnh6MQj5GAV2Q+tT68ALLAwJP0ZuNXU2m0wX07uxX4qyLRdHgSzH4QJ73CveKBuSOYvh7vQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint-config-standard": "17.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0", + "typescript": "*" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", @@ -8093,6 +8112,16 @@ "dev": true, "requires": {} }, + "eslint-config-standard-with-typescript": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-22.0.0.tgz", + "integrity": "sha512-VA36U7UlFpwULvkdnh6MQj5GAV2Q+tT68ALLAwJP0ZuNXU2m0wX07uxX4qyLRdHgSzH4QJ73CveKBuSOYvh7vQ==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^5.0.0", + "eslint-config-standard": "17.0.0" + } + }, "eslint-import-resolver-node": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz", diff --git a/package.json b/package.json index 149d76f..b4e99f5 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "autoprefixer": "^10.4.8", "eslint": "^8.20.0", "eslint-config-standard": "^17.0.0", + "eslint-config-standard-with-typescript": "^22.0.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-n": "^15.2.4", "eslint-plugin-promise": "^6.0.0", diff --git a/src/App.tsx b/src/App.tsx index 2b8c7fd..5f20f93 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,25 +1,27 @@ import * as React from 'react'; import './App.scss'; import { MainMenu } from './Components/MainMenu/MainMenu'; -import { ContainerModel, findContainerById, IContainerModel, MakeIterator } from './Interfaces/ContainerModel'; +import { ContainerModel, IContainerModel } from './Interfaces/ContainerModel'; +import { findContainerById, MakeIterator } from './utils/itertools'; import Editor, { IEditorState } from './Editor'; -import { AvailableContainer } from './Interfaces/AvailableContainer'; import { Configuration } from './Interfaces/Configuration'; export interface IHistoryState { - MainContainer: IContainerModel | null, - SelectedContainer: IContainerModel | null, - SelectedContainerId: string, + MainContainer: IContainerModel | null + SelectedContainer: IContainerModel | null + SelectedContainerId: string TypeCounters: Record } +// App will never have props +// eslint-disable-next-line @typescript-eslint/no-empty-interface interface IAppProps { } interface IAppState { - configuration: Configuration, - history: IHistoryState[], - historyCurrentStep: number, + configuration: Configuration + history: IHistoryState[] + historyCurrentStep: number isLoaded: boolean } @@ -32,7 +34,12 @@ export class App extends React.Component { configuration: { AvailableContainers: [], AvailableSymbols: [], - MainContainer: {} as AvailableContainer + MainContainer: { + Type: 'EmptyContainer', + Width: 3000, + Height: 200, + Style: {} + } }, history: [], historyCurrentStep: 0, @@ -40,7 +47,7 @@ export class App extends React.Component { }; } - componentDidMount() { + componentDidMount(): void { const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); const state = urlParams.get('state'); @@ -50,35 +57,39 @@ export class App extends React.Component { } fetch(state) - .then((response) => response.json()) + .then( + async(response) => await response.json(), + (error) => { throw new Error(error); } + ) .then((data: IEditorState) => { this.LoadState(data); - }); + }, (error) => { throw new Error(error); }); } - public NewEditor() { + public NewEditor(): void { // Fetch the configuration from the API - fetchConfiguration().then((configuration: Configuration) => { + fetchConfiguration() + .then((configuration: Configuration) => { // Set the main container from the given properties of the API - const MainContainer = new ContainerModel( - null, - { - id: 'main', - parentId: 'null', - x: 0, - y: 0, - width: configuration.MainContainer.Width, - height: configuration.MainContainer.Height, - fillOpacity: 0, - stroke: 'black' - } - ); + const MainContainer = new ContainerModel( + null, + { + id: 'main', + parentId: 'null', + x: 0, + y: 0, + width: configuration.MainContainer.Width, + height: configuration.MainContainer.Height, + fillOpacity: 0, + stroke: 'black' + } + ); - // Save the configuration and the new MainContainer - // and default the selected container to it - this.setState({ - configuration, - history: + // Save the configuration and the new MainContainer + // and default the selected container to it + this.setState({ + configuration, + history: [ { MainContainer, @@ -86,13 +97,14 @@ export class App extends React.Component { TypeCounters: {} } ], - historyCurrentStep: 0, - isLoaded: true - } as IAppState); - }); + historyCurrentStep: 0, + isLoaded: true + }); + }, (error) => { throw new Error(error); } + ); } - public LoadEditor(files: FileList | null) { + public LoadEditor(files: FileList | null): void { if (files === null) { return; } @@ -107,7 +119,7 @@ export class App extends React.Component { reader.readAsText(file); } - private LoadState(editorState: IEditorState) { + private LoadState(editorState: IEditorState): void { Revive(editorState); this.setState({ @@ -115,10 +127,10 @@ export class App extends React.Component { history: editorState.history, historyCurrentStep: editorState.historyCurrentStep, isLoaded: true - } as IAppState); + }); } - public render() { + public render(): JSX.Element { if (this.state.isLoaded) { return (
@@ -149,16 +161,17 @@ export class App extends React.Component { export async function fetchConfiguration(): Promise { const url = `${import.meta.env.VITE_API_URL}`; // The test library cannot use the Fetch API - // @ts-ignore + // @ts-expect-error + // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions if (window.fetch) { return await fetch(url, { method: 'POST' }) - .then((response) => - response.json() + .then(async(response) => + await response.json() ) as Configuration; } - return new Promise((resolve) => { + return await new Promise((resolve) => { const xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onreadystatechange = function() { // Call a function when the state changes. diff --git a/src/Components/ElementsSidebar/ElementsSidebar.tsx b/src/Components/ElementsSidebar/ElementsSidebar.tsx index c4ea51a..caa6081 100644 --- a/src/Components/ElementsSidebar/ElementsSidebar.tsx +++ b/src/Components/ElementsSidebar/ElementsSidebar.tsx @@ -1,31 +1,32 @@ import * as React from 'react'; import { motion } from 'framer-motion'; import { Properties } from '../Properties/Properties'; -import { IContainerModel, getDepth, MakeIterator } from '../../Interfaces/ContainerModel'; +import { IContainerModel } from '../../Interfaces/ContainerModel'; +import { getDepth, MakeIterator } from '../../utils/itertools'; interface IElementsSidebarProps { - MainContainer: IContainerModel | null, - isOpen: boolean, + MainContainer: IContainerModel | null + isOpen: boolean isHistoryOpen: boolean - SelectedContainer: IContainerModel | null, - onClick: () => void, - onPropertyChange: (key: string, value: string) => void, + SelectedContainer: IContainerModel | null + onClick: () => void + onPropertyChange: (key: string, value: string) => void selectContainer: (container: IContainerModel) => void } export class ElementsSidebar extends React.PureComponent { public iterateChilds(handleContainer: (container: IContainerModel) => void): React.ReactNode { - if (!this.props.MainContainer) { + if (this.props.MainContainer == null) { return null; } const it = MakeIterator(this.props.MainContainer); for (const container of it) { - handleContainer(container as IContainerModel); + handleContainer(container); } } - public render() { + public render(): JSX.Element { let isOpenClasses = '-right-64'; if (this.props.isOpen) { isOpenClasses = this.props.isHistoryOpen diff --git a/src/Components/FloatingButton/FloatingButton.tsx b/src/Components/FloatingButton/FloatingButton.tsx index 544c2bd..3145f45 100644 --- a/src/Components/FloatingButton/FloatingButton.tsx +++ b/src/Components/FloatingButton/FloatingButton.tsx @@ -9,7 +9,7 @@ interface IFloatingButtonProps { const toggleState = ( isHidden: boolean, setHidden: React.Dispatch> -) => { +): void => { setHidden(!isHidden); }; diff --git a/src/Components/History/History.tsx b/src/Components/History/History.tsx index e77a8c7..7487de1 100644 --- a/src/Components/History/History.tsx +++ b/src/Components/History/History.tsx @@ -2,19 +2,19 @@ import * as React from 'react'; import { IHistoryState } from '../../App'; interface IHistoryProps { - history: IHistoryState[], - historyCurrentStep: number, - isOpen: boolean, - onClick: () => void, + history: IHistoryState[] + historyCurrentStep: number + isOpen: boolean + onClick: () => void jumpTo: (move: number) => void } export class History extends React.PureComponent { - public render() { + public render(): JSX.Element { const isOpenClasses = this.props.isOpen ? 'right-0' : '-right-64'; const states = this.props.history.map((step, move) => { - const desc = move + const desc = move > 0 ? `Go to modification n°${move}` : 'Go to the beginning'; diff --git a/src/Components/MainMenu/MainMenu.tsx b/src/Components/MainMenu/MainMenu.tsx index 92b19d5..a9b3da2 100644 --- a/src/Components/MainMenu/MainMenu.tsx +++ b/src/Components/MainMenu/MainMenu.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; interface IMainMenuProps { - newEditor: () => void; + newEditor: () => void loadEditor: (files: FileList | null) => void } diff --git a/src/Components/Properties/Properties.tsx b/src/Components/Properties/Properties.tsx index 65cfefd..f7979a9 100644 --- a/src/Components/Properties/Properties.tsx +++ b/src/Components/Properties/Properties.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import ContainerProperties from '../../Interfaces/Properties'; interface IPropertiesProps { - properties?: ContainerProperties, + properties?: ContainerProperties onChange: (key: string, value: string) => void } @@ -27,7 +27,7 @@ export class Properties extends React.PureComponent { public handleProperties = ( [key, value]: [string, string | number], groupInput: React.ReactNode[] - ) => { + ): void => { const id = `property-${key}`; const type = 'text'; const isDisabled = key === 'id' || key === 'parentId'; // hardcoded diff --git a/src/Components/SVG/Elements/Container.tsx b/src/Components/SVG/Elements/Container.tsx index a352bcd..e64739c 100644 --- a/src/Components/SVG/Elements/Container.tsx +++ b/src/Components/SVG/Elements/Container.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; -import { getDepth, IContainerModel } from '../../../Interfaces/ContainerModel'; +import { IContainerModel } from '../../../Interfaces/ContainerModel'; +import { getDepth } from '../../../utils/itertools'; import { Dimension } from './Dimension'; export interface IContainerProps { @@ -20,11 +21,11 @@ export class Container extends React.PureComponent { const transform = `translate(${Number(this.props.model.properties.x)}, ${Number(this.props.model.properties.y)})`; // g style - const defaultStyle = { + const defaultStyle: React.CSSProperties = { transitionProperty: 'all', transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', transitionDuration: '150ms' - } as React.CSSProperties; + }; // Rect style const style = Object.assign( diff --git a/src/Components/SVG/Elements/Dimension.tsx b/src/Components/SVG/Elements/Dimension.tsx index 96ab2c9..612b3f7 100644 --- a/src/Components/SVG/Elements/Dimension.tsx +++ b/src/Components/SVG/Elements/Dimension.tsx @@ -1,19 +1,19 @@ import * as React from 'react'; interface IDimensionProps { - id: string; - xStart: number; - xEnd: number; - y: number; - text: string; - strokeWidth: number; + id: string + xStart: number + xEnd: number + y: number + text: string + strokeWidth: number } export class Dimension extends React.PureComponent { - public render() { - const style = { + public render(): JSX.Element { + const style: React.CSSProperties = { stroke: 'black' - } as React.CSSProperties; + }; return ( = (props) => { props.selected.properties.width, props.selected.properties.height ]; - const style = { + const style: React.CSSProperties = { stroke: '#3B82F6', // tw blue-500 strokeWidth: 4, fillOpacity: 0, @@ -26,7 +27,7 @@ export const Selector: React.FC = (props) => { transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)', transitionDuration: '150ms', animation: 'fadein 750ms ease-in alternate infinite' - } as React.CSSProperties; + }; return ( { - public state: ISVGState; public static ID = 'svg'; constructor(props: ISVGProps) { super(props); - this.state = { - value: { - viewerWidth: window.innerWidth, - viewerHeight: window.innerHeight - } as Value, - tool: TOOL_PAN - }; } render() { @@ -49,13 +36,11 @@ export class SVG extends React.PureComponent { return (
- this.setState({ value })} - tool={this.state.tool} onChangeTool={tool => this.setState({ tool })} miniatureProps={{ position: 'left', background: '#616264', @@ -67,9 +52,8 @@ export class SVG extends React.PureComponent { { children } - +
- ); }; } diff --git a/src/Components/Sidebar/Sidebar.tsx b/src/Components/Sidebar/Sidebar.tsx index dd34812..b474cc9 100644 --- a/src/Components/Sidebar/Sidebar.tsx +++ b/src/Components/Sidebar/Sidebar.tsx @@ -3,13 +3,13 @@ import { AvailableContainer } from '../../Interfaces/AvailableContainer'; interface ISidebarProps { componentOptions: AvailableContainer[] - isOpen: boolean; - onClick: () => void; - buttonOnClick: (type: string) => void; + isOpen: boolean + onClick: () => void + buttonOnClick: (type: string) => void } export default class Sidebar extends React.PureComponent { - public render() { + public render(): JSX.Element { const listElements = this.props.componentOptions.map(componentOption =>