diff --git a/src/libs/ConnectionListStorage.ts b/src/libs/ConnectionListStorage.ts index f11e170..fb9364e 100644 --- a/src/libs/ConnectionListStorage.ts +++ b/src/libs/ConnectionListStorage.ts @@ -28,15 +28,20 @@ export default class ConnectionListStorage { return this.connections; } - async save(data: Omit & { id?: string }) { + async save( + data: Omit & { id?: string }, + ): Promise { + const id = data.id ? data.id : uuidv1(); const newData: ConnectionStoreItem = { ...data, - id: data.id ? data.id : uuidv1(), + id, createdAt: data.createdAt ? data.createdAt : Math.ceil(Date.now() / 1000), }; this.dict[newData.id] = newData; db.table('connections').put(newData); + + return newData; } async remove(id: string) { diff --git a/src/renderer/components/ConnectionListTree/ConnectionToolbar.tsx b/src/renderer/components/ConnectionListTree/ConnectionToolbar.tsx index 75132f0..539484d 100644 --- a/src/renderer/components/ConnectionListTree/ConnectionToolbar.tsx +++ b/src/renderer/components/ConnectionListTree/ConnectionToolbar.tsx @@ -5,12 +5,11 @@ import useNewConnectionMenu from './useNewConnectionMenu'; export default function ConnectionToolbar() { const menu = useNewConnectionMenu(); - return ( } - text="" + icon={} + text="New Connection" items={menu} /> diff --git a/src/renderer/components/ConnectionListTree/EditConnectionModal.tsx b/src/renderer/components/ConnectionListTree/EditConnectionModal.tsx index caa94f6..ee445ee 100644 --- a/src/renderer/components/ConnectionListTree/EditConnectionModal.tsx +++ b/src/renderer/components/ConnectionListTree/EditConnectionModal.tsx @@ -11,15 +11,17 @@ export default function EditConnectionModal({ }: { initialValue: ConnectionStoreItemWithoutId; }) { - const { finishEditing, refresh, storage } = useConnectionList(); + const { finishEditing, refresh, storage, setSelectedItem } = + useConnectionList(); const [value, setValue] = useState(initialValue); const onSave = useCallback(() => { storage .save(value) - .then(() => { + .then((newItem) => { refresh(); finishEditing(); + setSelectedItem({ id: newItem.id, data: newItem }); }) .catch(console.error); }, [finishEditing, value]); diff --git a/src/renderer/components/ConnectionListTree/index.tsx b/src/renderer/components/ConnectionListTree/index.tsx index b8a8203..2c3406f 100644 --- a/src/renderer/components/ConnectionListTree/index.tsx +++ b/src/renderer/components/ConnectionListTree/index.tsx @@ -26,11 +26,15 @@ const ConnectionListContext = createContext<{ refresh: () => void; finishEditing: () => void; showEditConnection: (initialValue: ConnectionStoreItemWithoutId) => void; + setSelectedItem: React.Dispatch< + React.SetStateAction | undefined> + >; }>({ storage: new ConnectionListStorage(), refresh: NotImplementCallback, finishEditing: NotImplementCallback, showEditConnection: NotImplementCallback, + setSelectedItem: NotImplementCallback, }); export function useConnectionList() { @@ -39,34 +43,59 @@ export function useConnectionList() { function ConnectionListTreeBody({ connectionList, + selectedItem, + setSelectedItem, }: { connectionList: ConnectionStoreItem[]; + selectedItem: TreeViewItemData | undefined; + setSelectedItem: React.Dispatch< + React.SetStateAction | undefined> + >; }) { + const [collapsed, setCollapsed] = useState(['recent']); const { connect } = useConnection(); - const [selectedItem, setSelectedItem] = - useState>(); + const { storage, refresh } = useConnectionList(); const treeItemList: TreeViewItemData[] = useMemo(() => { - return connectionList.map( - (connection): TreeViewItemData => { - return { - id: connection.id, - data: connection, - icon: , - text: connection.name, - }; + const tmp = [...connectionList]; + tmp.sort((a, b) => b.lastUsedAt - a.lastUsedAt); + const recentConnections = tmp.slice(0, 5); + const recentIds = recentConnections.map((r) => r.id); + + const other = connectionList.filter((c) => !recentIds.includes(c.id)); + + return [ + { + id: 'recent', + text: 'Recent', + children: recentConnections.map( + (connection): TreeViewItemData => { + return { + id: connection.id, + data: connection, + icon: , + text: connection.name, + }; + }, + ), + }, + { + id: 'other', + text: 'Other', + children: other.map( + (connection): TreeViewItemData => { + return { + id: connection.id, + data: connection, + icon: , + text: connection.name, + }; + }, + ), }, - ); + ].filter((r) => r.children.length > 0); }, [connectionList]); - useEffect(() => { - if (selectedItem && treeItemList) { - if (!treeItemList.map((t) => t.id).includes(selectedItem.id)) { - setSelectedItem(undefined); - } - } - }, [treeItemList, selectedItem, setSelectedItem]); - const { handleContextMenu } = useConnectionContextMenu({ selectedItem: selectedItem?.data, }); @@ -77,9 +106,13 @@ function ConnectionListTreeBody({ selected={selectedItem} onContextMenu={handleContextMenu} onSelectChange={setSelectedItem} + collapsedKeys={collapsed} + onCollapsedChange={setCollapsed} onDoubleClick={(e) => { if (e.data) { connect(e.data); + storage.updateLastUsed(e.data.id); + refresh(); } }} emptyState={ @@ -91,9 +124,10 @@ function ConnectionListTreeBody({ export default function ConnectionListTree() { const storage = useMemo(() => new ConnectionListStorage(), []); - const [editingItem, setEditingItem] = useState< - ConnectionStoreItemWithoutId | undefined - >(); + const [selectedItem, setSelectedItem] = + useState>(); + const [editingItem, setEditingItem] = + useState(); const [connectionList, setConnectionList] = useState( [], ); @@ -118,13 +152,18 @@ export default function ConnectionListTree() { refresh: onRefresh, finishEditing: onFinishEditing, showEditConnection: setEditingItem, + setSelectedItem: setSelectedItem, }} > - + {editingItem && } diff --git a/src/renderer/components/ConnectionListTree/useConnectionContextMenu.tsx b/src/renderer/components/ConnectionListTree/useConnectionContextMenu.tsx index 8038eb0..54fe29a 100644 --- a/src/renderer/components/ConnectionListTree/useConnectionContextMenu.tsx +++ b/src/renderer/components/ConnectionListTree/useConnectionContextMenu.tsx @@ -9,7 +9,8 @@ export default function useConnectionContextMenu({ }: { selectedItem?: ConnectionStoreItem; }) { - const { storage, refresh, showEditConnection } = useConnectionList(); + const { storage, refresh, showEditConnection, setSelectedItem } = + useConnectionList(); const newConnectionMenu = useNewConnectionMenu(); const onRemoveClick = useCallback(async () => { @@ -24,8 +25,9 @@ export default function useConnectionContextMenu({ storage.remove(selectedItem.id); refresh(); + setSelectedItem(undefined); } - }, [selectedItem, storage]); + }, [selectedItem, storage, setSelectedItem]); const { handleContextMenu } = useContextMenu(() => { return [ diff --git a/src/renderer/components/Toolbar/index.tsx b/src/renderer/components/Toolbar/index.tsx index 31addf1..887b462 100644 --- a/src/renderer/components/Toolbar/index.tsx +++ b/src/renderer/components/Toolbar/index.tsx @@ -59,7 +59,14 @@ Toolbar.Item = function ({ onClick={disabled ? undefined : onClick} style={{ opacity: disabled ? 0.5 : 1 }} > - {icon && {icon}} + {icon && ( + + {icon} + + )} {text && {text}} {badge ? {badge} : <>} {keyboard && } @@ -119,7 +126,14 @@ Toolbar.ContextMenu = function ToolbarContextMenu({ const activator = useCallback(() => { return (
  • - {icon && {icon}} + {icon && ( + + {icon} + + )} {text}
  • ); diff --git a/src/renderer/components/Toolbar/styles.module.scss b/src/renderer/components/Toolbar/styles.module.scss index fe7c51b..0989270 100644 --- a/src/renderer/components/Toolbar/styles.module.scss +++ b/src/renderer/components/Toolbar/styles.module.scss @@ -17,7 +17,6 @@ span.icon { display: inline-block; - margin-right: 8px; } span.icon:last-child { diff --git a/src/renderer/screens/HomeScreen/index copy.txt b/src/renderer/screens/HomeScreen/index copy.txt deleted file mode 100644 index 7961a91..0000000 --- a/src/renderer/screens/HomeScreen/index copy.txt +++ /dev/null @@ -1,201 +0,0 @@ -import { useState, useMemo, useEffect } from 'react'; -import styles from './styles.module.scss'; -import DatabaseConfigEditor from './DatabaseConfigEditor'; -import ButtonGroup from 'renderer/components/ButtonGroup'; -import Button from 'renderer/components/Button'; - -import WelcomeScreen from '../WelcomeScreen'; -import { useConnection } from 'renderer/App'; -import SplitterLayout from 'renderer/components/Splitter/Splitter'; -import TreeView, { TreeViewItemData } from 'renderer/components/TreeView'; -import useConnectionContextMenu from './useConnectionContextMenu'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faAdd } from '@fortawesome/free-solid-svg-icons'; -import ListViewEmptyState from 'renderer/components/ListView/ListViewEmptyState'; -import { useKeybinding } from 'renderer/contexts/KeyBindingProvider'; -import Layout from 'renderer/components/Layout'; -import Toolbar from 'renderer/components/Toolbar'; -import useNewConnectionMenu from './useNewConnectionMenu'; -import { ConnectionStoreItem } from 'drivers/base/SQLLikeConnection'; -import ConnectionListStorage from 'libs/ConnectionListStorage'; -import Icon from 'renderer/components/Icon'; - -export default function HomeScreen() { - const { connect } = useConnection(); - const [collapsedKeys, setCollapsedKeys] = useState(); - - const { binding } = useKeybinding(); - const keyRenaming = binding['rename']; - - const [renameSelectedItem, setRenameSelectedItem] = useState(false); - - const storage = useMemo(() => new ConnectionListStorage(), []); - const [connectionList, setConnectionList] = useState( - [], - ); - - useEffect(() => { - storage.loadAll().then(() => setConnectionList(storage.getAll())); - }, [storage, setConnectionList]); - - const treeItemList: TreeViewItemData[] = useMemo(() => { - return connectionList.map( - (connection): TreeViewItemData => { - return { - id: connection.id, - data: connection, - icon: - connection.type === 'mysql' ? : , - text: connection.name, - }; - }, - ); - }, [connectionList]); - - // ----------------------------------------------- - // Handle before select change - // ----------------------------------------------- - // const onBeforeSelectChange = useCallback(async () => { - // if (hasChange && selectedItemChanged) { - // const buttonIndex = await window.electron.showMessageBox({ - // title: 'Save modifications?', - // type: 'warning', - // message: `Setting for ${selectedItemChanged.name} were changed`, - // buttons: ['Yes', 'No', 'Cancel'], - // }); - - // if (buttonIndex === 0) { - // onSaveClick(); - // } - - // if (buttonIndex === 2) { - // return false; - // } - // } - - // return true; - // }, [selectedItemChanged, onSaveClick, hasChange]); - - const newConnectionMenu = useNewConnectionMenu({ - collapsedKeys, - setSelectedItem, - setConnections, - setRenameSelectedItem, - selectedItem, - connectionTree, - }); - - const { handleContextMenu } = useConnectionContextMenu({ - connections, - setSaveCollapsedKeys, - collapsedKeys, - setSelectedItem, - setConnections, - setRenameSelectedItem, - selectedItem, - connectionTree, - newConnectionMenu, - }); - - return ( -
    - -
    { - if (keyRenaming.match(e)) { - setRenameSelectedItem(true); - } - }} - tabIndex={0} - > - - - - } - text="" - items={newConnectionMenu} - /> - - - - { - if (item.data?.config) { - connect(item.data?.config); - } - }} - selected={selectedItem} - onBeforeSelectChange={onBeforeSelectChange} - onContextMenu={handleContextMenu} - emptyState={ - - } - /> - - -
    - -
    - {selectedItemChanged && ( -
    - - - - -
    - )} - - {selectedItemChanged ? ( -
    - -
    - ) : !selectedItem?.data?.nodeType ? ( - - ) : ( -
    - )} -
    - -
    - ); -}