Skip to content
This repository has been archived by the owner on Oct 18, 2024. It is now read-only.

feat: added json validation and import error modal #25

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions electron/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,26 +108,26 @@ async function openFolder() {
});
const { filePaths } = data;
console.log(`filePath`, filePaths[0]);
const files = await fs.readdir(filePaths[0]);
const promises = files.map((file: string) => {
const fileType = file.split('.').pop()?.toLowerCase();
if (fileType === 'json' || fileType === 'geojson') {
console.log('(geo)json found');
return fs.readFile(`${filePaths[0]}${path.sep}${file}`);
}
const files = await fs
.readdir(filePaths[0])
.then((f) => f.map((file) => `${filePaths[0]}${path.sep}${file}`));
const validFiles = files.filter((filename: string) => {
const fileType = filename.split('.').pop()?.toLowerCase();
return fileType === 'json' || fileType === 'geojson';
});

const mapData = await Promise.all(promises);
console.log(`MAIN: ${fileChannel}: `);
console.dir(
mapData.map((data) => (data ? JSON.parse(data.toString()) : null))
);
win?.webContents.send(
fileChannel,
mapData
.map((data) => (data ? JSON.parse(data.toString()) : null))
.filter((data) => !!data)
const filesData = await Promise.all(
validFiles.map((filepath) =>
fs.readFile(filepath).then((content) => ({
filepath,
content: JSON.parse(content.toString())
}))
)
);

console.log(`MAIN: ${fileChannel}: `);
console.dir(filesData);
win?.webContents.send(fileChannel, filesData);
} catch (error) {
console.error(error);
}
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@testing-library/user-event": "^12.1.10",
"@types/electron-devtools-installer": "^2.2.0",
"@types/jest": "^26.0.15",
"@types/lodash": "^4.14.172",
"@types/node": "^12.0.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
Expand All @@ -29,6 +30,8 @@
"electron-is-dev": "^1.2.0",
"electron-reload": "^1.5.0",
"eslint": "^7.28.0",
"jsonschema": "^1.4.0",
"lodash": "^4.17.21",
"mapbox-gl": "^2.3.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
Expand Down
35 changes: 28 additions & 7 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
/* eslint @typescript-eslint/no-var-requires: "off" */
import React, { useCallback, useEffect, useState } from 'react';
import { FeatureCollection, Feature } from 'geojson';
import Map from './Map';
import styled, {
createGlobalStyle // TODO: Establish a theme and hook this up to everything
} from 'styled-components';
import Left from './Left';
import Properties from './Properties';
import type { IpcRendererEvent } from 'electron';
import { MapEvent } from 'react-map-gl';
import { partition } from 'lodash';
import { isFeatureCollection } from './util/validateJson';
import Map from './Map';
import Left from './Left';
import Properties from './Properties';
import FileErrorModal from './components/Modal/FileErrorModal';

const { ipcRenderer } = window.require('electron');

Expand All @@ -29,20 +32,32 @@ const carleton = {
longitude: -75.69608
};

interface FileData {
filepath: string;
content: FeatureCollection;
}

const fileChannel = 'file-content';
const App: React.FC = () => {
const [feature, setFeature] = useState<Feature[] | undefined>(undefined);
const [mapData, setData] = useState<FeatureCollection[] | null>(null);
const [errorFiles, setErrorFiles] = useState<string[]>([]);

const displayFeature = useCallback(({ features }: MapEvent) => {
setFeature(features);
}, []);
const [mapData, setData] = useState<FeatureCollection[] | null>(null);

const onFileOpen = useCallback(
(event: IpcRendererEvent, contents: FeatureCollection[]) => {
(event: IpcRendererEvent, contents: FileData[]) => {
console.log(`RENDERER: ${fileChannel}`, contents);
setData(contents);
// TODO: Add file validation
const [validFiles, invalidFiles] = partition(contents, (f) =>
isFeatureCollection(f.content)
);
// if a bad file was found, num of validated files will be less than input contents
if (validFiles.length < contents.length) {
setErrorFiles(invalidFiles.map((f) => f.filepath));
}
setData(validFiles.map((f) => f.content));
},
[]
);
Expand All @@ -67,6 +82,12 @@ const App: React.FC = () => {
displayFeature={displayFeature}
/>
<Properties features={feature} />
{errorFiles.length > 0 ? (
<FileErrorModal
onClose={() => setErrorFiles([])}
files={errorFiles}
/>
) : null}
Comment on lines +85 to +90
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{errorFiles.length > 0 ? (
<FileErrorModal
onClose={() => setErrorFiles([])}
files={errorFiles}
/>
) : null}
{errorFiles.length > 0 && (
<FileErrorModal
onClose={() => setErrorFiles([])}
files={errorFiles}
/>
)}

</StyledDiv>
</>
);
Expand Down
51 changes: 51 additions & 0 deletions src/components/Modal/FileErrorModal/FileErrorModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import styled from 'styled-components';
import Modal from '..';

interface FileErrorModalProps {
files: string[];
onClose: () => void;
}

const ErrorModal: React.FC<FileErrorModalProps> = ({
onClose,
files
}: FileErrorModalProps) => {
return (
<Modal onClose={onClose}>
<ErrorHeader>There was an error importing some files.</ErrorHeader>
<ErrorText>Files that could not be imported:</ErrorText>
Comment on lines +16 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a header and description value to FileErrorModalProps to make it easier to reuse in the future.

<ErrorList>
{files.map((file, i) => (
<ErrorItem key={`${file}-${i}`}>{file}</ErrorItem>
))}
</ErrorList>
Comment on lines +18 to +22
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise for the error content, the list could be specified via a prop.

</Modal>
);
};

const ErrorHeader = styled.h2`
font-size: 1rem;
line-height: 1.5rem;
margin: 0px;
margin-bottom: 1em;
`;

const ErrorText = styled.p`
font-size: 0.875rem;
line-height: 1.25rem;
margin: 0px;
`;

const ErrorList = styled.ul`
padding-left: 1em;
margin: 0px;
`;

const ErrorItem = styled.li`
font-size: 0.875rem;
line-height: 1.25rem;
margin: 0px;
`;

export default ErrorModal;
3 changes: 3 additions & 0 deletions src/components/Modal/FileErrorModal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import FileErrorModal from './FileErrorModal';

export default FileErrorModal;
85 changes: 85 additions & 0 deletions src/components/Modal/Modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import styled from 'styled-components';

interface ModalProps {
onClose: () => void;
children?: React.ReactNode;
}

const Modal: React.FC<ModalProps> = ({ onClose, children }: ModalProps) => {
return (
<ModalContainer>
<ModalBackground onClick={() => onClose()} />
<ModalContent>
<ModalHeader>
<ModalCloseButton onClick={() => onClose()}>
<svg
xmlns='http://www.w3.org/2000/svg'
style={{ width: '1.25rem' }}
fill='none'
viewBox='0 0 24 24'
stroke='currentColor'
>
<path
strokeLinecap='round'
strokeLinejoin='round'
strokeWidth={2}
d='M6 18L18 6M6 6l12 12'
/>
</svg>
</ModalCloseButton>
</ModalHeader>
{children}
</ModalContent>
</ModalContainer>
);
};

const ModalContainer = styled.div`
position: fixed;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
`;

const ModalBackground = styled.div`
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
background-color: #000;
opacity: 0.8;
`;

const ModalContent = styled.div`
position: relative;
display: flex;
flex-direction: column;
background-color: #fff;
opacity: 1;
padding: 2em;
padding-bottom: 3em;
border-radius: 0.25rem;
`;

const ModalHeader = styled.div`
display: flex;
justify-content: flex-end;
width: 100%;
`;

const ModalCloseButton = styled.button`
margin: 0px;
padding: 0px;
cursor: pointer;
background: none;
border: none;
`;

export default Modal;
3 changes: 3 additions & 0 deletions src/components/Modal/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Modal from './Modal';

export default Modal;
Loading