From 8c8da9e3948f5c3123223cdc09ba7403267ac9c9 Mon Sep 17 00:00:00 2001 From: epodol <34190213+epodol@users.noreply.github.com> Date: Mon, 31 Oct 2022 13:33:03 -0700 Subject: [PATCH] Remove Alert Dialogs (#155) * Add TextDialog Component Add React Hooks ESLint Clean Code * Add Delete Node Dialog * Disable Delete and Unlock Menu Items for Root Node * Remove Alerts * Remove Gear * Fix Double Click Binding * ESLint Fixes * Bug Fixes --- .eslintrc.js | 13 +- .../Dialogs/ConfirmationDialog/index.tsx | 50 +-- src/components/Dialogs/TextDialog/index.tsx | 90 ++++++ src/pages/ManageMindMaps/index.tsx | 93 ++++-- src/pages/MindMap/Bubble.tsx | 79 ++++- src/pages/MindMap/MindMapSimulation.tsx | 292 ++++++++++-------- src/pages/MindMap/index.tsx | 10 +- .../overlays/BottomBar/KeyBindsDialog.tsx | 130 +++----- .../MindMap/overlays/BottomBar/index.tsx | 1 + .../MindMap/overlays/GenIdeaPanel/index.tsx | 13 +- 10 files changed, 465 insertions(+), 306 deletions(-) create mode 100644 src/components/Dialogs/TextDialog/index.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 6eba46c..7ea603d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,7 +6,14 @@ module.exports = { node: true, }, parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint', 'jest', 'jest-dom', 'jsx-a11y', 'prettier'], + plugins: [ + '@typescript-eslint', + 'jest', + 'jest-dom', + 'jsx-a11y', + 'prettier', + 'react-hooks', + ], extends: [ 'airbnb', 'airbnb-typescript', @@ -34,6 +41,10 @@ module.exports = { 2, { namedComponents: 'arrow-function' }, ], + 'react/prop-types': 'off', + 'react/require-default-props': 'off', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', }, settings: { jest: { diff --git a/src/components/Dialogs/ConfirmationDialog/index.tsx b/src/components/Dialogs/ConfirmationDialog/index.tsx index bac90a2..33380cb 100644 --- a/src/components/Dialogs/ConfirmationDialog/index.tsx +++ b/src/components/Dialogs/ConfirmationDialog/index.tsx @@ -28,26 +28,36 @@ const ConfirmationDialog = ({ suggestedAction: 'approve' | 'reject' | undefined; }) => ( - {title} - - {description} - - - - - +
{ + onApprove(); + onReject(); + e.preventDefault(); + }} + > + {title} + + {description} + + + + + +
); diff --git a/src/components/Dialogs/TextDialog/index.tsx b/src/components/Dialogs/TextDialog/index.tsx new file mode 100644 index 0000000..e034618 --- /dev/null +++ b/src/components/Dialogs/TextDialog/index.tsx @@ -0,0 +1,90 @@ +import React, { ReactNode, useEffect, useState } from 'react'; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + TextField, + TextFieldProps, +} from '@mui/material'; + +const TextDialog = ({ + isOpen, + onApprove, + onReject, + title, + description, + approveButtonText, + rejectButtonText, + suggestedAction, + initialValue, + updateOnInitialValueChange, + textFieldProps, +}: { + isOpen: boolean; + onApprove: (value: string) => void; + onReject: () => void; + title: string; + description?: ReactNode; + approveButtonText?: string; + rejectButtonText?: string; + suggestedAction?: 'approve' | 'reject' | undefined; + initialValue?: string; + updateOnInitialValueChange?: boolean; + textFieldProps?: TextFieldProps; +}) => { + const [value, setValue] = useState(initialValue || ''); + + useEffect(() => { + if (updateOnInitialValueChange && initialValue) setValue(initialValue); + }, [initialValue, updateOnInitialValueChange]); + + const onClose = () => { + setValue(initialValue || ''); + onReject(); + }; + + return ( + +
{ + onApprove(value); + onClose(); + e.preventDefault(); + }} + > + {title} + + {description && {description}} + setValue(e.target.value)} + inputProps={{ autoFocus: true }} + // eslint-disable-next-line react/jsx-props-no-spreading + {...textFieldProps} + /> + + + + + +
+
+ ); +}; + +export default TextDialog; diff --git a/src/pages/ManageMindMaps/index.tsx b/src/pages/ManageMindMaps/index.tsx index 07063a2..b2e8016 100644 --- a/src/pages/ManageMindMaps/index.tsx +++ b/src/pages/ManageMindMaps/index.tsx @@ -45,6 +45,7 @@ import { import { MindMap, RecursivePartial, WithID } from '../../types'; import ShareDialog from './ShareDialog'; import ConfirmationDialog from '../../components/Dialogs/ConfirmationDialog'; +import TextDialog from '../../components/Dialogs/TextDialog'; const ManageMindMaps = () => { const user = useUser().data; @@ -82,6 +83,13 @@ const ManageMindMaps = () => { } ).data as WithID[]; + const [isCreateMindMapDialogOpen, setIsCreateMindMapDialogOpen] = + useState(false); + + const [isRenameMindMapDialogOpen, setIsRenameMindMapDialogOpen] = useState< + null | string + >(null); + const createMindMap = (title: string) => { const newDocData: MindMap = { metadata: { @@ -110,15 +118,6 @@ const ManageMindMaps = () => { }); }; - const handleCreateMindMap = () => { - // eslint-disable-next-line no-alert - const title = window.prompt('What would you like to ideate upon?')?.trim(); - createMindMap( - // eslint-disable-next-line - title === null || title === '' ? 'Untitled Mind Map' : title! - ); - }; - // Snackbar stuff interface SnackbarMessage { message: string; @@ -203,11 +202,61 @@ const ManageMindMaps = () => { startIcon={} variant="contained" onClick={() => { - handleCreateMindMap(); + setIsCreateMindMapDialogOpen(true); }} > New Mind Map + createMindMap(title)} + onReject={() => { + setIsCreateMindMapDialogOpen(false); + }} + rejectButtonText="Cancel" + title="Create Mind Map" + suggestedAction="approve" + textFieldProps={{ + label: 'Title', + }} + initialValue="" + /> + { + const mindmap = mindmaps.find( + (m) => m.ID === isRenameMindMapDialogOpen + ); + if (!mindmap) return; + + const updatedDocFields: RecursivePartial = { + metadata: { + updatedAt: serverTimestamp() as Timestamp, + updatedBy: user.uid, + }, + title: newTitle || 'Untitled Mind Map', + }; + setDoc(doc(firestore, 'mindmaps', mindmap.ID), updatedDocFields, { + merge: true, + }); + }} + onReject={() => { + setIsRenameMindMapDialogOpen(null); + }} + rejectButtonText="Cancel" + title="Rename Mind Map" + suggestedAction="approve" + textFieldProps={{ + label: 'Title', + }} + initialValue={ + mindmaps.find((m) => m.ID === isRenameMindMapDialogOpen)?.title || + '' + } + updateOnInitialValueChange + />

@@ -376,27 +425,7 @@ const ManageMindMaps = () => { { - // eslint-disable-next-line no-alert - const newTitle = prompt( - `What would you like to rename ${mindmap.title} to?`, - mindmap.title - ); - if (newTitle) { - const updatedDocFields: RecursivePartial = { - metadata: { - updatedAt: serverTimestamp() as Timestamp, - updatedBy: user.uid, - }, - title: newTitle, - }; - setDoc( - doc(firestore, 'mindmaps', mindmap.ID), - updatedDocFields, - { - merge: true, - } - ); - } + setIsRenameMindMapDialogOpen(mindmap.ID); }} aria-label="rename mindmap" > @@ -410,7 +439,7 @@ const ManageMindMaps = () => {
childRef.current.handleAddNode()} selectedNode={selectedNode} resetCanvas={resetCanvas} /> diff --git a/src/pages/MindMap/index.tsx b/src/pages/MindMap/index.tsx index 8f21bda..52d872a 100644 --- a/src/pages/MindMap/index.tsx +++ b/src/pages/MindMap/index.tsx @@ -28,7 +28,7 @@ import GenIdeaPanel from './overlays/GenIdeaPanel'; const keyMap = { ADD_NODE: 'ctrl+enter', DELETE_NODE: ['del', 'backspace'], - EDIT_NODE_TEXT: 'enter', + EDIT_NODE_TEXT: 'shift+enter', // Not Implemented GENERATE_IDEAS: 'ctrl+shift+enter', // Not Implemented @@ -37,8 +37,8 @@ const keyMap = { TOGGLE_SETTINGS: 'ctrl+shift+p', MOVE_SELECTION_TO_PARENT: ['up', '`'], MOVE_SELECTION_TO_CHILD: 'down', - MOVE_SELECTION_TO_NEXT_SIBLING: ['right', 'tab'], - MOVE_SELECTION_TO_PREVIOUS_SIBLING: ['left', 'shift+tab'], + MOVE_SELECTION_TO_NEXT_SIBLING: ['right'], + MOVE_SELECTION_TO_PREVIOUS_SIBLING: ['left'], MOVE_SELECTION_TO_ROOT: ['0', 'ctrl+up'], RESET_VIEW: ['ctrl+0', 'home'], LOCK_NODE: ['l', 'ctrl+l', 'space'], @@ -172,8 +172,6 @@ const MindMap = () => { } }; - if (!mindmap) return
Sorry, I couldn't find that mindmap.
; - const shortcutHandlers = { TOGGLE_SETTINGS: () => { // eslint-disable-next-line no-console @@ -184,6 +182,8 @@ const MindMap = () => { // Get the mindmap node with id 0, which is the root node const rootNode = mindmap.nodes.find((o) => o.id === 0); + if (!mindmap) throw new Error('Sorry, I couldn't find that mindmap.'); + if (!rootNode) { throw new Error('Root node not found!'); } diff --git a/src/pages/MindMap/overlays/BottomBar/KeyBindsDialog.tsx b/src/pages/MindMap/overlays/BottomBar/KeyBindsDialog.tsx index a9ee1ad..5f3f7b0 100644 --- a/src/pages/MindMap/overlays/BottomBar/KeyBindsDialog.tsx +++ b/src/pages/MindMap/overlays/BottomBar/KeyBindsDialog.tsx @@ -37,9 +37,6 @@ const KeyBindsDialog = ({ const aboutPanel = ( - - About - This is a mind map editor designed and built by Saketh Reddy and Eric @@ -49,7 +46,13 @@ const KeyBindsDialog = ({ This project is open source. You can find the source code on{' '} - GitHub. + + GitHub + + . @@ -59,6 +62,7 @@ const KeyBindsDialog = ({ href="mailto: support@bubblemap.app " + style={{ color: 'white' }} > support@bubblemap.app @@ -96,38 +100,42 @@ const KeyBindsDialog = ({ {mapEntry( 'Ctrl + Enter', - 'Add Child Node to Selected Node' + 'Add Bubble to Selected Bubble' )} {mapEntry( // Double space 'or' statements using    'Delete  or  Backspace', - 'Delete Selected Node' + 'Delete Selected Bubble' )} {mapEntry( - // Enter -> Edit Node Text - 'Enter', - 'Edit Selected Node Text' + 'Shift + Enter', + 'Edit Selected Bubble' )} - Node Selection Manipulation + + Bubble Selection Manipulation + - {mapEntry('Up', 'Select Parent Node')} - {mapEntry('Down', 'Select Child Node')} + {mapEntry('Up', 'Select Parent Bubble')} + {mapEntry('Down', 'Select Child Bubble')} {mapEntry( 'Left', - 'Select Next Sibling Node (Counter-Clockwise)' + 'Select Next Sibling Bubble (Counter-Clockwise)' + )} + {mapEntry( + 'Right', + 'Select Next Sibling Bubble (Clockwise)' )} - {mapEntry('Right', 'Select Next Sibling Node (Clockwise)')} {/* 'Move selection to root" is '0' or 'ctrl'+'up */} {mapEntry( '0  or  Ctrl + Up', - 'Select Root Node' + 'Select Root Bubble' )} @@ -139,11 +147,11 @@ const KeyBindsDialog = ({ - {/* Space, 'l', or 'ctrl+l' map to Lock Node */} + {/* Space, 'l', or 'ctrl+l' map to Lock Bubble */} {mapEntry( 'Space  or  L', - 'Lock Node' + 'Lock Bubble' )} {/* Reset Pan and Zoom back to Default */} {mapEntry( @@ -179,25 +187,28 @@ const KeyBindsDialog = ({ - Mouse-Node Interactions + Mouse-Bubble Interactions - {mapEntry('Left Click Node', 'Select Node')} - {mapEntry('Drag Node', 'Move Node')} - {mapEntry('Right Click Node', 'Open Context Menu')} + {mapEntry('Left Click Bubble', 'Select Bubble')} + {mapEntry('Drag Bubble', 'Move Bubble')} + {mapEntry('Right Click Bubble', 'Open Context Menu')} {/* Double Click */} - {mapEntry('Double Click Node', 'Edit Node Text')} + {mapEntry('Double Click Bubble', 'Edit Bubble Text')} {/* Control Click */} {mapEntry( - 'Ctrl + Left Click Node', - 'Add Child Node to Selected Node' + 'Ctrl + Left Click Bubble', + 'Add Bubble to Selected Bubble' )} {/* Shift Click */} - {mapEntry('Shift + Left Click Node', 'Lock Node Position')} + {mapEntry( + 'Shift + Left Click Bubble', + 'Lock Bubble Position' + )} {/* Alt Click */} - {mapEntry('Alt + Left Click Node', 'Delete Node')} + {mapEntry('Alt + Left Click Bubble', 'Delete Bubble')} {/* Mouse-Canvas Interaction */} @@ -215,73 +226,6 @@ const KeyBindsDialog = ({ ); - // const mouseShortcutsPanel = ( - // // table displaying an icon, the mouse actions, and corresponding shortcuts side by side - // - // - // - // - // Icon - // Action - // Effect - // - // - // - // - // - // {/* */} - // - // Left Click Node - // Select Node - // - // - // - // {/* */} - // - // Drag Node - // Move Node - // - // - // - // {/* */} - // - // Right Click Node - // Open Node Menu - // - // - // - // {/* */} - // - // Double Click Node - // Edit Node - // - // - // - // {/* */} - // - // Control + Click Node - // Add Child Node - // - // - // - // {/* */} - // - // Shift + Click Node - // Lock Node - // - // - // - // {/* */} - // - // Alt + Click Node - // Delete Node - // - // {/* End */} - // - //
- //
- // ); - return ( { @@ -288,11 +285,11 @@ const PersistentDrawerRight = ({ }} /> {/* PART ONE */} - + {/* - + */} {gpt3Cache[selectedNode.id]?.map((idea) => (