diff --git a/client/src/components/bodyEditor/BodyEditor.tsx b/client/src/components/bodyEditor/BodyEditor.tsx index 6c3af2a1..b3916324 100644 --- a/client/src/components/bodyEditor/BodyEditor.tsx +++ b/client/src/components/bodyEditor/BodyEditor.tsx @@ -17,6 +17,7 @@ type BodyEditorProps = { contentType: string; setContentType: any; setContentTypeHeader: any; + setEditorView?: any; }; function BodyEditor({ @@ -28,6 +29,7 @@ function BodyEditor({ contentType, setContentType, setContentTypeHeader, + setEditorView, }: BodyEditorProps) { const toast = useToast(); function handleBeautifyClick() { @@ -71,6 +73,7 @@ function BodyEditor({ setContent={setContent} selectedEnvData={selectedEnvData} contentType={contentType} + setEditorView={setEditorView} /> ); } diff --git a/client/src/components/bodyEditor/BodyTextEditor.tsx b/client/src/components/bodyEditor/BodyTextEditor.tsx index a282cc50..6d6d0bcb 100644 --- a/client/src/components/bodyEditor/BodyTextEditor.tsx +++ b/client/src/components/bodyEditor/BodyTextEditor.tsx @@ -22,6 +22,7 @@ type BodyTextEditorProps = { setContent: any; selectedEnvData: Record; contentType: string; + setEditorView?: any; }; function BodyTextEditor({ @@ -29,6 +30,7 @@ function BodyTextEditor({ setContent, selectedEnvData, contentType, + setEditorView, }: BodyTextEditorProps) { const { colorMode } = useColorMode(); @@ -51,7 +53,7 @@ function BodyTextEditor({ const ref = useRef(null); - const { setContainer } = useCodeMirror({ + const { setContainer, view } = useCodeMirror({ container: ref.current, onChange: (value: string) => setContent(value), extensions: [extensions], @@ -60,6 +62,12 @@ function BodyTextEditor({ style: { height: '100%' }, }); + useEffect(() => { + if (view && setEditorView) { + setEditorView(view); + } + }, [view, setEditorView]); + useEffect(() => { if (ref.current) { setContainer(ref.current); diff --git a/client/src/components/cmdPalette/CmdPalette.tsx b/client/src/components/cmdPalette/CmdPalette.tsx index 82012739..b8de6b09 100644 --- a/client/src/components/cmdPalette/CmdPalette.tsx +++ b/client/src/components/cmdPalette/CmdPalette.tsx @@ -1,68 +1,318 @@ -import { useState } from 'react'; +import React, { useContext, useState } from 'react'; import CommandPalette, { Command } from 'react-command-palette'; +import { CurrentModalContext } from '../../context/CurrentModalContext'; +import { FocusElementsContext } from '../../context/FocusElementsContext'; import Collection, { CurrentCollection } from '../../model/Collection'; -import { CurrentRestRequest } from '../../model/Request'; +import { CurrentRestRequest, RestRequest, WebsocketRequest } from '../../model/Request'; +import Script, { CurrentScript } from '../../model/Script'; +import { + OpenHistory, + OpenHistoryAction, + OpenHistoryActionType, +} from '../../state/openHistory'; import { useKeyPress } from '../../utils/useKeyPress'; type CmdPaletteProps = { collections: Collection[]; currentRequest?: CurrentRestRequest; currentCollection?: CurrentCollection; + currentScript?: CurrentScript; selectCollection: any; + selectRequest: any; + selectScript: any; setCollectionPanelTabIndex: (index: number) => void; + setRequestPanelTabIndex: (index: number) => void; + openHistory: OpenHistory; + dispatchOpenHistory: React.Dispatch; }; function CmdPalette({ collections, currentRequest, currentCollection, + currentScript, selectCollection, + selectRequest, + selectScript, setCollectionPanelTabIndex, + setRequestPanelTabIndex, + openHistory, + dispatchOpenHistory, }: CmdPaletteProps) { const [isOpen, setIsOpen] = useState(false); + const { setCurrentModal: setModal } = useContext(CurrentModalContext); + const { uriBarView } = useContext(FocusElementsContext); + useKeyPress(() => setIsOpen((open) => !open), 'p', true, true); + + function getCurrentCollectionId() { + return ( + currentCollection?.id || currentRequest?.collectionId || currentScript?.collectionId + ); + } useKeyPress(() => setIsOpen(true), 'p', true, true); async function openCollectionPanelTab(index: number) { - const collectionId = currentCollection?.id || currentRequest?.collectionId; + const collectionId = getCurrentCollectionId(); if (!collectionId) return; + await selectCollection.current(collectionId); + setCollectionPanelTabIndex(index); + } - if (collectionId !== currentCollection?.id) { - await selectCollection.current(collectionId); + async function openRequestPanelTab(index: number, focusElement?: any) { + const requestId = currentRequest?.id; + if (!requestId) return; + await selectRequest.current(requestId); + setRequestPanelTabIndex(index); + if (focusElement) { + focusElement.focus(); } - - setCollectionPanelTabIndex(index); } - const commands: Command[] = [ - { - id: 0, - name: 'Open Current Collection', - async command() { - if (!currentRequest) return; - await selectCollection.current(currentRequest.collectionId); + const commands = []; + + if ( + currentCollection?.id || + (currentRequest?.id && currentRequest?.id !== -1) || + currentScript?.id + ) { + const collectionCommands: Command[] = [ + { + id: commands.length, + name: 'Open Current Collection', + command: () => openCollectionPanelTab(0), + color: '', + }, + { + id: commands.length, + name: 'Open Current Environment', + command: () => openCollectionPanelTab(1), + color: '', + }, + { + id: commands.length, + name: 'Open Collection Headers', + command: () => openCollectionPanelTab(2), + color: '', }, - color: 'red', - }, - { - id: 1, - name: 'Open Current Environment', - async command() { - await openCollectionPanelTab(1); + { + id: commands.length, + name: 'Open Collection Auth', + command: () => openCollectionPanelTab(3), + color: '', + }, + { + id: commands.length, + name: 'Open Collection Request Script', + command: () => openCollectionPanelTab(4), + color: '', + }, + { + id: commands.length, + name: 'Open Collection Response Script', + command: () => openCollectionPanelTab(5), + color: '', + }, + { + id: commands.length, + name: 'Open Collection Settings', + command: () => openCollectionPanelTab(6), + color: '', + }, + { + id: commands.length, + name: 'New Request', + command: () => { + setModal('newRequest', 'collection', getCurrentCollectionId() ?? 0); + }, + color: '', + }, + ]; + commands.push(...collectionCommands); + } + + if (openHistory.index !== openHistory.items.length - 1) { + commands.push({ + id: commands.length, + name: 'History: Forward', + command: () => { + const item = openHistory.items[openHistory.index + 1]; + if (!item) { + return; + } + dispatchOpenHistory({ type: OpenHistoryActionType.FORWARD }); + if (item.type === 'request') { + selectRequest.current(item.id, false); + } else if (item.type === 'collection') { + selectCollection.current(item.id, false); + } else if (item.type === 'script') { + selectScript.current(item.id, false); + } }, color: '', - }, - { - id: 2, - name: 'Open Collection Headers', - async command() { - await openCollectionPanelTab(2); + }); + } + + if (openHistory.index > 0) { + commands.push({ + id: commands.length, + name: 'History: Backward', + command: () => { + const item = openHistory.items[openHistory.index - 1]; + if (!item) { + return; + } + dispatchOpenHistory({ type: OpenHistoryActionType.BACKWARD }); + if (item.type === 'request') { + selectRequest.current(item.id, false); + } else if (item.type === 'collection') { + selectCollection.current(item.id, false); + } else if (item.type === 'script') { + selectScript.current(item.id, false); + } }, color: '', - }, - ]; + }); + } + + if (currentRequest) { + const requestCommands = [ + // { + // id: commands.length, + // name: 'Edit Request Description', + // command: () => openRequestPanelTab(0), + // color: '', + // }, + // { + // id: commands.length, + // name: 'Edit Request Parameters', + // command: () => openRequestPanelTab(1), + // color: '', + // }, + // { + // id: commands.length, + // name: 'Edit Request Headers', + // command: () => openRequestPanelTab(2), + // color: '', + // }, + // { + // id: commands.length, + // name: 'Edit Request Body', + // command: () => openRequestPanelTab(3, bodyEditorView), + // color: '', + // }, + // { + // id: commands.length, + // name: 'Edit Request Auth', + // command: () => openRequestPanelTab(4), + // color: '', + // }, + // { + // id: commands.length, + // name: 'Edit Request Script', + // command: () => openRequestPanelTab(5, requestScriptEditorView), + // color: '', + // }, + // { + // id: commands.length, + // name: 'Edit Response Script', + // command: () => openRequestPanelTab(6, responseScriptEditorView), + // color: '', + // }, + // { + // id: commands.length, + // name: 'Open Request Code', + // command: () => openRequestPanelTab(7), + // color: '', + // }, + { + id: commands.length, + name: 'Edit Request URL', + command: () => { + console.log('edit request url', uriBarView); + if (uriBarView) { + uriBarView.focus(); + } + }, + color: '', + }, + ]; + commands.push(...requestCommands); + } + + function getFlattenedCollections(collections: Collection[]): Collection[] { + if (!collections) return []; + const res = [...collections]; + for (const c of collections) { + res.push(...getFlattenedCollections(c.children)); + } + return res; + } + function getFlattenedRequests( + collections: Collection[], + ): (RestRequest | WebsocketRequest)[] { + return getFlattenedCollections(collections).flatMap((c) => c.requests ?? []); + } + function getFlattenedScripts(collections: Collection[]): Script[] { + return getFlattenedCollections(collections).flatMap((c) => c.scripts ?? []); + } + + const openCollectionCommands = getFlattenedCollections(collections).map( + (c, i) => + ({ + id: commands.length + i, + name: `Open Collection: ${c.data.name} [${c.id}]`, + command: () => selectCollection.current(c.id), + color: '', + } as Command), + ); + + commands.push(...openCollectionCommands); + + const openRequestCommands = getFlattenedRequests(collections).map( + (r, i) => + ({ + id: commands.length + i, + name: `Open Request: ${r.data.name} [${r.id}]`, + command: () => selectRequest.current(r.id), + color: '', + } as Command), + ); + + commands.push(...openRequestCommands); + + const openScriptCommands = getFlattenedScripts(collections).map( + (s, i) => + ({ + id: commands.length + i, + name: `Open Script: ${s.data.name} [${s.id}]`, + command: () => selectScript.current(s.id), + color: '', + } as Command), + ); + + commands.push(...openScriptCommands); - return ; + return ( +
+ setIsOpen(false)} + resetInputOnOpen={true} + /> +
+ ); } export default CmdPalette; diff --git a/client/src/components/collectionPanel/CollectionPanel.tsx b/client/src/components/collectionPanel/CollectionPanel.tsx index d2f7fe8d..72954e2e 100644 --- a/client/src/components/collectionPanel/CollectionPanel.tsx +++ b/client/src/components/collectionPanel/CollectionPanel.tsx @@ -29,7 +29,7 @@ import { } from '../../state/currentCollection'; import { BASE_PATH, cn, errorToast, successToast } from '../../utils'; import { getMergedEnvData } from '../../utils/env'; -import { getSelectedEnv, getSelectedEnvs } from '../../utils/store'; +import { getSelectedEnvs } from '../../utils/store'; import { useKeyPress } from '../../utils/useKeyPress'; import AuthTab from '../authTab'; import Editor from '../editor'; @@ -62,9 +62,6 @@ export default function CollectionPanel({ const envName = getSelectedEnvs()[currentCollection.id]; return envName ? envName : null; }, [currentCollection.id]); - const selectedEnv = useMemo(() => { - return currentCollection ? getSelectedEnv(currentCollection) : null; - }, [currentCollection]); const selectedEnvData = useMemo(() => { if (!currentCollection) return null; return selectedEnvName diff --git a/client/src/components/collectionView/MoveableHeader.tsx b/client/src/components/collectionView/MoveableHeader.tsx index 29d03e30..0ca61234 100644 --- a/client/src/components/collectionView/MoveableHeader.tsx +++ b/client/src/components/collectionView/MoveableHeader.tsx @@ -31,6 +31,7 @@ import { import api from '../../api'; import { UserContext } from '../../context'; +import { CurrentModalContext } from '../../context/CurrentModalContext'; import Collection, { SidebarCollection } from '../../model/Collection'; import { RestRequest, WebsocketRequest } from '../../model/Request'; import Script from '../../model/Script'; @@ -64,7 +65,6 @@ type MoveableHeaderState = { name: string; newRequestName: string; importData: string; - currentModal: string; newCollectionName: string; groups: string[]; uploadFile: any; @@ -114,10 +114,23 @@ function MoveableHeader({ isCollectionDescendant, }: MoveableHeaderProps) { const { user } = useContext(UserContext); + const { + currentModalState, + currentModalType, + currentModalId, + clearCurrentModal, + setCurrentModal, + } = useContext(CurrentModalContext); + const isOpen = useMemo( + () => + !!currentModalState && + currentModalType === 'collection' && + currentModalId === collection.id, + [currentModalState, currentModalType, currentModalId, collection.id], + ); const [state, setState] = useState({ name: collection.name, newRequestName: '', - currentModal: '', importData: '', newCollectionName: '', groups: user?.data?.groups ?? [], @@ -135,7 +148,6 @@ function MoveableHeader({ const { colorMode } = useColorMode(); const { onCopy } = useClipboard(`${window.location.origin}/#/${collection.id}`); const toast = useToast(); - const { isOpen, onOpen, onClose } = useDisclosure(); const headerVariants = useMemo(() => { return currentCollectionId === collection.id ? ['selected'] : []; }, [currentCollectionId, collection.id]); @@ -362,7 +374,7 @@ function MoveableHeader({ newScriptName: '', importData: '', }); - onClose(); + clearCurrentModal(); } async function handleCreateRESTRequestClick() { @@ -511,7 +523,7 @@ function MoveableHeader({ // correctly on response scripts const currentModal = !isOpen ? null - : ((s: string) => { + : ((s: string | undefined) => { switch (s) { case 'newRestRequest': return ( @@ -777,7 +789,7 @@ function MoveableHeader({ ); } - })(state.currentModal); + })(currentModalState); return (
} onClick={(e) => { e.stopPropagation(); - setState({ ...state, currentModal: 'newRestRequest' }); - onOpen(); + setCurrentModal('newRequest', 'collection', collection.id); }} > New Request @@ -857,8 +868,7 @@ function MoveableHeader({ icon={} onClick={(e) => { e.stopPropagation(); - setState({ ...state, currentModal: 'newWebsocketRequest' }); - onOpen(); + setCurrentModal('newWebsocketRequest', 'collection', collection.id); }} > New Websocket Request @@ -867,8 +877,7 @@ function MoveableHeader({ icon={} onClick={(e) => { e.stopPropagation(); - setState({ ...state, currentModal: 'newCollection' }); - onOpen(); + setCurrentModal('newCollection', 'collection', collection.id); }} > New Collection @@ -877,8 +886,7 @@ function MoveableHeader({ icon={} onClick={(e) => { e.stopPropagation(); - setState({ ...state, currentModal: 'newScript' }); - onOpen(); + setCurrentModal('newScript', 'collection', collection.id); }} > New Job Script @@ -899,11 +907,9 @@ function MoveableHeader({ e.stopPropagation(); setState({ ...state, - currentModal: 'duplicate', name: `${collection.name} (copy)`, }); - - onOpen(); + setCurrentModal('duplicate', 'collection', collection.id); }} > Duplicate @@ -913,8 +919,7 @@ function MoveableHeader({ icon={} onClick={(e) => { e.stopPropagation(); - setState({ ...state, currentModal: 'delete' }); - onOpen(); + setCurrentModal('delete', 'collection', collection.id); }} > Delete diff --git a/client/src/components/editor/Editor.tsx b/client/src/components/editor/Editor.tsx index aca594a3..4a94a830 100644 --- a/client/src/components/editor/Editor.tsx +++ b/client/src/components/editor/Editor.tsx @@ -28,7 +28,7 @@ function Editor({ content, setContent }: EditorProps) { const ref = useRef(null); - const { setContainer } = useCodeMirror({ + const { setContainer, view } = useCodeMirror({ container: ref.current, onChange: (value: string) => setContent(value), extensions: [extensions], @@ -40,8 +40,9 @@ function Editor({ content, setContent }: EditorProps) { useEffect(() => { if (ref.current) { setContainer(ref.current); + view?.focus(); } - }, [ref, setContainer]); + }, [ref, setContainer, view]); function handleBeautifyClick() { try { diff --git a/client/src/components/requestPanel/RequestPanel.tsx b/client/src/components/requestPanel/RequestPanel.tsx index 9af5fbc4..29f8acd3 100644 --- a/client/src/components/requestPanel/RequestPanel.tsx +++ b/client/src/components/requestPanel/RequestPanel.tsx @@ -4,6 +4,7 @@ import { Dispatch, useCallback, useContext, useMemo } from 'react'; import { VscSave } from 'react-icons/vsc'; import { UserContext } from '../../context'; +import { FocusElementsContext } from '../../context/FocusElementsContext'; import KVRow from '../../model/KVRow'; import { AuthData, CurrentRestRequest, RestRequest } from '../../model/Request'; import { RestResponse } from '../../model/Response'; @@ -33,6 +34,8 @@ type RequestPanelProps = { sendRequest(request: RestRequest, envName?: string, n?: number): Promise; saveOnSend: (request: RestRequest) => Promise; handleSaveRequestClick: () => Promise; + tabIndex: number; + setTabIndex: (index: number) => void; selectedEnvData: Record; selectedEnvName?: string; }; @@ -43,6 +46,8 @@ function RequestPanel({ sendRequest, saveOnSend, handleSaveRequestClick, + tabIndex, + setTabIndex, selectedEnvData, selectedEnvName, }: RequestPanelProps) { @@ -229,6 +234,7 @@ function RequestPanel({ setMethod={setMethod} handleSendButtonClick={handleSendButtonClick} isLoading={currentRequest.isLoading} + currentRequestId={currentRequest.id} selectedEnvData={selectedEnvData} /> setTabIndex(index)} colorScheme="green" mt="1" display="flex" diff --git a/client/src/components/requestPanel/RequestSender.tsx b/client/src/components/requestPanel/RequestSender.tsx index d7a63bad..87f1ac98 100644 --- a/client/src/components/requestPanel/RequestSender.tsx +++ b/client/src/components/requestPanel/RequestSender.tsx @@ -102,6 +102,8 @@ type RequestSenderProps = { isExtInitialized: MutableRefObject; extVersion: MutableRefObject; openExtModal: () => void; + tabIndex: number; + setTabIndex: (index: number) => void; }; function RequestSender({ @@ -112,6 +114,8 @@ function RequestSender({ isExtInitialized, extVersion, openExtModal, + tabIndex, + setTabIndex, }: RequestSenderProps) { const [newReqForm, setNewReqForm] = useState({ collectionId: -1, @@ -597,6 +601,8 @@ function RequestSender({ return sendRequest(request, reqCollections, selectedEnvName ?? undefined); }} saveOnSend={saveOnSend} + tabIndex={tabIndex} + setTabIndex={setTabIndex} selectedEnvData={selectedEnvData ?? {}} selectedEnvName={selectedEnvName ?? ''} /> diff --git a/client/src/components/sidebar/Sidebar.module.css b/client/src/components/sidebar/Sidebar.module.css index bb0baeff..1378c0d6 100644 --- a/client/src/components/sidebar/Sidebar.module.css +++ b/client/src/components/sidebar/Sidebar.module.css @@ -6,7 +6,7 @@ align-items: center; position: sticky; top: 0; - z-index: 1000; + z-index: 1; } .searchContainer--light { diff --git a/client/src/components/uriBar/UriBar.tsx b/client/src/components/uriBar/UriBar.tsx index a221f86d..127507af 100644 --- a/client/src/components/uriBar/UriBar.tsx +++ b/client/src/components/uriBar/UriBar.tsx @@ -2,8 +2,9 @@ import { Spinner, useColorMode } from '@chakra-ui/react'; import { history } from '@codemirror/commands'; import { drawSelection } from '@codemirror/view'; import { useCodeMirror } from '@uiw/react-codemirror'; -import { FormEvent, useEffect, useRef } from 'react'; +import { FormEvent, RefObject, useContext, useEffect, useRef } from 'react'; +import { FocusElementsContext } from '../../context/FocusElementsContext'; import { cn } from '../../utils'; import { helpCursor, @@ -27,6 +28,8 @@ type UriBarProps = { setMethod: any; isLoading: boolean; handleSendButtonClick: () => void; + // NOTE: we need this to set the focus element on changing the request + currentRequestId: number; selectedEnvData: Record; }; @@ -41,12 +44,14 @@ function UriBar({ setMethod, isLoading, handleSendButtonClick, + currentRequestId, selectedEnvData, }: UriBarProps) { const { colorMode } = useColorMode(); const ref = useRef(null); + const { setUriBarView } = useContext(FocusElementsContext); - const { setContainer } = useCodeMirror({ + const { setContainer, view } = useCodeMirror({ container: ref.current, onChange: (value: string) => setUri(value), extensions: [ @@ -67,6 +72,12 @@ function UriBar({ basicSetup: singleLineSetupOptions, }); + useEffect(() => { + if (view) { + setUriBarView(view); + } + }, [view, currentRequestId, setUriBarView]); + useEffect(() => { if (ref.current) { setContainer(ref.current); diff --git a/client/src/context/CurrentModalContext.tsx b/client/src/context/CurrentModalContext.tsx new file mode 100644 index 00000000..dc19f45a --- /dev/null +++ b/client/src/context/CurrentModalContext.tsx @@ -0,0 +1,50 @@ +import { createContext, FunctionComponent, useState } from 'react'; + +interface ICurrentModalContext { + currentModalState?: string; + currentModalType?: string; + currentModalId?: number; + setCurrentModal: (modal: string, type: string, id: number) => void; + clearCurrentModal: () => void; +} + +const CurrentModalContext = createContext({ + setCurrentModal: () => {}, + clearCurrentModal: () => {}, +}); + +type CurrentModalState = { + currentModalState?: string; + currentModalType?: string; + currentModalId?: number; +}; + +const CurrentModalProvider: FunctionComponent = ({ children }) => { + const [state, setState] = useState({}); + + return ( + { + setState({ currentModalState: currentModal, currentModalType, currentModalId }); + }, + clearCurrentModal: () => { + setState({}); + }, + }} + > + {children} + + ); +}; + +export { CurrentModalContext }; + +export default CurrentModalProvider; diff --git a/client/src/context/FocusElementsContext.tsx b/client/src/context/FocusElementsContext.tsx new file mode 100644 index 00000000..c6b22fb4 --- /dev/null +++ b/client/src/context/FocusElementsContext.tsx @@ -0,0 +1,58 @@ +import { createContext, FunctionComponent, useState } from 'react'; + +interface IFocusElementsContext { + uriBarView: any; + setUriBarView: any; + bodyEditorView: any; + setBodyEditorView: any; + requestScriptEditorView: any; + setRequestScriptEditorView: any; + responseScriptEditorView: any; + setResponseScriptEditorView: any; + i: number; + setI: any; +} + +const FocusElementsContext = createContext({ + uriBarView: null, + setUriBarView: () => {}, + bodyEditorView: null, + setBodyEditorView: () => {}, + requestScriptEditorView: null, + setRequestScriptEditorView: () => {}, + responseScriptEditorView: null, + setResponseScriptEditorView: () => {}, + i: 0, + setI: () => {}, +}); + +const FocusElementsProvider: FunctionComponent = ({ children }) => { + const [uriBarView, setUriBarView] = useState(); + const [bodyEditorView, setBodyEditorView] = useState(); + const [requestScriptEditorView, setRequestScriptEditorView] = useState(); + const [responseScriptEditorView, setResponseScriptEditorView] = useState(); + const [i, setI] = useState(0); + + return ( + + {children} + + ); +}; + +export { FocusElementsContext }; + +export default FocusElementsProvider; diff --git a/client/src/context/index.tsx b/client/src/context/index.tsx index 955f4db7..5af40a1d 100644 --- a/client/src/context/index.tsx +++ b/client/src/context/index.tsx @@ -1,9 +1,17 @@ import { FunctionComponent } from 'react'; +import CurrentModalProvider from './CurrentModalContext'; +import FocusElementsProvider from './FocusElementsContext'; import UserProvider, { UserContext } from './UserContext'; const ContextProvider: FunctionComponent = ({ children }) => { - return {children}; + return ( + + + {children} + + + ); }; export { UserContext }; diff --git a/client/src/index.css b/client/src/index.css index d115ec5d..a9093280 100644 --- a/client/src/index.css +++ b/client/src/index.css @@ -37,3 +37,8 @@ input:focus { ::-webkit-scrollbar-thumb:hover { background: #555; } + +/* NOTE: this makes sure that the cmd palette is in front of all other elements */ +.atom-overlay { + z-index: 9999999; +} diff --git a/client/src/pages/dashboard/Dashboard.tsx b/client/src/pages/dashboard/Dashboard.tsx index 02619175..8b6d61f8 100644 --- a/client/src/pages/dashboard/Dashboard.tsx +++ b/client/src/pages/dashboard/Dashboard.tsx @@ -27,6 +27,7 @@ import { useEventListener } from 'usehooks-ts'; import api from '../../api'; import BasicModal from '../../components/basicModal'; +import CmdPalette from '../../components/cmdPalette'; import CollectionPanel from '../../components/collectionPanel'; import Header from '../../components/header'; import RequestSender from '../../components/requestPanel/RequestSender'; @@ -65,6 +66,11 @@ import { defaultCurrentRequest, } from '../../state/currentRequest'; import { CurrentScriptActionType, currentScriptReducer } from '../../state/currentScript'; +import { + defaultOpenHistory, + OpenHistoryActionType, + openHistoryReducer, +} from '../../state/openHistory'; import { BASE_PATH, errorToast, parseLocation, successToast } from '../../utils'; import { getMergedEnvData } from '../../utils/env'; import interpolate from '../../utils/interpolate'; @@ -139,6 +145,10 @@ function Dashboard() { currentScriptReducer, undefined, ); + const [openHistory, dispatchOpenHistory] = useReducer( + openHistoryReducer, + defaultOpenHistory, + ); const [selectedRequestId, setSelectedRequestId] = useState( undefined, ); @@ -206,6 +216,10 @@ function Dashboard() { dispatchCurrentScript({ type: CurrentScriptActionType.UNSET, }); + dispatchOpenHistory({ + type: OpenHistoryActionType.PUSH, + item: { type: 'request', id: r.id }, + }); const code = new URLSearchParams(window.location.search).get('code'); if (code && r.type === 'REST') { await getRequestAccessTokenFromCode(code, r, collections); @@ -225,6 +239,10 @@ function Dashboard() { dispatchCurrentCollection({ type: CurrentCollectionActionType.UNSET, }); + dispatchOpenHistory({ + type: OpenHistoryActionType.PUSH, + item: { type: 'script', id: s.id }, + }); } openCollectionTree(collections, loc.collectionId); } else if (loc.collectionId) { @@ -240,6 +258,10 @@ function Dashboard() { dispatchCurrentScript({ type: CurrentScriptActionType.UNSET, }); + dispatchOpenHistory({ + type: OpenHistoryActionType.PUSH, + item: { type: 'collection', id: c.id }, + }); const code = new URLSearchParams(window.location.search).get('code'); if (code) { await getCollectionAccessTokenFromCode(code, c, collections); @@ -428,7 +450,7 @@ function Dashboard() { }; const dispatchSelectCollection = useCallback( - (id: number) => { + (id: number, pushHistory: boolean = true) => { const collection = findCollection(collections, id); if (!collection) throw new Error("Collection doesn't exist"); navigate(`/${id}`); @@ -442,12 +464,18 @@ function Dashboard() { dispatchCurrentScript({ type: CurrentScriptActionType.UNSET, }); + if (pushHistory) { + dispatchOpenHistory({ + type: OpenHistoryActionType.PUSH, + item: { type: 'collection', id: id }, + }); + } }, [collections, navigate], ); const dispatchSelectRequest = useCallback( - (id: number) => { + (id: number, pushHistory: boolean = true) => { const request = findRequest(collections, id); if (!request) throw new Error("Request doesn't exist"); navigate(`/${request.collectionId}/${request.id}`); @@ -461,12 +489,18 @@ function Dashboard() { dispatchCurrentScript({ type: CurrentScriptActionType.UNSET, }); + if (pushHistory) { + dispatchOpenHistory({ + type: OpenHistoryActionType.PUSH, + item: { type: 'request', id: id }, + }); + } }, [collections, navigate], ); const dispatchSelectScript = useCallback( - (id: number) => { + (id: number, pushHistory: boolean = true) => { const script = findScript(collections, id); if (!script) throw new Error(`Script ${id} doesn't exist`); navigate(`/${script.collectionId}/s-${script.id}`); @@ -480,6 +514,12 @@ function Dashboard() { type: CurrentScriptActionType.SET, script: script, }); + if (pushHistory) { + dispatchOpenHistory({ + type: OpenHistoryActionType.PUSH, + item: { type: 'script', id: id }, + }); + } }, [collections, navigate], ); @@ -541,7 +581,7 @@ function Dashboard() { }, [currentRequest, updateRequest]); const selectRequest = useCallback( - async (id: number) => { + async (id: number, pushHistory: boolean = true) => { try { if (currentRequest?.id === id) return; if (user?.data?.settings?.saveOnClose) { @@ -567,7 +607,7 @@ function Dashboard() { data: { ...currentScript.data }, }); } - dispatchSelectRequest(id); + dispatchSelectRequest(id, pushHistory); } else if (currentRequest?.isChanged && currentRequest?.id !== -1) { setSelectedRequestId(id); onSaveReqOpen(); @@ -578,8 +618,14 @@ function Dashboard() { setSelectedCollectionId(id); onSaveScriptOpen(); } else { - dispatchSelectRequest(id); + dispatchSelectRequest(id, pushHistory); } + const req = findRequest(collections, id); + if (!req) return; + dispatchCollections({ + type: CollectionsActionType.OPEN_COLLECTION_TREE, + id: req?.collectionId, + }); } catch (e) { console.error(e); errorToast('Could not select request', toast); @@ -590,6 +636,7 @@ function Dashboard() { user?.data?.settings?.saveOnClose, currentCollection, currentScript, + collections, dispatchSelectRequest, updateRequest, updateCollection, @@ -608,7 +655,7 @@ function Dashboard() { }, [selectRequest]); const selectCollection = useCallback( - async (id: number) => { + async (id: number, pushHistory: boolean = true) => { try { if (currentCollection?.id === id) return; if (user?.data?.settings?.saveOnClose) { @@ -634,7 +681,7 @@ function Dashboard() { data: { ...currentScript.data }, }); } - dispatchSelectCollection(id); + dispatchSelectCollection(id, pushHistory); } else if (currentRequest?.isChanged && currentRequest?.id !== -1) { setSelectedCollectionId(id); onSaveReqOpen(); @@ -645,8 +692,12 @@ function Dashboard() { setSelectedCollectionId(id); onSaveScriptOpen(); } else { - dispatchSelectCollection(id); + dispatchSelectCollection(id, pushHistory); } + dispatchCollections({ + type: CollectionsActionType.OPEN_COLLECTION_TREE, + id: id, + }); } catch (e) { console.error(e); errorToast('Could not select collection', toast); @@ -675,7 +726,7 @@ function Dashboard() { }, [selectCollection]); const selectScript = useCallback( - async (id: number) => { + async (id: number, pushHistory: boolean = true) => { try { if (currentScript?.id === id) return; if (user?.data?.settings?.saveOnClose) { @@ -701,7 +752,7 @@ function Dashboard() { data: { ...currentScript.data }, }); } - dispatchSelectScript(id); + dispatchSelectScript(id, pushHistory); } else if (currentRequest?.isChanged && currentRequest?.id !== -1) { setSelectedCollectionId(id); onSaveReqOpen(); @@ -712,14 +763,21 @@ function Dashboard() { setSelectedCollectionId(id); onSaveScriptOpen(); } else { - dispatchSelectScript(id); + dispatchSelectScript(id, pushHistory); } + const script = findScript(collections, id); + if (!script) return; + dispatchCollections({ + type: CollectionsActionType.OPEN_COLLECTION_TREE, + id: script?.collectionId, + }); } catch (e) { console.error(e); errorToast('Could not select script', toast); } }, [ + collections, currentCollection, currentRequest, currentScript, @@ -964,6 +1022,8 @@ function Dashboard() { isExtInitialized={isExtInitialized} extVersion={extVersion} openExtModal={onOpen} + tabIndex={requestPanelTabIndex} + setTabIndex={setRequestPanelTabIndex} />
@@ -1174,13 +1234,19 @@ function Dashboard() {
Do you want to save the changes now? - {/* */} + setRequestPanelTabIndex={setRequestPanelTabIndex} + openHistory={openHistory} + dispatchOpenHistory={dispatchOpenHistory} + />
); } diff --git a/client/src/state/collections.tsx b/client/src/state/collections.tsx index 9d44bbf6..cf6058ea 100644 --- a/client/src/state/collections.tsx +++ b/client/src/state/collections.tsx @@ -21,6 +21,7 @@ enum CollectionsActionType { CHANGE_REQUEST_COLLECTION = 'CHANGE_REQUEST_COLLECTION', CLOSE_ALL = 'CLOSE_ALL', TOGGLE_OPEN_COLLECTION = 'TOGGLE_OPEN_COLLECTION', + OPEN_COLLECTION_TREE = 'OPEN_COLLECTION_TREE', SET_ENV_VAR = 'SET_ENV_VAR', PATCH_SCRIPT_DATA = 'PATCH_SCRIPT_DATA', MOVE_SCRIPT = 'MOVE_SCRIPT', @@ -89,6 +90,11 @@ type ToggleOpenCollectionAction = { id: number; }; +type OpenCollectionTreeAction = { + type: CollectionsActionType.OPEN_COLLECTION_TREE; + id: number; +}; + type SetEnvVarPayload = { collectionId: number; envName: string; @@ -458,6 +464,18 @@ function toggleOpenCollection(state: Collection[], id: number): Collection[] { }); } +function openCollectionTree(state: Collection[], id: number): Collection[] { + let c = findCollection(state, id); + while (c) { + c.open = true; + if (!c.data.parentId) { + break; + } + c = findCollection(state, c.data.parentId); + } + return [...state]; +} + function setEnvVar(state: Collection[], payload: SetEnvVarPayload): Collection[] { return modifyCollection(state, payload.collectionId, (c) => { const envs = c.data?.envs; @@ -563,6 +581,7 @@ type CollectionsAction = | MoveRequestAction | CloseAllAction | ToggleOpenCollectionAction + | OpenCollectionTreeAction | SetEnvVarAction | PatchScriptDataAction | MoveScriptAction @@ -597,6 +616,8 @@ function collectionsReducer( return closeAll(state); case CollectionsActionType.TOGGLE_OPEN_COLLECTION: return toggleOpenCollection(state, action.id); + case CollectionsActionType.OPEN_COLLECTION_TREE: + return openCollectionTree(state, action.id); case CollectionsActionType.SET_ENV_VAR: return setEnvVar(state, action.payload); case CollectionsActionType.PATCH_SCRIPT_DATA: diff --git a/client/src/state/openHistory.tsx b/client/src/state/openHistory.tsx new file mode 100644 index 00000000..06205a6f --- /dev/null +++ b/client/src/state/openHistory.tsx @@ -0,0 +1,92 @@ +type OpenHistoryItem = { + type: 'collection' | 'request' | 'script'; + id: number; +}; + +type OpenHistory = { + items: OpenHistoryItem[]; + index: number; +}; + +const defaultOpenHistory: OpenHistory = { + items: [], + index: -1, +}; + +enum OpenHistoryActionType { + PUSH = 'PUSH', + FORWARD = 'FORWARD', + BACKWARD = 'BACKWARD', +} + +type PushAction = { + type: OpenHistoryActionType.PUSH; + item: OpenHistoryItem; +}; + +type ForwardAction = { + type: OpenHistoryActionType.FORWARD; +}; + +type BackwardAction = { + type: OpenHistoryActionType.BACKWARD; +}; + +function push(state: OpenHistory, item: OpenHistoryItem): OpenHistory { + if (state.index === state.items.length - 1) { + return { + items: [...state.items, item], + index: state.index + 1, + }; + } + if ( + state.items[state.index + 1].id === item.id && + state.items[state.index + 1].type === item.type + ) { + return { + items: [...state.items], + index: state.index + 1, + }; + } + return { + items: [...state.items.slice(0, state.index + 1), item], + index: state.index + 1, + }; +} + +function forward(state: OpenHistory): OpenHistory { + return { + items: state.items, + index: Math.min(state.index + 1, state.items.length - 1), + }; +} + +function backward(state: OpenHistory): OpenHistory { + return { + items: state.items, + index: Math.max(state.index - 1, 0), + }; +} + +type OpenHistoryAction = PushAction | ForwardAction | BackwardAction; + +function openHistoryReducer( + state: OpenHistory | undefined = defaultOpenHistory, + action: OpenHistoryAction, +) { + switch (action.type) { + case OpenHistoryActionType.PUSH: + return push(state, action.item); + case OpenHistoryActionType.FORWARD: + return forward(state); + case OpenHistoryActionType.BACKWARD: + return backward(state); + default: + console.error('Invalid action type', action); + return state; + } +} + +export type { OpenHistory, OpenHistoryAction }; + +export { defaultOpenHistory, OpenHistoryActionType, openHistoryReducer }; diff --git a/server/src/main/kotlin/com/espero/yaade/server/routes/CollectionRoute.kt b/server/src/main/kotlin/com/espero/yaade/server/routes/CollectionRoute.kt index 129b7104..915e3249 100644 --- a/server/src/main/kotlin/com/espero/yaade/server/routes/CollectionRoute.kt +++ b/server/src/main/kotlin/com/espero/yaade/server/routes/CollectionRoute.kt @@ -95,6 +95,7 @@ class CollectionRoute(private val daoManager: DaoManager, private val vertx: Ver val data = ctx.body().asJsonObject() val userId = ctx.user().principal().getLong("id") val newCollection = CollectionDb(data, userId) + newCollection.createEnv("default", JsonObject().put("proxy", "server")) val parentId = newCollection.jsonData().getLong("parentId") if (parentId != null) {