mirror of
https://github.com/Kong/insomnia.git
synced 2026-06-02 13:19:21 -04:00
feat(Git Sync): improve git migration onboarding UX and local file system access [INS-2462] (#9890)
* feat(Migration): enhance migration summary with total projects count and improve UI feedback * fix(Migration): clarify update instructions and improve user messaging * style(ManualCommitForm): adjust text sizes for improved readability * style(StagingModal): adjust layout and spacing for improved UI consistency * fix(ManualCommitForm): update clipboard text to include 'cd' command for easier navigation * fix(ProjectSettingsForm): update repository path copy functionality and add option to open in file system * fix(ManualCommitForm): enhance file system interaction with tooltips for better user guidance * fix(ProjectSettingsForm): add tooltip for 'Open in file system' button to enhance user guidance * fix(GitProjectSyncDropdown): add 'Open folder' action to sync dropdown for easier access to repository path * fix(Component): display relative path of current issue in modal for better context * fix(git-service): count only successfully migrated projects in totalProjects * fix(project-settings-form): platform-aware shell quoting for cd command * fix(git-project-staging-modal): platform-aware shell quoting for cd command * fix(project-settings-form): update aria-label to reflect cd command clipboard content * fix(git-project-staging-modal): update aria-label to reflect shell command clipboard content * fix(ManualCommitForm): replace tooltip with dialog for enhanced information display * fix(MigrationView): update migrated count calculation to reflect total projects --------- Co-authored-by: James Gatz <jamesgatzos@gmail.com>
This commit is contained in:
@@ -2886,6 +2886,7 @@ async function getCurrentBranchByRepositoryId({
|
||||
export interface MigrationSummary {
|
||||
logs: string[];
|
||||
failedProjects: { id: string; name: string }[];
|
||||
totalProjects: number;
|
||||
}
|
||||
|
||||
export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
|
||||
@@ -2895,7 +2896,7 @@ export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
|
||||
const allProjects = await services.project.all();
|
||||
const gitProjects = allProjects.filter((p): p is GitProject => models.project.isConnectedGitProject(p));
|
||||
|
||||
if (gitProjects.length === 0) return { logs, failedProjects };
|
||||
if (gitProjects.length === 0) return { logs, failedProjects, totalProjects: 0 };
|
||||
|
||||
// Batch-fetch all git repositories in one query instead of N individual lookups.
|
||||
const repoIds = gitProjects.map(p => models.project.getEffectiveRepoId(p)).filter(Boolean) as string[];
|
||||
@@ -2913,6 +2914,8 @@ export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
|
||||
`${ts()} [INFO] Starting migration v${CURRENT_MIGRATION_VERSION} for ${gitProjects.length} repo(s): ${projectList}`,
|
||||
);
|
||||
|
||||
let migratedCount = 0;
|
||||
|
||||
await Promise.all(
|
||||
gitProjects.map(async project => {
|
||||
const gitRepository = repoById.get(models.project.getEffectiveRepoId(project)!);
|
||||
@@ -2933,6 +2936,8 @@ export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
|
||||
const success = await migrateRepoStructureIfNeeded(baseDir, project._id, repoId, logger);
|
||||
if (!success) {
|
||||
failedProjects.push({ id: project._id, name: project.name });
|
||||
} else {
|
||||
migratedCount++;
|
||||
}
|
||||
}),
|
||||
);
|
||||
@@ -2965,7 +2970,7 @@ export async function runAllGitRepoMigrations(): Promise<MigrationSummary> {
|
||||
}),
|
||||
);
|
||||
|
||||
return { logs, failedProjects };
|
||||
return { logs, failedProjects, totalProjects: migratedCount };
|
||||
}
|
||||
|
||||
export interface GitServiceAPI {
|
||||
|
||||
@@ -9,24 +9,37 @@ import git_migration from '~/ui/images/git-migration/git.png';
|
||||
|
||||
type MigrationStatus = 'default' | 'running' | 'completed' | 'partiallyCompleted' | 'error';
|
||||
|
||||
const MIN_DISPLAY_MS = 3000;
|
||||
|
||||
const MigrationView = () => {
|
||||
const [status, setStatus] = useState<MigrationStatus>('default');
|
||||
const [migrationLogs, setMigrationLogs] = useState<string[]>([]);
|
||||
const [failedProjects, setFailedProjects] = useState<{ id: string; name: string }[]>([]);
|
||||
const [migratedCount, setMigratedCount] = useState(0);
|
||||
|
||||
const handleMigration = () => {
|
||||
setStatus('running');
|
||||
const startTime = Date.now();
|
||||
window.main.git
|
||||
.runAllGitRepoMigrations()
|
||||
.then((result: { logs: string[]; failedProjects: { id: string; name: string }[] }) => {
|
||||
setMigrationLogs(result.logs);
|
||||
setFailedProjects(result.failedProjects);
|
||||
setStatus(result.failedProjects.length > 0 ? 'partiallyCompleted' : 'completed');
|
||||
.then((result: { logs: string[]; failedProjects: { id: string; name: string }[]; totalProjects: number }) => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const remaining = Math.max(0, MIN_DISPLAY_MS - elapsed);
|
||||
setTimeout(() => {
|
||||
setMigrationLogs(result.logs);
|
||||
setFailedProjects(result.failedProjects);
|
||||
setMigratedCount(result.totalProjects);
|
||||
setStatus(result.failedProjects.length > 0 ? 'partiallyCompleted' : 'completed');
|
||||
}, remaining);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
const elapsed = Date.now() - startTime;
|
||||
const remaining = Math.max(0, MIN_DISPLAY_MS - elapsed);
|
||||
const errorMsg = err instanceof Error ? err.message : 'An unexpected error occurred.';
|
||||
setMigrationLogs(prev => [...prev, `[ERROR] ${errorMsg}`]);
|
||||
setStatus('error');
|
||||
setTimeout(() => {
|
||||
setMigrationLogs(prev => [...prev, `[ERROR] ${errorMsg}`]);
|
||||
setStatus('error');
|
||||
}, remaining);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -39,7 +52,8 @@ const MigrationView = () => {
|
||||
<div className="flex h-full min-h-[500px] w-[600px] flex-col items-center justify-center">
|
||||
<div className="relative flex w-full flex-col items-center justify-center gap-(--padding-sm) rounded-md border border-solid border-(--hl-sm) bg-(--hl-xs) p-8">
|
||||
<div className="relative flex min-h-[150px] flex-col justify-between gap-4 text-left text-(--color-font)">
|
||||
<h1 className="text-xl">
|
||||
<h1 className="flex items-center gap-2 text-xl">
|
||||
{isUpdateCompletedSuccessfully && <i className="fa fa-check-circle text-emerald-500" />}
|
||||
{isUpdateCompletedSuccessfully
|
||||
? 'Update Successful'
|
||||
: isUpdateErrored
|
||||
@@ -51,8 +65,9 @@ const MigrationView = () => {
|
||||
|
||||
{isUpdateCompletedSuccessfully ? (
|
||||
<p className="text-sm">
|
||||
Your file system has been successfully updated. Now you can explore all of your Insomnia files on your
|
||||
local system and use git on your CLI to manage changes.
|
||||
All {migratedCount} Insomnia git project{migratedCount !== 1 ? 's' : ''} on your local system have been
|
||||
updated. You can now explore them, use git from your favourite CLI, or modify them from any other tool of
|
||||
your choice.
|
||||
</p>
|
||||
) : isUpdateCompletedWithErrors ? (
|
||||
<>
|
||||
@@ -73,7 +88,7 @@ const MigrationView = () => {
|
||||
<>
|
||||
<p className="text-sm">We hit an unexpected error while updating your file system. Please try again.</p>
|
||||
<p className="text-sm text-[#828282]">
|
||||
Having trouble and need to contact us, or back up to an old version? See our docs
|
||||
Having trouble and need to contact us, or back up to an old version? See our{' '}
|
||||
<Link className="underline" to="https://developer.konghq.com/insomnia/git-sync/">
|
||||
docs.
|
||||
</Link>
|
||||
@@ -82,23 +97,20 @@ const MigrationView = () => {
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm">
|
||||
In order to continue with this update, we need to adjust your local file system. This is required to
|
||||
enable managing Insomnia changes using git on the CLI.
|
||||
In order to continue with this update, we need to adjust your local projects. This is required to enable
|
||||
managing Insomnia changes using git on the CLI.
|
||||
</p>
|
||||
<p className="text-sm">
|
||||
{isUpdateRunning
|
||||
? 'Note: Your data is safe and the update only takes seconds.'
|
||||
: 'Note: This update does NOT change your data and only affects how your local Insomnia files are stored.'}
|
||||
: 'Note: This update does NOT affect any ongoing work or pending changes, and only affects how your local Insomnia files are stored.'}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex h-[32px] w-full justify-end">
|
||||
{isUpdateCompletedSuccessfully ? (
|
||||
<Link
|
||||
className="h-[32px] rounded-xs border border-solid border-(--hl-md) bg-(--color-surprise) px-3 py-2 text-sm text-(--color-font-surprise) transition-colors hover:bg-(--color-surprise)/90 hover:no-underline"
|
||||
to="/organization"
|
||||
>
|
||||
<Link className="border border-solid border-(--hl-md) bg-(--color-surprise) font-semibold text-(--color-font-surprise) flex h-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm ring-1 ring-transparent transition-all focus:ring-(--hl-md) focus:ring-inset aria-pressed:opacity-80" to="/organization">
|
||||
Open Insomnia
|
||||
</Link>
|
||||
) : isUpdateCompletedWithErrors ? (
|
||||
@@ -111,10 +123,7 @@ const MigrationView = () => {
|
||||
<i className="fa fa-copy" />
|
||||
Copy Error Logs
|
||||
</CopyButton>
|
||||
<Link
|
||||
className="h-[32px] rounded-xs border border-solid border-(--hl-md) bg-(--color-surprise) px-3 py-2 text-sm text-(--color-font-surprise) transition-colors hover:bg-(--color-surprise)/90 hover:no-underline"
|
||||
to="/organization"
|
||||
>
|
||||
<Link className="border border-solid border-(--hl-md) bg-(--color-surprise) font-semibold text-(--color-font-surprise) flex h-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm ring-1 ring-transparent transition-all focus:ring-(--hl-md) focus:ring-inset aria-pressed:opacity-80" to="/organization">
|
||||
Open Insomnia
|
||||
</Link>
|
||||
</div>
|
||||
@@ -128,20 +137,12 @@ const MigrationView = () => {
|
||||
<i className="fa fa-copy" />
|
||||
Copy Error Logs
|
||||
</CopyButton>
|
||||
<Button
|
||||
className="h-[32px] rounded-xs border border-solid border-(--hl-md) bg-(--color-surprise) px-3 py-2 text-sm text-(--color-font-surprise) transition-colors hover:bg-(--color-surprise)/90 hover:no-underline"
|
||||
onClick={handleMigration}
|
||||
isDisabled={isUpdateRunning}
|
||||
>
|
||||
<Button className="border border-solid border-(--hl-md) bg-(--color-surprise) font-semibold text-(--color-font-surprise) flex h-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm ring-1 ring-transparent transition-all focus:ring-(--hl-md) focus:ring-inset aria-pressed:opacity-80" onClick={handleMigration} isDisabled={isUpdateRunning}>
|
||||
{isUpdateRunning ? 'Updating...' : 'Retry Update'}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
className="h-[32px] rounded-xs border border-solid border-(--hl-md) bg-(--color-surprise) px-3 py-2 text-sm text-(--color-font-surprise) transition-colors hover:bg-(--color-surprise)/90 hover:no-underline"
|
||||
onClick={handleMigration}
|
||||
isDisabled={isUpdateRunning}
|
||||
>
|
||||
<Button className="border border-solid border-(--hl-md) bg-(--color-surprise) font-semibold text-(--color-font-surprise) flex h-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm ring-1 ring-transparent transition-all focus:ring-(--hl-md) focus:ring-inset aria-pressed:opacity-80" onClick={handleMigration} isDisabled={isUpdateRunning}>
|
||||
{isUpdateRunning ? 'Updating...' : 'Update Now'}
|
||||
</Button>
|
||||
)}
|
||||
@@ -181,7 +182,7 @@ const Component = () => {
|
||||
</div>
|
||||
<div className="flex w-full justify-end">
|
||||
<Button
|
||||
className="h-[32px] rounded-xs border border-solid border-(--hl-md) bg-(--color-surprise) px-3 py-2 text-sm text-(--color-font-surprise) transition-colors hover:bg-(--color-surprise)/90"
|
||||
className="border border-solid border-(--hl-md) bg-(--color-surprise) font-semibold text-(--color-font-surprise) flex h-full items-center justify-center gap-2 rounded-md px-4 py-2 text-sm ring-1 ring-transparent transition-all focus:ring-(--hl-md) focus:ring-inset aria-pressed:opacity-80"
|
||||
onClick={() => {
|
||||
setShowMigrationView(true);
|
||||
}}
|
||||
|
||||
@@ -428,6 +428,13 @@ const Component = () => {
|
||||
<div className="flex flex-col gap-3">
|
||||
<h2 className="text-2xl font-semibold text-(--color-font)">{modalText.modalTitle}</h2>
|
||||
<p className="max-w-2xl text-lg text-(--hl)">{modalText.summary}</p>
|
||||
{currentIssue.relPath && (
|
||||
<ul className="list-disc pl-5 text-left text-sm text-(--hl)">
|
||||
<li>
|
||||
<span className="font-mono">{currentIssue.relPath}</span>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
onPress={handleBackToList}
|
||||
|
||||
@@ -569,6 +569,10 @@ export const GitProjectSyncDropdown: FC<Props> = ({ gitRepository, activeProject
|
||||
]
|
||||
: [];
|
||||
|
||||
const repoPath = gitRepository?._id
|
||||
? window.path.join(window.app.getPath('userData'), 'version-control', 'git', gitRepository._id)
|
||||
: '';
|
||||
|
||||
const gitSyncActions: {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -576,6 +580,13 @@ export const GitProjectSyncDropdown: FC<Props> = ({ gitRepository, activeProject
|
||||
isDisabled?: boolean;
|
||||
action: () => void;
|
||||
}[] = [
|
||||
{
|
||||
id: 'open-folder',
|
||||
label: 'Open folder',
|
||||
isDisabled: !repoPath,
|
||||
icon: 'folder-open',
|
||||
action: () => window.shell.openPath(repoPath),
|
||||
},
|
||||
{
|
||||
id: 'branches',
|
||||
label: 'Branches',
|
||||
|
||||
@@ -11,6 +11,7 @@ import React, {
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
GridList,
|
||||
GridListItem,
|
||||
Heading,
|
||||
@@ -18,6 +19,7 @@ import {
|
||||
Label,
|
||||
Modal,
|
||||
ModalOverlay,
|
||||
Popover,
|
||||
TextArea,
|
||||
TextField,
|
||||
Tooltip,
|
||||
@@ -56,6 +58,7 @@ import { showSettingsModal } from '~/ui/components/modals/settings-modal';
|
||||
import { SvgIcon } from '~/ui/components/svg-icon';
|
||||
import { useAIFeatureStatus } from '~/ui/hooks/use-organization-features';
|
||||
|
||||
import { platform } from '../../../common/platform';
|
||||
import { DiffEditor } from '../diff-view-editor';
|
||||
import { Icon } from '../icon';
|
||||
import { showToast } from '../toast-notification';
|
||||
@@ -1057,37 +1060,63 @@ const ManualCommitForm: FC<ManualCommitFormProps> = ({
|
||||
PREVIEW
|
||||
</span>
|
||||
<span className="font-semibold">Manage changes on the Git CLI</span>
|
||||
<DialogTrigger>
|
||||
<Button
|
||||
className="flex items-center justify-center rounded-xs p-0.5 text-(--hl) hover:bg-(--hl-xs)"
|
||||
aria-label="More information"
|
||||
>
|
||||
<Icon icon="circle-info" className="size-3.5" />
|
||||
</Button>
|
||||
<Popover
|
||||
offset={8}
|
||||
className="max-w-xs rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) px-3 py-2 text-sm text-(--color-font) shadow-lg"
|
||||
>
|
||||
<Dialog className="outline-none" >
|
||||
You can now browse Git Sync project files on your local file system and manage changes using your normal
|
||||
Git workflows.{' '}
|
||||
<a href="https://developer.konghq.com/insomnia/git-sync/" className="underline">
|
||||
Learn more ↗
|
||||
</a>
|
||||
</Dialog>
|
||||
</Popover>
|
||||
</DialogTrigger>
|
||||
</div>
|
||||
<p className="mb-3 text-sm text-(--color-font)">
|
||||
You can now browse Git Sync project files on your local file system and manage changes using your normal Git
|
||||
workflows.{' '}
|
||||
<a href="https://developer.konghq.com/insomnia/git-sync/" className="underline">
|
||||
Learn more ↗
|
||||
</a>
|
||||
</p>
|
||||
<p className="mb-1 font-semibold">Path to this project:</p>
|
||||
<div className="mb-3 flex items-center justify-between rounded-xs bg-(--hl-xxs) px-2 py-2 font-mono text-(--color-font)">
|
||||
<p className="mb-1 text-xs font-semibold">Path to this project:</p>
|
||||
<div className="flex items-center justify-between rounded-xs bg-(--hl-xxs) px-2 py-2 font-mono text-(--color-font)">
|
||||
<span className="min-w-0 flex-1 truncate" title={repoPath}>
|
||||
{repoPath}
|
||||
</span>
|
||||
<Button
|
||||
onPress={() => {
|
||||
window.clipboard.writeText(repoPath);
|
||||
const cmd =
|
||||
platform === 'win32'
|
||||
? `cd "${repoPath.replace(/"/g, '\\"')}"`
|
||||
: `cd '${repoPath.replace(/'/g, "'\\''")}'`;
|
||||
window.clipboard.writeText(cmd);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}}
|
||||
className="mb-1 flex items-center justify-center rounded-xs p-1 hover:bg-(--hl-xs)"
|
||||
aria-label="Copy path"
|
||||
className="flex items-center justify-center rounded-xs p-1 hover:bg-(--hl-xs)"
|
||||
aria-label="Copy shell command"
|
||||
>
|
||||
<Icon icon={copied ? 'check' : 'copy'} className="size-4" />
|
||||
</Button>
|
||||
<TooltipTrigger>
|
||||
<Button
|
||||
onPress={() => window.shell.openPath(repoPath)}
|
||||
className="flex items-center justify-center rounded-xs p-1 hover:bg-(--hl-xs)"
|
||||
aria-label="Open in file system"
|
||||
>
|
||||
<Icon icon="folder-open" className="size-4" />
|
||||
</Button>
|
||||
<Tooltip
|
||||
offset={8}
|
||||
className="rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) px-3 py-2 text-sm text-(--color-font) shadow-lg"
|
||||
>
|
||||
Open in file system
|
||||
</Tooltip>
|
||||
</TooltipTrigger>
|
||||
</div>
|
||||
<Button
|
||||
onPress={() => window.shell.showItemInFolder(repoPath)}
|
||||
className="cursor-pointer text-(--hl) underline"
|
||||
>
|
||||
Open in file system
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -1370,8 +1399,8 @@ const OriginalGitProjectStagingModal: FC<
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="grid h-full grid-cols-[300px_1fr] gap-2 divide-x divide-solid divide-(--hl-md) overflow-hidden">
|
||||
<div className="flex flex-1 flex-col gap-4 overflow-y-auto p-2">
|
||||
<div className="grid h-full grid-cols-[350px_1fr] gap-4 divide-x divide-solid divide-(--hl-md) overflow-hidden">
|
||||
<div className="flex flex-1 flex-col gap-4 overflow-y-auto p-4">
|
||||
{isGenerateCommitMessagesWithAIEnabled && (
|
||||
<div className="flex flex-col gap-3 rounded-sm border border-solid border-(--hl-md) p-3">
|
||||
<h3 className="font-semibold">
|
||||
|
||||
@@ -148,7 +148,7 @@ export const GitStagingModal: FC<{ onClose: () => void }> = ({ onClose }) => {
|
||||
<Icon icon="x" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid h-full grid-cols-[300px_1fr] gap-2 divide-x divide-solid divide-(--hl-md) overflow-hidden">
|
||||
<div className="grid h-full grid-cols-[350px_1fr] gap-4 divide-x divide-solid divide-(--hl-md) overflow-hidden">
|
||||
<div className="flex flex-1 flex-col gap-4 overflow-hidden">
|
||||
<form
|
||||
onSubmit={e => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@@ -176,7 +176,7 @@ export const SyncStagingModal = ({ onClose, status, syncItems }: Props) => {
|
||||
<Icon icon="x" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid h-full grid-cols-[300px_1fr] gap-2 divide-x divide-solid divide-(--hl-md) overflow-hidden">
|
||||
<div className="grid h-full grid-cols-[350px_1fr] gap-4 divide-x divide-solid divide-(--hl-md) overflow-hidden">
|
||||
<div className="flex flex-1 flex-col gap-4 overflow-hidden p-2">
|
||||
<form
|
||||
onSubmit={e => {
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
Select,
|
||||
SelectValue,
|
||||
TextField,
|
||||
Tooltip,
|
||||
TooltipTrigger,
|
||||
} from 'react-aria-components';
|
||||
import { useParams } from 'react-router';
|
||||
|
||||
@@ -33,6 +35,7 @@ import { useActiveView } from '~/ui/components/project/utils';
|
||||
import { useIsLightTheme } from '~/ui/hooks/theme';
|
||||
import { useIsGitSyncEnabled } from '~/ui/hooks/use-organization-features';
|
||||
|
||||
import { platform } from '../../../common/platform';
|
||||
import { useProjectUpdateActionFetcher } from '../../../routes/organization.$organizationId.project.$projectId.update';
|
||||
import { Icon } from '../icon';
|
||||
|
||||
@@ -323,35 +326,40 @@ export const ProjectSettingsForm: FC<Props> = ({
|
||||
Learn more ↗
|
||||
</a>
|
||||
</div>
|
||||
<div className="flex items-stretch justify-between gap-2">
|
||||
<span
|
||||
title={repoPath}
|
||||
className="min-w-0 flex-1 truncate rounded-xs border border-solid border-(--hl-sm) bg-(--color-bg) px-2 py-1 font-mono text-base leading-8 text-(--hl-xl)"
|
||||
>
|
||||
<div className="flex items-center justify-between rounded-xs bg-(--hl-xxs) px-2 py-2 font-mono text-(--color-font)">
|
||||
<span className="min-w-0 flex-1 truncate" title={repoPath}>
|
||||
{repoPath}
|
||||
</span>
|
||||
<Button
|
||||
onPress={() => {
|
||||
window.clipboard.writeText(repoPath);
|
||||
const cmd =
|
||||
platform === 'win32'
|
||||
? `cd "${repoPath.replace(/"/g, '\\"')}"`
|
||||
: `cd '${repoPath.replace(/'/g, "'\\''")}'`;
|
||||
window.clipboard.writeText(cmd);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
}}
|
||||
className="flex shrink-0 items-center gap-1.5 rounded-xs border border-solid border-(--hl-sm) px-2 text-sm text-(--color-font) transition-colors hover:bg-(--hl-xs)"
|
||||
aria-label="Copy repository path"
|
||||
className="flex items-center justify-center rounded-xs p-1 hover:bg-(--hl-xs)"
|
||||
aria-label="Copy cd command for repository path"
|
||||
>
|
||||
<Icon icon={copied ? 'check' : 'copy'} />
|
||||
<span>{copied ? 'Copied!' : 'Copy'}</span>
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() => {
|
||||
window.shell.showItemInFolder(repoPath);
|
||||
}}
|
||||
className="flex shrink-0 items-center gap-1.5 rounded-xs border border-solid border-(--hl-sm) px-2 text-sm text-(--color-font) transition-colors hover:bg-(--hl-xs)"
|
||||
aria-label="Open repository folder"
|
||||
>
|
||||
<Icon icon={'folder'} />
|
||||
<span>Open</span>
|
||||
<Icon icon={copied ? 'check' : 'copy'} className="size-4" />
|
||||
</Button>
|
||||
<TooltipTrigger>
|
||||
<Button
|
||||
onPress={() => window.shell.openPath(repoPath)}
|
||||
className="flex items-center justify-center rounded-xs p-1 hover:bg-(--hl-xs)"
|
||||
aria-label="Open in file system"
|
||||
>
|
||||
<Icon icon="folder-open" className="size-4" />
|
||||
</Button>
|
||||
<Tooltip
|
||||
offset={8}
|
||||
className="rounded-md border border-solid border-(--hl-sm) bg-(--color-bg) px-3 py-2 text-sm text-(--color-font) shadow-lg"
|
||||
>
|
||||
Open in file system
|
||||
</Tooltip>
|
||||
</TooltipTrigger>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user