diff --git a/client/package.json b/client/package.json index fc6bebaec..1ba4d56de 100644 --- a/client/package.json +++ b/client/package.json @@ -41,6 +41,7 @@ "react-router": "^5.2.0", "react-router-dom": "^5.2.0", "react-syntax-highlighter": "^15.4.4", + "react-virtualized-auto-sizer": "^1.0.5", "react-window": "^1.8.6", "rehype-katex": "^5.0.0", "remark-gfm": "^1.0.0", @@ -57,6 +58,7 @@ "@types/react-dom": "^17.0.9", "@types/react-router-dom": "^5.1.8", "@types/react-syntax-highlighter": "^13.5.2", + "@types/react-virtualized-auto-sizer": "^1.0.1", "@types/react-window": "^1.8.4", "@types/uuid": "^8.3.1", "@typescript-eslint/eslint-plugin": "^4.28.5", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index b6cf9debc..3a16bb2d0 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -18,6 +18,7 @@ specifiers: '@types/react-dom': ^17.0.9 '@types/react-router-dom': ^5.1.8 '@types/react-syntax-highlighter': ^13.5.2 + '@types/react-virtualized-auto-sizer': ^1.0.1 '@types/react-window': ^1.8.4 '@types/uuid': ^8.3.1 '@typescript-eslint/eslint-plugin': ^4.28.5 @@ -53,6 +54,7 @@ specifiers: react-router: ^5.2.0 react-router-dom: ^5.2.0 react-syntax-highlighter: ^15.4.4 + react-virtualized-auto-sizer: ^1.0.5 react-window: ^1.8.6 rehype-katex: ^5.0.0 remark-gfm: ^1.0.0 @@ -83,6 +85,7 @@ dependencies: react-router: 5.2.0_react@17.0.2 react-router-dom: 5.2.0_react@17.0.2 react-syntax-highlighter: 15.4.4_react@17.0.2 + react-virtualized-auto-sizer: 1.0.5_react-dom@17.0.2+react@17.0.2 react-window: 1.8.6_react-dom@17.0.2+react@17.0.2 rehype-katex: 5.0.0 remark-gfm: 1.0.0 @@ -99,6 +102,7 @@ devDependencies: '@types/react-dom': 17.0.9 '@types/react-router-dom': 5.1.8 '@types/react-syntax-highlighter': 13.5.2 + '@types/react-virtualized-auto-sizer': 1.0.1 '@types/react-window': 1.8.4 '@types/uuid': 8.3.1 '@typescript-eslint/eslint-plugin': 4.28.5_514553717ff968e20f6d1c6e521f8616 @@ -2509,6 +2513,15 @@ packages: '@types/react': 17.0.15 dev: true + /@types/react-virtualized-auto-sizer/1.0.1: + resolution: + { + integrity: sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong==, + } + dependencies: + '@types/react': 17.0.15 + dev: true + /@types/react-window/1.8.4: resolution: { @@ -8236,6 +8249,20 @@ packages: refractor: 3.4.0 dev: false + /react-virtualized-auto-sizer/1.0.5_react-dom@17.0.2+react@17.0.2: + resolution: + { + integrity: sha512-kivjYVWX15TX2IUrm8F1jaCEX8EXrpy3DD+u41WGqJ1ZqbljWpiwscV+VxOM1l7sSIM1jwi2LADjhhAJkJ9dxA==, + } + engines: { node: '>8.0.0' } + peerDependencies: + react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 + react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 + dependencies: + react: 17.0.2 + react-dom: 17.0.2_react@17.0.2 + dev: false + /react-window/1.8.6_react-dom@17.0.2+react@17.0.2: resolution: { diff --git a/client/src/Components/SelectionView/TitleValueViewPair.tsx b/client/src/Components/SelectionView/TitleValueViewPair.tsx deleted file mode 100644 index 9a93d83f3..000000000 --- a/client/src/Components/SelectionView/TitleValueViewPair.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' -import { Optional } from '../../util/types' - -interface ClassViewProps { - title: string - value: Optional -} - -export default function TitleValueViewPair(props: ClassViewProps): JSX.Element { - return ( - <> - {props.value && <>{props.title + ': ' + props.value}} -
- - ) -} diff --git a/client/src/Components/TreeView/ClassNode.tsx b/client/src/Components/TreeView/ClassNode.tsx deleted file mode 100644 index 32bdeb4a7..000000000 --- a/client/src/Components/TreeView/ClassNode.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import { FaChalkboard } from 'react-icons/fa' -import PythonClass from '../../model/python/PythonClass' -import { isEmptyList } from '../../util/listOperations' -import FunctionNode from './FunctionNode' -import TreeNode from './TreeNode' - -interface ClassNodeProps { - pythonClass: PythonClass -} - -export default function ClassNode(props: ClassNodeProps): JSX.Element { - const hasMethods = !isEmptyList(props.pythonClass.methods) - - return ( - - {props.pythonClass.methods.map((method) => ( - - ))} - - ) -} diff --git a/client/src/Components/TreeView/FunctionNode.tsx b/client/src/Components/TreeView/FunctionNode.tsx deleted file mode 100644 index 49b31bc3f..000000000 --- a/client/src/Components/TreeView/FunctionNode.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react' -import { FaCogs } from 'react-icons/fa' -import PythonFunction from '../../model/python/PythonFunction' -import { isEmptyList } from '../../util/listOperations' -import ParameterNode from './ParameterNode' -import TreeNode from './TreeNode' - -interface FunctionNodeProps { - pythonFunction: PythonFunction -} - -export default function FunctionNode(props: FunctionNodeProps): JSX.Element { - const hasParameters = !isEmptyList(props.pythonFunction.parameters) - - return ( - - {props.pythonFunction.parameters.map((parameter) => ( - - ))} - - ) -} diff --git a/client/src/Components/TreeView/ModuleNode.tsx b/client/src/Components/TreeView/ModuleNode.tsx deleted file mode 100644 index a5eac599f..000000000 --- a/client/src/Components/TreeView/ModuleNode.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react' -import { FaArchive } from 'react-icons/fa' -import PythonModule from '../../model/python/PythonModule' -import { isEmptyList } from '../../util/listOperations' -import ClassNode from './ClassNode' -import FunctionNode from './FunctionNode' -import TreeNode from './TreeNode' - -interface ModuleNodeProps { - pythonModule: PythonModule -} - -export default function ModuleNode(props: ModuleNodeProps): JSX.Element { - const hasClasses = !isEmptyList(props.pythonModule.classes) - const hasFunctions = !isEmptyList(props.pythonModule.functions) - const hasChildren = hasClasses || hasFunctions - - return ( - - {props.pythonModule.classes.map((moduleClass) => ( - - ))} - - {props.pythonModule.functions.map((moduleFunction) => ( - - ))} - - ) -} diff --git a/client/src/Components/TreeView/ParameterNode.tsx b/client/src/Components/TreeView/ParameterNode.tsx deleted file mode 100644 index 947eb98d6..000000000 --- a/client/src/Components/TreeView/ParameterNode.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react' -import { FaKeyboard } from 'react-icons/fa' -import PythonParameter from '../../model/python/PythonParameter' -import TreeNode from './TreeNode' - -interface ParameterNodeProps { - pythonParameter: PythonParameter -} - -export default function ParameterNode(props: ParameterNodeProps): JSX.Element { - return -} diff --git a/client/src/Components/TreeView/TreeNode.tsx b/client/src/Components/TreeView/TreeNode.tsx deleted file mode 100644 index 7462d363e..000000000 --- a/client/src/Components/TreeView/TreeNode.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import { Box, Icon } from '@chakra-ui/react' -import React, { useState } from 'react' -import { IconType } from 'react-icons/lib' -import { useLocation } from 'react-router' -import { useHistory } from 'react-router-dom' -import PythonDeclaration from '../../model/python/PythonDeclaration' -import { ChildrenProp } from '../../util/types' -import VisibilityIndicator from './VisibilityIndicator' - -interface TreeNodeProps extends ChildrenProp { - declaration: PythonDeclaration - icon: IconType - isExpandable: boolean -} - -export default function TreeNode(props: TreeNodeProps): JSX.Element { - const currentPathname = useLocation().pathname - const [showChildren, setShowChildren] = useState(selfOrChildIsSelected(props.declaration, currentPathname)) - const history = useHistory() - - const level = levelOf(props.declaration) - const paddingLeft = level === 0 ? '1rem' : `calc(0.5 * ${level} * (1.25em + 0.25rem) + 1rem)` - const backgroundColor = isSelected(props.declaration, currentPathname) ? 'cornflowerblue' : undefined - const color = isSelected(props.declaration, currentPathname) ? 'white' : undefined - - const handleClick = () => { - setShowChildren((prevState) => !prevState) - history.push(`/${props.declaration.pathAsString()}`) - } - - return ( - <> - - - - {props.declaration.name} - - {showChildren && props.children} - - ) -} - -function levelOf(declaration: PythonDeclaration): number { - return declaration.path().length - 2 -} - -function isSelected(declaration: PythonDeclaration, currentPathname: string): boolean { - return `/${declaration.pathAsString()}` === currentPathname -} - -function selfOrChildIsSelected(declaration: PythonDeclaration, currentPathname: string): boolean { - const declarationPath = `/${declaration.pathAsString()}` - const currentPath = currentPathname - - // The slash prevents /sklearn/sklearn from opening when the path is /sklearn/sklearn.base - return currentPath === declarationPath || currentPath.startsWith(`${declarationPath}/`) -} diff --git a/client/src/Components/TreeView/TreeView.tsx b/client/src/Components/TreeView/TreeView.tsx deleted file mode 100644 index 1484a8a1c..000000000 --- a/client/src/Components/TreeView/TreeView.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Box } from '@chakra-ui/react' -import React from 'react' -import PythonPackage from '../../model/python/PythonPackage' -import ModuleNode from './ModuleNode' - -interface TreeViewProps { - pythonPackage: PythonPackage -} - -export default function TreeView(props: TreeViewProps): JSX.Element { - return ( - - {/**/} - {props.pythonPackage.modules.map((module) => ( - - ))} - {/**/} - - ) -} - -// const a = memo() diff --git a/client/src/app/App.tsx b/client/src/app/App.tsx index 003b6718b..8946335a8 100644 --- a/client/src/app/App.tsx +++ b/client/src/app/App.tsx @@ -1,43 +1,39 @@ import { Grid, GridItem } from '@chakra-ui/react' import * as idb from 'idb-keyval' import React, { useEffect, useState } from 'react' -import { useHistory } from 'react-router-dom' -import MenuBar from '../Components/Menu/MenuBar' -import SelectionView from '../Components/SelectionView/SelectionView' -import TreeView from '../Components/TreeView/TreeView' +import { useLocation } from 'react-router' +import MenuBar from '../common/MenuBar' +import { Setter } from '../common/util/types' import AnnotationImportDialog from '../features/annotations/AnnotationImportDialog' import { + AnnotationsState, initializeAnnotations, + selectAnnotations, selectCurrentUserAction, selectShowAnnotationImportDialog, } from '../features/annotations/annotationSlice' import EnumForm from '../features/annotations/forms/EnumForm' import RenameForm from '../features/annotations/forms/RenameForm' import ApiDataImportDialog from '../features/apiData/ApiDataImportDialog' -import { selectShowApiDataImportDialog } from '../features/apiData/apiDataSlice' -import { PythonFilter } from '../model/python/PythonFilter' -import PythonPackage from '../model/python/PythonPackage' -import { parsePythonPackageJson, PythonPackageJson } from '../model/python/PythonPackageBuilder' +import { selectShowApiDataImportDialog, toggleExpandedInTreeView } from '../features/apiData/apiDataSlice' +import { PythonFilter } from '../features/apiData/model/PythonFilter' +import PythonPackage from '../features/apiData/model/PythonPackage' +import { parsePythonPackageJson, PythonPackageJson } from '../features/apiData/model/PythonPackageBuilder' +import SelectionView from '../features/apiData/selectionView/SelectionView' +import TreeView from '../features/apiData/treeView/TreeView' import { useAppDispatch, useAppSelector } from './hooks' const App: React.FC = () => { const [pythonPackage, setPythonPackage] = useState(new PythonPackage('empty')) const currentUserAction = useAppSelector(selectCurrentUserAction) - const history = useHistory() - // const [treeView] + const currentPathName = useLocation().pathname useEffect(() => { - const getPythonPackageFromIndexedDB = async () => { - const storedPackage = (await idb.get('package')) as PythonPackageJson - if (storedPackage) { - setPythonPackage(parsePythonPackageJson(storedPackage)) - } - } - - getPythonPackageFromIndexedDB() + // noinspection JSIgnoredPromiseFromCall + getPythonPackageFromIndexedDB(setPythonPackage) }, []) - const annotationStore = useAppSelector((state) => state.annotations) + const annotationStore = useAppSelector(selectAnnotations) const dispatch = useAppDispatch() useEffect(() => { @@ -45,12 +41,19 @@ const App: React.FC = () => { }, [dispatch]) useEffect(() => { - const setAnnotationsInIndexedDB = async () => { - await idb.set('annotations', annotationStore) + // noinspection JSIgnoredPromiseFromCall + setAnnotationsInIndexedDB(annotationStore) + }, [annotationStore]) + + useEffect(() => { + const parts = currentPathName.split('/').slice(1) + + for (let i = 2; i < parts.length; i++) { + dispatch(toggleExpandedInTreeView(parts.slice(0, i).join('/'))) } - setAnnotationsInIndexedDB() - }, [annotationStore]) + // eslint-disable-next-line + }, []) const [filter, setFilter] = useState('') const pythonFilter = PythonFilter.fromFilterBoxInput(filter) @@ -98,4 +101,15 @@ const App: React.FC = () => { ) } +async function getPythonPackageFromIndexedDB(setPythonPackage: Setter) { + const storedPackage = (await idb.get('package')) as PythonPackageJson + if (storedPackage) { + setPythonPackage(parsePythonPackageJson(storedPackage)) + } +} + +async function setAnnotationsInIndexedDB(annotationStore: AnnotationsState) { + await idb.set('annotations', annotationStore) +} + export default App diff --git a/client/src/Components/Menu/MenuBar.tsx b/client/src/common/MenuBar.tsx similarity index 92% rename from client/src/Components/Menu/MenuBar.tsx rename to client/src/common/MenuBar.tsx index ad0b16217..c70219b7a 100644 --- a/client/src/Components/Menu/MenuBar.tsx +++ b/client/src/common/MenuBar.tsx @@ -28,12 +28,12 @@ import React, { useRef } from 'react' import { FaCheck, FaChevronDown } from 'react-icons/fa' import { useLocation } from 'react-router' import { NavLink } from 'react-router-dom' -import { useAppDispatch, useAppSelector } from '../../app/hooks' -import { toggleAnnotationImportDialog } from '../../features/annotations/annotationSlice' -import { toggleApiDataImportDialog } from '../../features/apiData/apiDataSlice' -import { PythonFilter } from '../../model/python/PythonFilter' -import PythonPackage from '../../model/python/PythonPackage' -import { Setter } from '../../util/types' +import { useAppDispatch, useAppSelector } from '../app/hooks' +import { toggleAnnotationImportDialog } from '../features/annotations/annotationSlice' +import { toggleApiDataImportDialog } from '../features/apiData/apiDataSlice' +import { PythonFilter } from '../features/apiData/model/PythonFilter' +import PythonPackage from '../features/apiData/model/PythonPackage' +import { Setter } from './util/types' interface MenuBarProps { setPythonPackage: Setter diff --git a/client/src/util/listOperations.test.ts b/client/src/common/util/listOperations.test.ts similarity index 100% rename from client/src/util/listOperations.test.ts rename to client/src/common/util/listOperations.test.ts diff --git a/client/src/util/listOperations.ts b/client/src/common/util/listOperations.ts similarity index 100% rename from client/src/util/listOperations.ts rename to client/src/common/util/listOperations.ts diff --git a/client/src/util/types.ts b/client/src/common/util/types.ts similarity index 100% rename from client/src/util/types.ts rename to client/src/common/util/types.ts diff --git a/client/src/util/validation.test.ts b/client/src/common/util/validation.test.ts similarity index 100% rename from client/src/util/validation.test.ts rename to client/src/common/util/validation.test.ts diff --git a/client/src/util/validation.ts b/client/src/common/util/validation.ts similarity index 100% rename from client/src/util/validation.ts rename to client/src/common/util/validation.ts diff --git a/client/src/features/annotations/AnnotationImportDialog.tsx b/client/src/features/annotations/AnnotationImportDialog.tsx index 153f63937..1ef0fa8d8 100644 --- a/client/src/features/annotations/AnnotationImportDialog.tsx +++ b/client/src/features/annotations/AnnotationImportDialog.tsx @@ -16,7 +16,7 @@ import { import React, { useState } from 'react' import { useAppDispatch } from '../../app/hooks' import StyledDropzone from '../../common/StyledDropzone' -import { isValidJsonFile } from '../../util/validation' +import { isValidJsonFile } from '../../common/util/validation' import { AnnotationsState, setAnnotations, toggleAnnotationImportDialog } from './annotationSlice' const AnnotationImportDialog: React.FC = () => { diff --git a/client/src/features/annotations/annotationSlice.ts b/client/src/features/annotations/annotationSlice.ts index cf4e065fa..9d73812f9 100644 --- a/client/src/features/annotations/annotationSlice.ts +++ b/client/src/features/annotations/annotationSlice.ts @@ -166,7 +166,7 @@ export const { } = actions export default reducer -const selectAnnotations = (state: RootState) => state.annotations +export const selectAnnotations = (state: RootState) => state.annotations export const selectEnum = (target: string) => (state: RootState): EnumAnnotation | undefined => diff --git a/client/src/features/annotations/forms/EnumForm.tsx b/client/src/features/annotations/forms/EnumForm.tsx index 63a76e39b..594c6e04d 100644 --- a/client/src/features/annotations/forms/EnumForm.tsx +++ b/client/src/features/annotations/forms/EnumForm.tsx @@ -13,7 +13,7 @@ import { useFieldArray, useForm } from 'react-hook-form' import { FaPlus, FaTrash } from 'react-icons/fa' import { useAppDispatch, useAppSelector } from '../../../app/hooks' import { pythonIdentifierPattern } from '../../../common/validation' -import PythonDeclaration from '../../../model/python/PythonDeclaration' +import PythonDeclaration from '../../apiData/model/PythonDeclaration' import { hideAnnotationForms, selectEnum, upsertEnum } from '../annotationSlice' import AnnotationForm from './AnnotationForm' diff --git a/client/src/features/annotations/forms/RenameForm.tsx b/client/src/features/annotations/forms/RenameForm.tsx index 204c2e816..e30e70464 100644 --- a/client/src/features/annotations/forms/RenameForm.tsx +++ b/client/src/features/annotations/forms/RenameForm.tsx @@ -3,7 +3,7 @@ import React, { useEffect } from 'react' import { useForm } from 'react-hook-form' import { useAppDispatch, useAppSelector } from '../../../app/hooks' import { pythonIdentifierPattern } from '../../../common/validation' -import PythonDeclaration from '../../../model/python/PythonDeclaration' +import PythonDeclaration from '../../apiData/model/PythonDeclaration' import { hideAnnotationForms, selectRenaming, upsertRenaming } from '../annotationSlice' import AnnotationForm from './AnnotationForm' diff --git a/client/src/features/apiData/ApiDataImportDialog.tsx b/client/src/features/apiData/ApiDataImportDialog.tsx index 71438deae..de2535e4d 100644 --- a/client/src/features/apiData/ApiDataImportDialog.tsx +++ b/client/src/features/apiData/ApiDataImportDialog.tsx @@ -18,12 +18,12 @@ import React, { useState } from 'react' import { useHistory } from 'react-router-dom' import { useAppDispatch } from '../../app/hooks' import StyledDropzone from '../../common/StyledDropzone' -import PythonPackage from '../../model/python/PythonPackage' -import { parsePythonPackageJson, PythonPackageJson } from '../../model/python/PythonPackageBuilder' -import { Setter } from '../../util/types' -import { isValidJsonFile } from '../../util/validation' +import { Setter } from '../../common/util/types' +import { isValidJsonFile } from '../../common/util/validation' import { resetAnnotations } from '../annotations/annotationSlice' import { toggleApiDataImportDialog } from './apiDataSlice' +import PythonPackage from './model/PythonPackage' +import { parsePythonPackageJson, PythonPackageJson } from './model/PythonPackageBuilder' interface ImportPythonPackageDialogProps { setPythonPackage: Setter diff --git a/client/src/features/apiData/apiDataSlice.ts b/client/src/features/apiData/apiDataSlice.ts index 2e7d326cc..5afa2dcd6 100644 --- a/client/src/features/apiData/apiDataSlice.ts +++ b/client/src/features/apiData/apiDataSlice.ts @@ -1,13 +1,17 @@ -import { createSlice } from '@reduxjs/toolkit' +import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { RootState } from '../../app/store' export interface ApiDataState { + expandedInTreeView: { + [target: string]: true + } showImportDialog: boolean } // Initial state ------------------------------------------------------------------------------------------------------- const initialState: ApiDataState = { + expandedInTreeView: {}, showImportDialog: false, } @@ -17,6 +21,13 @@ const apiDataSlice = createSlice({ name: 'apiData', initialState, reducers: { + toggleExpandedInTreeView(state, action: PayloadAction) { + if (state.expandedInTreeView[action.payload]) { + delete state.expandedInTreeView[action.payload] + } else { + state.expandedInTreeView[action.payload] = true + } + }, toggleImportDialog(state) { state.showImportDialog = !state.showImportDialog }, @@ -24,8 +35,14 @@ const apiDataSlice = createSlice({ }) const { actions, reducer } = apiDataSlice -export const { toggleImportDialog: toggleApiDataImportDialog } = actions +export const { toggleExpandedInTreeView, toggleImportDialog: toggleApiDataImportDialog } = actions export default reducer const selectApiData = (state: RootState) => state.apiData +export const selectIsExpandedInTreeView = + (target: string) => + (state: RootState): boolean => + Boolean(selectApiData(state).expandedInTreeView[target]) +export const selectAllExpandedInTreeView = (state: RootState): { [target: string]: true } => + selectApiData(state).expandedInTreeView export const selectShowApiDataImportDialog = (state: RootState): boolean => selectApiData(state).showImportDialog diff --git a/client/src/model/python/PythonClass.test.ts b/client/src/features/apiData/model/PythonClass.test.ts similarity index 100% rename from client/src/model/python/PythonClass.test.ts rename to client/src/features/apiData/model/PythonClass.test.ts diff --git a/client/src/model/python/PythonClass.ts b/client/src/features/apiData/model/PythonClass.ts similarity index 95% rename from client/src/model/python/PythonClass.ts rename to client/src/features/apiData/model/PythonClass.ts index cf0f00c1f..63223e90a 100644 --- a/client/src/model/python/PythonClass.ts +++ b/client/src/features/apiData/model/PythonClass.ts @@ -1,5 +1,5 @@ -import { isEmptyList } from '../../util/listOperations' -import { Optional } from '../../util/types' +import { isEmptyList } from '../../../common/util/listOperations' +import { Optional } from '../../../common/util/types' import PythonDeclaration from './PythonDeclaration' import { PythonFilter } from './PythonFilter' import PythonFunction from './PythonFunction' diff --git a/client/src/model/python/PythonDeclaration.ts b/client/src/features/apiData/model/PythonDeclaration.ts similarity index 89% rename from client/src/model/python/PythonDeclaration.ts rename to client/src/features/apiData/model/PythonDeclaration.ts index 49d3db7f4..878d6e209 100644 --- a/client/src/model/python/PythonDeclaration.ts +++ b/client/src/features/apiData/model/PythonDeclaration.ts @@ -1,5 +1,5 @@ -import { isEmptyList } from '../../util/listOperations' -import { Optional } from '../../util/types' +import { isEmptyList } from '../../../common/util/listOperations' +import { Optional } from '../../../common/util/types' export default abstract class PythonDeclaration { abstract readonly name: string diff --git a/client/src/model/python/PythonFilter.ts b/client/src/features/apiData/model/PythonFilter.ts similarity index 100% rename from client/src/model/python/PythonFilter.ts rename to client/src/features/apiData/model/PythonFilter.ts diff --git a/client/src/model/python/PythonFromImport.test.ts b/client/src/features/apiData/model/PythonFromImport.test.ts similarity index 100% rename from client/src/model/python/PythonFromImport.test.ts rename to client/src/features/apiData/model/PythonFromImport.test.ts diff --git a/client/src/model/python/PythonFromImport.ts b/client/src/features/apiData/model/PythonFromImport.ts similarity index 91% rename from client/src/model/python/PythonFromImport.ts rename to client/src/features/apiData/model/PythonFromImport.ts index 652acf7d6..13ca802ab 100644 --- a/client/src/model/python/PythonFromImport.ts +++ b/client/src/features/apiData/model/PythonFromImport.ts @@ -1,4 +1,4 @@ -import { Optional } from '../../util/types' +import { Optional } from '../../../common/util/types' export default class PythonFromImport { readonly module: string diff --git a/client/src/model/python/PythonFunction.test.ts b/client/src/features/apiData/model/PythonFunction.test.ts similarity index 100% rename from client/src/model/python/PythonFunction.test.ts rename to client/src/features/apiData/model/PythonFunction.test.ts diff --git a/client/src/model/python/PythonFunction.ts b/client/src/features/apiData/model/PythonFunction.ts similarity index 97% rename from client/src/model/python/PythonFunction.ts rename to client/src/features/apiData/model/PythonFunction.ts index c3214784a..40411255e 100644 --- a/client/src/model/python/PythonFunction.ts +++ b/client/src/features/apiData/model/PythonFunction.ts @@ -1,4 +1,4 @@ -import { Optional } from '../../util/types' +import { Optional } from '../../../common/util/types' import PythonClass from './PythonClass' import PythonDeclaration from './PythonDeclaration' import { PythonFilter } from './PythonFilter' diff --git a/client/src/model/python/PythonImport.test.ts b/client/src/features/apiData/model/PythonImport.test.ts similarity index 100% rename from client/src/model/python/PythonImport.test.ts rename to client/src/features/apiData/model/PythonImport.test.ts diff --git a/client/src/model/python/PythonImport.ts b/client/src/features/apiData/model/PythonImport.ts similarity index 88% rename from client/src/model/python/PythonImport.ts rename to client/src/features/apiData/model/PythonImport.ts index 7ee32d98b..d37d228b1 100644 --- a/client/src/model/python/PythonImport.ts +++ b/client/src/features/apiData/model/PythonImport.ts @@ -1,4 +1,4 @@ -import { Optional } from '../../util/types' +import { Optional } from '../../../common/util/types' export default class PythonImport { readonly module: string diff --git a/client/src/model/python/PythonModule.test.ts b/client/src/features/apiData/model/PythonModule.test.ts similarity index 100% rename from client/src/model/python/PythonModule.test.ts rename to client/src/features/apiData/model/PythonModule.test.ts diff --git a/client/src/model/python/PythonModule.ts b/client/src/features/apiData/model/PythonModule.ts similarity index 95% rename from client/src/model/python/PythonModule.ts rename to client/src/features/apiData/model/PythonModule.ts index caee8484f..189bc90ec 100644 --- a/client/src/model/python/PythonModule.ts +++ b/client/src/features/apiData/model/PythonModule.ts @@ -1,5 +1,5 @@ -import { isEmptyList } from '../../util/listOperations' -import { Optional } from '../../util/types' +import { isEmptyList } from '../../../common/util/listOperations' +import { Optional } from '../../../common/util/types' import PythonClass from './PythonClass' import PythonDeclaration from './PythonDeclaration' import { PythonFilter } from './PythonFilter' diff --git a/client/src/model/python/PythonPackage.test.ts b/client/src/features/apiData/model/PythonPackage.test.ts similarity index 100% rename from client/src/model/python/PythonPackage.test.ts rename to client/src/features/apiData/model/PythonPackage.test.ts diff --git a/client/src/model/python/PythonPackage.ts b/client/src/features/apiData/model/PythonPackage.ts similarity index 94% rename from client/src/model/python/PythonPackage.ts rename to client/src/features/apiData/model/PythonPackage.ts index 35543a77d..02744ae21 100644 --- a/client/src/model/python/PythonPackage.ts +++ b/client/src/features/apiData/model/PythonPackage.ts @@ -1,4 +1,4 @@ -import { isEmptyList } from '../../util/listOperations' +import { isEmptyList } from '../../../common/util/listOperations' import PythonDeclaration from './PythonDeclaration' import { PythonFilter } from './PythonFilter' import PythonModule from './PythonModule' diff --git a/client/src/model/python/PythonPackageBuilder.ts b/client/src/features/apiData/model/PythonPackageBuilder.ts similarity index 98% rename from client/src/model/python/PythonPackageBuilder.ts rename to client/src/features/apiData/model/PythonPackageBuilder.ts index 8b50bb7fb..430d6647d 100644 --- a/client/src/model/python/PythonPackageBuilder.ts +++ b/client/src/features/apiData/model/PythonPackageBuilder.ts @@ -1,4 +1,4 @@ -import { Optional } from '../../util/types' +import { Optional } from '../../../common/util/types' import PythonClass from './PythonClass' import PythonFromImport from './PythonFromImport' import PythonFunction from './PythonFunction' diff --git a/client/src/model/python/PythonParameter.test.ts b/client/src/features/apiData/model/PythonParameter.test.ts similarity index 100% rename from client/src/model/python/PythonParameter.test.ts rename to client/src/features/apiData/model/PythonParameter.test.ts diff --git a/client/src/model/python/PythonParameter.ts b/client/src/features/apiData/model/PythonParameter.ts similarity index 96% rename from client/src/model/python/PythonParameter.ts rename to client/src/features/apiData/model/PythonParameter.ts index c58db5431..d6752afcb 100644 --- a/client/src/model/python/PythonParameter.ts +++ b/client/src/features/apiData/model/PythonParameter.ts @@ -1,4 +1,4 @@ -import { Optional } from '../../util/types' +import { Optional } from '../../../common/util/types' import PythonDeclaration from './PythonDeclaration' import PythonFunction from './PythonFunction' diff --git a/client/src/model/python/PythonResult.test.ts b/client/src/features/apiData/model/PythonResult.test.ts similarity index 100% rename from client/src/model/python/PythonResult.test.ts rename to client/src/features/apiData/model/PythonResult.test.ts diff --git a/client/src/model/python/PythonResult.ts b/client/src/features/apiData/model/PythonResult.ts similarity index 94% rename from client/src/model/python/PythonResult.ts rename to client/src/features/apiData/model/PythonResult.ts index 0fc8e31f7..6a64c8b76 100644 --- a/client/src/model/python/PythonResult.ts +++ b/client/src/features/apiData/model/PythonResult.ts @@ -1,4 +1,4 @@ -import { Optional } from '../../util/types' +import { Optional } from '../../../common/util/types' import PythonDeclaration from './PythonDeclaration' import PythonFunction from './PythonFunction' diff --git a/client/src/Components/SelectionView/ClassView.tsx b/client/src/features/apiData/selectionView/ClassView.tsx similarity index 86% rename from client/src/Components/SelectionView/ClassView.tsx rename to client/src/features/apiData/selectionView/ClassView.tsx index 3332b37fa..7f7f91dd7 100644 --- a/client/src/Components/SelectionView/ClassView.tsx +++ b/client/src/features/apiData/selectionView/ClassView.tsx @@ -1,8 +1,8 @@ import { Box, Heading, HStack, Stack, Text } from '@chakra-ui/react' import React from 'react' -import AnnotationDropdown from '../../features/annotations/AnnotationDropdown' -import AnnotationView from '../../features/annotations/AnnotationView' -import PythonClass from '../../model/python/PythonClass' +import AnnotationDropdown from '../../annotations/AnnotationDropdown' +import AnnotationView from '../../annotations/AnnotationView' +import PythonClass from '../model/PythonClass' import DocumentationText from './DocumentationText' import SectionListViewItem from './SectionListViewItem' diff --git a/client/src/Components/SelectionView/DocumentationText.tsx b/client/src/features/apiData/selectionView/DocumentationText.tsx similarity index 100% rename from client/src/Components/SelectionView/DocumentationText.tsx rename to client/src/features/apiData/selectionView/DocumentationText.tsx diff --git a/client/src/Components/SelectionView/FunctionView.tsx b/client/src/features/apiData/selectionView/FunctionView.tsx similarity index 87% rename from client/src/Components/SelectionView/FunctionView.tsx rename to client/src/features/apiData/selectionView/FunctionView.tsx index a88fe5d7b..ad538a06f 100644 --- a/client/src/Components/SelectionView/FunctionView.tsx +++ b/client/src/features/apiData/selectionView/FunctionView.tsx @@ -1,9 +1,9 @@ import { Box, Heading, HStack, Stack, Text } from '@chakra-ui/react' import React from 'react' -import AnnotationDropdown from '../../features/annotations/AnnotationDropdown' -import AnnotationView from '../../features/annotations/AnnotationView' -import PythonFunction from '../../model/python/PythonFunction' -import { isEmptyList } from '../../util/listOperations' +import { isEmptyList } from '../../../common/util/listOperations' +import AnnotationDropdown from '../../annotations/AnnotationDropdown' +import AnnotationView from '../../annotations/AnnotationView' +import PythonFunction from '../model/PythonFunction' import DocumentationText from './DocumentationText' import ParameterNode from './ParameterNode' diff --git a/client/src/Components/SelectionView/ModuleView.tsx b/client/src/features/apiData/selectionView/ModuleView.tsx similarity index 96% rename from client/src/Components/SelectionView/ModuleView.tsx rename to client/src/features/apiData/selectionView/ModuleView.tsx index b95c6dd67..853bc9285 100644 --- a/client/src/Components/SelectionView/ModuleView.tsx +++ b/client/src/features/apiData/selectionView/ModuleView.tsx @@ -6,8 +6,8 @@ import { Light as SyntaxHighlighter } from 'react-syntax-highlighter' import python from 'react-syntax-highlighter/dist/esm/languages/hljs/python' import { atomOneDark as dark, atomOneLight as light } from 'react-syntax-highlighter/dist/esm/styles/hljs' import remarkGfm from 'remark-gfm' -import PythonModule from '../../model/python/PythonModule' -import { groupBy, isEmptyList } from '../../util/listOperations' +import { groupBy, isEmptyList } from '../../../common/util/listOperations' +import PythonModule from '../model/PythonModule' interface ModuleViewProps { pythonModule: PythonModule diff --git a/client/src/Components/SelectionView/ParameterNode.tsx b/client/src/features/apiData/selectionView/ParameterNode.tsx similarity index 85% rename from client/src/Components/SelectionView/ParameterNode.tsx rename to client/src/features/apiData/selectionView/ParameterNode.tsx index f0d0ed65e..c8e2ce7ce 100644 --- a/client/src/Components/SelectionView/ParameterNode.tsx +++ b/client/src/features/apiData/selectionView/ParameterNode.tsx @@ -1,8 +1,8 @@ import { Box, Heading, HStack, Stack, Text } from '@chakra-ui/react' import React from 'react' -import AnnotationDropdown from '../../features/annotations/AnnotationDropdown' -import AnnotationView from '../../features/annotations/AnnotationView' -import PythonParameter from '../../model/python/PythonParameter' +import AnnotationDropdown from '../../annotations/AnnotationDropdown' +import AnnotationView from '../../annotations/AnnotationView' +import PythonParameter from '../model/PythonParameter' import DocumentationText from './DocumentationText' interface ParameterNodeProps { diff --git a/client/src/Components/SelectionView/ParameterView.tsx b/client/src/features/apiData/selectionView/ParameterView.tsx similarity index 71% rename from client/src/Components/SelectionView/ParameterView.tsx rename to client/src/features/apiData/selectionView/ParameterView.tsx index 1f5b3b967..63f509b85 100644 --- a/client/src/Components/SelectionView/ParameterView.tsx +++ b/client/src/features/apiData/selectionView/ParameterView.tsx @@ -1,8 +1,7 @@ import { Heading, Stack, Text } from '@chakra-ui/react' import React from 'react' -import PythonParameter from '../../model/python/PythonParameter' +import PythonParameter from '../model/PythonParameter' import ParameterNode from './ParameterNode' -import TitleValueViewPair from './TitleValueViewPair' interface ParameterViewProps { pythonParameter: PythonParameter @@ -12,9 +11,16 @@ export default function ParameterView(props: ParameterViewProps): JSX.Element { return ( + {props.pythonParameter.hasDefault && ( - + + + Default value + + {props.pythonParameter.defaultValue} + )} + {props.pythonParameter.type && ( diff --git a/client/src/Components/SelectionView/SectionListViewItem.tsx b/client/src/features/apiData/selectionView/SectionListViewItem.tsx similarity index 63% rename from client/src/Components/SelectionView/SectionListViewItem.tsx rename to client/src/features/apiData/selectionView/SectionListViewItem.tsx index dcdea8625..0ead0b9c4 100644 --- a/client/src/Components/SelectionView/SectionListViewItem.tsx +++ b/client/src/features/apiData/selectionView/SectionListViewItem.tsx @@ -1,6 +1,6 @@ -import { Box, Heading, Stack, Text } from '@chakra-ui/react' +import { Heading, ListItem, Stack, Text, UnorderedList } from '@chakra-ui/react' import React from 'react' -import { isEmptyList } from '../../util/listOperations' +import { isEmptyList } from '../../../common/util/listOperations' interface ClassViewItemProps { title: string @@ -18,11 +18,11 @@ export default function SectionListViewItem(props: ClassViewItemProps): JSX.Elem {props.title} {!isEmptyList(props.inputElements) ? ( - props.inputElements.map((listElement, index) => ( - - {listElement} - - )) + + {props.inputElements.map((listElement, index) => ( + {listElement} + ))} + ) : ( There are no {props.title.toLowerCase()}. diff --git a/client/src/Components/SelectionView/SelectionView.tsx b/client/src/features/apiData/selectionView/SelectionView.tsx similarity index 76% rename from client/src/Components/SelectionView/SelectionView.tsx rename to client/src/features/apiData/selectionView/SelectionView.tsx index 685e59a95..3ebc380d3 100644 --- a/client/src/Components/SelectionView/SelectionView.tsx +++ b/client/src/features/apiData/selectionView/SelectionView.tsx @@ -1,11 +1,11 @@ import { Box } from '@chakra-ui/react' import React from 'react' import { useLocation } from 'react-router' -import PythonClass from '../../model/python/PythonClass' -import PythonFunction from '../../model/python/PythonFunction' -import PythonModule from '../../model/python/PythonModule' -import PythonPackage from '../../model/python/PythonPackage' -import PythonParameter from '../../model/python/PythonParameter' +import PythonClass from '../model/PythonClass' +import PythonFunction from '../model/PythonFunction' +import PythonModule from '../model/PythonModule' +import PythonPackage from '../model/PythonPackage' +import PythonParameter from '../model/PythonParameter' import ClassView from './ClassView' import FunctionView from './FunctionView' import ModuleView from './ModuleView' diff --git a/client/src/features/apiData/treeView/ClassNode.tsx b/client/src/features/apiData/treeView/ClassNode.tsx new file mode 100644 index 000000000..43f56f47c --- /dev/null +++ b/client/src/features/apiData/treeView/ClassNode.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import { FaChalkboard } from 'react-icons/fa' +import { isEmptyList } from '../../../common/util/listOperations' +import PythonClass from '../model/PythonClass' +import TreeNode from './TreeNode' + +interface ClassNodeProps { + pythonClass: PythonClass +} + +const ClassNode: React.FC = (props: ClassNodeProps) => { + const hasMethods = !isEmptyList(props.pythonClass.methods) + + return +} + +export default ClassNode diff --git a/client/src/features/apiData/treeView/FunctionNode.tsx b/client/src/features/apiData/treeView/FunctionNode.tsx new file mode 100644 index 000000000..a5b17af53 --- /dev/null +++ b/client/src/features/apiData/treeView/FunctionNode.tsx @@ -0,0 +1,17 @@ +import React from 'react' +import { FaCogs } from 'react-icons/fa' +import { isEmptyList } from '../../../common/util/listOperations' +import PythonFunction from '../model/PythonFunction' +import TreeNode from './TreeNode' + +interface FunctionNodeProps { + pythonFunction: PythonFunction +} + +const FunctionNode: React.FC = ({ pythonFunction }) => { + const hasParameters = !isEmptyList(pythonFunction.parameters) + + return +} + +export default FunctionNode diff --git a/client/src/features/apiData/treeView/ModuleNode.tsx b/client/src/features/apiData/treeView/ModuleNode.tsx new file mode 100644 index 000000000..324b9c732 --- /dev/null +++ b/client/src/features/apiData/treeView/ModuleNode.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import { FaArchive } from 'react-icons/fa' +import { isEmptyList } from '../../../common/util/listOperations' +import PythonModule from '../model/PythonModule' +import TreeNode from './TreeNode' + +interface ModuleNodeProps { + pythonModule: PythonModule +} + +const ModuleNode: React.FC = ({ pythonModule }) => { + const hasClasses = !isEmptyList(pythonModule.classes) + const hasFunctions = !isEmptyList(pythonModule.functions) + const hasChildren = hasClasses || hasFunctions + + return +} + +export default ModuleNode diff --git a/client/src/features/apiData/treeView/ParameterNode.tsx b/client/src/features/apiData/treeView/ParameterNode.tsx new file mode 100644 index 000000000..6d8b72a38 --- /dev/null +++ b/client/src/features/apiData/treeView/ParameterNode.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { FaKeyboard } from 'react-icons/fa' +import PythonParameter from '../model/PythonParameter' +import TreeNode from './TreeNode' + +interface ParameterNodeProps { + pythonParameter: PythonParameter +} + +const ParameterNode: React.FC = ({ pythonParameter }) => { + return +} + +export default ParameterNode diff --git a/client/src/features/apiData/treeView/TreeNode.tsx b/client/src/features/apiData/treeView/TreeNode.tsx new file mode 100644 index 000000000..13d7d8d61 --- /dev/null +++ b/client/src/features/apiData/treeView/TreeNode.tsx @@ -0,0 +1,66 @@ +import { HStack, Icon, Text } from '@chakra-ui/react' +import React from 'react' +import { IconType } from 'react-icons/lib' +import { useLocation } from 'react-router' +import { useHistory } from 'react-router-dom' +import { useAppDispatch, useAppSelector } from '../../../app/hooks' +import { ChildrenProp } from '../../../common/util/types' +import { selectIsExpandedInTreeView, toggleExpandedInTreeView } from '../apiDataSlice' +import PythonDeclaration from '../model/PythonDeclaration' +import VisibilityIndicator from './VisibilityIndicator' + +interface TreeNodeProps extends ChildrenProp { + declaration: PythonDeclaration + icon: IconType + isExpandable: boolean +} + +const TreeNode: React.FC = ({ children, declaration, icon, isExpandable }) => { + const currentPathname = useLocation().pathname + const history = useHistory() + const dispatch = useAppDispatch() + + const showChildren = useAppSelector(selectIsExpandedInTreeView(declaration.pathAsString())) + + const level = levelOf(declaration) + const paddingLeft = level === 0 ? '1rem' : `${1 + 0.75 * level}rem` + const backgroundColor = isSelected(declaration, currentPathname) ? 'cornflowerblue' : undefined + const color = isSelected(declaration, currentPathname) ? 'white' : undefined + + const handleClick = () => { + dispatch(toggleExpandedInTreeView(declaration.pathAsString())) + history.push(`/${declaration.pathAsString()}`) + } + + return ( + <> + + + + {declaration.name} + + {showChildren && children} + + ) +} + +function levelOf(declaration: PythonDeclaration): number { + return declaration.path().length - 2 +} + +function isSelected(declaration: PythonDeclaration, currentPathname: string): boolean { + return `/${declaration.pathAsString()}` === currentPathname +} + +export default TreeNode diff --git a/client/src/features/apiData/treeView/TreeView.tsx b/client/src/features/apiData/treeView/TreeView.tsx new file mode 100644 index 000000000..efe2674c0 --- /dev/null +++ b/client/src/features/apiData/treeView/TreeView.tsx @@ -0,0 +1,74 @@ +import { Box } from '@chakra-ui/react' +import React from 'react' +import AutoSizer from 'react-virtualized-auto-sizer' +import { FixedSizeList, ListChildComponentProps } from 'react-window' +import { useAppSelector } from '../../../app/hooks' +import { selectAllExpandedInTreeView } from '../apiDataSlice' +import PythonClass from '../model/PythonClass' +import PythonDeclaration from '../model/PythonDeclaration' +import PythonFunction from '../model/PythonFunction' +import PythonModule from '../model/PythonModule' +import PythonPackage from '../model/PythonPackage' +import PythonParameter from '../model/PythonParameter' +import ClassNode from './ClassNode' +import FunctionNode from './FunctionNode' +import ModuleNode from './ModuleNode' +import ParameterNode from './ParameterNode' + +interface TreeViewProps { + pythonPackage: PythonPackage +} + +const TreeView: React.FC = ({ pythonPackage }) => { + const allExpandedInTreeView = useAppSelector(selectAllExpandedInTreeView) + const children = walkChildrenWithPreOrder(allExpandedInTreeView, pythonPackage) + + return ( + + {({ height }) => ( + + {TreeNodeGenerator} + + )} + + ) +} + +function walkChildrenWithPreOrder( + allExpandedInTreeView: { [target: string]: true }, + declaration: PythonDeclaration, +): PythonDeclaration[] { + return declaration.children().flatMap((it) => { + if (allExpandedInTreeView[it.pathAsString()]) { + return [it, ...walkChildrenWithPreOrder(allExpandedInTreeView, it)] + } else { + return [it] + } + }) +} + +const TreeNodeGenerator: React.FC = ({ data, index, style }) => { + const declaration = data[index] + + return ( + + {declaration instanceof PythonModule && } + {declaration instanceof PythonClass && } + {declaration instanceof PythonFunction && } + {declaration instanceof PythonParameter && } + + ) +} + +export default TreeView diff --git a/client/src/Components/TreeView/VisibilityIndicator.tsx b/client/src/features/apiData/treeView/VisibilityIndicator.tsx similarity index 69% rename from client/src/Components/TreeView/VisibilityIndicator.tsx rename to client/src/features/apiData/treeView/VisibilityIndicator.tsx index 06ee6738b..d5b76e0d9 100644 --- a/client/src/Components/TreeView/VisibilityIndicator.tsx +++ b/client/src/features/apiData/treeView/VisibilityIndicator.tsx @@ -8,20 +8,17 @@ interface VisibilityIndicatorProps { isSelected?: boolean } -export default function VisibilityIndicator({ - hasChildren, - showChildren, - isSelected = false, -}: VisibilityIndicatorProps): JSX.Element { +const VisibilityIndicator: React.FC = ({ hasChildren, showChildren, isSelected = false }) => { const isClosed = !isSelected && !showChildren const closedColor = useColorModeValue('gray.200', 'gray.700') return ( ) } + +export default VisibilityIndicator diff --git a/client/tsconfig.json b/client/tsconfig.json index abcca0c4c..37b96f2cd 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -15,6 +15,6 @@ "noEmit": true, "jsx": "react" }, - "include": ["./src"], + "include": ["src/"], "exclude": ["**/*.test.ts"] }