diff --git a/client/web-sveltekit/src/lib/repo/filePopover/FilePopover.gql b/client/web-sveltekit/src/lib/repo/filePopover/FilePopover.gql new file mode 100644 index 00000000000..439dc63a572 --- /dev/null +++ b/client/web-sveltekit/src/lib/repo/filePopover/FilePopover.gql @@ -0,0 +1,63 @@ +query FileOrDirPopoverQuery($repoName: String!, $revision: String!, $filePath: String!) { + repository(name: $repoName) { + commit(rev: $revision) { + path(path: $filePath) { + ...on GitBlob { + ...FilePopoverFragment + } + ...on GitTree { + ...DirPopoverFragment + } + } + } + } +} + +fragment FilePopoverFragment on GitBlob { + __typename + path + languages + byteSize + totalLines + history(first: 1) { + nodes { + commit { + ...FilePopoverLastCommitFragment + } + } + } + ...FileIcon_GitBlob +} + +fragment DirPopoverFragment on GitTree { + __typename + path + # TODO(camdencheek): fix this to use a count, which currently does not exist in our API. + # This currently does not scale well with very large directories. + files { + name + } + directories { + name + } + history(first: 1) { + nodes { + commit { + ...FilePopoverLastCommitFragment + } + } + } +} + +fragment FilePopoverLastCommitFragment on GitCommit { + abbreviatedOID + oid + subject + canonicalURL + author { + date + person { + ...Avatar_Person + } + } +} diff --git a/client/web-sveltekit/src/lib/repo/filePopover/FilePopover.stories.svelte b/client/web-sveltekit/src/lib/repo/filePopover/FilePopover.stories.svelte new file mode 100644 index 00000000000..01e2bb60d93 --- /dev/null +++ b/client/web-sveltekit/src/lib/repo/filePopover/FilePopover.stories.svelte @@ -0,0 +1,58 @@ + + + + + + + diff --git a/client/web-sveltekit/src/lib/repo/filePopover/FilePopover.svelte b/client/web-sveltekit/src/lib/repo/filePopover/FilePopover.svelte new file mode 100644 index 00000000000..be51681a694 --- /dev/null +++ b/client/web-sveltekit/src/lib/repo/filePopover/FilePopover.svelte @@ -0,0 +1,188 @@ + + + + +
+
+ + {displayRepoName(repoName).replaceAll('/', ' / ')} + · + {dirName ? `${dirName.replaceAll('/', ' / ')}` : '/'} + +
+ +
+ {#if entry.__typename === 'GitBlob'} + +
+
{baseName}
+ + {entry.languages[0] ? `${entry.languages[0]} ·` : ''} + {entry.totalLines} + {pluralize('Line', entry.totalLines)} · + {formatBytes(entry.byteSize)} + +
+ {:else if entry.__typename === 'GitTree'} + +
+
{baseName}
+ + Subdirectories {entry.directories.length} + · Files {entry.files.length} + +
+ {/if} +
+ +
Last Changed @
+ +
+
+
+ + + {lastCommit.abbreviatedOID} + + +
{lastCommit.subject}
+
+ + {lastCommit.author.person.displayName} + · + +
+
+
+
+ + diff --git a/client/web-sveltekit/src/lib/repo/filePopover/NodeLine.svelte b/client/web-sveltekit/src/lib/repo/filePopover/NodeLine.svelte new file mode 100644 index 00000000000..ff99725b4a0 --- /dev/null +++ b/client/web-sveltekit/src/lib/repo/filePopover/NodeLine.svelte @@ -0,0 +1,29 @@ +
+
+
+
+
+ + diff --git a/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/+layout.svelte b/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/+layout.svelte index 29cdc450dec..cc47ec80772 100644 --- a/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/+layout.svelte +++ b/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/+layout.svelte @@ -96,9 +96,9 @@ } $: if (!!lastCommitQuery) { - // Reset commit history when the query observable changes. Without - // this we are showing the commit history of the previously selected - // file/folder until the new commit history is loaded. + // Reset last commit when the query observable changes. Without + // this we are showing the last commit of the previously selected + // file/folder until the last commit is loaded. lastCommit = null } diff --git a/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/FileTree.svelte b/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/FileTree.svelte index b1f6b755a61..3d681ec0096 100644 --- a/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/FileTree.svelte +++ b/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/FileTree.svelte @@ -5,12 +5,14 @@ import { goto } from '$app/navigation' import Icon from '$lib/Icon.svelte' + import Popover from '$lib/Popover.svelte' import { type FileTreeProvider, NODE_LIMIT, type TreeEntry } from '$lib/repo/api/tree' import FileIcon from '$lib/repo/FileIcon.svelte' + import FilePopover, { fetchPopoverData } from '$lib/repo/filePopover/FilePopover.svelte' import { getSidebarFileTreeStateForRepo } from '$lib/repo/stores' import { replaceRevisionInURL } from '$lib/shared' import TreeView, { setTreeContext } from '$lib/TreeView.svelte' - import { createForwardStore } from '$lib/utils' + import { createForwardStore, delay } from '$lib/utils' import { Alert } from '$lib/wildcard' export let repoName: string @@ -122,19 +124,27 @@ We handle navigation via the TreeView's select event, to preserve the focus state. Using a link here allows us to benefit from data preloading. --> - {}} - data-go-up={isRoot ? true : undefined} - > - {#if entry.isDirectory} - - {:else} - - {/if} - {isRoot ? '..' : entry.name} - + + {}} + tabindex={-1} + data-go-up={isRoot ? true : undefined} + use:registerTrigger + > + {#if entry.isDirectory} + + {:else} + + {/if} + {isRoot ? '..' : entry.name} + + + {#await delay(fetchPopoverData({ repoName, revision, filePath: entry.path }), 300) then entry} + + {/await} + + {/if} diff --git a/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/page.spec.ts b/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/page.spec.ts index be88a39bc71..66d1be5a193 100644 --- a/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/page.spec.ts +++ b/client/web-sveltekit/src/routes/[...repo=reporev]/(validrev)/(code)/page.spec.ts @@ -239,3 +239,15 @@ test('history panel', async ({ page, sg }) => { await page.getByRole('tab', { name: 'History' }).click() await expect(page.getByText('Test commit')).toBeHidden() }) + +test('file popover', async ({ page }) => { + await page.goto(`/${repoName}`) + + await page.locator('#sidebar-panel').getByRole('button').click() + + await page.getByRole('link', { name: 'index.js' }).hover() + await expect(page.getByText('Last Changed')).toBeVisible() + + await page.getByRole('link', { name: 'Sourcegraph', exact: true }).hover() + await expect(page.getByText('Last Changed')).toBeHidden() +})