From 7ef1428284124d4c55c772539263121ffb96a5ba Mon Sep 17 00:00:00 2001 From: Sebastian Martinez Date: Thu, 20 Jan 2022 14:42:10 +0100 Subject: [PATCH] Add commit detail view Signed-off-by: Sebastian Martinez --- public/index.css | 5 + src/Icon.svelte | 18 ++- src/base/projects/Commit.svelte | 96 +++++++++++ src/base/projects/Commit/CommitTeaser.svelte | 26 ++- src/base/projects/{Commit => }/History.svelte | 30 +++- src/base/projects/ProjectContentRoutes.svelte | 17 +- .../projects/SourceBrowser/Changeset.svelte | 92 +++++++++++ .../projects/SourceBrowser/FileDiff.svelte | 153 ++++++++++++++++++ .../projects/Commit/lib.ts => commit.ts} | 29 +++- src/diff.ts | 91 +++++++++++ src/project.ts | 26 ++- 11 files changed, 552 insertions(+), 31 deletions(-) create mode 100644 src/base/projects/Commit.svelte rename src/base/projects/{Commit => }/History.svelte (61%) create mode 100644 src/base/projects/SourceBrowser/Changeset.svelte create mode 100644 src/base/projects/SourceBrowser/FileDiff.svelte rename src/{base/projects/Commit/lib.ts => commit.ts} (78%) create mode 100644 src/diff.ts diff --git a/public/index.css b/public/index.css index bd6007377..8676e7fa4 100644 --- a/public/index.css +++ b/public/index.css @@ -101,6 +101,7 @@ body { html { height: 100%; + overflow-y: scroll; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; -ms-overflow-style: scrollbar; @@ -354,6 +355,10 @@ label.input { align-items: center; } +.font-mono { + font-family: var(--font-family-monospace); +} + .mobile { display: none !important; } diff --git a/src/Icon.svelte b/src/Icon.svelte index fe58f9160..fd7fd0916 100644 --- a/src/Icon.svelte +++ b/src/Icon.svelte @@ -4,6 +4,7 @@ export let height: number | null = null; export let inline = false; export let fill = false; + export let clickHandler: (() => void) | undefined = undefined; const icons = [ { @@ -31,6 +32,17 @@ size: 16, offset: { x: -1, y: 0 }, data: `` + }, + { + name: "browse", + size: 16, + data: `` + }, + { + name: "chevron", + size: 15, + offset: { x: 0, y: 1 }, + data: `` } ]; const svg = icons.find(e => e.name === name); @@ -43,13 +55,17 @@ svg.inline { height: 1.6rem; } + svg.clickable { + cursor: pointer; + } {#if svg} + viewBox="{svg.offset?.x || 0} {svg.offset?.y || 0} {svg.size} {svg.size}" + on:click|stopPropagation={clickHandler}> {@html svg.data} {/if} diff --git a/src/base/projects/Commit.svelte b/src/base/projects/Commit.svelte new file mode 100644 index 000000000..468a040ee --- /dev/null +++ b/src/base/projects/Commit.svelte @@ -0,0 +1,96 @@ + + + + +{#await proj.getCommit(urn, revision, config) then commit} +
+
+
+

{commit.header.summary}

+
+ commit + {commit.header.sha1} +
+
+ {formatCommit(commit.header.sha1)} +
+
+
{commit.header.description}
+
+ Committed by {commit.header.committer.name} + <{commit.header.committer.email}> + {formatCommitTime(commit.header.committerTime)} +
+
+ Authored by {commit.header.author.name} + <{commit.header.author.email}> +
+
+ navigateCommit(event.detail)} /> +
+{:catch err} +
+
+
API request to {err.url} failed.
+
API needs to be version ^0.2.
+
+
+{/await} diff --git a/src/base/projects/Commit/CommitTeaser.svelte b/src/base/projects/Commit/CommitTeaser.svelte index ee4b9f0e0..92611efc7 100644 --- a/src/base/projects/Commit/CommitTeaser.svelte +++ b/src/base/projects/Commit/CommitTeaser.svelte @@ -1,9 +1,17 @@ + +
+ {#if diff.modified.length > 0} + {diff.modified.length} file(s) changed + with + {stats.additions} additions + and + {stats.deletions} deletions + {/if} +
+
+ {#each diff.created as path (path)} +
+
+

{path}

+ created +
+ dispatch("browse", path)} name="browse" width={20} inline fill /> +
+ {/each} + {#each diff.deleted as path (path)} +
+
+

{path}

+ deleted +
+ dispatch("browse", path)} name="browse" width={20} inline fill /> +
+ {/each} +
+ +{#each diff.modified as file} + +{/each} diff --git a/src/base/projects/SourceBrowser/FileDiff.svelte b/src/base/projects/SourceBrowser/FileDiff.svelte new file mode 100644 index 000000000..cad1c6d85 --- /dev/null +++ b/src/base/projects/SourceBrowser/FileDiff.svelte @@ -0,0 +1,153 @@ + + + + +
+
+
+ +

{file.path}

+
+ dispatch("browse", file.path)} name="browse" width={20} inline fill /> +
+ {#if !collapsed} +
+ {#if file.diff.type === "plain" && file.diff.hunks.length > 0} + + {#each file.diff.hunks as hunk} + + + + {#each hunk.lines as line} + + + + + + + {/each} + {/each} +
+ + {hunk.header} +
+ {lineNumberL(line)} + + {lineNumberR(line)} + + {lineSign(line)} + {line.line}
+ {:else} +
Binary file
+ {/if} +
+ {/if} +
diff --git a/src/base/projects/Commit/lib.ts b/src/commit.ts similarity index 78% rename from src/base/projects/Commit/lib.ts rename to src/commit.ts index 8e9b508c8..b36005a5a 100644 --- a/src/base/projects/Commit/lib.ts +++ b/src/commit.ts @@ -1,4 +1,5 @@ -import type { CommitHeader, Stats } from "@app/project"; +import type { Stats } from "@app/project"; +import type { Diff } from "@app/diff"; export interface CommitsHistory { headers: CommitHeader[]; @@ -9,6 +10,12 @@ export interface GroupedCommitsHistory { stats: Stats; } +export interface Author { + avatar: string; + email: string; + name: string; +} + export interface CommitStats { branches: number; commits: number; @@ -20,12 +27,32 @@ export interface GroupedCommitsHistory { stats: Stats; } +export interface CommitHeader { + author: Author; + committer: Author; + committerTime: number; + description: string; + sha1: string; + summary: string; +} + // A set of commits grouped by time. export interface CommitGroup { time: string; commits: CommitHeader[]; } +export interface CommitStats { + additions: number; + deletions: number; +} + +export interface Commit { + header: CommitHeader; + stats: CommitStats; + diff: Diff; + branches: string[]; +} export function formatGroupTime(timestamp: number): string { return new Date(timestamp).toLocaleDateString("en-US", { diff --git a/src/diff.ts b/src/diff.ts new file mode 100644 index 000000000..2b763b1b3 --- /dev/null +++ b/src/diff.ts @@ -0,0 +1,91 @@ +export const lineNumberR = (line: LineDiff): string | number => { + switch (line.type) { + case LineDiffType.Addition: { + return line.lineNum; + } + case LineDiffType.Context: { + return line.lineNumNew; + } + case LineDiffType.Deletion: { + return " "; + } + } +}; + +export const lineNumberL = (line: LineDiff): string | number => { + switch (line.type) { + case LineDiffType.Addition: { + return " "; + } + case LineDiffType.Context: { + return line.lineNumOld; + } + case LineDiffType.Deletion: { + return line.lineNum; + } + } +}; + +export const lineSign = (line: LineDiff): string => { + switch (line.type) { + case LineDiffType.Addition: { + return "+"; + } + case LineDiffType.Context: { + return " "; + } + case LineDiffType.Deletion: { + return "-"; + } + } +}; + +export enum LineDiffType { + Addition = "addition", + Context = "context", + Deletion = "deletion", +} + +export interface Addition { + type: LineDiffType.Addition; + line: string; + lineNum: number; +} + +export interface Context { + type: LineDiffType.Context; + line: string; + lineNumNew: number; + lineNumOld: number; +} + +export interface Deletion { + type: LineDiffType.Deletion; + line: string; + lineNum: number; +} + +export type LineDiff = Addition | Deletion | Context; + +export interface FileDiff { + path: string; + diff: Changeset; +} + +export interface Changeset { + type: string; + hunks: Hunk[]; +} + +export interface Hunk { + header: string; + lines: LineDiff[]; +} + +export interface Diff { + created: string[]; + deleted: string[]; + moved: string[]; + copied: string[]; + modified: FileDiff[]; +} diff --git a/src/project.ts b/src/project.ts index 9eaa6dd5b..84978eedc 100644 --- a/src/project.ts +++ b/src/project.ts @@ -1,6 +1,6 @@ import type { Config } from '@app/config'; import * as api from '@app/api'; -import type { CommitsHistory } from '@app/base/projects/Commit/lib'; +import type { Commit, CommitHeader, CommitsHistory } from '@app/commit'; import { isOid } from '@app/utils'; import type { Profile } from '@app/profile'; @@ -43,6 +43,7 @@ export interface PendingProject extends Project { export enum ProjectContent { Tree, History, + Commit, } export interface Info { @@ -70,26 +71,11 @@ export interface Stats { contributors: number; } -export interface Author { - avatar: string; - email: string; - name: string; -} - export enum ObjectType { Blob = "BLOB", Tree = "TREE", } -export interface CommitHeader { - author: Author; - committer: Author; - committerTime: number; - description: string; - sha1: string; - summary: string; -} - export interface EntryInfo { name: string; objectType: ObjectType; @@ -118,6 +104,10 @@ export async function getInfo(urn: string, config: Config): Promise { } export async function getCommits(urn: string, commit: string, config: Config): Promise { + return api.get(`projects/${urn}/commits?from=${commit}`, {}, config); +} + +export async function getCommit(urn: string, commit: string, config: Config): Promise { return api.get(`projects/${urn}/commits/${commit}`, {}, config); } @@ -194,6 +184,10 @@ export function path( result.push("history"); break; + case ProjectContent.Commit: + result.push("commit"); + break; + default: result.push("tree"); break;