From 6294f565a675a3b4bb6c35cedee7e8538eaff289 Mon Sep 17 00:00:00 2001
From: Keith Daulton <keith.daulton@gitkraken.com>
Date: Tue, 11 Jul 2023 14:59:30 -0400
Subject: [PATCH] Adds basic focus view component WIP

---
 .../apps/plus/focus/components/gk-issue-row.ts     | 112 ++++++++++++++++++
 .../plus/focus/components/gk-pull-request-row.ts   | 125 +++++++++++++++++++++
 .../apps/plus/focus/components/pull-request-row.ts |  17 ++-
 src/webviews/apps/plus/focus/focus.html            |  34 +++++-
 src/webviews/apps/plus/focus/focus.scss            |   7 ++
 src/webviews/apps/plus/focus/focus.ts              |  92 ++++++++++++++-
 6 files changed, 375 insertions(+), 12 deletions(-)
 create mode 100644 src/webviews/apps/plus/focus/components/gk-issue-row.ts
 create mode 100644 src/webviews/apps/plus/focus/components/gk-pull-request-row.ts

diff --git a/src/webviews/apps/plus/focus/components/gk-issue-row.ts b/src/webviews/apps/plus/focus/components/gk-issue-row.ts
new file mode 100644
index 0000000..eb7c2e3
--- /dev/null
+++ b/src/webviews/apps/plus/focus/components/gk-issue-row.ts
@@ -0,0 +1,112 @@
+import { css, html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { repeat } from 'lit/directives/repeat.js';
+import { when } from 'lit/directives/when.js';
+import type { IssueMember, IssueShape } from '../../../../../git/models/issue';
+import { elementBase } from '../../../shared/components/styles/lit/base.css';
+import '@gitkraken/shared-web-components';
+
+@customElement('gk-issue-row')
+export class GkIssueRow extends LitElement {
+	static override styles = [
+		elementBase,
+		css`
+			:host {
+				--gk-avatar-background-color: var(--background-10);
+				--color-background: var(--vscode-editor-background);
+				display: block;
+			}
+
+			p {
+				margin: 0;
+			}
+
+			a {
+				color: var(--vscode-textLink-foreground);
+				text-decoration: none;
+			}
+			a:hover {
+				text-decoration: underline;
+			}
+		`,
+	];
+
+	@property({ type: Number })
+	public rank?: number;
+
+	@property({ type: Object })
+	public issue?: IssueShape;
+
+	get lastUpdatedDate() {
+		return new Date(this.issue!.date);
+	}
+
+	get assignees() {
+		const assignees = this.issue?.assignees;
+		if (assignees == null) {
+			return [];
+		}
+		const author: IssueMember | undefined = this.issue!.author;
+		if (author != null) {
+			return assignees.filter(assignee => assignee.name !== author.name);
+		}
+
+		return assignees;
+	}
+
+	override render() {
+		if (!this.issue) return undefined;
+
+		return html`
+			<gk-focus-row>
+				<span slot="rank">${this.rank}</span>
+				<gk-focus-item>
+					<span slot="type"
+						><code-icon icon="${this.issue.closed === true ? 'pass' : 'issues'}"></code-icon
+					></span>
+					<p>
+						<strong>${this.issue.title} <a href="${this.issue.url}">#${this.issue.id}</a></strong>
+						<!-- &nbsp;
+						<gk-badge>pending suggestions</gk-badge> -->
+					</p>
+					<p>
+						<gk-tag>
+							<span slot="prefix"><code-icon icon="repo"></code-icon></span>
+							${this.issue.repository.repo}
+						</gk-tag>
+						<gk-tag>
+							<span slot="prefix"><code-icon icon="comment-discussion"></code-icon></span>
+							${this.issue.commentsCount}
+						</gk-tag>
+						<gk-tag>
+							<span slot="prefix"><code-icon icon="thumbsup"></code-icon></span>
+							${this.issue.thumbsUpCount}
+						</gk-tag>
+					</p>
+					<span slot="people">
+						<gk-avatar-group>
+							${when(
+								this.issue.author != null,
+								() => html`<gk-avatar src="${this.issue!.author.avatarUrl}"></gk-avatar>`,
+							)}
+							${when(
+								this.assignees.length > 0,
+								() => html`
+									${repeat(
+										this.assignees,
+										item => item.url,
+										(item, index) => html`<gk-avatar src="${item.avatarUrl}"></gk-avatar>`,
+									)}
+								`,
+							)}
+						</gk-avatar-group>
+					</span>
+					<span slot="date">
+						<gk-date-from date="${this.lastUpdatedDate}"></gk-date-from>
+					</span>
+					<nav slot="actions"></nav>
+				</gk-focus-item>
+			</gk-focus-row>
+		`;
+	}
+}
diff --git a/src/webviews/apps/plus/focus/components/gk-pull-request-row.ts b/src/webviews/apps/plus/focus/components/gk-pull-request-row.ts
new file mode 100644
index 0000000..a715222
--- /dev/null
+++ b/src/webviews/apps/plus/focus/components/gk-pull-request-row.ts
@@ -0,0 +1,125 @@
+import { css, html, LitElement } from 'lit';
+import { customElement, property } from 'lit/decorators.js';
+import { repeat } from 'lit/directives/repeat.js';
+import { when } from 'lit/directives/when.js';
+import type { PullRequestMember, PullRequestShape } from '../../../../../git/models/pullRequest';
+import { elementBase } from '../../../shared/components/styles/lit/base.css';
+import '@gitkraken/shared-web-components';
+
+@customElement('gk-pull-request-row')
+export class GkPullRequestRow extends LitElement {
+	static override styles = [
+		elementBase,
+		css`
+			:host {
+				--gk-avatar-background-color: var(--background-10);
+				--color-background: var(--vscode-editor-background);
+				display: block;
+			}
+
+			p {
+				margin: 0;
+			}
+
+			a {
+				color: var(--vscode-textLink-foreground);
+				text-decoration: none;
+			}
+			a:hover {
+				text-decoration: underline;
+			}
+		`,
+	];
+
+	@property({ type: Number })
+	public rank?: number;
+
+	@property({ type: Object })
+	public pullRequest?: PullRequestShape;
+
+	@property({ type: Boolean })
+	public isCurrentBranch = false;
+
+	@property({ type: Boolean })
+	public isCurrentWorktree = false;
+
+	@property({ type: Boolean })
+	public hasWorktree = false;
+
+	@property({ type: Boolean })
+	public hasLocalBranch = false;
+
+	get lastUpdatedDate() {
+		return new Date(this.pullRequest!.date);
+	}
+
+	get assignees() {
+		const assignees = this.pullRequest?.assignees;
+		if (assignees == null) {
+			return [];
+		}
+		const author: PullRequestMember | undefined = this.pullRequest!.author;
+		if (author != null) {
+			return assignees.filter(assignee => assignee.name !== author.name);
+		}
+
+		return assignees;
+	}
+
+	override render() {
+		if (!this.pullRequest) return undefined;
+
+		return html`
+			<gk-focus-row>
+				<span slot="rank">${this.rank}</span>
+				<gk-focus-item>
+					<span slot="type"><code-icon icon="git-pull-request"></code-icon></span>
+					<p>
+						<strong
+							>${this.pullRequest.title}
+							<a href="${this.pullRequest.url}">#${this.pullRequest.id}</a></strong
+						>
+						<!-- &nbsp;
+						<gk-badge>pending suggestions</gk-badge> -->
+					</p>
+					<p>
+						<gk-tag>
+							<span slot="prefix"><code-icon icon="source-control"></code-icon></span>
+							${this.pullRequest.refs?.base.branch}
+						</gk-tag>
+						<gk-tag>
+							<span slot="prefix"><code-icon icon="repo"></code-icon></span>
+							${this.pullRequest.refs?.base.repo}
+						</gk-tag>
+						<gk-additions-deletions>
+							<span slot="additions">${this.pullRequest.additions}</span>
+							<span slot="deletions">${this.pullRequest.deletions}</span>
+						</gk-additions-deletions>
+					</p>
+					<span slot="people">
+						<gk-avatar-group>
+							${when(
+								this.pullRequest.author != null,
+								() => html`<gk-avatar src="${this.pullRequest!.author.avatarUrl}"></gk-avatar>`,
+							)}
+							${when(
+								this.assignees.length > 0,
+								() => html`
+									${repeat(
+										this.assignees,
+										item => item.url,
+										(item, index) => html`<gk-avatar src="${item.avatarUrl}"></gk-avatar>`,
+									)}
+								`,
+							)}
+						</gk-avatar-group>
+					</span>
+					<span slot="date">
+						<gk-date-from date="${this.lastUpdatedDate}"></gk-date-from>
+					</span>
+					<nav slot="actions"><gkc-button variant="ghost">Checkout branch</gkc-button></nav>
+				</gk-focus-item>
+			</gk-focus-row>
+		`;
+	}
+}
diff --git a/src/webviews/apps/plus/focus/components/pull-request-row.ts b/src/webviews/apps/plus/focus/components/pull-request-row.ts
index 2be4dd4..0953c77 100644
--- a/src/webviews/apps/plus/focus/components/pull-request-row.ts
+++ b/src/webviews/apps/plus/focus/components/pull-request-row.ts
@@ -14,11 +14,11 @@ const template = html<PullRequestRow>`
 	<template role="row">
 		<table-cell class="status">
 			${when(
-				x => x.pullRequest!.isDraft === true,
+				x => x.pullRequest?.isDraft === true,
 				html`<code-icon icon="git-pull-request-draft" title="draft"></code-icon>`,
 			)}
 			${when(
-				x => x.pullRequest!.isDraft !== true,
+				x => x.pullRequest?.isDraft !== true,
 				html`<code-icon class="pull-request-draft" icon="git-pull-request" title="open"></code-icon>`,
 			)}
 			${when(
@@ -295,27 +295,36 @@ export class PullRequestRow extends FASTElement {
 
 	@volatile
 	get lastUpdatedDate() {
-		return new Date(this.pullRequest!.date);
+		return this.pullRequest ? new Date(this.pullRequest.date) : undefined;
 	}
 
 	@volatile
 	get lastUpdatedState() {
+		if (!this.lastUpdatedDate) {
+			return;
+		}
 		return fromDateRange(this.lastUpdatedDate);
 	}
 
 	@volatile
 	get lastUpdated() {
+		if (!this.lastUpdatedDate) {
+			return;
+		}
 		return fromNow(this.lastUpdatedDate, true);
 	}
 
 	@volatile
 	get lastUpdatedLabel() {
+		if (!this.lastUpdatedDate) {
+			return;
+		}
 		return fromNow(this.lastUpdatedDate);
 	}
 
 	@volatile
 	get lastUpdatedClass() {
-		switch (this.lastUpdatedState.status) {
+		switch (this.lastUpdatedState?.status) {
 			case 'danger':
 				return 'indicator-error';
 			case 'warning':
diff --git a/src/webviews/apps/plus/focus/focus.html b/src/webviews/apps/plus/focus/focus.html
index e178f03..1f47471 100644
--- a/src/webviews/apps/plus/focus/focus.html
+++ b/src/webviews/apps/plus/focus/focus.html
@@ -57,6 +57,33 @@
 				<main class="app__main">
 					<section class="focus-section app__section">
 						<header class="focus-section__header">
+							<h2>Focus View</h2>
+							<nav class="tab-filter" id="filter-focus-items">
+								<button class="tab-filter__tab is-active" type="button" data-tab="">All</button>
+								<button class="tab-filter__tab" type="button" data-tab="authored">Opened by Me</button>
+								<button class="tab-filter__tab" type="button" data-tab="assigned">
+									Assigned to Me
+								</button>
+								<button class="tab-filter__tab" type="button" data-tab="review-requested">
+									Needs my Review
+								</button>
+								<button class="tab-filter__tab" type="button" data-tab="mentioned">Mentions Me</button>
+							</nav>
+						</header>
+						<div class="focus-section__content">
+							<gk-focus-container id="list-focus-items"></gk-focus-container>
+							<div class="alert" id="loading-focus-items">
+								<span class="alert__content"
+									><code-icon modifier="spin" icon="loading"></code-icon> Loading</span
+								>
+							</div>
+							<div class="alert" id="no-focus-items">
+								<span class="alert__content">None found</span>
+							</div>
+						</div>
+					</section>
+					<!-- <section class="focus-section app__section">
+						<header class="focus-section__header">
 							<h2>My Pull Requests</h2>
 							<nav class="tab-filter" id="pr-filter">
 								<button class="tab-filter__tab is-active" type="button" data-tab="">All</button>
@@ -80,8 +107,6 @@
 										<code-icon icon="gl-clock"></code-icon>
 									</table-cell>
 									<table-cell class="data-body" header="column" pinned>Pull Request</table-cell>
-									<!-- <table-cell class="data-author" header="column" pinned>Author</table-cell>
-								<table-cell class="data-assigned" header="column" pinned>Assigned</table-cell> -->
 									<table-cell class="data-participants" header="column" pinned title="Participants">
 										<code-icon icon="organization"></code-icon>
 									</table-cell>
@@ -97,6 +122,7 @@
 									></table-cell>
 								</table-row>
 							</table-container>
+							<gk-focus-container id="share-pull-requests"></gk-focus-container>
 							<div class="alert" id="loading-pull-requests">
 								<span class="alert__content"
 									><code-icon modifier="spin" icon="loading"></code-icon> Loading</span
@@ -129,8 +155,6 @@
 										<code-icon icon="gl-clock"></code-icon>
 									</table-cell>
 									<table-cell header="column" pinned>Title</table-cell>
-									<!-- <table-cell class="data-author" header="column" pinned>Author</table-cell>
-								<table-cell class="data-assigned" header="column" pinned>Assigned</table-cell> -->
 									<table-cell class="data-participants" header="column" pinned title="Participants">
 										<code-icon icon="organization"></code-icon>
 									</table-cell>
@@ -154,7 +178,7 @@
 								<span class="alert__content">No issues found</span>
 							</div>
 						</div>
-					</section>
+					</section> -->
 				</main>
 			</div>
 		</div>
diff --git a/src/webviews/apps/plus/focus/focus.scss b/src/webviews/apps/plus/focus/focus.scss
index e05a731..bd27fe1 100644
--- a/src/webviews/apps/plus/focus/focus.scss
+++ b/src/webviews/apps/plus/focus/focus.scss
@@ -22,6 +22,8 @@ body {
 	--table-text: var(--color-foreground--65);
 	--table-pinned-background: var(--color-background);
 	--layout-gutter-outer: 20px;
+
+	--gk-color-primary: var(--vscode-errorForeground);
 }
 
 .vscode-high-contrast,
@@ -259,6 +261,11 @@ h3 {
 	}
 }
 
+gk-focus-view::part(base) {
+	font-size: 1.5rem;
+	font-weight: bold;
+}
+
 .app {
 	display: flex;
 	flex-direction: column;
diff --git a/src/webviews/apps/plus/focus/focus.ts b/src/webviews/apps/plus/focus/focus.ts
index 4e2304e..20fa902 100644
--- a/src/webviews/apps/plus/focus/focus.ts
+++ b/src/webviews/apps/plus/focus/focus.ts
@@ -1,3 +1,4 @@
+// import { FocusView } from '@gitkraken/shared-web-components';
 import type { PullRequestShape } from '../../../../git/models/pullRequest';
 import type { State } from '../../../../plus/webviews/focus/protocol';
 import {
@@ -11,6 +12,8 @@ import { App } from '../../shared/appBase';
 import type { FeatureGate } from '../../shared/components/feature-gate';
 import type { FeatureGateBadge } from '../../shared/components/feature-gate-badge';
 import { DOM } from '../../shared/dom';
+import type { GkIssueRow } from './components/gk-issue-row';
+import type { GkPullRequestRow } from './components/gk-pull-request-row';
 import type { IssueRow } from './components/issue-row';
 import type { PullRequestRow } from './components/pull-request-row';
 import '../../shared/components/button';
@@ -24,13 +27,17 @@ import '../../shared/components/table/table-cell';
 import '../../shared/components/feature-gate-badge';
 import './components/issue-row';
 import './components/pull-request-row';
+import './components/gk-pull-request-row';
+import './components/gk-issue-row';
 import './focus.scss';
+import '@gitkraken/shared-web-components';
 
 export class FocusApp extends App<State> {
 	constructor() {
 		super('FocusApp');
 	}
 
+	private _focusFilter?: string;
 	private _prFilter?: string;
 	private _issueFilter?: string;
 
@@ -54,6 +61,12 @@ export class FocusApp extends App<State> {
 					this.renderIssues();
 				}),
 			),
+			DOM.on('#focus-filter [data-tab]', 'click', e =>
+				this.onSelectTab(e, val => {
+					this._focusFilter = val;
+					this.renderIssues();
+				}),
+			),
 			DOM.on<PullRequestRow, PullRequestShape>('pull-request-row', 'open-worktree', (e, target: HTMLElement) =>
 				this.onOpenWorktree(e, target),
 			),
@@ -106,13 +119,65 @@ export class FocusApp extends App<State> {
 		const $badge = document.getElementById('subscription-gate-badge')! as FeatureGateBadge;
 		$badge.subscription = this.state.access.subscription.current;
 
-		this.renderPullRequests();
-		this.renderIssues();
+		// this.renderPullRequests();
+		// this.renderIssues();
+		this.renderFocusList();
+	}
+
+	renderFocusList() {
+		const tableEl = document.getElementById('list-focus-items');
+		if (tableEl == null) return;
+
+		tableEl.innerHTML = '';
+
+		const noneEl = document.getElementById('no-focus-items')!;
+		const loadingEl = document.getElementById('loading-focus-items')!;
+		if (this.state.access.allowed !== true || (this.state.pullRequests == null && this.state.issues == null)) {
+			noneEl.setAttribute('hidden', 'true');
+			loadingEl.removeAttribute('hidden');
+		} else if (
+			(this.state.pullRequests == null || this.state.pullRequests.length === 0) &&
+			(this.state.issues == null || this.state.issues.length === 0)
+		) {
+			noneEl.removeAttribute('hidden');
+			loadingEl.setAttribute('hidden', 'true');
+		} else {
+			noneEl.setAttribute('hidden', 'true');
+			loadingEl.setAttribute('hidden', 'true');
+			let rank = 0;
+			this.state.pullRequests?.forEach(
+				({ pullRequest, reasons, isCurrentBranch, isCurrentWorktree, hasWorktree, hasLocalBranch }, i) => {
+					if (this._focusFilter == null || this._focusFilter === '' || reasons.includes(this._focusFilter)) {
+						const rowEl = document.createElement('gk-pull-request-row') as GkPullRequestRow;
+						rowEl.pullRequest = pullRequest;
+						rowEl.rank = ++rank;
+						// rowEl2.reasons = reasons;
+						rowEl.isCurrentBranch = isCurrentBranch;
+						rowEl.isCurrentWorktree = isCurrentWorktree;
+						rowEl.hasWorktree = hasWorktree;
+						rowEl.hasLocalBranch = hasLocalBranch;
+
+						tableEl.append(rowEl);
+					}
+				},
+			);
+
+			this.state.issues?.forEach(({ issue, reasons }) => {
+				if (this._focusFilter == null || this._focusFilter === '' || reasons.includes(this._focusFilter)) {
+					const rowEl = document.createElement('gk-issue-row') as GkIssueRow;
+					rowEl.rank = ++rank;
+					rowEl.issue = issue;
+
+					tableEl.append(rowEl);
+				}
+			});
+		}
 	}
 
 	renderPullRequests() {
 		const tableEl = document.getElementById('pull-requests');
 		if (tableEl == null) return;
+		const tableEl2 = document.getElementById('share-pull-requests')!;
 
 		const rowEls = tableEl.querySelectorAll('pull-request-row');
 		rowEls.forEach(el => el.remove());
@@ -128,8 +193,9 @@ export class FocusApp extends App<State> {
 		} else {
 			noneEl.setAttribute('hidden', 'true');
 			loadingEl.setAttribute('hidden', 'true');
+			tableEl2.innerHTML = '';
 			this.state.pullRequests.forEach(
-				({ pullRequest, reasons, isCurrentBranch, isCurrentWorktree, hasWorktree, hasLocalBranch }) => {
+				({ pullRequest, reasons, isCurrentBranch, isCurrentWorktree, hasWorktree, hasLocalBranch }, i) => {
 					if (this._prFilter == null || this._prFilter === '' || reasons.includes(this._prFilter)) {
 						const rowEl = document.createElement('pull-request-row') as PullRequestRow;
 						rowEl.pullRequest = pullRequest;
@@ -140,6 +206,17 @@ export class FocusApp extends App<State> {
 						rowEl.hasLocalBranch = hasLocalBranch;
 
 						tableEl.append(rowEl);
+
+						const rowEl2 = document.createElement('gk-pull-request-row') as GkPullRequestRow;
+						rowEl2.pullRequest = pullRequest;
+						rowEl2.rank = i + 1;
+						// rowEl2.reasons = reasons;
+						rowEl2.isCurrentBranch = isCurrentBranch;
+						rowEl2.isCurrentWorktree = isCurrentWorktree;
+						rowEl2.hasWorktree = hasWorktree;
+						rowEl2.hasLocalBranch = hasLocalBranch;
+
+						tableEl2.append(rowEl2);
 					}
 				},
 			);
@@ -151,6 +228,7 @@ export class FocusApp extends App<State> {
 
 		const rowEls = tableEl.querySelectorAll('issue-row');
 		rowEls.forEach(el => el.remove());
+		const tableEl2 = document.getElementById('share-pull-requests')!;
 
 		const noneEl = document.getElementById('no-issues')!;
 		const loadingEl = document.getElementById('loading-issues')!;
@@ -170,6 +248,11 @@ export class FocusApp extends App<State> {
 					rowEl.reasons = reasons;
 
 					tableEl.append(rowEl);
+
+					const rowEl2 = document.createElement('gk-issue-row') as GkIssueRow;
+					rowEl2.issue = issue;
+
+					tableEl2.append(rowEl2);
 				}
 			});
 		}
@@ -191,4 +274,7 @@ export class FocusApp extends App<State> {
 	}
 }
 
+// customElements.define(FocusView.tag, FocusView);
+// FocusView.define();
+
 new FocusApp();