diff --git a/src/components/InputPanel.tsx b/src/components/InputPanel.tsx deleted file mode 100644 index 6798aaa..0000000 --- a/src/components/InputPanel.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { useCallback, useContext, useEffect, useMemo, useState } from 'react'; -import InputContext, { INPUT_TYPES } from '../contexts/InputContext'; -import styles from '../css/RMLMappingEditor.module.scss'; -import CodeEditor from './CodeEditor'; -import { ReactComponent as PlusIcon } from '../images/plus.svg'; -import { ReactComponent as DownArrow } from '../images/down-arrow.svg'; - -const views = { - inputs: 'Input Files', - // functions: 'Functions', -} - -export interface InputPanelProps { - addNewInput: () => void; -} - -function InputPanel({ addNewInput }: InputPanelProps) { - const [view, setView] = useState(views.inputs); - const { inputFiles, setInputFiles } = useContext(InputContext); - const [selectedInputFileIndex, setSelectedInputFileIndex] = useState(); - const selectedInputFile = useMemo( - () => selectedInputFileIndex !== undefined ? inputFiles[selectedInputFileIndex] : undefined, - [inputFiles, selectedInputFileIndex] - ); - const [prevInputFilesLength, setPrevInputFilesLength] = useState(inputFiles.length); - - const inputType = useMemo(() => { - if (selectedInputFile) { - if (selectedInputFile.name.endsWith('.json')) { - return INPUT_TYPES.json; - } else if (selectedInputFile.name.endsWith('.xml')) { - return INPUT_TYPES.xml; - } else if (selectedInputFile.name.endsWith('.csv')) { - return INPUT_TYPES.csv; - } - } - }, [selectedInputFile]); - - const changeToInputView = useCallback(() => setView(views.inputs), [setView]); - - const updateSelectedInputFile = useCallback((input: string) => { - if (selectedInputFileIndex !== undefined) { - setInputFiles(inputFiles.map((inputFile, index) => { - if (index === selectedInputFileIndex) { - return { ...inputFile, contents: input } - } - return inputFile; - })) - } - }, [selectedInputFileIndex, setInputFiles, inputFiles]); - - const closeSelectedInputFile = useCallback(() => setSelectedInputFileIndex(undefined), []); - - useEffect(() => { - if (inputFiles.length !== prevInputFilesLength) { - setPrevInputFilesLength(inputFiles.length); - if (inputFiles.length > prevInputFilesLength) { - setSelectedInputFileIndex(inputFiles.length-1); - } - } - }, [inputFiles, prevInputFilesLength]); - - return ( -
-
- -
- - {/* - */} -
- { !selectedInputFile && ( -
- { inputFiles.map((inputFile, index) => { - return
{inputFile.name}
- })} -
- )} - { selectedInputFile && ( - <> -
- -
{selectedInputFile.name}
-
- - - )} -
- ) -} - -export default InputPanel; \ No newline at end of file diff --git a/src/components/InputPanel/InputItem.tsx b/src/components/InputPanel/InputItem.tsx new file mode 100644 index 0000000..d22262e --- /dev/null +++ b/src/components/InputPanel/InputItem.tsx @@ -0,0 +1,20 @@ +import React, { PropsWithChildren, useCallback } from 'react'; +import styles from '../../css/RMLMappingEditor.module.scss'; + +interface InputItemProps extends PropsWithChildren { + onClick: (index: number) => void; + index: number; +} + +function InputItem({ onClick, index, children }: InputItemProps) { + const handleClick = useCallback(() => { + onClick(index); + }, [index, onClick]); + return ( +
+ {children} +
+ ); +} + +export default InputItem; diff --git a/src/components/InputPanel/ViewButton.tsx b/src/components/InputPanel/ViewButton.tsx new file mode 100644 index 0000000..c044eb7 --- /dev/null +++ b/src/components/InputPanel/ViewButton.tsx @@ -0,0 +1,28 @@ +import React, { PropsWithChildren, useCallback } from 'react'; +import { ViewType } from '../../util/TypeUtil'; +import styles from '../../css/RMLMappingEditor.module.scss'; + +interface ViewButtonProps extends PropsWithChildren { + name: ViewType; + onClick: (name: ViewType) => void; + isSelected: boolean; +} + +function ViewButton({ name, onClick, isSelected, children }: ViewButtonProps) { + const handleClick = useCallback(() => { + onClick(name); + }, [onClick, name]); + + return ( + + ); +} + +export default ViewButton; diff --git a/src/components/InputPanel/index.tsx b/src/components/InputPanel/index.tsx new file mode 100644 index 0000000..280a7eb --- /dev/null +++ b/src/components/InputPanel/index.tsx @@ -0,0 +1,178 @@ +import React, { + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; +import InputContext, { INPUT_TYPES } from '../../contexts/InputContext'; +import styles from '../../css/RMLMappingEditor.module.scss'; +import CodeEditor from '../CodeEditor'; +import { ReactComponent as PlusIcon } from '../../images/plus.svg'; +import { ReactComponent as DownArrow } from '../../images/down-arrow.svg'; +import ViewButton from './ViewButton'; +import { ViewType } from '../../util/TypeUtil'; +import InputItem from './InputItem'; + +const views: Record = { + [ViewType.INPUTS]: 'Input Files', + [ViewType.FUNCTIONS]: 'Functions', +}; +// type ViewType = keyof typeof views; + +const functionList = [ + { + name: 'Capitalize', + function: (str: string) => str.toUpperCase(), + }, + { + name: 'Lower case', + function: (str: string) => str.toLowerCase(), + }, + { + name: 'Concatenate', + function: (strList: string[], delimiter: '-') => strList.join(delimiter), + }, +]; + +export interface InputPanelProps { + addNewInput: () => void; +} + +function InputPanel({ addNewInput }: InputPanelProps) { + const [view, setView] = useState(ViewType.INPUTS); + const { inputFiles, setInputFiles } = useContext(InputContext); + const [selectedInputFileIndex, setSelectedInputFileIndex] = + useState(); + const selectedInputFile = useMemo( + () => + selectedInputFileIndex !== undefined + ? inputFiles[selectedInputFileIndex] + : undefined, + [inputFiles, selectedInputFileIndex] + ); + const [prevInputFilesLength, setPrevInputFilesLength] = useState( + inputFiles.length + ); + + const inputType = useMemo(() => { + if (selectedInputFile) { + if (selectedInputFile.name.endsWith('.json')) { + return INPUT_TYPES.json; + } else if (selectedInputFile.name.endsWith('.xml')) { + return INPUT_TYPES.xml; + } else if (selectedInputFile.name.endsWith('.csv')) { + return INPUT_TYPES.csv; + } + } + }, [selectedInputFile]); + + const changeView = useCallback((view: ViewType) => setView(view), [setView]); + + const updateSelectedInputFile = useCallback( + (input: string) => { + if (selectedInputFileIndex !== undefined) { + setInputFiles( + inputFiles.map((inputFile, index) => { + if (index === selectedInputFileIndex) { + return { ...inputFile, contents: input }; + } + return inputFile; + }) + ); + } + }, + [selectedInputFileIndex, setInputFiles, inputFiles] + ); + + const closeSelectedInputFile = useCallback( + () => setSelectedInputFileIndex(undefined), + [] + ); + + useEffect(() => { + if (inputFiles.length !== prevInputFilesLength) { + setPrevInputFilesLength(inputFiles.length); + if (inputFiles.length > prevInputFilesLength) { + setSelectedInputFileIndex(inputFiles.length - 1); + } + } + }, [inputFiles, prevInputFilesLength]); + + return ( +
+
+ {/* */} + {Object.keys(views).map((viewType) => ( + + {views[viewType as ViewType]} + + ))} +
+ + {/* + */} +
+ + {!selectedInputFile && view === 'inputs' && ( +
+ {inputFiles.map((inputFile, index) => { + return ( + + {inputFile.name} + + ); + })} +
+ )} + {view === 'functions' && ( +
+
Sample function list
+ {functionList.map((fn) => ( +
+ {fn.name} +
+ ))} +
+ )} + {selectedInputFile && ( + <> +
+ +
+ {selectedInputFile.name} +
+
+ + + )} +
+ ); +} + +export default InputPanel; diff --git a/src/css/RMLMappingEditor.module.scss b/src/css/RMLMappingEditor.module.scss index 0f7f936..039d2cf 100644 --- a/src/css/RMLMappingEditor.module.scss +++ b/src/css/RMLMappingEditor.module.scss @@ -14,11 +14,11 @@ .rmlEditorDark { composes: rmlEditor; color-scheme: dark; - + --header-icon-color: rgb(227, 230, 234); --secondary-icon-color: rgb(84, 88, 93); --primary-icon-color: rgb(142, 146, 150); - + --primary-text-color: rgb(215, 218, 224); --secondary-text-color: #abb2bf; --tertiary-text-color: rgba(215, 218, 224, 0.5); @@ -97,7 +97,6 @@ justify-content: center; } - .header { composes: centered; padding: 0 10px; @@ -133,18 +132,18 @@ .draggableViewSection::after { z-index: 2; - content: ''; + content: ""; -webkit-transition: background-color 0.2s; transition: background-color 0.2s; } -.draggableViewSection .dragHandle, +.draggableViewSection .dragHandle, .draggableViewSection::after { position: absolute; background-color: transparent; } -.draggableViewSection.dragHandleLongHover::after, +.draggableViewSection.dragHandleLongHover::after, .draggableViewSection.dragging::after { background-color: var(--context-menu-border-color); } @@ -163,7 +162,7 @@ .draggableViewContainer:not(.vertical) { .draggableViewSection > .dragHandle, - .draggableViewSection.dragHandleLongHover::after, + .draggableViewSection.dragHandleLongHover::after, .draggableViewSection.dragging::after { height: 100%; right: -3px; @@ -175,7 +174,7 @@ .panel { background-color: var(--side-panel-background-color); - height:100%; + height: 100%; } .stretch { @@ -229,7 +228,7 @@ display: flex; align-items: center; width: 100%; - height:32px; + height: 32px; padding: 0 6px; border-bottom: 1px solid var(--header-border-color); } @@ -275,7 +274,7 @@ align-items: center; justify-content: flex-end; width: 100%; - height:32px; + height: 32px; padding: 0 10px; font-size: 13px; color: var(--warning-text-color); @@ -294,13 +293,13 @@ overflow: auto; } -.inputFilesList { +.inputItemsList { composes: stretch; overflow: auto; padding: 8px; } -.inputFile { +.inputItem { cursor: pointer; padding: 7px 10px; border-radius: 5px; @@ -312,11 +311,11 @@ color: var(--secondary-text-color); } -.inputFile:hover { +.inputItem:hover { background-color: var(--explorer-item-hover-color); } -.inputFile.Selected { +.inputItem.Selected { background-color: var(--explorer-item-selected-color); color: var(--primary-text-color); } @@ -332,7 +331,7 @@ top: 0; bottom: 0; z-index: 101; - background-color: rgba(0,0,0,0.5); + background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; @@ -349,10 +348,10 @@ flex-direction: column; background-color: var(--main-background-color); border: 1px solid var(--dropdown-border-color); - } -.modalHeader, .modalFooter { +.modalHeader, +.modalFooter { padding: 20px 20px 15px 20px; display: flex; align-items: center; @@ -416,6 +415,11 @@ &:hover { background-color: var(--button-selected-color); - color: var(--button-selected-text-color) + color: var(--button-selected-text-color); } } + +.functionsListHeader { + color: var(--button-selected-text-color); + padding: 8px 12px; +} diff --git a/src/util/TypeUtil.ts b/src/util/TypeUtil.ts index 29af3c9..d478fda 100644 --- a/src/util/TypeUtil.ts +++ b/src/util/TypeUtil.ts @@ -12,4 +12,9 @@ Pick> export type ValueOf = T[keyof T]; -export type ClickEvent = React.MouseEvent; \ No newline at end of file +export type ClickEvent = React.MouseEvent; + +export enum ViewType { + INPUTS = 'inputs', + FUNCTIONS = 'functions', +}