diff --git a/package.json b/package.json
index 38bb90d8dfa87..214d2c1b655b6 100644
--- a/package.json
+++ b/package.json
@@ -11681,6 +11681,7 @@
},
"dependencies": {
"@gitkraken/gitkraken-components": "1.0.0-rc.13",
+ "@microsoft/fast-element": "^1.10.5",
"@octokit/core": "4.0.5",
"@vscode/codicons": "0.0.32",
"@vscode/webview-ui-toolkit": "1.0.1",
diff --git a/src/storage.ts b/src/storage.ts
index 21e707723784f..262872d85aac3 100644
--- a/src/storage.ts
+++ b/src/storage.ts
@@ -182,6 +182,7 @@ export interface WorkspaceStorage {
};
commitDetails: {
autolinksExpanded?: boolean;
+ filesAsTree?: boolean;
};
};
diff --git a/src/webviews/apps/commitDetails/commitDetails.html b/src/webviews/apps/commitDetails/commitDetails.html
index 0f6a65e837343..5e6ff6cd8d476 100644
--- a/src/webviews/apps/commitDetails/commitDetails.html
+++ b/src/webviews/apps/commitDetails/commitDetails.html
@@ -115,6 +115,13 @@
Best practice
Files changed
+
+
-
diff --git a/src/webviews/apps/commitDetails/commitDetails.scss b/src/webviews/apps/commitDetails/commitDetails.scss
index 9580a954b1d00..cd4e34fd8abf7 100644
--- a/src/webviews/apps/commitDetails/commitDetails.scss
+++ b/src/webviews/apps/commitDetails/commitDetails.scss
@@ -6,16 +6,14 @@
// generic resets
html {
font-size: 62.5%;
- box-sizing: border-box;
+ // box-sizing: border-box;
font-family: var(--font-family);
}
-* {
- &,
- &::before,
- &::after {
- box-sizing: inherit;
- }
+*,
+*:before,
+*:after {
+ box-sizing: border-box;
}
body {
@@ -100,6 +98,64 @@ ul {
max-width: 30rem;
}
+.switch {
+ margin-left: auto;
+ display: inline-flex;
+ flex-direction: row;
+ border-radius: 0.25em;
+ gap: 0.1rem;
+
+ .vscode-dark & {
+ background-color: var(--color-background--lighten-075);
+ }
+ .vscode-light & {
+ background-color: var(--color-background--darken-075);
+ }
+
+ &__option {
+ display: inline-flex;
+ justify-content: center;
+ align-items: flex-end;
+ border-radius: 0.25em;
+ color: inherit;
+ padding: 0.2rem 0.8rem;
+ text-decoration: none;
+ background: none;
+ border: none;
+ cursor: pointer;
+
+ > * {
+ pointer-events: none;
+ }
+
+ &:focus {
+ outline: 1px solid var(--vscode-focusBorder);
+ outline-offset: -1px;
+ }
+
+ &:hover {
+ color: var(--vscode-foreground);
+ text-decoration: none;
+ .vscode-dark & {
+ background-color: var(--color-background--lighten-10);
+ }
+ .vscode-light & {
+ background-color: var(--color-background--darken-10);
+ }
+ }
+
+ &.is-selected {
+ color: var(--vscode-foreground);
+ .vscode-dark & {
+ background-color: var(--color-background--lighten-15);
+ }
+ .vscode-light & {
+ background-color: var(--color-background--darken-15);
+ }
+ }
+ }
+}
+
@media (min-width: 640px) {
.button-container {
max-width: 100%;
@@ -279,12 +335,15 @@ ul {
}
&__file {
+ --tree-level: 1;
padding: {
- left: var(--gitlens-gutter-width);
+ left: calc(var(--gitlens-gutter-width) * var(--tree-level));
right: var(--gitlens-scrollbar-gutter-width);
top: 1px;
bottom: 1px;
}
+ line-height: 22px;
+ height: 22px;
}
&__item-skeleton {
padding: {
@@ -323,3 +382,5 @@ ul {
@import '../shared/codicons';
@import '../shared/glicons';
+
+
diff --git a/src/webviews/apps/commitDetails/commitDetails.ts b/src/webviews/apps/commitDetails/commitDetails.ts
index 59ac8e501fd37..1ddbe7d5351f9 100644
--- a/src/webviews/apps/commitDetails/commitDetails.ts
+++ b/src/webviews/apps/commitDetails/commitDetails.ts
@@ -1,4 +1,6 @@
/*global*/
+import type { HierarchicalItem } from '../../../system/array';
+import { makeHierarchical } from '../../../system/array';
import type { Serialized } from '../../../system/serialize';
import type { IpcMessage } from '../../../webviews/protocol';
import { onIpc } from '../../../webviews/protocol';
@@ -18,7 +20,7 @@ import {
SearchCommitCommandType,
} from '../../commitDetails/protocol';
import { App } from '../shared/appBase';
-import type { FileChangeItem, FileChangeItemEventDetail } from '../shared/components/commit/file-change-item';
+import type { FileChangeListItem, FileChangeListItemDetail } from '../shared/components/list/file-change-list-item';
import type { WebviewPane, WebviewPaneExpandedChangeEventDetail } from '../shared/components/webview-pane';
import { DOM } from '../shared/dom';
import './commitDetails.scss';
@@ -28,8 +30,10 @@ import '../shared/components/formatted-date';
import '../shared/components/rich/issue-pull-request';
import '../shared/components/skeleton-loader';
import '../shared/components/commit/commit-stats';
-import '../shared/components/commit/file-change-item';
import '../shared/components/webview-pane';
+import '../shared/components/list/list-container';
+import '../shared/components/list/list-item';
+import '../shared/components/list/file-change-list-item';
const uncommittedSha = '0000000000000000000000000000000000000000';
@@ -48,38 +52,26 @@ export class CommitDetailsApp extends App> {
override onBind() {
const disposables = [
- DOM.on('file-change-item', 'file-open-on-remote', e =>
+ DOM.on('file-change-list-item', 'file-open-on-remote', e =>
this.onOpenFileOnRemote(e.detail),
),
- DOM.on('file-change-item', 'file-open', e =>
+ DOM.on('file-change-list-item', 'file-open', e =>
this.onOpenFile(e.detail),
),
- DOM.on('file-change-item', 'file-compare-working', e =>
+ DOM.on('file-change-list-item', 'file-compare-working', e =>
this.onCompareFileWithWorking(e.detail),
),
- DOM.on('file-change-item', 'file-compare-previous', e =>
+ DOM.on('file-change-list-item', 'file-compare-previous', e =>
this.onCompareFileWithPrevious(e.detail),
),
- DOM.on('file-change-item', 'file-more-actions', e =>
+ DOM.on('file-change-list-item', 'file-more-actions', e =>
this.onFileMoreActions(e.detail),
),
DOM.on('[data-action="commit-actions"]', 'click', e => this.onCommitActions(e)),
DOM.on('[data-action="pick-commit"]', 'click', e => this.onPickCommit(e)),
DOM.on('[data-action="search-commit"]', 'click', e => this.onSearchCommit(e)),
DOM.on('[data-action="autolink-settings"]', 'click', e => this.onAutolinkSettings(e)),
- DOM.on('file-change-item', 'keydown', (e, target: HTMLElement) => {
- if (e.key === 'Enter' || e.key === ' ') {
- (target as FileChangeItem).open(e.key === 'Enter' ? { preserveFocus: false } : undefined);
- } else if (e.key === 'ArrowUp') {
- const $previous: HTMLElement | null = target.parentElement?.previousElementSibling
- ?.firstElementChild as HTMLElement;
- $previous?.focus();
- } else if (e.key === 'ArrowDown') {
- const $next: HTMLElement | null = target.parentElement?.nextElementSibling
- ?.firstElementChild as HTMLElement;
- $next?.focus();
- }
- }),
+ DOM.on('[data-switch-value]', 'click', e => this.onTreeSetting(e)),
DOM.on('[data-action="pin"]', 'click', e => this.onTogglePin(e)),
DOM.on(
'[data-region="rich-pane"]',
@@ -127,6 +119,22 @@ export class CommitDetailsApp extends App> {
}
}
+ private onTreeSetting(e: MouseEvent) {
+ const isTree = (e.target as HTMLElement)?.getAttribute('data-switch-value') === 'list-tree';
+
+ if (isTree === this.state.preferences?.filesAsTree) {
+ return;
+ }
+
+ this.state.preferences = { ...this.state.preferences, filesAsTree: isTree };
+
+ this.renderFiles(this.state as CommitState);
+
+ this.sendCommand(PreferencesCommandType, {
+ filesAsTree: isTree,
+ });
+ }
+
private onExpandedChange(e: WebviewPaneExpandedChangeEventDetail) {
this.sendCommand(PreferencesCommandType, {
autolinksExpanded: e.expanded,
@@ -151,23 +159,23 @@ export class CommitDetailsApp extends App> {
this.sendCommand(PickCommitCommandType, undefined);
}
- private onOpenFileOnRemote(e: FileChangeItemEventDetail) {
+ private onOpenFileOnRemote(e: FileChangeListItemDetail) {
this.sendCommand(OpenFileOnRemoteCommandType, e);
}
- private onOpenFile(e: FileChangeItemEventDetail) {
+ private onOpenFile(e: FileChangeListItemDetail) {
this.sendCommand(OpenFileCommandType, e);
}
- private onCompareFileWithWorking(e: FileChangeItemEventDetail) {
+ private onCompareFileWithWorking(e: FileChangeListItemDetail) {
this.sendCommand(OpenFileCompareWorkingCommandType, e);
}
- private onCompareFileWithPrevious(e: FileChangeItemEventDetail) {
+ private onCompareFileWithPrevious(e: FileChangeListItemDetail) {
this.sendCommand(OpenFileComparePreviousCommandType, e);
}
- private onFileMoreActions(e: FileChangeItemEventDetail) {
+ private onFileMoreActions(e: FileChangeListItemDetail) {
this.sendCommand(FileActionsCommandType, e);
}
@@ -221,7 +229,9 @@ export class CommitDetailsApp extends App> {
renderActions(state: CommitState) {
const isHidden = state.selected?.sha === uncommittedSha ? 'true' : 'false';
- [...document.querySelectorAll('[data-action-type="graph"],[data-action-type="more"]')].forEach($el => $el.setAttribute('aria-hidden', isHidden));
+ [...document.querySelectorAll('[data-action-type="graph"],[data-action-type="more"]')].forEach($el =>
+ $el.setAttribute('aria-hidden', isHidden),
+ );
}
renderPin(state: CommitState) {
@@ -328,16 +338,76 @@ export class CommitDetailsApp extends App> {
}
if (state.selected.files?.length) {
- const stashAttr = state.selected.isStash ? 'stash ' : state.selected.sha === uncommittedSha ? 'uncommitted ' : '';
- $el.innerHTML = state.selected.files
- .map(
- (file: Record) => `
-
-
-
-
- `,
- )
- .join('');
+ const isTree = state.preferences?.filesAsTree === true;
+ document.querySelector('[data-switch-value="list"]')?.classList.toggle('is-selected', !isTree);
+ document.querySelector('[data-switch-value="list-tree"]')?.classList.toggle('is-selected', isTree);
+
+ const stashAttr = state.selected.isStash
+ ? 'stash '
+ : state.selected.sha === uncommittedSha
+ ? 'uncommitted '
+ : '';
+
+ if (isTree) {
+ const tree = makeHierarchical(
+ state.selected.files,
+ n => n.path.split('/'),
+ (...parts: string[]) => parts.join('/'),
+ true,
+ );
+ const flatTree = flattenHeirarchy(tree);
+
+ $el.innerHTML = `
+ -
+
+ ${flatTree
+ .map(({ level, item }) => {
+ if (item.name === '') {
+ return '';
+ }
+
+ if (item.value == null) {
+ return `
+
+
+ ${item.name}
+
+ `;
+ }
+
+ return `
+
+ `;
+ })
+ .join('')}
+
+
`;
+ } else {
+ $el.innerHTML = `
+ -
+
+ ${state.selected.files
+ .map(
+ (file: Record) => `
+
+ `,
+ )
+ .join('')}
+
+
`;
+ }
$el.setAttribute('aria-hidden', 'false');
} else {
$el.innerHTML = '';
@@ -509,4 +579,39 @@ export class CommitDetailsApp extends App> {
function assertsSerialized(obj: unknown): asserts obj is Serialized {}
+function flattenHeirarchy(item: HierarchicalItem, level = 0): { level: number; item: HierarchicalItem }[] {
+ const flattened: { level: number; item: HierarchicalItem }[] = [];
+
+ if (item == null) {
+ return flattened;
+ }
+
+ flattened.push({ level: level, item: item });
+
+ if (item.children != null) {
+ const children = Array.from(item.children.values());
+ children.sort((a, b) => {
+ if (!a.value || !b.value) {
+ return (a.value ? 1 : -1) - (b.value ? 1 : -1);
+ }
+
+ if (a.relativePath < b.relativePath) {
+ return -1;
+ }
+
+ if (a.relativePath > b.relativePath) {
+ return 1;
+ }
+
+ return 0;
+ });
+
+ children.forEach(child => {
+ flattened.push(...flattenHeirarchy(child, level + 1));
+ });
+ }
+
+ return flattened;
+}
+
new CommitDetailsApp();
diff --git a/src/webviews/apps/shared/components/commit/file-change-item.ts b/src/webviews/apps/shared/components/commit/file-change-item.ts
index bfe08262c4ca3..b25953056e886 100644
--- a/src/webviews/apps/shared/components/commit/file-change-item.ts
+++ b/src/webviews/apps/shared/components/commit/file-change-item.ts
@@ -162,6 +162,9 @@ export class FileChangeItem extends LitElement {
@property({ type: Boolean, reflect: true })
uncommitted = false;
+ @property({ type: Boolean, reflect: true })
+ tree = false;
+
private renderIcon() {
if (this.icon !== '') {
return html``;
@@ -183,14 +186,14 @@ export class FileChangeItem extends LitElement {
const statusName = this.status !== '' ? statusTextMap[this.status] : '';
const pathIndex = this.path.lastIndexOf('/');
const fileName = pathIndex > -1 ? this.path.substring(pathIndex + 1) : this.path;
- const filePath = pathIndex > -1 ? this.path.substring(0, pathIndex) : '';
+ const filePath = !this.tree && pathIndex > -1 ? this.path.substring(0, pathIndex) : '';
return html`
${this.renderIcon()}${fileName}
- ${filePath}
+ ${!this.tree ? html`${filePath}` : nothing}