Browse Source

Updates focus view to Lit component

main
Keith Daulton 1 year ago
parent
commit
72036fddd6
4 changed files with 236 additions and 271 deletions
  1. +140
    -71
      src/webviews/apps/plus/focus/components/focus-app.ts
  2. +74
    -3
      src/webviews/apps/plus/focus/components/gk-pull-request-row.ts
  3. +2
    -0
      src/webviews/apps/plus/focus/focus.scss
  4. +20
    -197
      src/webviews/apps/plus/focus/focus.ts

+ 140
- 71
src/webviews/apps/plus/focus/components/focus-app.ts View File

@ -1,13 +1,23 @@
import { html, LitElement } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { map } from 'lit/directives/map.js';
import { repeat } from 'lit/directives/repeat.js';
import { when } from 'lit/directives/when.js';
import type { State } from '../../../../../plus/webviews/focus/protocol';
import { debounce } from '../../../../../system/function';
import type { FeatureGate } from '../../../shared/components/feature-gate';
import type { FeatureGateBadge } from '../../../shared/components/feature-gate-badge';
@customElement('gl-focus-app')
export class GlFocusApp extends LitElement {
private readonly tabFilters = ['authored', 'assigned', 'review-requested', 'mentioned'];
private readonly tabFilterOptions = [
{ label: 'All', value: '' },
{ label: 'Opened by Me', value: 'authored' },
{ label: 'Assigned to Me', value: 'assigned' },
{ label: 'Needs my Review', value: 'review-requested' },
{ label: 'Mentions Me', value: 'mentioned' },
];
@query('#subscription-gate', true)
private subscriptionEl!: FeatureGate;
@ -18,10 +28,10 @@ export class GlFocusApp extends LitElement {
private subScriptionBadgeEl!: FeatureGateBadge;
@state()
private focusFilter?: string;
private selectedTabFilter?: string;
@state()
private loading = true;
private searchText?: string;
@property({ type: Object })
state?: State;
@ -38,44 +48,80 @@ export class GlFocusApp extends LitElement {
return this.state?.access.allowed === true && !(this.state?.repos?.some(r => r.isConnected) ?? false);
}
get filteredItems() {
const items: { isPullrequest: boolean; rank: number; state: Record<string, any> }[] = [];
get items() {
if (this.isLoading) {
return [];
}
const items: { isPullrequest: boolean; rank: number; state: Record<string, any>; reasons: string[] }[] = [];
let rank = 0;
this.state?.pullRequests?.forEach(
({ pullRequest, reasons, isCurrentBranch, isCurrentWorktree, hasWorktree, hasLocalBranch }, i) => {
if (this.focusFilter == null || this.focusFilter === '' || reasons.includes(this.focusFilter)) {
items.push({
isPullrequest: true,
state: {
pullRequest: pullRequest,
// reasons: reasons,
isCurrentBranch: isCurrentBranch,
isCurrentWorktree: isCurrentWorktree,
hasWorktree: hasWorktree,
hasLocalBranch: hasLocalBranch,
},
rank: ++rank,
});
}
},
);
this.state?.issues?.forEach(({ issue, reasons }) => {
if (this.focusFilter == null || this.focusFilter === '' || reasons.includes(this.focusFilter)) {
({ pullRequest, reasons, isCurrentBranch, isCurrentWorktree, hasWorktree, hasLocalBranch }) => {
items.push({
isPullrequest: false,
rank: ++rank,
isPullrequest: true,
state: {
issue: issue,
pullRequest: pullRequest,
isCurrentBranch: isCurrentBranch,
isCurrentWorktree: isCurrentWorktree,
hasWorktree: hasWorktree,
hasLocalBranch: hasLocalBranch,
},
rank: ++rank,
reasons: reasons,
});
}
},
);
this.state?.issues?.forEach(({ issue, reasons }) => {
items.push({
isPullrequest: false,
rank: ++rank,
state: {
issue: issue,
},
reasons: reasons,
});
});
return items;
}
get filteredItems() {
if (this.items.length === 0) {
return this.items;
}
const hasSearch = this.searchText != null && this.searchText !== '';
const hasTabFilter = this.selectedTabFilter != null && this.selectedTabFilter !== '';
if (!hasSearch && !hasTabFilter) {
return this.items;
}
const searchText = this.searchText?.toLowerCase();
return this.items.filter(i => {
if (hasTabFilter && !i.reasons.includes(this.selectedTabFilter!)) {
return false;
}
if (hasSearch) {
if (i.state.issue && !i.state.issue.title.toLowerCase().includes(searchText)) {
return false;
}
if (i.state.pullRequest && !i.state.pullRequest.title.toLowerCase().includes(searchText)) {
return false;
}
}
return true;
});
}
get isLoading() {
return this.state?.pullRequests == null || this.state?.issues == null;
}
override render() {
if (this.state == null) {
return undefined;
@ -123,19 +169,25 @@ export class GlFocusApp extends LitElement {
<header class="focus-section__header">
<div class="focus-section__header-group">
<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>
${map(
this.tabFilterOptions,
({ label, value }, i) => html`
<button
class="tab-filter__tab ${(
this.selectedTabFilter
? value === this.selectedTabFilter
: i === 0
)
? 'is-active'
: ''}"
type="button"
data-tab="${value}"
@click=${() => (this.selectedTabFilter = value)}
>
${label}
</button>
`,
)}
</nav>
</div>
<div class="focus-section__header-group">
@ -144,6 +196,7 @@ export class GlFocusApp extends LitElement {
label="Search field"
label-visibility="sr-only"
placeholder="Search"
@input=${debounce(this.onSearchInput.bind(this), 200)}
>
<code-icon slot="prefix" icon="search"></code-icon>
</gk-input>
@ -152,42 +205,46 @@ export class GlFocusApp extends LitElement {
<div class="focus-section__content">
<gk-focus-container id="list-focus-items">
${when(
this.filteredItems.length > 0,
() => html`
${repeat(
this.filteredItems,
item => item.rank,
({ isPullrequest, rank, state }) =>
when(
isPullrequest,
() =>
html`<gk-pull-request-row
.rank=${rank}
.pullRequest=${state.pullRequest}
></gk-pull-request-row>`,
() =>
html`<gk-issue-row
.rank=${rank}
.issue=${state.issue}
></gk-issue-row>`,
),
)}
`,
this.isLoading,
() => html`
<div class="alert">
<span class="alert__content">None found</span>
<span class="alert__content"
><code-icon modifier="spin" icon="loading"></code-icon>
Loading</span
>
</div>
`,
() =>
when(
this.filteredItems.length > 0,
() => html`
${repeat(
this.filteredItems,
item => item.rank,
({ isPullrequest, rank, state }) =>
when(
isPullrequest,
() =>
html`<gk-pull-request-row
.rank=${rank}
.pullRequest=${state.pullRequest}
></gk-pull-request-row>`,
() =>
html`<gk-issue-row
.rank=${rank}
.issue=${state.issue}
></gk-issue-row>`,
),
)}
`,
() => html`
<div class="alert">
<span class="alert__content">None found</span>
</div>
`,
),
)}
</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>
</main>
@ -196,6 +253,18 @@ export class GlFocusApp extends LitElement {
`;
}
onSearchInput(e: Event) {
const input = e.target as HTMLInputElement;
const value = input.value;
if (value === '' || value.length < 3) {
this.searchText = undefined;
return;
}
this.searchText = value;
}
protected override createRenderRoot() {
return this;
}

+ 74
- 3
src/webviews/apps/plus/focus/components/gk-pull-request-row.ts View File

@ -67,6 +67,19 @@ export class GkPullRequestRow extends LitElement {
.actions a code-icon {
font-size: 1.6rem;
}
.indicator-info {
color: var(--vscode-problemsInfoIcon-foreground);
}
.indicator-warning {
color: var(--vscode-problemsWarningIcon-foreground);
}
.indicator-error {
color: var(--vscode-problemsErrorIcon-foreground);
}
.indicator-neutral {
color: var(--color-alert-neutralBorder);
}
`,
];
@ -105,6 +118,20 @@ export class GkPullRequestRow extends LitElement {
return assignees;
}
get indicator() {
if (this.pullRequest == null) return '';
if (this.pullRequest.reviewDecision === 'ChangesRequested') {
return 'changes';
} else if (this.pullRequest.reviewDecision === 'Approved' && this.pullRequest.mergeableState === 'Mergeable') {
return 'ready';
} else if (this.pullRequest.mergeableState === 'Conflicting') {
return 'conflicting';
}
return '';
}
get dateStyle() {
return `indicator-${fromDateRange(this.lastUpdatedDate).status}`;
}
@ -114,7 +141,36 @@ export class GkPullRequestRow extends LitElement {
return html`
<gk-focus-row>
<span slot="rank">${this.rank}</span>
<span slot="rank">
${this.rank}
${when(
this.indicator === 'changes',
() =>
html`<code-icon
class="indicator-error"
icon="request-changes"
title="changes requested"
></code-icon>`,
)}
${when(
this.indicator === 'ready',
() =>
html`<code-icon
class="indicator-info"
icon="pass"
title="approved and ready to merge"
></code-icon>`,
)}
${when(
this.indicator === 'conflicting',
() =>
html`<code-icon
class="indicator-error"
icon="bracket-error"
title="cannot be merged due to merge conflicts"
></code-icon>`,
)}
</span>
<gk-focus-item>
<span slot="type"><code-icon icon="git-pull-request"></code-icon></span>
<p>
@ -190,6 +246,21 @@ export class GkPullRequestRow extends LitElement {
`;
}
public onOpenWorktreeClick(e: MouseEvent) {}
public onSwitchBranchClick(e: MouseEvent) {}
onOpenWorktreeClick(e: Event) {
if (this.isCurrentWorktree) {
e.preventDefault();
e.stopImmediatePropagation();
return;
}
this.dispatchEvent(new CustomEvent('open-worktree', { detail: this.pullRequest! }));
}
onSwitchBranchClick(e: Event) {
if (this.isCurrentBranch) {
e.preventDefault();
e.stopImmediatePropagation();
return;
}
this.dispatchEvent(new CustomEvent('switch-branch', { detail: this.pullRequest! }));
}
}

+ 2
- 0
src/webviews/apps/plus/focus/focus.scss View File

@ -25,6 +25,8 @@ body {
--gk-input-background-color: var(--vscode-input-background);
--gk-input-border-color: var(--vscode-input-border);
--gk-input-color: var(--vscode-input-foreground);
--gk-text-secondary-color: var(--color-foreground--65);
--gk-button-ghost-color: var(--color-foreground--50);
}
.vscode-high-contrast,

+ 20
- 197
src/webviews/apps/plus/focus/focus.ts View File

@ -44,8 +44,6 @@ export class FocusApp extends App {
private _issueFilter?: string;
override onInitialize() {
this.renderContent();
this.attachState();
}
@ -53,37 +51,31 @@ export class FocusApp extends App {
const disposables = super.onBind?.() ?? [];
disposables.push(
// DOM.on('#pr-filter [data-tab]', 'click', e =>
// this.onSelectTab(e, val => {
// this._prFilter = val;
// this.renderPullRequests();
// }),
// ),
// DOM.on('#issue-filter [data-tab]', 'click', e =>
// this.onSelectTab(e, val => {
// this._issueFilter = val;
// this.renderIssues();
// }),
// ),
DOM.on('#filter-focus-items [data-tab]', 'click', e =>
this.onSelectTab(e, val => {
this._focusFilter = val;
this.renderFocusList();
}),
DOM.on<GkPullRequestRow, PullRequestShape>(
'gk-pull-request-row',
'open-worktree',
(e, target: HTMLElement) => this.onOpenWorktree(e, target),
),
DOM.on<GkPullRequestRow, PullRequestShape>(
'gk-pull-request-row',
'switch-branch',
(e, target: HTMLElement) => this.onSwitchBranch(e, target),
),
// DOM.on<PullRequestRow, PullRequestShape>('pull-request-row', 'open-worktree', (e, target: HTMLElement) =>
// this.onOpenWorktree(e, target),
// ),
// DOM.on<PullRequestRow, PullRequestShape>('pull-request-row', 'switch-branch', (e, target: HTMLElement) =>
// this.onSwitchBranch(e, target),
// ),
);
return disposables;
}
private _component?: GlFocusApp;
private get component() {
if (this._component == null) {
this._component = (document.getElementById('app') as GlFocusApp)!;
}
return this._component;
}
attachState() {
(document.getElementById('app') as GlFocusApp)!.state = this.state;
this.component.state = this.state;
}
private onSwitchBranch(e: CustomEvent<PullRequestShape>, _target: HTMLElement) {
@ -105,181 +97,12 @@ export class FocusApp extends App {
onIpc(DidChangeNotificationType, msg, params => {
this.state = params.state;
this.setState(this.state);
this.renderContent();
// this.renderContent();
this.attachState();
});
break;
}
}
renderContent() {
// let $gate = document.getElementById('subscription-gate')! as FeatureGate;
// if ($gate != null) {
// $gate.state = this.state.access.subscription.current.state;
// $gate.visible = this.state.access.allowed !== true;
// }
// $gate = document.getElementById('connection-gate')! as FeatureGate;
// if ($gate != null) {
// $gate.visible =
// this.state.access.allowed === true && !(this.state.repos?.some(r => r.isConnected) ?? false);
// }
// const $badge = document.getElementById('subscription-gate-badge')! as FeatureGateBadge;
// $badge.subscription = this.state.access.subscription.current;
// 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());
// const noneEl = document.getElementById('no-pull-requests')!;
// const loadingEl = document.getElementById('loading-pull-requests')!;
// if (this.state.pullRequests == null || this.state.access.allowed !== true) {
// noneEl.setAttribute('hidden', 'true');
// loadingEl.removeAttribute('hidden');
// } else if (this.state.pullRequests.length === 0) {
// noneEl.removeAttribute('hidden');
// loadingEl.setAttribute('hidden', 'true');
// } else {
// noneEl.setAttribute('hidden', 'true');
// loadingEl.setAttribute('hidden', 'true');
// tableEl2.innerHTML = '';
// this.state.pullRequests.forEach(
// ({ 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;
// rowEl.reasons = reasons;
// rowEl.isCurrentBranch = isCurrentBranch;
// rowEl.isCurrentWorktree = isCurrentWorktree;
// rowEl.hasWorktree = hasWorktree;
// 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);
// }
// },
// );
// }
// }
// renderIssues() {
// const tableEl = document.getElementById('issues')!;
// 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')!;
// if (this.state.issues == null || this.state.access.allowed !== true) {
// noneEl.setAttribute('hidden', 'true');
// loadingEl.removeAttribute('hidden');
// } else if (this.state.issues.length === 0) {
// noneEl.removeAttribute('hidden');
// loadingEl.setAttribute('hidden', 'true');
// } else {
// noneEl.setAttribute('hidden', 'true');
// loadingEl.setAttribute('hidden', 'true');
// this.state.issues.forEach(({ issue, reasons }) => {
// if (this._issueFilter == null || this._issueFilter === '' || reasons.includes(this._issueFilter)) {
// const rowEl = document.createElement('issue-row') as IssueRow;
// rowEl.issue = issue;
// rowEl.reasons = reasons;
// tableEl.append(rowEl);
// const rowEl2 = document.createElement('gk-issue-row') as GkIssueRow;
// rowEl2.issue = issue;
// tableEl2.append(rowEl2);
// }
// });
// }
// }
onSelectTab(e: Event, callback?: (val?: string) => void) {
const thisEl = e.target as HTMLElement;
const tab = thisEl.dataset.tab!;
thisEl.parentElement?.querySelectorAll('[data-tab]')?.forEach(el => {
if ((el as HTMLElement).dataset.tab !== tab) {
el.classList.remove('is-active');
} else {
el.classList.add('is-active');
}
});
callback?.(tab);
}
}
// customElements.define(FocusView.tag, FocusView);

Loading…
Cancel
Save