feat: add breadcrumbs to folder setting (#296)

This commit is contained in:
Zhizhen He
2026-02-03 23:40:24 +08:00
committed by GitHub
parent 431dc1c896
commit 0b91d3aaff
3 changed files with 114 additions and 81 deletions
+2 -9
View File
@@ -1,21 +1,14 @@
import { getModel } from '@yaakapp-internal/models'; import { getModel } from '@yaakapp-internal/models';
import { Icon } from '../components/core/Icon';
import { HStack } from '../components/core/Stacks';
import type { FolderSettingsTab } from '../components/FolderSettingsDialog'; import type { FolderSettingsTab } from '../components/FolderSettingsDialog';
import { FolderSettingsDialog } from '../components/FolderSettingsDialog'; import { FolderSettingsDialog } from '../components/FolderSettingsDialog';
import { showDialog } from '../lib/dialog'; import { showDialog } from '../lib/dialog';
import { resolvedModelName } from '../lib/resolvedModelName';
export function openFolderSettings(folderId: string, tab?: FolderSettingsTab) { export function openFolderSettings(folderId: string, tab?: FolderSettingsTab) {
const folder = getModel('folder', folderId); const folder = getModel('folder', folderId);
if (folder == null) return;
showDialog({ showDialog({
id: 'folder-settings', id: 'folder-settings',
title: ( title: null,
<HStack space={2} alignItems="center">
<Icon icon="folder_cog" size="xl" color="secondary" />
{resolvedModelName(folder)}
</HStack>
),
size: 'lg', size: 'lg',
className: 'h-[50rem]', className: 'h-[50rem]',
noPadding: true, noPadding: true,
+111 -72
View File
@@ -1,12 +1,18 @@
import { createWorkspaceModel, foldersAtom, patchModel } from '@yaakapp-internal/models'; import {
createWorkspaceModel,
foldersAtom,
patchModel,
} from '@yaakapp-internal/models';
import { useAtomValue } from 'jotai'; import { useAtomValue } from 'jotai';
import { useMemo } from 'react'; import { Fragment, useMemo } from 'react';
import { useAuthTab } from '../hooks/useAuthTab'; import { useAuthTab } from '../hooks/useAuthTab';
import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown'; import { useEnvironmentsBreakdown } from '../hooks/useEnvironmentsBreakdown';
import { useHeadersTab } from '../hooks/useHeadersTab'; import { useHeadersTab } from '../hooks/useHeadersTab';
import { useInheritedHeaders } from '../hooks/useInheritedHeaders'; import { useInheritedHeaders } from '../hooks/useInheritedHeaders';
import { useModelAncestors } from '../hooks/useModelAncestors';
import { Button } from './core/Button'; import { Button } from './core/Button';
import { CountBadge } from './core/CountBadge'; import { CountBadge } from './core/CountBadge';
import { Icon } from './core/Icon';
import { Input } from './core/Input'; import { Input } from './core/Input';
import { Link } from './core/Link'; import { Link } from './core/Link';
import { VStack } from './core/Stacks'; import { VStack } from './core/Stacks';
@@ -37,6 +43,8 @@ export type FolderSettingsTab =
export function FolderSettingsDialog({ folderId, tab }: Props) { export function FolderSettingsDialog({ folderId, tab }: Props) {
const folders = useAtomValue(foldersAtom); const folders = useAtomValue(foldersAtom);
const folder = folders.find((f) => f.id === folderId) ?? null; const folder = folders.find((f) => f.id === folderId) ?? null;
const ancestors = useModelAncestors(folder);
const breadcrumbs = useMemo(() => ancestors.toReversed(), [ancestors]);
const authTab = useAuthTab(TAB_AUTH, folder); const authTab = useAuthTab(TAB_AUTH, folder);
const headersTab = useHeadersTab(TAB_HEADERS, folder); const headersTab = useHeadersTab(TAB_HEADERS, folder);
const inheritedHeaders = useInheritedHeaders(folder); const inheritedHeaders = useInheritedHeaders(folder);
@@ -67,76 +75,107 @@ export function FolderSettingsDialog({ folderId, tab }: Props) {
if (folder == null) return null; if (folder == null) return null;
return ( return (
<Tabs <div className="h-full flex flex-col">
defaultValue={tab ?? TAB_GENERAL} <div className="flex items-center gap-3 px-6 pr-10 mt-4 mb-2 min-w-0 text-xl">
label="Folder Settings" <Icon icon="folder_cog" size="lg" color="secondary" className="flex-shrink-0" />
className="pt-2 pb-2 pl-3 pr-1" <div className="flex items-center gap-1.5 font-semibold text-text min-w-0 overflow-hidden flex-1">
layout="horizontal" {breadcrumbs.map((item, index) => (
addBorders <Fragment key={item.id}>
tabs={tabs} {index > 0 && (
> <Icon
<TabContent value={TAB_AUTH} className="overflow-y-auto h-full px-4"> icon="chevron_right"
<HttpAuthenticationEditor model={folder} /> size="lg"
</TabContent> className="opacity-50 flex-shrink-0"
<TabContent value={TAB_GENERAL} className="overflow-y-auto h-full px-4"> />
<VStack space={3} className="pb-3 h-full"> )}
<Input <span className="text-text-subtle truncate min-w-0" title={item.name}>
label="Folder Name" {item.name}
defaultValue={folder.name} </span>
onChange={(name) => patchModel(folder, { name })} </Fragment>
stateKey={`name.${folder.id}`} ))}
{breadcrumbs.length > 0 && (
<Icon icon="chevron_right" size="lg" className="opacity-50 flex-shrink-0" />
)}
<span
className="whitespace-nowrap"
title={folder.name}
>
{folder.name}
</span>
</div>
</div>
<Tabs
defaultValue={tab ?? TAB_GENERAL}
label="Folder Settings"
className="pt-2 pb-2 pl-3 pr-1 flex-1"
layout="horizontal"
addBorders
tabs={tabs}
>
<TabContent value={TAB_AUTH} className="overflow-y-auto h-full px-4">
<HttpAuthenticationEditor model={folder} />
</TabContent>
<TabContent value={TAB_GENERAL} className="overflow-y-auto h-full px-4">
<VStack space={3} className="pb-3 h-full">
<Input
label="Folder Name"
defaultValue={folder.name}
onChange={(name) => patchModel(folder, { name })}
stateKey={`name.${folder.id}`}
/>
<MarkdownEditor
name="folder-description"
placeholder="Folder description"
className="border border-border px-2"
defaultValue={folder.description}
stateKey={`description.${folder.id}`}
onChange={(description) => patchModel(folder, { description })}
/>
</VStack>
</TabContent>
<TabContent value={TAB_HEADERS} className="overflow-y-auto h-full px-4">
<HeadersEditor
inheritedHeaders={inheritedHeaders}
forceUpdateKey={folder.id}
headers={folder.headers}
onChange={(headers) => patchModel(folder, { headers })}
stateKey={`headers.${folder.id}`}
/> />
<MarkdownEditor </TabContent>
name="folder-description" <TabContent value={TAB_VARIABLES} className="overflow-y-auto h-full px-4">
placeholder="Folder description" {folderEnvironment == null ? (
className="border border-border px-2" <EmptyStateText>
defaultValue={folder.description} <VStack alignItems="center" space={1.5}>
stateKey={`description.${folder.id}`} <p>
onChange={(description) => patchModel(folder, { description })} Override{' '}
/> <Link href="https://yaak.app/docs/using-yaak/environments-and-variables">
</VStack> Variables
</TabContent> </Link>{' '}
<TabContent value={TAB_HEADERS} className="overflow-y-auto h-full px-4"> for requests within this folder.
<HeadersEditor </p>
inheritedHeaders={inheritedHeaders} <Button
forceUpdateKey={folder.id} variant="border"
headers={folder.headers} size="sm"
onChange={(headers) => patchModel(folder, { headers })} onClick={async () => {
stateKey={`headers.${folder.id}`} await createWorkspaceModel({
/> workspaceId: folder.workspaceId,
</TabContent> parentModel: 'folder',
<TabContent value={TAB_VARIABLES} className="overflow-y-auto h-full px-4"> parentId: folder.id,
{folderEnvironment == null ? ( model: 'environment',
<EmptyStateText> name: 'Folder Environment',
<VStack alignItems="center" space={1.5}> });
<p> }}
Override{' '} >
<Link href="https://yaak.app/docs/using-yaak/environments-and-variables"> Create Folder Environment
Variables </Button>
</Link>{' '} </VStack>
for requests within this folder. </EmptyStateText>
</p> ) : (
<Button <EnvironmentEditor hideName environment={folderEnvironment} />
variant="border" )}
size="sm" </TabContent>
onClick={async () => { </Tabs>
await createWorkspaceModel({ </div>
workspaceId: folder.workspaceId,
parentModel: 'folder',
parentId: folder.id,
model: 'environment',
name: 'Folder Environment',
});
}}
>
Create Folder Environment
</Button>
</VStack>
</EmptyStateText>
) : (
<EnvironmentEditor hideName environment={folderEnvironment} />
)}
</TabContent>
</Tabs>
); );
} }
+1
View File
@@ -135,6 +135,7 @@ export function Workspace() {
open={!floatingSidebarHidden} open={!floatingSidebarHidden}
portalName="sidebar" portalName="sidebar"
onClose={() => setFloatingSidebarHidden(true)} onClose={() => setFloatingSidebarHidden(true)}
zIndex={20}
> >
<m.div <m.div
initial={{ opacity: 0, x: -20 }} initial={{ opacity: 0, x: -20 }}