Skip to content

Commit

Permalink
Merge pull request #17 from rafgugi/edit-yaml-improvement
Browse files Browse the repository at this point in the history
feat: enable open and save the family data file, add diagram preview when editing yaml
  • Loading branch information
rafgugi committed Aug 6, 2023
2 parents 99c340b + e1c2d63 commit 37bc0a9
Show file tree
Hide file tree
Showing 6 changed files with 432 additions and 260 deletions.
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"dependencies": {
"bootstrap": "^5.2.3",
"file-saver": "^2.0.5",
"gojs": "^2.3.6",
"lodash": "^4.17.21",
"react": "^18.2.0",
Expand Down Expand Up @@ -40,6 +41,7 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/file-saver": "^2.0.5",
"@types/jest": "^27.5.2",
"@types/lodash": "^4.14.195",
"@types/node": "^16.18.25",
Expand Down
59 changes: 56 additions & 3 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Button, Container, Form, FormGroup, Input, Label } from 'reactstrap';
import { useMemo, useState } from 'react';
import { stringify } from 'yaml';
import { parse, stringify } from 'yaml';
import { saveAs } from 'file-saver';
import { Person } from '../family.interface';
import AppContext from './AppContext';
import Family from './Family';
Expand All @@ -9,7 +10,12 @@ import ModalAddSpouse from './ModalAddSpouse';
import ModalAddTree from './ModalAddTree';
import ModalDeletePerson from './ModalDeletePerson';
import ModalEditYaml from './ModalEditYaml';
import { deletePerson, enrichTreeData, treesToRecord } from '../family.util';
import {
deletePerson,
enrichTreeData,
treesToRecord,
unrichTreeData,
} from '../family.util';
import { useCache } from '../useCache';

interface AppProps {
Expand Down Expand Up @@ -53,7 +59,7 @@ function App(props: AppProps) {
const [showModalEditYaml, setShowModalEditYaml] = useState(false);
const toggleModalEditYaml = () => setShowModalEditYaml(!showModalEditYaml);
const openModalEditYaml = () => {
setTreeYaml(stringify(trees));
setTreeYaml(stringify(unrichTreeData(trees)));
setShowModalEditYaml(true);
};

Expand All @@ -65,6 +71,39 @@ function App(props: AppProps) {
setShowModalDeletePerson(true);
};

const handleSave = () => {
try {
const unrichedTrees = unrichTreeData(trees);
const treeYaml = stringify(unrichedTrees as {});
const blob = new Blob([treeYaml], { type: 'text/yaml;charset=utf-8' });
saveAs(blob, 'family_data.yaml');
} catch (error) {
console.error('Error saving YAML file:', error);
}
};

const handleLoad = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = () => {
try {
const treeYaml = reader.result as string;
// assert valid treeYaml
const rawFamilyData = parse(treeYaml);
const trees = enrichTreeData(rawFamilyData?.trees, rawFamilyData?.people);

const unrichedTrees = unrichTreeData(trees);
setTreeYaml(stringify(unrichedTrees as {}));
setShowModalEditYaml(true);
} catch (error) {
console.error('Error loading YAML file:', error);
}
};
reader.readAsText(file);
}
};

const treeMap = useMemo(() => treesToRecord(trees), [trees]);

const upsertPerson = (person: Person) => {
Expand Down Expand Up @@ -174,6 +213,20 @@ function App(props: AppProps) {
Delete person
</Button>
</FormGroup>
<FormGroup>
<Button size="sm" tag="label">
Import
<Input
type="file"
className="d-none"
accept=".yaml, .yml"
onChange={handleLoad}
/>
</Button>{' '}
<Button size="sm" onClick={handleSave}>
Export
</Button>
</FormGroup>
</Form>
</Container>

Expand Down
40 changes: 28 additions & 12 deletions src/components/ModalEditYaml.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
} from 'react';
import {
Button,
Card,
CardBody,
FormFeedback,
FormGroup,
Input,
Expand All @@ -16,9 +18,11 @@ import {
ModalFooter,
ModalHeader,
} from 'reactstrap';
import { Person } from '../family.interface';
import { enrichTreeData } from '../family.util';
import { parse } from 'yaml';
import AppContext from './AppContext';
import FamilyDiagram from './FamilyDiagram';

interface ModalDeletePersonProps {
treeYaml: string;
Expand All @@ -30,36 +34,40 @@ interface ModalDeletePersonProps {
const MIN_ROW = 3;
const MAX_ROW = 20;

function ModalDeletePerson({
function ModalEditYaml({
treeYaml,
setTreeYaml,
isOpen,
toggle,
}: ModalDeletePersonProps) {
const { setTreesValue } = useContext(AppContext);
const [yamlError, setYamlError] = useState('');
const [trees, setTrees] = useState([] as Person[]);
const deferredTree = useDeferredValue(trees);
const deferredTreeYaml = useDeferredValue(treeYaml);
const loading = deferredTreeYaml !== treeYaml;

const validForm = treeYaml && !yamlError;

const treesFromYaml = (yaml: string) => {
const rawFamilyData = parse(yaml);
return enrichTreeData(rawFamilyData?.trees, rawFamilyData?.people);
};

useEffect(() => {
try {
// test parsing the yaml
enrichTreeData(parse(deferredTreeYaml), []);
if (yamlError !== '') {
setYamlError('');
}
const trees = treesFromYaml(deferredTreeYaml);
setTrees(trees);
setYamlError('');
} catch (e: any) {
setYamlError(e.message);
}
}, [deferredTreeYaml, yamlError]);
}, [deferredTreeYaml]);

const rows = useMemo(() => {
const lines = treeYaml.split('\n').length;
if (lines < MIN_ROW) return MIN_ROW;
if (lines > MAX_ROW) return MAX_ROW;
return lines;
return Math.min(Math.max(lines, MIN_ROW), MAX_ROW);
}, [treeYaml]);

const handleTreeYamlChange = (event: React.ChangeEvent<HTMLInputElement>) => {
Expand All @@ -70,7 +78,7 @@ function ModalDeletePerson({
const handleSubmit = () => {
if (loading || !validForm) return;

const trees = parse(treeYaml);
const trees = treesFromYaml(treeYaml);
setTreesValue(trees);
toggle();
};
Expand All @@ -80,7 +88,15 @@ function ModalDeletePerson({
<ModalHeader toggle={toggle}>Edit tree</ModalHeader>
<ModalBody>
<FormGroup>
<Label for="edit-tree">Tree</Label>
<Label for="tree-preview">Tree preview</Label>
<Card for="tree-preview" outline color={validForm ? '' : 'danger'}>
<CardBody style={{ opacity: validForm ? 1 : 0.3 }}>
<FamilyDiagram trees={deferredTree} />
</CardBody>
</Card>
</FormGroup>
<FormGroup>
<Label for="edit-tree">Edit tree</Label>
<Input
type="textarea"
id="edit-tree"
Expand All @@ -106,4 +122,4 @@ function ModalDeletePerson({
);
}

export default ModalDeletePerson;
export default ModalEditYaml;
Loading

0 comments on commit 37bc0a9

Please sign in to comment.