diff --git a/package.json b/package.json index 77aa8be762..0627f0da88 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@reach/combobox": "^0.7.3", "@rehooks/local-storage": "^2.1.1", "@rematch/core": "^1.3.0", + "browser-nativefs": "^0.3.1", "codesandbox": "^2.1.11", "coloreact": "^0.3.1", "copy-to-clipboard": "^3.2.1", diff --git a/src/App.tsx b/src/App.tsx index f7146baf05..65686ed2b3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,7 +27,6 @@ const App = () => { - diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 02dae0f3b5..378058b8e9 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -28,6 +28,7 @@ import useDispatch from '../hooks/useDispatch' import { useSelector } from 'react-redux' import { getComponents } from '../core/selectors/components' import { getShowLayout, getShowCode } from '../core/selectors/app' +import HeaderMenu from './HeaderMenu' const CodeSandboxButton = () => { const components = useSelector(getComponents) @@ -95,6 +96,9 @@ const Header = () => { + + + { - {({ onClose }) => ( <> diff --git a/src/components/HeaderMenu.tsx b/src/components/HeaderMenu.tsx new file mode 100644 index 0000000000..b24a6f33b6 --- /dev/null +++ b/src/components/HeaderMenu.tsx @@ -0,0 +1,96 @@ +import React, { memo } from 'react' +import { + Box, + Button, + LightMode, + Menu, + MenuButton, + MenuList, + MenuItem, + MenuDivider, + LinkProps, + MenuItemProps, + MenuButtonProps, + ButtonProps, +} from '@chakra-ui/core' +import useDispatch from '../hooks/useDispatch' +import { loadFromJSON, saveAsJSON } from '../utils/import' +import { useSelector } from 'react-redux' +import { getComponents } from '../core/selectors/components' +import { FaBomb, FaSave } from 'react-icons/fa' +import { GoRepo } from 'react-icons/go' +import { FiUpload } from 'react-icons/fi' + +type MenuItemLinkProps = MenuItemProps | LinkProps + +// Ignore because of AS typing issues +// @ts-ignore +const MenuItemLink: React.FC = React.forwardRef( + (props, ref: React.Ref) => { + // @ts-ignore + return + }, +) + +// @ts-ignore +const CustomMenuButton: React.FC< + MenuButtonProps | ButtonProps +> = React.forwardRef((props, ref: React.Ref) => { + // @ts-ignore + return +}) + +const ExportMenuItem = () => { + const components = useSelector(getComponents) + + return ( + saveAsJSON(components)}> + + Save components + + ) +} +const HeaderMenu = () => { + const dispatch = useDispatch() + + return ( + + + Editor + + + + + { + const components = await loadFromJSON() + dispatch.components.reset(components) + }} + > + + Import components + + + + + + + Chakra UI Docs + + + + Report issue + + + + + ) +} + +export default memo(HeaderMenu) diff --git a/src/components/inspector/controls/ColorsControl.tsx b/src/components/inspector/controls/ColorsControl.tsx index b7455df1e5..8be602c7ad 100644 --- a/src/components/inspector/controls/ColorsControl.tsx +++ b/src/components/inspector/controls/ColorsControl.tsx @@ -1,6 +1,5 @@ import React, { ReactNode, useState, memo } from 'react' import { - theme, Popover, PopoverTrigger, PopoverContent, @@ -20,6 +19,7 @@ import { TabPanels, TabPanel, Input, + useTheme, } from '@chakra-ui/core' import FormControl from './FormControl' import { useForm } from '../../../hooks/useForm' @@ -39,6 +39,7 @@ const ColorsControl = (props: ColorControlPropsType) => { const { setValue, setValueFromEvent } = useForm() const [hue, setHue] = useState(500) const value = usePropsSelector(props.name) + const theme = useTheme() const themeColors: any = omit(theme.colors, [ 'transparent', @@ -115,7 +116,6 @@ const ColorsControl = (props: ColorControlPropsType) => { {props.label} - diff --git a/src/core/models/app.ts b/src/core/models/app.ts index 61bc63aafa..9f6fa98590 100644 --- a/src/core/models/app.ts +++ b/src/core/models/app.ts @@ -35,7 +35,6 @@ const app = createModel({ inputTextFocused: !state.inputTextFocused, } }, - setOverlay(state: AppState, overlay: Overlay | undefined): AppState { return { ...state, diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index 938fc664ae..13440e092e 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1,6 +1,7 @@ /// ; declare module 'prettier/standalone' declare module 'coloreact' +declare module 'browser-nativefs' type ComponentType = | 'AspectRatioBox' diff --git a/src/utils/import.ts b/src/utils/import.ts new file mode 100644 index 0000000000..c6a15310b5 --- /dev/null +++ b/src/utils/import.ts @@ -0,0 +1,39 @@ +import { fileOpen, fileSave } from 'browser-nativefs' +import { INITIAL_COMPONENTS } from '../core/models/components' + +export async function loadFromJSON() { + const blob = await fileOpen({ + extensions: ['json'], + mimeTypes: ['application/json'], + }) + + const contents: string = await new Promise(resolve => { + const reader = new FileReader() + reader.readAsText(blob, 'utf8') + reader.onloadend = () => { + if (reader.readyState === FileReader.DONE) { + resolve(reader.result as string) + } + } + }) + + try { + return JSON.parse(contents) + } catch (error) {} + + return INITIAL_COMPONENTS +} + +export async function saveAsJSON(components: IComponents) { + const serialized = JSON.stringify(components) + const name = `components.json` + + await fileSave( + new Blob([serialized], { type: 'application/json' }), + { + fileName: name, + description: 'Excalidraw file', + }, + (window as any).handle, + ) +} diff --git a/yarn.lock b/yarn.lock index 17d2007103..c9a2bb9a1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -930,13 +930,20 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.3.1", "@babel/runtime@^7.5.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6": +"@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.3.tgz#0811944f73a6c926bb2ad35e918dcc1bfab279f1" integrity sha512-fVHx1rzEmwB130VTkLnxR+HmxcTjGzH12LYQcFFoBwakMd3aOMD4OsRN7tGG/UOYE2ektgFrS8uACAoRk1CY0w== dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.8.3": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" + integrity sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ== + dependencies: + regenerator-runtime "^0.13.2" + "@babel/template@^7.4.0", "@babel/template@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.7.4.tgz#428a7d9eecffe27deac0a98e23bf8e3675d2a77b" @@ -1731,19 +1738,20 @@ pretty-format "^24.9.0" wait-for-expect "^3.0.0" -"@testing-library/jest-dom@^4.2.4": - version "4.2.4" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-4.2.4.tgz#00dfa0cbdd837d9a3c2a7f3f0a248ea6e7b89742" - integrity sha512-j31Bn0rQo12fhCWOUWy9fl7wtqkp7In/YP2p5ZFyRuiiB9Qs3g+hS4gAmDWONbAHcRmVooNJ5eOHQDCOmUFXHg== +"@testing-library/jest-dom@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.1.1.tgz#e88a5c08f9b9f36b384f948a0532eae2abbc8204" + integrity sha512-7xnmBFcUmmUVAUhFiZ/u3CxFh1e46THAwra4SiiKNCW4By26RedCRwEk0rtleFPZG0wlTSNOKDvJjWYy93dp0w== dependencies: - "@babel/runtime" "^7.5.1" - chalk "^2.4.1" - css "^2.2.3" + "@babel/runtime" "^7.8.3" + "@types/testing-library__jest-dom" "^5.0.0" + chalk "^3.0.0" + css "^2.2.4" css.escape "^1.5.1" - jest-diff "^24.0.0" - jest-matcher-utils "^24.0.0" - lodash "^4.17.11" - pretty-format "^24.0.0" + jest-diff "^25.1.0" + jest-matcher-utils "^25.1.0" + lodash "^4.17.15" + pretty-format "^25.1.0" redent "^3.0.0" "@testing-library/react@^9.4.0": @@ -1755,10 +1763,10 @@ "@testing-library/dom" "^6.11.0" "@types/testing-library__react" "^9.1.2" -"@testing-library/user-event@^7.1.2": - version "7.2.1" - resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-7.2.1.tgz#2ad4e844175a3738cb9e7064be5ea070b8863a1c" - integrity sha512-oZ0Ib5I4Z2pUEcoo95cT1cr6slco9WY7yiPpG+RGNkj8YcYgJnM7pXmYmorNOReh8MIGcKSqXyeGjxnr8YiZbA== +"@testing-library/user-event@^8.1.0": + version "8.1.3" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-8.1.3.tgz#f1d803da6fafa6c6b89392f3b8da9ae2df1a419d" + integrity sha512-l8IX2Zs6cLZgwJNmBJaJT2yvstwiNi8kKyO+USrZWJV6DSyUlrWfgWSSic8YLiOHLWUNRLJBOPN43nxTKHXKfg== "@types/babel__core@^7.1.0": version "7.1.3" @@ -1845,6 +1853,14 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/jest@*": + version "25.1.3" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.1.3.tgz#9b0b5addebccfb631175870be8ba62182f1bc35a" + integrity sha512-jqargqzyJWgWAJCXX96LBGR/Ei7wQcZBvRv0PLEu9ZByMfcs23keUJrKv9FMR6YZf9YCbfqDqgmY+JUBsnqhrg== + dependencies: + jest-diff "^25.1.0" + pretty-format "^25.1.0" + "@types/jest@^25.1.2": version "25.1.2" resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.1.2.tgz#1c4c8770c27906c7d8def5d2033df9dbd39f60da" @@ -1949,6 +1965,13 @@ dependencies: pretty-format "^24.3.0" +"@types/testing-library__jest-dom@^5.0.0": + version "5.0.1" + resolved "https://registry.yarnpkg.com/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.0.1.tgz#cc7f384535a3d9597e27f58d38a795f5c137cc53" + integrity sha512-GiPXQBVF9O4DG9cssD2d266vozBJvC5Tnv6aeH5ujgYJgys1DYm9AFCz7YC+STR5ksGxq3zCt+yP8T1wbk2DFg== + dependencies: + "@types/jest" "*" + "@types/testing-library__react@^9.1.2": version "9.1.2" resolved "https://registry.yarnpkg.com/@types/testing-library__react/-/testing-library__react-9.1.2.tgz#e33af9124c60a010fc03a34eff8f8a34a75c4351" @@ -2968,6 +2991,11 @@ brorand@^1.0.1: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= +browser-nativefs@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/browser-nativefs/-/browser-nativefs-0.3.1.tgz#17545ca47b1b8912f543f12590f1b1335864c7d7" + integrity sha512-dLcodrZMuyyJvNGJaLKuhPF3uAX/mU2+D3nwbzIc7a1RaNd2enpJhATbq6xZZWeYEvckNRg7KmqgiX+0Ht3zzA== + browser-process-hrtime@^0.1.2: version "0.1.3" resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4" @@ -4085,7 +4113,7 @@ css.escape@^1.5.1: resolved "https://registry.yarnpkg.com/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" integrity sha1-QuJ9T6BK4y+TGktNQZH6nN3ul8s= -css@^2.0.0, css@^2.2.3: +css@^2.0.0, css@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== @@ -7132,7 +7160,7 @@ jest-config@^24.9.0: pretty-format "^24.9.0" realpath-native "^1.1.0" -jest-diff@^24.0.0, jest-diff@^24.9.0: +jest-diff@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-24.9.0.tgz#931b7d0d5778a1baf7452cb816e325e3724055da" integrity sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ== @@ -7261,7 +7289,7 @@ jest-leak-detector@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" -jest-matcher-utils@^24.0.0, jest-matcher-utils@^24.9.0: +jest-matcher-utils@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073" integrity sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA== @@ -7271,6 +7299,16 @@ jest-matcher-utils@^24.0.0, jest-matcher-utils@^24.9.0: jest-get-type "^24.9.0" pretty-format "^24.9.0" +jest-matcher-utils@^25.1.0: + version "25.1.0" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.1.0.tgz#fa5996c45c7193a3c24e73066fc14acdee020220" + integrity sha512-KGOAFcSFbclXIFE7bS4C53iYobKI20ZWleAdAFun4W1Wz1Kkej8Ng6RRbhL8leaEvIOjGXhGf/a1JjO8bkxIWQ== + dependencies: + chalk "^3.0.0" + jest-diff "^25.1.0" + jest-get-type "^25.1.0" + pretty-format "^25.1.0" + jest-message-util@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-24.9.0.tgz#527f54a1e380f5e202a8d1149b0ec872f43119e3" @@ -10190,7 +10228,7 @@ pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -pretty-format@^24.0.0, pretty-format@^24.3.0, pretty-format@^24.9.0: +pretty-format@^24.3.0, pretty-format@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.9.0.tgz#12fac31b37019a4eea3c11aa9a959eb7628aa7c9" integrity sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==