diff --git a/src/plus/webviews/workspaces/protocol.ts b/src/plus/webviews/workspaces/protocol.ts index d6d7cdf..6f17ab5 100644 --- a/src/plus/webviews/workspaces/protocol.ts +++ b/src/plus/webviews/workspaces/protocol.ts @@ -1 +1,20 @@ -export type State = Record; +import type { IssueShape } from '../../../git/models/issue'; +import type { PullRequestShape } from '../../../git/models/pullRequest'; + +export type State = { + pullRequests?: PullRequestResult[]; + issues?: IssueResult[]; + [key: string]: unknown; +}; + +export interface SearchResultBase { + reasons: string[]; +} + +export interface IssueResult extends SearchResultBase { + issue: IssueShape; +} + +export interface PullRequestResult extends SearchResultBase { + pullRequest: PullRequestShape; +} diff --git a/src/plus/webviews/workspaces/workspacesWebview.ts b/src/plus/webviews/workspaces/workspacesWebview.ts index 1f7ccb1..8df8038 100644 --- a/src/plus/webviews/workspaces/workspacesWebview.ts +++ b/src/plus/webviews/workspaces/workspacesWebview.ts @@ -3,9 +3,13 @@ import { Commands, ContextKeys } from '../../../constants'; import type { Container } from '../../../container'; import { setContext } from '../../../context'; import type { SearchedIssue } from '../../../git/models/issue'; -import { serializeIssue } from '../../../git/models/issue'; +// import { serializeIssue } from '../../../git/models/issue'; import type { SearchedPullRequest } from '../../../git/models/pullRequest'; -import { serializePullRequest } from '../../../git/models/pullRequest'; +import { + PullRequestMergeableState, + PullRequestReviewDecision, + serializePullRequest, +} from '../../../git/models/pullRequest'; import type { GitRemote } from '../../../git/models/remote'; import type { RichRemoteProvider } from '../../../git/remotes/richRemoteProvider'; import { registerCommand } from '../../../system/command'; @@ -53,22 +57,24 @@ export class WorkspacesWebview extends WebviewBase { } private async getState(): Promise { + // return Promise.resolve({}); + const prs = await this.getMyPullRequests(); const serializedPrs = prs.map(pr => ({ pullRequest: serializePullRequest(pr.pullRequest), reasons: pr.reasons, })); - const issues = await this.getMyIssues(); - const serializedIssues = issues.map(issue => ({ - issue: serializeIssue(issue.issue), - reasons: issue.reasons, - })); + // const issues = await this.getMyIssues(); + // const serializedIssues = issues.map(issue => ({ + // issue: serializeIssue(issue.issue), + // reasons: issue.reasons, + // })); return { // workspaces: await this.getWorkspaces(), - myPullRequests: serializedPrs, - myIssues: serializedIssues, + pullRequests: serializedPrs, + // myIssues: serializedIssues, }; } @@ -97,10 +103,45 @@ export class WorkspacesWebview extends WebviewBase { if (prs == null) { continue; } - allPrs.push(...prs); + allPrs.push(...prs.filter(pr => pr.reasons.length > 0)); + } + + function getScore(pr: SearchedPullRequest) { + let score = 0; + if (pr.reasons.includes('author')) { + score += 1000; + } else if (pr.reasons.includes('assignee')) { + score += 900; + } else if (pr.reasons.includes('reviewer')) { + score += 800; + } else if (pr.reasons.includes('mentioned')) { + score += 700; + } + + if (pr.pullRequest.reviewDecision === PullRequestReviewDecision.Approved) { + if (pr.pullRequest.mergeableState === PullRequestMergeableState.Mergeable) { + score += 100; + } else if (pr.pullRequest.mergeableState === PullRequestMergeableState.Conflicting) { + score += 90; + } else { + score += 80; + } + } else if (pr.pullRequest.reviewDecision === PullRequestReviewDecision.ChangesRequested) { + score += 70; + } + + return score; } - return allPrs; + return allPrs.sort((a, b) => { + const scoreA = getScore(a); + const scoreB = getScore(b); + + if (scoreA === scoreB) { + return a.pullRequest.date.getTime() - b.pullRequest.date.getTime(); + } + return (scoreB ?? 0) - (scoreA ?? 0); + }); } private async getMyIssues(): Promise { @@ -111,9 +152,9 @@ export class WorkspacesWebview extends WebviewBase { if (issues == null) { continue; } - allIssues.push(...issues); + allIssues.push(...issues.filter(pr => pr.reasons.length > 0)); } - return allIssues; + return allIssues.sort((a, b) => b.issue.date.getTime() - a.issue.date.getTime()); } } diff --git a/src/webviews/apps/plus/workspaces/components/git-avatars.ts b/src/webviews/apps/plus/workspaces/components/git-avatars.ts new file mode 100644 index 0000000..c9a90f4 --- /dev/null +++ b/src/webviews/apps/plus/workspaces/components/git-avatars.ts @@ -0,0 +1,66 @@ +import { css, customElement, FASTElement, html, observable, repeat, volatile, when } from '@microsoft/fast-element'; + +import '../../../shared/components/avatars/avatar-item'; +import '../../../shared/components/avatars/avatar-stack'; + +const template = html` + + ${repeat( + x => x.avatarItems, + html``, + )} + ${when( + x => x.avatarPlusItems != null, + html`+${x => x.avatarPlusItems?.length}`, + )} + +`; + +const styles = css``; + +interface AvatarShape { + name: string; + avatarUrl: string; + url: string; +} + +@customElement({ + name: 'git-avatars', + template: template, + styles: styles, +}) +export class GitAvatars extends FASTElement { + @observable + avatars: AvatarShape[] = []; + + @volatile + get avatarItems(): AvatarShape[] { + if (this.avatars.length <= 3) { + return this.avatars; + } + return this.avatars.slice(0, 2); + } + + @volatile + get avatarPlusItems(): AvatarShape[] | undefined { + const len = this.avatars.length; + if (len <= 3) { + return undefined; + } + return this.avatars.slice(2); + } + + @volatile + get avatarPlusLabel(): string | undefined { + if (this.avatarPlusItems == null) { + return undefined; + } + const len = this.avatarPlusItems.length; + return this.avatarPlusItems.reduce( + (all, current, i) => `${all}, ${len === i - 1 ? 'and ' : ''}${current.name}`, + '', + ); + } +} diff --git a/src/webviews/apps/plus/workspaces/components/pull-request-row.ts b/src/webviews/apps/plus/workspaces/components/pull-request-row.ts new file mode 100644 index 0000000..3c3c6a2 --- /dev/null +++ b/src/webviews/apps/plus/workspaces/components/pull-request-row.ts @@ -0,0 +1,249 @@ +import { css, customElement, FASTElement, html, observable, ref, volatile, when } from '@microsoft/fast-element'; +import type { PullRequestShape } from '../../../../../git/models/pullRequest'; +import { fromNow } from '../../../../../system/date'; +import { focusOutline, srOnly } from '../../../shared/components/styles/a11y'; +import { elementBase } from '../../../shared/components/styles/base'; + +import '../../../shared/components/table/table-cell'; +import '../../../shared/components/avatars/avatar-item'; +import '../../../shared/components/avatars/avatar-stack'; +import '../../../shared/components/code-icon'; +import './git-avatars'; + +const template = html` + +`; + +const styles = css` + ${elementBase} + + :host { + display: table-row; + } + + :host(:focus) { + ${focusOutline} + } + + a { + color: var(--vscode-textLink-foreground); + text-decoration: none; + } + + a:hover { + color: var(--vscode-textLink-activeForeground); + text-decoration: underline; + } + + a:focus { + ${focusOutline} + } + + code-icon { + font-size: inherit; + } + + .tag { + display: inline-block; + padding: 0.1rem 0.2rem; + background-color: var(--background-05); + color: var(--color-foreground--85); + white-space: nowrap; + } + .tag code-icon { + margin-right: 0.2rem; + } + + .status { + } + + .time { + } + + .icon-only { + } + + .stats { + } + + .actions { + text-align: right; + } + + .stat-added { + color: var(--vscode-gitDecoration-addedResourceForeground); + } + .stat-deleted { + color: var(--vscode-gitDecoration-deletedResourceForeground); + } + + .issue-open { + color: var(--vscode-gitlens-openAutolinkedIssueIconColor); + } + .issue-closed { + color: var(--vscode-gitlens-closedAutolinkedIssueIconColor); + } + + .indicator-info { + color: var(--color-alert-infoBorder); + } + .indicator-warning { + color: var(--color-alert-warningBorder); + } + .indicator-error { + color: var(--color-alert-errorBorder); + } + .indicator-neutral { + color: var(--color-alert-neutralBorder); + } + + .pull-request-draft { + /* color: var(--vscode-pullRequests-draft); */ + color: var(--color-foreground--85); + } + .pull-request-open { + color: var(--vscode-gitlens-openPullRequestIconColor); + } + .pull-request-merged { + color: var(--vscode-gitlens-mergedPullRequestIconColor); + } + .pull-request-closed { + color: var(--vscode-gitlens-closedPullRequestIconColor); + } + .pull-request-notification { + color: var(--vscode-pullRequests-notification); + } + + ${srOnly} +`; + +@customElement({ + name: 'pull-request-row', + template: template, + styles: styles, +}) +export class PullRequestRow extends FASTElement { + @observable + public pullRequest?: PullRequestShape; + + @observable + public reasons?: string[]; + + @observable + public checks?: boolean; + + @volatile + get lastUpdated() { + return fromNow(new Date(this.pullRequest!.date), true); + } + + @volatile + get indicator() { + if (this.pullRequest == null) return ''; + + console.log(this.pullRequest); + if (this.pullRequest.reviewDecision === 'ChangesRequested') { + return 'changes'; + } else if (this.pullRequest.reviewDecision === 'Approved' && this.pullRequest.mergeableState === 'Mergeable') { + return 'ready'; + } + + if (this.pullRequest.mergeableState === 'Conflicting') { + return 'conflicting'; + } + + return ''; + } + + @volatile + get indicatorLabel() { + return undefined; + } +} diff --git a/src/webviews/apps/plus/workspaces/workspaces.html b/src/webviews/apps/plus/workspaces/workspaces.html index fd8c854..c810dda 100644 --- a/src/webviews/apps/plus/workspaces/workspaces.html +++ b/src/webviews/apps/plus/workspaces/workspaces.html @@ -6,159 +6,38 @@
-

Workspaces

+

Focus View

-

My Pull Requests Issues

+

My Pull Requests

- + - + + - Repo - Title - Author - Assigned - + Pull Request + Author + Assigned + - - - - + - + - - - Links - - - 7h - GitKraken - #7094 GK-1769 Engine Mods - eamodio - d13 +2 - 10 - - - 471 - -206 - - - - 7h - GitKraken - #7094 GK-1769 Engine Mods - eamodio - d13 +2 - 10 - - - 471 - -206 - - - - 7h - GitKraken - #7094 GK-1769 Engine Mods - eamodio - d13 +2 - 10 - - - 471 - -206 - - - - 7h - GitKraken - #7094 GK-1769 Engine Mods - eamodio - d13 +2 - 10 - - - 471 - -206 - - - - 7h - GitKraken - #7094 GK-1769 Engine Mods - eamodio - d13 +2 - 10 - - - 471 - -206 - - - - 7h - GitKraken - #7094 GK-1769 Engine Mods - eamodio - d13 +2 - 10 - - - 471 - -206 - - - - 7h - GitKraken - #7094 GK-1769 Engine Mods - eamodio - d13 +2 - 10 - - - 471 - -206 - - - - 7h - GitKraken - #7094 GK-1769 Engine Mods - eamodio - d13 +2 - 10 - - - 471 - -206 - @@ -172,89 +51,44 @@
- + - Repo Title - Author - Assigned - + Author + Assignees + - + 6d - vscode-gitlens - #2058 Improve git blame contrast - mejiaj - d13 - 2 - 0 - - - 6d - vscode-gitlens - #2058 Improve git blame contrast - mejiaj - d13 - 2 - 0 - - - 6d - vscode-gitlens - #2058 Improve git blame contrast - mejiaj - d13 - 2 - 0 - - - 6d - vscode-gitlens - #2058 Improve git blame contrast - mejiaj - d13 - 2 - 0 - - - 6d - vscode-gitlens - #2058 Improve git blame contrast - mejiaj - d13 - 2 - 0 - - - 6d - vscode-gitlens - #2058 Improve git blame contrast - mejiaj - d13 - 2 - 0 - - - 6d - vscode-gitlens - #2058 Improve git blame contrast - mejiaj - d13 - 2 - 0 - - - 6d - vscode-gitlens - #2058 Improve git blame contrast - mejiaj - d13 + + Improve git blame contrast #2058
+ + vscode-gitlens + +
+ + + + + + + + + +2 + + 2 0
@@ -270,15 +104,26 @@ Repo - Branch Stats + Branch Remote - vscode-gitlens - feature/workspaces - +100 -50 - gitkraken/vscode-gitlens + vscode-gitlens + +50 ~100 + -206 + feature/workspaces + gitkraken/vscode-gitlens
diff --git a/src/webviews/apps/plus/workspaces/workspaces.scss b/src/webviews/apps/plus/workspaces/workspaces.scss index 9ab52da..1354972 100644 --- a/src/webviews/apps/plus/workspaces/workspaces.scss +++ b/src/webviews/apps/plus/workspaces/workspaces.scss @@ -82,6 +82,17 @@ code-icon { font-size: inherit; } +.tag { + display: inline-block; + padding: 0.1rem 0.2rem; + background-color: var(--color-background--lighten-05); + color: var(--color-foreground--85); + + code-icon { + margin-right: 0.2rem; + } +} + .button { width: 2.4rem; height: 2.4rem; @@ -149,3 +160,55 @@ code-icon { flex: 0 1 auto; } } + +.tag { + display: inline-block; + padding: 0.1rem 0.2rem; + background-color: var(--background-05); + color: var(--color-foreground--85); + white-space: nowrap; +} +.tag code-icon { + margin-right: 0.2rem; +} + +.stat-added { + white-space: nowrap; + color: var(--vscode-gitDecoration-addedResourceForeground); +} +.stat-deleted { + white-space: nowrap; + color: var(--vscode-gitDecoration-deletedResourceForeground); +} +.stat-modified { + white-space: nowrap; + color: var(--vscode-gitDecoration-modifiedResourceForeground); +} + +.pr { + &-status { + width: 5rem; + } + &-time { + width: 4rem; + } + &-body { + } + &-author { + width: 8.8rem; + } + &-assigned { + width: 8.8rem; + } + &-comments { + width: 4rem; + } + &-checks { + width: 3.2rem; + } + &-stats { + width: 9.2rem; + } + &-actions { + } +} diff --git a/src/webviews/apps/plus/workspaces/workspaces.ts b/src/webviews/apps/plus/workspaces/workspaces.ts index c2485cb..d016257 100644 --- a/src/webviews/apps/plus/workspaces/workspaces.ts +++ b/src/webviews/apps/plus/workspaces/workspaces.ts @@ -1,13 +1,13 @@ import type { State } from '../../../../plus/webviews/workspaces/protocol'; import { App } from '../../shared/appBase'; +import type { PullRequestRow } from './components/pull-request-row'; import '../../shared/components/code-icon'; import '../../shared/components/avatars/avatar-item'; import '../../shared/components/avatars/avatar-stack'; import '../../shared/components/table/table-container'; import '../../shared/components/table/table-row'; import '../../shared/components/table/table-cell'; -import './components/workspace-list'; -import './components/workspace-item'; +import './components/pull-request-row'; import './workspaces.scss'; export class WorkspacesApp extends App { @@ -21,7 +21,23 @@ export class WorkspacesApp extends App { console.log(this.state); } - renderContent() {} + renderContent() { + this.renderPullRequests(); + } + + renderPullRequests() { + const prTableEl = document.getElementById('pull-requests'); + + if (this.state.pullRequests != null && this.state.pullRequests?.length > 0) { + const els = this.state.pullRequests.map(({ pullRequest }) => { + const prRowEl = document.createElement('pull-request-row') as PullRequestRow; + prRowEl.pullRequest = pullRequest; + + return prRowEl; + }); + prTableEl?.append(...els); + } + } } new WorkspacesApp();