Browse Source

Adds timed snooze menu to Focus UI

main
Keith Daulton 1 year ago
parent
commit
9a9e70d3d4
7 changed files with 178 additions and 42 deletions
  1. +2
    -0
      src/plus/webviews/focus/protocol.ts
  2. +19
    -2
      src/webviews/apps/plus/focus/components/common.css.ts
  3. +20
    -16
      src/webviews/apps/plus/focus/components/gk-issue-row.ts
  4. +18
    -14
      src/webviews/apps/plus/focus/components/gk-pull-request-row.ts
  5. +7
    -4
      src/webviews/apps/plus/focus/components/gk-theme.css.ts
  6. +98
    -0
      src/webviews/apps/plus/focus/components/snooze.ts
  7. +14
    -6
      src/webviews/apps/plus/focus/focus.ts

+ 2
- 0
src/plus/webviews/focus/protocol.ts View File

@ -60,6 +60,7 @@ export const SwitchToBranchCommandType = new IpcCommandType
export interface SnoozePrParams {
pullRequest: PullRequestShape;
expiresAt?: string;
snooze?: string;
}
export const SnoozePrCommandType = new IpcCommandType<SnoozePrParams>('focus/pr/snooze');
@ -72,6 +73,7 @@ export const PinPrCommandType = new IpcCommandType('focus/pr/pin');
export interface SnoozeIssueParams {
issue: IssueShape;
expiresAt?: string;
snooze?: string;
}
export const SnoozeIssueCommandType = new IpcCommandType<SnoozeIssueParams>('focus/issue/snooze');

+ 19
- 2
src/webviews/apps/plus/focus/components/common.css.ts View File

@ -94,6 +94,13 @@ export const rowBaseStyles = css`
line-height: 2.4rem;
}
gk-focus-row:not(:hover):not(:focus-within) gl-snooze:not([snoozed]),
gk-focus-row:not(:hover):not(:focus-within) .pin:not(.is-active) {
opacity: 0;
}
`;
export const pinStyles = css`
.icon {
box-sizing: border-box;
display: inline-flex;
@ -109,16 +116,26 @@ export const rowBaseStyles = css`
cursor: pointer;
opacity: 0.4;
}
.pin:hover {
opacity: 0.64;
text-decoration: none;
}
gk-focus-row:not(:hover):not(:focus-within) .pin:not(.is-active) {
opacity: 0;
.pin:focus {
outline: 1px solid var(--vscode-focusBorder);
outline-offset: -1px;
}
.pin.is-active {
opacity: 1;
}
.pin-menu {
width: max-content;
}
gk-tooltip gk-menu {
z-index: 10;
}
`;

+ 20
- 16
src/webviews/apps/plus/focus/components/gk-issue-row.ts View File

@ -15,14 +15,23 @@ 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 { repoBranchStyles } from './branch-tag.css';
import { rowBaseStyles } from './common.css';
import { pinStyles, rowBaseStyles } from './common.css';
import { dateAgeStyles } from './date-styles.css';
import { themeProperties } from './gk-theme.css';
import { fromDateRange } from './helpers';
import './snooze';
@customElement('gk-issue-row')
export class GkIssueRow extends LitElement {
static override styles = [themeProperties, elementBase, dateAgeStyles, repoBranchStyles, rowBaseStyles, css``];
static override styles = [
themeProperties,
elementBase,
dateAgeStyles,
repoBranchStyles,
pinStyles,
rowBaseStyles,
css``,
];
@property({ type: Number })
public rank?: number;
@ -31,10 +40,10 @@ export class GkIssueRow extends LitElement {
public issue?: IssueShape;
@property()
public pinned = false;
public pinned?: string;
@property()
public snoozed = false;
public snoozed?: string;
constructor() {
super();
@ -80,16 +89,7 @@ export class GkIssueRow extends LitElement {
></a>
<span>${this.pinned ? 'Unpin' : 'Pin'}</span>
</gk-tooltip>
<gk-tooltip>
<a
href="#"
class="icon pin ${this.snoozed ? ' is-active' : ''}"
slot="trigger"
@click="${this.onSnoozeClick}"
><code-icon icon="${this.snoozed ? 'bell' : 'bell-slash'}"></code-icon
></a>
<span>${this.snoozed ? 'Unsnooze' : 'Snooze'}</span>
</gk-tooltip>
<gl-snooze .snoozed=${this.snoozed} @gl-snooze-action=${this.onSnoozeAction}></gl-snooze>
</span>
<span slot="date">
<gk-date-from class="date ${this.dateStyle}" date="${this.lastUpdatedDate}"></gk-date-from>
@ -159,11 +159,15 @@ export class GkIssueRow extends LitElement {
`;
}
onSnoozeClick(e: Event) {
onSnoozeAction(e: CustomEvent<{ expiresAt: never; snooze: string } | { expiresAt?: string; snooze: never }>) {
e.preventDefault();
this.dispatchEvent(
new CustomEvent('snooze-item', {
detail: { item: this.issue!, snooze: this.snoozed },
detail: {
item: this.issue!,
expiresAt: e.detail.expiresAt,
snooze: this.snoozed,
},
}),
);
}

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

@ -16,14 +16,23 @@ 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 { repoBranchStyles } from './branch-tag.css';
import { rowBaseStyles } from './common.css';
import { pinStyles, rowBaseStyles } from './common.css';
import { dateAgeStyles } from './date-styles.css';
import { themeProperties } from './gk-theme.css';
import { fromDateRange } from './helpers';
import './snooze';
@customElement('gk-pull-request-row')
export class GkPullRequestRow extends LitElement {
static override styles = [themeProperties, elementBase, dateAgeStyles, repoBranchStyles, rowBaseStyles, css``];
static override styles = [
themeProperties,
elementBase,
dateAgeStyles,
repoBranchStyles,
pinStyles,
rowBaseStyles,
css``,
];
@property({ type: Number })
public rank?: number;
@ -142,16 +151,7 @@ export class GkPullRequestRow extends LitElement {
></a>
<span>${this.pinned ? 'Unpin' : 'Pin'}</span>
</gk-tooltip>
<gk-tooltip>
<a
href="#"
class="icon pin ${this.snoozed ? ' is-active' : ''}"
slot="trigger"
@click="${this.onSnoozeClick}"
><code-icon icon="${this.snoozed ? 'bell' : 'bell-slash'}"></code-icon
></a>
<span>${this.snoozed ? 'Unsnooze' : 'Snooze'}</span>
</gk-tooltip>
<gl-snooze .snoozed=${this.snoozed} @gl-snooze-action=${this.onSnoozeAction}></gl-snooze>
</span>
<span slot="date">
<gk-date-from class="date ${this.dateStyle}" date="${this.lastUpdatedDate}"></gk-date-from>
@ -305,11 +305,15 @@ export class GkPullRequestRow extends LitElement {
this.dispatchEvent(new CustomEvent('switch-branch', { detail: this.pullRequest! }));
}
onSnoozeClick(e: Event) {
onSnoozeAction(e: CustomEvent<{ expiresAt: never; snooze: string } | { expiresAt?: string; snooze: never }>) {
e.preventDefault();
this.dispatchEvent(
new CustomEvent('snooze-item', {
detail: { item: this.pullRequest!, snooze: this.snoozed },
detail: {
item: this.pullRequest!,
expiresAt: e.detail.expiresAt,
snooze: this.snoozed,
},
}),
);
}

+ 7
- 4
src/webviews/apps/plus/focus/components/gk-theme.css.ts View File

@ -2,6 +2,9 @@ import { css } from 'lit';
export const themeProperties = css`
:host {
--focus-color: var(--vscode-focusBorder);
--gk-focus-border-color: var(--focus-color);
--gk-additions-color: var(--vscode-gitDecoration-addedResourceForeground);
--gk-deletions-color: var(--vscode-gitDecoration-deletedResourceForeground);
@ -9,10 +12,10 @@ export const themeProperties = css`
--gk-tag-background-color: var(--background-10);
--gk-text-secondary-color: var(--color-foreground--85);
--gk-menu-border-color: var(--background-30);
--gk-menu-background-color: var(--background-10);
--gk-menu-item-background-color-hover: var(--background-15);
--gk-menu-item-font-color-disabled: var(--color-foreground--50);
--gk-menu-border-color: var(--vscode-menu-border);
--gk-menu-background-color: var(--vscode-menu-background);
--gk-menu-item-background-color-hover: var(--vscode-menu-selectionBackground);
--gk-menu-item-background-color-active: var(--vscode-menu-background);
--gk-button-ghost-color: var(--color-foreground);
--gk-button-ghost-color-active: var(--color-foreground--85);

+ 98
- 0
src/webviews/apps/plus/focus/components/snooze.ts View File

@ -0,0 +1,98 @@
import { defineGkElement, Menu, MenuItem, Popover, Tooltip } from '@gitkraken/shared-web-components';
import { html, LitElement } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { pinStyles } from './common.css';
import { themeProperties } from './gk-theme.css';
const HOUR = 60 * 60 * 1000;
@customElement('gl-snooze')
class GlSnooze extends LitElement {
static override styles = [themeProperties, pinStyles];
@property({ reflect: true })
public snoozed?: string;
constructor() {
super();
defineGkElement(Menu, MenuItem, Popover, Tooltip);
}
override render() {
if (this.snoozed) {
return html`
<gk-tooltip>
<a href="#" class="icon pin is-active" slot="trigger" @click=${this.onUnsnoozeClick}
><code-icon icon="bell"></code-icon
></a>
<span>Unsnooze</span>
</gk-tooltip>
`;
}
return html`
<gk-popover placement="bottom-start">
<a href="#" class="icon pin" slot="trigger"><code-icon icon="bell-slash"></code-icon></a>
<gk-menu class="pin-menu" @select=${this.onSelectDuration}>
<gk-menu-item data-value="unlimited">Snooze</gk-menu-item>
<gk-menu-item data-value="1hr">Snooze for 1 hour</gk-menu-item>
<gk-menu-item data-value="4hr">Snooze for 4 hours</gk-menu-item>
<gk-menu-item data-value="tomorrow-9am">Snooze until tomorrow at 9:00 AM</gk-menu-item>
</gk-menu>
</gk-popover>
`;
}
private onSnoozeActionCore(expiresAt?: string) {
this.dispatchEvent(
new CustomEvent('gl-snooze-action', {
detail: { expiresAt: expiresAt, snooze: this.snoozed },
}),
);
}
onUnsnoozeClick(e: Event) {
e.preventDefault();
this.onSnoozeActionCore();
}
onSelectDuration(e: CustomEvent<{ target: MenuItem }>) {
e.preventDefault();
const duration = e.detail.target.dataset.value;
if (!duration) return;
if (duration === 'unlimited') {
this.onSnoozeActionCore();
return;
}
const now = new Date();
let nowTime = now.getTime();
switch (duration) {
case '1hr':
nowTime += HOUR;
break;
case '4hr':
nowTime += HOUR * 4;
break;
case 'tomorrow-9am':
now.setDate(now.getDate() + 1);
now.setHours(9, 0, 0, 0);
nowTime = now.getTime();
break;
}
this.onSnoozeActionCore(new Date(nowTime).toISOString());
}
}
declare global {
interface HTMLElementTagNameMap {
'gl-snooze': GlSnooze;
}
interface HTMLElementEventMap {
'gl-snooze-action': CustomEvent<{ expiresAt: never; snooze: string } | { expiresAt?: string; snooze: never }>;
}
}

+ 14
- 6
src/webviews/apps/plus/focus/focus.ts View File

@ -47,22 +47,22 @@ export class FocusApp extends App {
'switch-branch',
(e, target: HTMLElement) => this.onSwitchBranch(e, target),
),
DOM.on<GkPullRequestRow, { item: PullRequestShape | IssueShape; snooze?: string }>(
DOM.on<GkPullRequestRow, { item: PullRequestShape; date?: number; snooze?: string }>(
'gk-pull-request-row',
'snooze-item',
(e, _target: HTMLElement) => this.onSnoozeItem(e, false),
),
DOM.on<GkPullRequestRow, { item: PullRequestShape | IssueShape; pin?: string }>(
DOM.on<GkPullRequestRow, { item: PullRequestShape; pin?: string }>(
'gk-pull-request-row',
'pin-item',
(e, _target: HTMLElement) => this.onPinItem(e, false),
),
DOM.on<GkIssueRow, { item: PullRequestShape | IssueShape; snooze?: string }>(
DOM.on<GkIssueRow, { item: IssueShape; date?: number; snooze?: string }>(
'gk-issue-row',
'snooze-item',
(e, _target: HTMLElement) => this.onSnoozeItem(e, true),
),
DOM.on<GkIssueRow, { item: PullRequestShape | IssueShape; pin?: string }>(
DOM.on<GkIssueRow, { item: IssueShape; pin?: string }>(
'gk-issue-row',
'pin-item',
(e, _target: HTMLElement) => this.onPinItem(e, true),
@ -99,12 +99,20 @@ export class FocusApp extends App {
this.sendCommand(OpenWorktreeCommandType, { pullRequest: e.detail });
}
private onSnoozeItem(e: CustomEvent<{ item: PullRequestShape | IssueShape; snooze?: string }>, isIssue: boolean) {
private onSnoozeItem(
e: CustomEvent<{ item: PullRequestShape | IssueShape; expiresAt?: string; snooze?: string }>,
isIssue: boolean,
) {
if (isIssue) {
this.sendCommand(SnoozeIssueCommandType, { issue: e.detail.item as IssueShape, snooze: e.detail.snooze });
this.sendCommand(SnoozeIssueCommandType, {
issue: e.detail.item as IssueShape,
expiresAt: e.detail.expiresAt,
snooze: e.detail.snooze,
});
} else {
this.sendCommand(SnoozePrCommandType, {
pullRequest: e.detail.item as PullRequestShape,
expiresAt: e.detail.expiresAt,
snooze: e.detail.snooze,
});
}

Loading…
Cancel
Save