Skip to content

Commit

Permalink
feat: add ability to edit through tree view (#376)
Browse files Browse the repository at this point in the history
  • Loading branch information
AykutSarac authored Jan 13, 2024
1 parent 874476d commit d3f7bb8
Show file tree
Hide file tree
Showing 7 changed files with 293 additions and 74 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
"lodash.debounce": "^4.0.8",
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2",
"lodash.unset": "^4.5.2",
"lodash.update": "^4.10.2",
"maketypes": "^1.1.2",
"million": "^2.6.4",
"next": "13.5.6",
Expand Down Expand Up @@ -66,6 +68,8 @@
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.get": "^4.4.9",
"@types/lodash.set": "^4.3.9",
"@types/lodash.unset": "^4.5.9",
"@types/lodash.update": "^4.10.9",
"@types/node": "^20.4.7",
"@types/react": "18.2.45",
"@types/react-dom": "^18.2.18",
Expand Down
32 changes: 32 additions & 0 deletions pnpm-lock.yaml

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

74 changes: 0 additions & 74 deletions src/containers/Views/TreeView.tsx

This file was deleted.

87 changes: 87 additions & 0 deletions src/containers/Views/TreeView/EditModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from "react";
import { Button, Divider, Group, Modal, TextInput } from "@mantine/core";
import _unset from "lodash.unset";
import _update from "lodash.update";
import { VscLock } from "react-icons/vsc";
import useFile from "src/store/useFile";
import useJson from "src/store/useJson";
import useModal from "src/store/useModal";
import useUser from "src/store/useUser";

interface EditModalProps {
opened: boolean;
setOpened: React.Dispatch<React.SetStateAction<boolean>>;
selectedValue: string | number | null;
path: (string | number)[];
value: string;
setValue: React.Dispatch<React.SetStateAction<string>>;
errorMessage: string | null;
setErrorMessage: React.Dispatch<React.SetStateAction<string | null>>;
}

export const EditModal = ({
opened,
setOpened,
selectedValue,
path,
value,
setValue,
errorMessage,
setErrorMessage,
}: EditModalProps) => {
const setContents = useFile(state => state.setContents);
const getJson = useJson(state => state.getJson);
const showPremiumModal = useModal(state => state.setVisible("premium"));
const premium = useUser(state => state.premium);

return (
<Modal centered title={selectedValue} opened={opened} onClose={() => setOpened(false)}>
<TextInput
value={value}
onChange={e => {
setValue(e.currentTarget.value);
}}
error={errorMessage}
/>
<Divider my="md" />
<Group justify="right">
<Button
color="red"
onClick={() => {
try {
if (!premium) return showPremiumModal(true);
const updatedJson = JSON.parse(getJson());
_unset(updatedJson, path);

setContents({ contents: JSON.stringify(updatedJson, null, 2) });
setErrorMessage(null);
setOpened(false);
} catch (error: any) {
setErrorMessage(error.message);
}
}}
rightSection={!premium && <VscLock />}
>
Delete
</Button>
<Button
onClick={() => {
try {
if (!premium) return showPremiumModal(true);
const updatedJson = _update(JSON.parse(getJson()), path, () => JSON.parse(value));
setContents({ contents: JSON.stringify(updatedJson, null, 2) });

setErrorMessage(null);
setOpened(false);
} catch (error: any) {
setErrorMessage(error.message);
}
}}
rightSection={!premium && <VscLock />}
>
Apply
</Button>
</Group>
</Modal>
);
};
69 changes: 69 additions & 0 deletions src/containers/Views/TreeView/Label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from "react";
import { Button, HoverCard } from "@mantine/core";
import { styled, DefaultTheme } from "styled-components";
import _get from "lodash.get";
import { VscEdit } from "react-icons/vsc";
import { KeyPath } from "react-json-tree";
import useJson from "src/store/useJson";

interface LabelProps {
keyPath: KeyPath;
nodeType: string;
setOpened: React.Dispatch<React.SetStateAction<boolean>>;
setSelectedValue: React.Dispatch<React.SetStateAction<string | number | null>>;
setPath: React.Dispatch<React.SetStateAction<(string | number)[]>>;
setValue: React.Dispatch<React.SetStateAction<string>>;
}

function getLabelColor({ $type, theme }: { $type?: string; theme: DefaultTheme }) {
if ($type === "Object") return theme.NODE_COLORS.PARENT_OBJ;
if ($type === "Array") return theme.NODE_COLORS.PARENT_ARR;
return theme.NODE_COLORS.PARENT_OBJ;
}

const StyledLabel = styled.span<{ $nodeType?: string }>`
color: ${({ theme, $nodeType }) => getLabelColor({ theme, $type: $nodeType })};
&:hover {
filter: brightness(1.5);
transition: filter 0.2s ease-in-out;
}
`;

export const Label = ({
keyPath,
nodeType,
setOpened,
setSelectedValue,
setPath,
setValue,
}: LabelProps) => {
const getJson = useJson(state => state.getJson);

return (
<HoverCard shadow="sm" openDelay={50} closeDelay={250} withArrow position="left">
<HoverCard.Target>
<StyledLabel $nodeType={nodeType}>{keyPath[0]}:</StyledLabel>
</HoverCard.Target>
<HoverCard.Dropdown py={4}>
<Button
variant="transparent"
size="xs"
onClick={() => {
setOpened(true);
setSelectedValue(keyPath[0]);

const path = keyPath.toReversed();
const value = _get(JSON.parse(getJson()), path);

setPath(path);
setValue(JSON.stringify(value));
}}
leftSection={<VscEdit size="12" />}
>
Click to edit
</Button>
</HoverCard.Dropdown>
</HoverCard>
);
};
41 changes: 41 additions & 0 deletions src/containers/Views/TreeView/Value.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from "react";
import { DefaultTheme, useTheme } from "styled-components";
import { TextRenderer } from "src/containers/Views/GraphView/CustomNode/TextRenderer";

type TextColorFn = {
theme: DefaultTheme;
$value?: string | unknown;
};

function getValueColor({ $value, theme }: TextColorFn) {
if ($value && !Number.isNaN(+$value)) return theme.NODE_COLORS.INTEGER;
if ($value === "true") return theme.NODE_COLORS.BOOL.TRUE;
if ($value === "false") return theme.NODE_COLORS.BOOL.FALSE;
if ($value === "null") return theme.NODE_COLORS.NULL;

// default
return theme.NODE_COLORS.NODE_VALUE;
}

interface ValueProps {
valueAsString: unknown;
value: unknown;
}

export const Value = (props: ValueProps) => {
const theme = useTheme();
const { valueAsString, value } = props;

return (
<span
style={{
color: getValueColor({
theme,
$value: valueAsString,
}),
}}
>
<TextRenderer>{JSON.stringify(value)}</TextRenderer>
</span>
);
};
Loading

0 comments on commit d3f7bb8

Please sign in to comment.