From 7228b4958d4c7860a85cb77a46aba04b3b92f472 Mon Sep 17 00:00:00 2001 From: Camden Cheek Date: Fri, 14 Jun 2024 12:11:20 -0600 Subject: [PATCH] Svelte: add repo header dropdown menu (#63257) Adds a dropdown menu when clicking the repo name for common repo-level actions. --- client/web-sveltekit/src/auto-imports.d.ts | 3 + .../src/lib/styles/dropdown.scss | 12 +- .../src/lib/wildcard/menu/DropdownMenu.svelte | 5 +- .../lib/wildcard/menu/MenuSeparator.svelte | 2 +- .../(code)/-/blob/[...path]/page.spec.ts | 21 +++ .../routes/[...repo=reporev]/+layout.svelte | 74 +++++------ .../routes/[...repo=reporev]/RepoMenu.svelte | 123 ++++++++++++++++++ .../src/routes/[...repo=reporev]/layout.gql | 5 +- .../routes/[...repo=reporev]/layout.spec.ts | 42 ++++++ 9 files changed, 235 insertions(+), 52 deletions(-) create mode 100644 client/web-sveltekit/src/routes/[...repo=reporev]/RepoMenu.svelte diff --git a/client/web-sveltekit/src/auto-imports.d.ts b/client/web-sveltekit/src/auto-imports.d.ts index 1d15e62e312..f7fff1490db 100644 --- a/client/web-sveltekit/src/auto-imports.d.ts +++ b/client/web-sveltekit/src/auto-imports.d.ts @@ -29,6 +29,7 @@ declare global { const ILucideCircleHelp: typeof import('~icons/lucide/circle-help')['default'] const ILucideCircleX: typeof import('~icons/lucide/circle-x')['default'] const ILucideCode: typeof import('~icons/lucide/code')['default'] + const ILucideCodesandbox: typeof import('~icons/lucide/codesandbox')['default'] const ILucideCopy: typeof import('~icons/lucide/copy')['default'] const ILucideCornerRightDown: typeof import('~icons/lucide/corner-right-down')['default'] const ILucideCornerRightUp: typeof import('~icons/lucide/corner-right-up')['default'] @@ -55,6 +56,7 @@ declare global { const ILucideFullscreen: typeof import('~icons/lucide/fullscreen')['default'] const ILucideGitBranch: typeof import('~icons/lucide/git-branch')['default'] const ILucideGitCommitVertical: typeof import('~icons/lucide/git-commit-vertical')['default'] + const ILucideGitCompare: typeof import('~icons/lucide/git-compare')['default'] const ILucideGitCompareArrows: typeof import('~icons/lucide/git-compare-arrows')['default'] const ILucideGitFork: typeof import('~icons/lucide/git-fork')['default'] const ILucideGitMerge: typeof import('~icons/lucide/git-merge')['default'] @@ -70,6 +72,7 @@ declare global { const ILucidePanelLeftOpen: typeof import('~icons/lucide/panel-left-open')['default'] const ILucidePencil: typeof import('~icons/lucide/pencil')['default'] const ILucideRegex: typeof import('~icons/lucide/regex')['default'] + const ILucideRepeat: typeof import('~icons/lucide/repeat')['default'] const ILucideSearch: typeof import('~icons/lucide/search')['default'] const ILucideSearchX: typeof import('~icons/lucide/search-x')['default'] const ILucideSettings: typeof import('~icons/lucide/settings')['default'] diff --git a/client/web-sveltekit/src/lib/styles/dropdown.scss b/client/web-sveltekit/src/lib/styles/dropdown.scss index 7bb34ec4eb6..3b0889d50f1 100644 --- a/client/web-sveltekit/src/lib/styles/dropdown.scss +++ b/client/web-sveltekit/src/lib/styles/dropdown.scss @@ -1,8 +1,8 @@ :root { --dropdown-inner-border-radius: 0.1875rem; - --dropdown-padding-y: 0.5rem; - --dropdown-item-padding-y: 0.25rem; - --dropdown-item-padding-x: 0.5rem; + --dropdown-padding-y: 0.375rem; + --dropdown-item-padding-y: 0.375rem; + --dropdown-item-padding-x: 0.75rem; --dropdown-item-padding: var(--dropdown-item-padding-y) var(--dropdown-item-padding-x); --dropdown-min-width: 10rem; --dropdown-spacer: 0.125rem; @@ -25,7 +25,8 @@ --dropdown-link-active-bg: var(--primary); --dropdown-link-active-color: var(--white); --dropdown-link-disabled-color: var(--text-muted); - --dropdown-shadow: 0 4px 16px -6px rgba(36, 41, 54, 0.2); + --dropdown-shadow: 0 193px 54px 0 rgba(0, 0, 0, 0), 0 123px 49px 0 rgba(0, 0, 0, 0.01), + 0 69px 42px 0 rgba(0, 0, 0, 0.04), 0 31px 31px 0 rgba(0, 0, 0, 0.07), 0 8px 17px 0 rgba(0, 0, 0, 0.08); } .theme-dark { @@ -39,5 +40,6 @@ --dropdown-link-active-bg: var(--primary); --dropdown-link-active-color: var(--white); --dropdown-link-disabled-color: var(--text-muted); - --dropdown-shadow: 0 4px 16px -6px rgba(11, 12, 15, 0.8); + --dropdown-shadow: 0 309px 87px 0 rgba(0, 0, 0, 0.01), 0 198px 79px 0 rgba(0, 0, 0, 0.07), + 0 111px 67px 0 rgba(0, 0, 0, 0.24), 0 49px 49px 0 rgba(0, 0, 0, 0.41), 0 12px 27px 0 rgba(0, 0, 0, 0.47); } diff --git a/client/web-sveltekit/src/lib/wildcard/menu/DropdownMenu.svelte b/client/web-sveltekit/src/lib/wildcard/menu/DropdownMenu.svelte index 0aec91549c4..123cdf15f74 100644 --- a/client/web-sveltekit/src/lib/wildcard/menu/DropdownMenu.svelte +++ b/client/web-sveltekit/src/lib/wildcard/menu/DropdownMenu.svelte @@ -1,5 +1,6 @@ @@ -126,10 +140,13 @@ }, }} > - - -

{displayRepoName}

-
+ @@ -145,12 +162,12 @@ {#if entry.visibility === 'user' || (entry.visibility === 'admin' && data.user?.siteAdmin)} {@const href = data.repoURL + entry.path} - +
{#if entry.icon} {/if} {entry.label} - +
{/if} {/each} @@ -176,40 +193,11 @@ overflow: hidden; border-bottom: 1px solid var(--border-color); background-color: var(--color-bg-1); - - a { - all: unset; - - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0 1rem; - cursor: pointer; - &:hover { - background-color: var(--color-bg-2); - } - - h1 { - display: contents; - font-size: 1rem; - white-space: nowrap; - color: var(--text-title); - font-weight: normal; - } - } - - :global([data-dropdown-trigger]) { - height: 100%; - align-self: stretch; - padding: 0.5rem; - --icon-fill-color: var(--text-muted); - } } .overflow-entry { - width: 100%; - display: inline-block; - padding: 0 0.25rem; - border-radius: var(--border-radius); + display: flex; + gap: 0.5rem; + align-items: center; } diff --git a/client/web-sveltekit/src/routes/[...repo=reporev]/RepoMenu.svelte b/client/web-sveltekit/src/routes/[...repo=reporev]/RepoMenu.svelte new file mode 100644 index 00000000000..4bc7eb8dc5d --- /dev/null +++ b/client/web-sveltekit/src/routes/[...repo=reporev]/RepoMenu.svelte @@ -0,0 +1,123 @@ + + + + +
+ +

+ {#each displayRepoName.split('/') as segment, i} + {#if i > 0}/{/if}{segment} + {/each} +

+
+
+ + + + + openFuzzyFinder('repos')}> + + + + + + {#if externalURL} + + +
+ + {#if externalServiceKind} + Hosted on {getHumanNameForCodeHost(externalServiceKind)} + {:else} + View on code host + {/if} + +
+ + {displayRepoName} +
+
+
+ {/if} +
+ + diff --git a/client/web-sveltekit/src/routes/[...repo=reporev]/layout.gql b/client/web-sveltekit/src/routes/[...repo=reporev]/layout.gql index 647e75b6c4b..e59041d424e 100644 --- a/client/web-sveltekit/src/routes/[...repo=reporev]/layout.gql +++ b/client/web-sveltekit/src/routes/[...repo=reporev]/layout.gql @@ -28,7 +28,10 @@ fragment ResolvedRepository on Repository { defaultBranch { abbrevName } - + externalURLs { + url + serviceKind + } ...RepoPage_ResolvedRevision ...BlobPage_ResolvedRevision } diff --git a/client/web-sveltekit/src/routes/[...repo=reporev]/layout.spec.ts b/client/web-sveltekit/src/routes/[...repo=reporev]/layout.spec.ts index fe93f959d89..ccd50501a59 100644 --- a/client/web-sveltekit/src/routes/[...repo=reporev]/layout.spec.ts +++ b/client/web-sveltekit/src/routes/[...repo=reporev]/layout.spec.ts @@ -1,3 +1,4 @@ +import { ExternalServiceKind } from '../../testing/graphql-type-mocks' import { test, expect } from '../../testing/integration' const repoName = 'github.com/sourcegraph/sourcegraph' @@ -93,3 +94,44 @@ test('not cloned', async ({ sg, page }) => { // Shows queue message await expect(page.getByText('queued for cloning')).toBeVisible() }) + +test.describe('repo menu', () => { + test.beforeEach(async ({ sg, page }) => { + sg.mockOperations({ + ResolveRepoRevision: ({ repoName }) => ({ + repositoryRedirect: { + id: '1', + name: repoName, + commit: { + oid: '123456789', + }, + externalURLs: [ + { + serviceKind: ExternalServiceKind.GITHUB, + url: 'https://github.com/sourcegraph/sourcegraph', + }, + ], + }, + }), + }) + await page.goto(`/${repoName}`) + }) + + test('click switch repo', async ({ page }) => { + await page.getByRole('heading', { name: 'sourcegraph/sourcegraph' }).click() + await page.getByRole('menuitem', { name: 'Switch repo' }).click() + await expect(page.getByPlaceholder('Enter a fuzzy query')).toBeVisible() + }) + + test('settings url', async ({ page }) => { + await page.getByRole('heading', { name: 'sourcegraph/sourcegraph' }).click() + const url = await page.getByRole('menuitem', { name: 'Settings' }).getAttribute('href') + expect(url).toEqual(`/${repoName}/-/settings`) + }) + + test('github url', async ({ page }) => { + await page.getByRole('heading', { name: 'sourcegraph/sourcegraph' }).click() + const url = await page.getByRole('menuitem', { name: 'Hosted on GitHub' }).getAttribute('href') + expect(url).toEqual(`https://github.com/sourcegraph/sourcegraph`) + }) +})