diff --git a/packages/insomnia/src/ui/components/dropdowns/git-sync-dropdown.tsx b/packages/insomnia/src/ui/components/dropdowns/git-sync-dropdown.tsx index 5815861b1e..5ad2e2c3d2 100644 --- a/packages/insomnia/src/ui/components/dropdowns/git-sync-dropdown.tsx +++ b/packages/insomnia/src/ui/components/dropdowns/git-sync-dropdown.tsx @@ -1,10 +1,9 @@ -import classnames from 'classnames'; -import React, { type FC, Fragment, useEffect, useRef, useState } from 'react'; -import { Button } from 'react-aria-components'; +import type { IconName, IconProp } from '@fortawesome/fontawesome-svg-core'; +import React, { type FC, useEffect, useState } from 'react'; +import { Button, Collection, Menu, MenuItem, MenuTrigger, Popover, Section, Tooltip, TooltipTrigger } from 'react-aria-components'; import { useFetcher, useParams, useRevalidator } from 'react-router-dom'; import { useInterval } from 'react-use'; -import { docsGitSync } from '../../../common/documentation'; import type { GitRepository } from '../../../models/git-repository'; import { deleteGitRepository } from '../../../models/helpers/git-repository-operations'; import { getOauth2FormatName } from '../../../sync/git/utils'; @@ -17,21 +16,12 @@ import type { PullFromGitRemoteResult, PushToGitRemoteResult, } from '../../routes/git-actions'; -import { - Dropdown, - type DropdownHandle, - DropdownItem, - DropdownSection, - ItemContent, -} from '../base/dropdown'; -import { Link } from '../base/link'; -import { HelpTooltip } from '../help-tooltip'; +import { Icon } from '../icon'; import { showAlert } from '../modals'; import { GitBranchesModal } from '../modals/git-branches-modal'; import { GitLogModal } from '../modals/git-log-modal'; import { GitRepositorySettingsModal } from '../modals/git-repository-settings-modal'; import { GitStagingModal } from '../modals/git-staging-modal'; -import { Tooltip } from '../tooltip'; interface Props { gitRepository: GitRepository | null; @@ -44,7 +34,6 @@ export const GitSyncDropdown: FC = ({ gitRepository, isInsomniaSyncEnable projectId: string; workspaceId: string; }; - const dropdownRef = useRef(null); const [isGitRepoSettingsModalOpen, setIsGitRepoSettingsModalOpen] = useState(false); @@ -166,13 +155,13 @@ export const GitSyncDropdown: FC = ({ gitRepository, isInsomniaSyncEnable ); } - let iconClassName = 'fa-brands fa-git-alt'; + let iconClassName: IconProp = ['fab', 'git-alt']; const providerName = getOauth2FormatName(gitRepository?.credentials); if (providerName === 'github') { - iconClassName = 'fa fa-github'; + iconClassName = ['fab', 'github']; } if (providerName === 'gitlab') { - iconClassName = 'fa fa-gitlab'; + iconClassName = ['fab', 'gitlab']; } const isLoading = @@ -189,22 +178,28 @@ export const GitSyncDropdown: FC = ({ gitRepository, isInsomniaSyncEnable ? gitRepoDataFetcher.data : { branches: [], branch: '' }; - let dropdown: React.ReactNode = null; const { revalidate } = useRevalidator(); - const currentBranchActions = [ + const currentBranchActions: { + id: string; + label: string; + icon: IconName; + isDisabled?: boolean; + action: () => void; + }[] = (isSynced ? [ { - id: 1, + id: 'commit', icon: 'check', + isDisabled: gitChangesFetcher.data?.changes.length === 0, label: 'Commit', - onClick: () => setIsGitStagingModalOpen(true), + action: () => setIsGitStagingModalOpen(true), }, { - id: 2, - stayOpenAfterClick: true, - icon: loadingPull ? 'refresh fa-spin' : 'cloud-download', + id: 'pull', + icon: loadingPull ? 'refresh' : 'cloud-download', label: 'Pull', - onClick: async () => { + isDisabled: false, + action: async () => { gitPullFetcher.submit( {}, { @@ -215,24 +210,25 @@ export const GitSyncDropdown: FC = ({ gitRepository, isInsomniaSyncEnable }, }, { - id: 3, - stayOpenAfterClick: true, - icon: loadingPush ? 'refresh fa-spin' : 'cloud-upload', + id: 'push', + icon: loadingPush ? 'refresh' : 'cloud-upload', label: 'Push', - onClick: () => handlePush({ force: false }), + isDisabled: !gitCanPushFetcher.data?.canPush, + action: () => handlePush({ force: false }), }, { - id: 4, - icon: 'clock-o', - label: History, - onClick: () => setIsGitLogModalOpen(true), + id: 'history', + icon: 'clock', + isDisabled: false, + label: 'History', + action: () => setIsGitLogModalOpen(true), }, { - id: 5, - stayOpenAfterClick: true, - icon: loadingFetch ? 'refresh fa-spin' : 'refresh', + id: 'fetch', + icon: loadingFetch ? 'refresh' : 'refresh', + isDisabled: false, label: 'Fetch', - onClick: () => { + action: () => { gitFetchFetcher.submit( {}, { @@ -242,7 +238,36 @@ export const GitSyncDropdown: FC = ({ gitRepository, isInsomniaSyncEnable ); }, }, - ]; + ] : []); + + const gitSyncActions: { + id: string; + label: string; + icon: IconName; + isDisabled?: boolean; + action: () => void; + }[] = (isSynced ? [ + { + id: 'repository-settings', + label: 'Repository Settings', + isDisabled: false, + icon: 'wrench', + action: () => setIsGitRepoSettingsModalOpen(true), + }, + { + id: 'branches', + label: 'Branches', + isDisabled: false, + icon: 'code-branch', + action: () => setIsGitBranchesModalOpen(true), + }, + ] : [{ + id: 'connect', + label: 'Connect Repository', + icon: 'plug', + isDisabled: false, + action: () => setIsGitRepoSettingsModalOpen(true), + }]); useInterval(() => { gitFetchFetcher.submit( @@ -258,280 +283,174 @@ export const GitSyncDropdown: FC = ({ gitRepository, isInsomniaSyncEnable const commitToolTipMsg = status?.localChanges ? 'Local changes made' : 'No local changes made'; - if (isSynced) { - dropdown = ( - -
- {iconClassName && ( - - )} -
{currentBranch}
+ const switchToInsomniaSyncList: { + id: string; + label: string; + icon: IconProp; + isDisabled?: boolean; + action: () => void; + }[] = isInsomniaSyncEnabled ? [ + { + id: 'switch-to-git-repo', + label: 'Switch to Insomnia Sync', + icon: 'cloud', + action: async () => { + gitRepository && await deleteGitRepository(gitRepository); + revalidate(); + }, + }, + ] : []; -
- - - -
-
- - + const branchesActionList: { + id: string; + label: string; + icon: IconName; + isDisabled?: boolean; + isActive: boolean; + action: () => void; + }[] = isSynced ? branches.map(branch => ({ + id: branch, + label: branch, + isActive: branch === currentBranch, + icon: 'code-branch', + action: async () => { + gitCheckoutFetcher.submit( + { + branch, + }, + { + action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/git/branch/checkout`, + method: 'post', } - > - - {item => ( - - - - )} - - - Git Sync - - Sync and collaborate with Git{' '} - - -
- Documentation -
- -
- - } - > - - { - setIsGitRepoSettingsModalOpen(true); - }} - /> - + ); + }, + })) : []; - - {currentBranch && ( - { - setIsGitBranchesModalOpen(true); - }} - /> - )} - -
- - ({ branch: b })) : []} - > - {({ branch }) => { - const isCurrentBranch = branch === currentBranch; - - return ( - - { - gitCheckoutFetcher.submit( - { - branch, - }, - { - action: `/organization/${organizationId}/project/${projectId}/workspace/${workspaceId}/git/branch/checkout`, - method: 'post', - } - ); - }} - /> - - ); - }} - - - - {({ id, ...action }) => ( - - - - )} - -
- ); - } else { - dropdown = ( - -
- {iconClassName && ( - - )} - Git Sync -
- - - } - > - - {item => ( - - - - )} - - - Git Sync - - Sync and collaborate with Git{' '} - - -
- Documentation -
- -
- - } - > - - { - setIsGitRepoSettingsModalOpen(true); - }} - /> - -
-
- ); - } + const allSyncMenuActionList = [...switchToInsomniaSyncList, ...gitSyncActions, ...branchesActionList, ...currentBranchActions]; return ( - - {dropdown} + <> + +
+ + + + + {commitToolTipMsg} + + +
+ + item?.isDisabled).map(item => item.id)} + onAction={key => { + const item = allSyncMenuActionList.find(item => item.id === key); + console.log('onAction', key, item); + item?.action(); + }} + className="border max-w-lg select-none text-sm border-solid border-[--hl-sm] shadow-lg bg-[--color-bg] py-2 rounded-md overflow-y-auto max-h-[85vh] focus:outline-none" + > +
+ + {item => ( + +
+ +
{item.label}
+
+
+ )} +
+
+
+ + {item => ( + + + {item.label} + + )} + +
+
+ + {item => ( + + + {item.label} + + )} + +
+
+ + {item => ( + + + {item.label} + + )} + +
+
+
+
{isGitRepoSettingsModalOpen && ( setIsGitRepoSettingsModalOpen(false)} /> )} - {isGitBranchesModalOpen && ( + {isGitBranchesModalOpen && gitRepository && ( setIsGitBranchesModalOpen(false)} + activeBranch={currentBranch} + branches={branches} /> )} - {isGitLogModalOpen && ( + {isGitLogModalOpen && gitRepository && ( setIsGitLogModalOpen(false)} /> )} - {isGitStagingModalOpen && ( - setIsGitStagingModalOpen(false)} /> + {isGitStagingModalOpen && gitRepository && ( + setIsGitStagingModalOpen(false)} + /> )} -
+ ); }; diff --git a/packages/insomnia/src/ui/routes/git-actions.tsx b/packages/insomnia/src/ui/routes/git-actions.tsx index 1ce018520c..ab3977be0c 100644 --- a/packages/insomnia/src/ui/routes/git-actions.tsx +++ b/packages/insomnia/src/ui/routes/git-actions.tsx @@ -293,7 +293,13 @@ export const gitChangesLoader: LoaderFunction = async ({ const repoId = workspaceMeta?.gitRepositoryId; - invariant(repoId, 'Workspace is not linked to a git repository'); + if (!repoId) { + return { + branch: '', + changes: [], + statusNames: {}, + }; + } const gitRepository = await models.gitRepository.getById(repoId); @@ -333,7 +339,11 @@ export const canPushLoader: LoaderFunction = async ({ params }): Promise