From 1d3345ca6116d23e4b940ac8fb629d388ad79a50 Mon Sep 17 00:00:00 2001 From: Keith Daulton Date: Mon, 24 Oct 2022 23:56:27 -0400 Subject: [PATCH] Adds initial workspaces webview --- package.json | 27 ++ src/constants.ts | 3 + src/container.ts | 7 + src/plus/webviews/workspaces/protocol.ts | 1 + src/plus/webviews/workspaces/workspacesWebview.ts | 37 +++ src/usageTracker.ts | 3 +- .../plus/workspaces/components/workspace-item.ts | 71 +++++ .../plus/workspaces/components/workspace-list.ts | 47 ++++ src/webviews/apps/plus/workspaces/workspaces.html | 303 +++++++++++++++++++++ src/webviews/apps/plus/workspaces/workspaces.scss | 151 ++++++++++ src/webviews/apps/plus/workspaces/workspaces.ts | 19 ++ src/webviews/apps/shared/components/code-icon.ts | 4 + .../apps/shared/components/table/table-cell.ts | 60 ++++ .../shared/components/table/table-container.ts | 41 +++ .../apps/shared/components/table/table-row.ts | 19 ++ src/webviews/webviewBase.ts | 2 +- webpack.config.js | 2 + 17 files changed, 795 insertions(+), 2 deletions(-) create mode 100644 src/plus/webviews/workspaces/protocol.ts create mode 100644 src/plus/webviews/workspaces/workspacesWebview.ts create mode 100644 src/webviews/apps/plus/workspaces/components/workspace-item.ts create mode 100644 src/webviews/apps/plus/workspaces/components/workspace-list.ts create mode 100644 src/webviews/apps/plus/workspaces/workspaces.html create mode 100644 src/webviews/apps/plus/workspaces/workspaces.scss create mode 100644 src/webviews/apps/plus/workspaces/workspaces.ts create mode 100644 src/webviews/apps/shared/components/table/table-cell.ts create mode 100644 src/webviews/apps/shared/components/table/table-container.ts create mode 100644 src/webviews/apps/shared/components/table/table-row.ts diff --git a/package.json b/package.json index 2327299..f1a99bd 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "onWebviewPanel:gitlens.welcome", "onWebviewPanel:gitlens.settings", "onWebviewPanel:gitlens.graph", + "onWebviewPanel:gitlens.workspaces", "onCommand:gitlens.plus.learn", "onCommand:gitlens.plus.loginOrSignUp", "onCommand:gitlens.plus.logout", @@ -75,6 +76,7 @@ "onCommand:gitlens.plus.purchase", "onCommand:gitlens.getStarted", "onCommand:gitlens.showGraphPage", + "onCommand:gitlens.showWorkspacesPage", "onCommand:gitlens.showSettingsPage", "onCommand:gitlens.showSettingsPage#views", "onCommand:gitlens.showSettingsPage#autolinks", @@ -4485,6 +4487,12 @@ "icon": "$(gitlens-graph)" }, { + "command": "gitlens.showWorkspacesPage", + "title": "Show Workspaces", + "category": "GitLens+", + "icon": "$(layers)" + }, + { "command": "gitlens.showSettingsPage", "title": "Open Settings", "category": "GitLens", @@ -6856,6 +6864,12 @@ "icon": "$(copy)" }, { + "command": "gitlens.workspaces.refresh", + "title": "Refresh", + "category": "GitLens", + "icon": "$(refresh)" + }, + { "command": "gitlens.graph.copyRemoteBranchUrl", "title": "Copy Remote Branch URL", "category": "GitLens", @@ -7410,6 +7424,10 @@ "when": "false" }, { + "command": "gitlens.showWorkspacesPage", + "when": "gitlens:enabled" + }, + { "command": "gitlens.showSettingsPage#views", "when": "false" }, @@ -9018,6 +9036,10 @@ "when": "false" }, { + "command": "gitlens.workspaces.refresh", + "when": "false" + }, + { "command": "gitlens.graph.copyRemoteBranchUrl", "when": "false" }, @@ -9446,6 +9468,11 @@ "command": "gitlens.showSettingsPage#commit-graph", "when": "gitlens:webview:graph:active", "group": "navigation@-98" + }, + { + "command": "gitlens.workspaces.refresh", + "when": "gitlens:workspaces:focused", + "group": "navigation@-98" } ], "editor/title/context": [ diff --git a/src/constants.ts b/src/constants.ts index 555a003..a9629b4 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -184,6 +184,7 @@ export const enum Commands { PlusValidate = 'gitlens.plus.validate', QuickOpenFileHistory = 'gitlens.quickOpenFileHistory', RefreshGraph = 'gitlens.graph.refresh', + RefreshWorkspaces = 'gitlens.workspaces.refresh', RefreshHover = 'gitlens.refreshHover', RefreshTimelinePage = 'gitlens.refreshTimelinePage', ResetAvatarCache = 'gitlens.resetAvatarCache', @@ -240,6 +241,7 @@ export const enum Commands { ShowTimelineView = 'gitlens.showTimelineView', ShowGraphPage = 'gitlens.showGraphPage', ShowWelcomePage = 'gitlens.showWelcomePage', + ShowWorkspacesPage = 'gitlens.showWorkspacesPage', StashApply = 'gitlens.stashApply', StashSave = 'gitlens.stashSave', StashSaveFiles = 'gitlens.stashSaveFiles', @@ -283,6 +285,7 @@ export const enum ContextKeys { DisabledToggleCodeLens = 'gitlens:disabledToggleCodeLens', Disabled = 'gitlens:disabled', Enabled = 'gitlens:enabled', + WorkspacesFocused = 'gitlens:workspaces:focused', HasConnectedRemotes = 'gitlens:hasConnectedRemotes', HasRemotes = 'gitlens:hasRemotes', HasRichRemotes = 'gitlens:hasRichRemotes', diff --git a/src/container.ts b/src/container.ts index 30884db..417042d 100644 --- a/src/container.ts +++ b/src/container.ts @@ -26,6 +26,7 @@ import { SubscriptionService } from './plus/subscription/subscriptionService'; import { GraphWebview } from './plus/webviews/graph/graphWebview'; import { TimelineWebview } from './plus/webviews/timeline/timelineWebview'; import { TimelineWebviewView } from './plus/webviews/timeline/timelineWebviewView'; +import { WorkspacesWebview } from './plus/webviews/workspaces/workspacesWebview'; import { StatusBarController } from './statusbar/statusBarController'; import type { Storage } from './storage'; import { executeCommand } from './system/command'; @@ -200,6 +201,7 @@ export class Container { context.subscriptions.push((this._welcomeWebview = new WelcomeWebview(this))); context.subscriptions.push((this._rebaseEditor = new RebaseEditorProvider(this))); context.subscriptions.push((this._graphWebview = new GraphWebview(this))); + context.subscriptions.push((this._workspacesWebview = new WorkspacesWebview(this))); context.subscriptions.push(new ViewFileDecorationProvider()); @@ -548,6 +550,11 @@ export class Container { return this._graphWebview; } + private _workspacesWebview: WorkspacesWebview; + get workspacesWebview() { + return this._workspacesWebview; + } + private _stashesView: StashesView | undefined; get stashesView() { if (this._stashesView == null) { diff --git a/src/plus/webviews/workspaces/protocol.ts b/src/plus/webviews/workspaces/protocol.ts new file mode 100644 index 0000000..d6d7cdf --- /dev/null +++ b/src/plus/webviews/workspaces/protocol.ts @@ -0,0 +1 @@ +export type State = Record; diff --git a/src/plus/webviews/workspaces/workspacesWebview.ts b/src/plus/webviews/workspaces/workspacesWebview.ts new file mode 100644 index 0000000..7b9c9cf --- /dev/null +++ b/src/plus/webviews/workspaces/workspacesWebview.ts @@ -0,0 +1,37 @@ +import type { Disposable } from 'vscode'; +import { Commands, ContextKeys } from '../../../constants'; +import type { Container } from '../../../container'; +import { setContext } from '../../../context'; +import { registerCommand } from '../../../system/command'; +import { WebviewBase } from '../../../webviews/webviewBase'; +import type { State } from './protocol'; + +export class WorkspacesWebview extends WebviewBase { + constructor(container: Container) { + super( + container, + 'gitlens.workspaces', + 'workspaces.html', + 'images/gitlens-icon.png', + 'Workspaces', + `${ContextKeys.WebviewPrefix}workspaces`, + 'workspacesWebview', + Commands.ShowWorkspacesPage, + ); + } + + protected override registerCommands(): Disposable[] { + return [registerCommand(Commands.RefreshWorkspaces, () => this.refresh(true))]; + } + + protected override onFocusChanged(focused: boolean): void { + if (focused) { + // If we are becoming focused, delay it a bit to give the UI time to update + setTimeout(() => void setContext(ContextKeys.WorkspacesFocused, focused), 0); + + return; + } + + void setContext(ContextKeys.WorkspacesFocused, focused); + } +} diff --git a/src/usageTracker.ts b/src/usageTracker.ts index a8f1e21..daed3eb 100644 --- a/src/usageTracker.ts +++ b/src/usageTracker.ts @@ -29,7 +29,8 @@ export type TrackedUsageFeatures = | 'timelineWebview' | 'timelineView' | 'welcomeWebview' - | 'workspaceView'; + | 'workspaceView' + | 'workspacesWebview'; export type TrackedUsageKeys = `${TrackedUsageFeatures}:shown`; export type UsageChangeEvent = { diff --git a/src/webviews/apps/plus/workspaces/components/workspace-item.ts b/src/webviews/apps/plus/workspaces/components/workspace-item.ts new file mode 100644 index 0000000..88faba2 --- /dev/null +++ b/src/webviews/apps/plus/workspaces/components/workspace-item.ts @@ -0,0 +1,71 @@ +import { css, customElement, FASTElement, html, ref } from '@microsoft/fast-element'; +import { focusOutline, srOnly } from '../../../shared/components/styles/a11y'; +import { elementBase } from '../../../shared/components/styles/base'; + +import '../../../shared/components/table/table-cell'; + +const template = html` + +`; + +const styles = css` + ${elementBase} + + :host { + display: table-row; + cursor: pointer; + } + + :host(:focus) { + ${focusOutline} + } + + .actions { + text-align: right; + } + + ${srOnly} +`; + +@customElement({ name: 'workspace-item', template: template, styles: styles, shadowOptions: { delegatesFocus: true } }) +export class WorkspaceItem extends FASTElement { + actions!: HTMLElement; + count!: HTMLElement; + shared!: HTMLElement; + + selectRow(e: Event) { + const path = e.composedPath(); + // exclude events triggered from a slot with actions + if ([this.actions, this.count, this.shared].find(el => path.indexOf(el) > 0) !== undefined) { + return; + } + + console.log('WorkspaceItem.selectRow', e, path); + this.$emit('selected'); + } +} diff --git a/src/webviews/apps/plus/workspaces/components/workspace-list.ts b/src/webviews/apps/plus/workspaces/components/workspace-list.ts new file mode 100644 index 0000000..2c50b06 --- /dev/null +++ b/src/webviews/apps/plus/workspaces/components/workspace-list.ts @@ -0,0 +1,47 @@ +import { css, customElement, FASTElement, html } from '@microsoft/fast-element'; +import { srOnly } from '../../../shared/components/styles/a11y'; +import { elementBase } from '../../../shared/components/styles/base'; + +import '../../../shared/components/table/table-container'; +import '../../../shared/components/table/table-row'; +import '../../../shared/components/table/table-cell'; + +const template = html` + + + Row selection + Workspace + Description + # of repos + Latest update + Shared with + Owner + Workspace actions + + + + + No workspaces + + + + + + + + + +`; + +const styles = css` + ${elementBase} + + .row { + display: table-row; + } + + ${srOnly} +`; + +@customElement({ name: 'workspace-list', template: template, styles: styles }) +export class WorkspaceList extends FASTElement {} diff --git a/src/webviews/apps/plus/workspaces/workspaces.html b/src/webviews/apps/plus/workspaces/workspaces.html new file mode 100644 index 0000000..fd8c854 --- /dev/null +++ b/src/webviews/apps/plus/workspaces/workspaces.html @@ -0,0 +1,303 @@ + + + + + + + +
+

Workspaces

+
+ +
+
+
+

My Pull Requests Issues

+
+
+ + + + + + Repo + Title + 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 + + + +
+
+ +
+
+

My Issues

+
+
+ + + + + + Repo + Title + Author + Assigned + + + + + + + + + 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 + 2 + 0 + + +
+
+ +
+
+

Work in Progress

+
+
+ + + Repo + Branch + Stats + Remote + + + vscode-gitlens + feature/workspaces + +100 -50 + gitkraken/vscode-gitlens + + +
+
+
+ #{endOfBody} + + + + diff --git a/src/webviews/apps/plus/workspaces/workspaces.scss b/src/webviews/apps/plus/workspaces/workspaces.scss new file mode 100644 index 0000000..9ab52da --- /dev/null +++ b/src/webviews/apps/plus/workspaces/workspaces.scss @@ -0,0 +1,151 @@ +:root { + --gitlens-z-inline: 1000; + --gitlens-z-sticky: 1100; + --gitlens-z-popover: 1200; + --gitlens-z-cover: 1300; + --gitlens-z-dialog: 1400; + --gitlens-z-modal: 1500; +} + +body { + --avatar-size: 2.4rem; + --focus-color: var(--vscode-focusBorder); + --table-separator: var(--vscode-textSeparator-foreground); + --table-heading: var(--color-foreground--50); + --table-text: var(--color-foreground--65); + --table-pinned-background: var(--color-background); + --layout-gutter-outer: 20px; +} + +.vscode-high-contrast, +.vscode-dark { + --avatar-bg: var(--color-background--lighten-30); + --background-05: var(--color-background--lighten-05); + --background-075: var(--color-background--lighten-075); + --background-10: var(--color-background--lighten-10); + --background-15: var(--color-background--lighten-15); + --background-30: var(--color-background--lighten-30); + --background-50: var(--color-background--lighten-50); +} + +.vscode-high-contrast-light, +.vscode-light { + --avatar-bg: var(--color-background--darken-30); + --background-05: var(--color-background--darken-05); + --background-075: var(--color-background--darken-075); + --background-10: var(--color-background--darken-10); + --background-15: var(--color-background--darken-15); + --background-30: var(--color-background--darken-30); + --background-50: var(--color-background--darken-50); +} + +:root { + font-size: 62.5%; + font-family: var(--font-family); + box-sizing: border-box; +} + +body { + font-family: var(--font-family); + font-size: var(--font-size); + color: var(--color-foreground); +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +:not(:defined) { + visibility: hidden; +} + +[hidden] { + display: none !important; +} + +a { + text-decoration: none; + + &:hover { + text-decoration: underline; + } + + &:focus { + outline: 1px solid var(--focus-color); + outline-offset: -1px; + } +} + +code-icon { + font-size: inherit; +} + +.button { + width: 2.4rem; + height: 2.4rem; + padding: 0; + color: inherit; + border: none; + background: none; + text-align: center; + font-size: 1.6rem; +} +.button[disabled] { + color: var(--vscode-disabledForeground); +} +.button:focus { + background-color: var(--vscode-toolbar-activeBackground); + outline: 1px solid var(--vscode-focusBorder); + outline-offset: -1px; +} +.button:not([disabled]) { + cursor: pointer; +} +.button:hover:not([disabled]) { + color: var(--vscode-foreground); + background-color: var(--vscode-toolbar-hoverBackground); +} + +.workspace-icon { + font-size: 1.6rem; + vertical-align: sub; +} + +.workspace-section { + display: flex; + flex-direction: column; + + &__header { + flex: none; + } + + &__content { + min-height: 0; + flex: 1 1 auto; + overflow: auto; + } +} + +.app { + display: flex; + flex-direction: column; + height: 100vh; + overflow: hidden; + + &__header { + flex: none; + } + + &__main { + flex: 1 1 auto; + display: flex; + flex-direction: column; + overflow: hidden; + } + &__section { + min-height: 15rem; + flex: 0 1 auto; + } +} diff --git a/src/webviews/apps/plus/workspaces/workspaces.ts b/src/webviews/apps/plus/workspaces/workspaces.ts new file mode 100644 index 0000000..53fa39b --- /dev/null +++ b/src/webviews/apps/plus/workspaces/workspaces.ts @@ -0,0 +1,19 @@ +import type { State } from '../../../../plus/webviews/workspaces/protocol'; +import { App } from '../../shared/appBase'; +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 './workspaces.scss'; + +export class WorkspacesApp extends App { + constructor() { + super('WorkspacesApp'); + } +} + +new WorkspacesApp(); diff --git a/src/webviews/apps/shared/components/code-icon.ts b/src/webviews/apps/shared/components/code-icon.ts index d4453ee..3608aee 100644 --- a/src/webviews/apps/shared/components/code-icon.ts +++ b/src/webviews/apps/shared/components/code-icon.ts @@ -1505,6 +1505,10 @@ const styles = css` font-family: 'glicons'; content: '\\f11a'; } + :host([icon='gl-clock']):before { + font-family: 'glicons'; + content: '\\f11d'; + } @keyframes codicon-spin { 100% { diff --git a/src/webviews/apps/shared/components/table/table-cell.ts b/src/webviews/apps/shared/components/table/table-cell.ts new file mode 100644 index 0000000..629bd52 --- /dev/null +++ b/src/webviews/apps/shared/components/table/table-cell.ts @@ -0,0 +1,60 @@ +import { attr, css, customElement, FASTElement, html } from '@microsoft/fast-element'; +import { elementBase } from '../styles/base'; + +const template = html` + +`; + +const styles = css` + ${elementBase} + + :host { + display: table-cell; + vertical-align: top; + padding: var(--table-spacing, 0.8rem); + /* border-bottom: 1px solid var(--table-separator); */ + text-align: left; + } + + :host(:first-child) { + padding-left: var(--table-edge-spacing, 1.2rem); + } + :host(:last-child) { + padding-right: var(--table-edge-spacing, 1.2rem); + } + + :host([role='columnheader']) { + text-transform: uppercase; + font-weight: normal; + padding-top: var(--table-heading-top-spacing, 0); + padding-bottom: var(--table-heading-bottom-spacing, 1.2rem); + } + + :host([pinned]) { + background-color: var(--table-pinned-background); + position: sticky; + top: 0; + } +`; + +@customElement({ name: 'table-cell', template: template, styles: styles }) +export class TableCell extends FASTElement { + @attr + header: 'column' | 'row' | '' = ''; + + @attr({ mode: 'boolean' }) + pinned = false; + + get cellRole() { + switch (this.header) { + case 'column': + return 'columnheader'; + case 'row': + return 'rowheader'; + default: + return 'cell'; + } + } +} diff --git a/src/webviews/apps/shared/components/table/table-container.ts b/src/webviews/apps/shared/components/table/table-container.ts new file mode 100644 index 0000000..1d2abea --- /dev/null +++ b/src/webviews/apps/shared/components/table/table-container.ts @@ -0,0 +1,41 @@ +import { css, customElement, FASTElement, html } from '@microsoft/fast-element'; +import { elementBase } from '../styles/base'; + +const template = html` + +`; + +const styles = css` + ${elementBase} + + :host { + display: table; + border-collapse: collapse; + width: 100%; + } + + .thead { + display: table-header-group; + color: var(--table-heading); + } + + .tbody { + display: table-row-group; + color: var(--table-text); + } + + .tbody ::slotted(*:hover), + .tbody ::slotted(*:focus-within) { + background-color: var(--background-05); + } +`; + +@customElement({ name: 'table-container', template: template, styles: styles }) +export class TableContainer extends FASTElement {} diff --git a/src/webviews/apps/shared/components/table/table-row.ts b/src/webviews/apps/shared/components/table/table-row.ts new file mode 100644 index 0000000..a781e15 --- /dev/null +++ b/src/webviews/apps/shared/components/table/table-row.ts @@ -0,0 +1,19 @@ +import { css, customElement, FASTElement, html } from '@microsoft/fast-element'; +import { elementBase } from '../styles/base'; + +const template = html` + +`; + +const styles = css` + ${elementBase} + + :host { + display: table-row; + } +`; + +@customElement({ name: 'table-row', template: template, styles: styles }) +export class TableRow extends FASTElement {} diff --git a/src/webviews/webviewBase.ts b/src/webviews/webviewBase.ts index b0f770b..439a7b6 100644 --- a/src/webviews/webviewBase.ts +++ b/src/webviews/webviewBase.ts @@ -31,7 +31,7 @@ function nextIpcId() { return `host:${ipcSequence}`; } -export type WebviewIds = 'graph' | 'settings' | 'timeline' | 'welcome'; +export type WebviewIds = 'graph' | 'settings' | 'timeline' | 'welcome' | 'workspaces'; @logName>((c, name) => `${name}(${c.id})`) export abstract class WebviewBase implements Disposable { diff --git a/webpack.config.js b/webpack.config.js index c831538..b4133f9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -329,6 +329,7 @@ function getWebviewsConfig(mode, env) { getHtmlPlugin('settings', false, mode, env), getHtmlPlugin('timeline', true, mode, env), getHtmlPlugin('welcome', false, mode, env), + getHtmlPlugin('workspaces', true, mode, env), getCspHtmlPlugin(mode, env), new InlineChunkHtmlPlugin(HtmlPlugin, mode === 'production' ? ['\\.css$'] : []), new CopyPlugin({ @@ -391,6 +392,7 @@ function getWebviewsConfig(mode, env) { settings: './settings/settings.ts', timeline: './plus/timeline/timeline.ts', welcome: './welcome/welcome.ts', + workspaces: './plus/workspaces/workspaces.ts', }, mode: mode, target: 'web',