ソースを参照

Reworks weclome & settings pages

main
Eric Amodio 5年前
コミット
74f4b9f61b
11個のファイルの変更4840行の追加4474行の削除
  1. +1
    -5
      package.json
  2. +885
    -887
      src/webviews/apps/scss/main.scss
  3. +3200
    -2969
      src/webviews/apps/settings/index.html
  4. +47
    -25
      src/webviews/apps/settings/index.ts
  5. +61
    -15
      src/webviews/apps/shared/appWithConfigBase.ts
  6. +1
    -0
      src/webviews/apps/shared/theme.ts
  7. +567
    -509
      src/webviews/apps/welcome/index.html
  8. +54
    -0
      src/webviews/helpers.ts
  9. +12
    -62
      src/webviews/settingsWebview.ts
  10. +6
    -2
      src/webviews/webviewBase.ts
  11. +6
    -0
      src/webviews/welcomeWebview.ts

+ 1
- 5
package.json ファイルの表示

@ -4374,25 +4374,21 @@
{
"command": "gitlens.showSettingsPage#file-history-view",
"when": "view =~ /^gitlens\\.views\\.fileHistory:/",
"args": "general",
"group": "9_gitlens"
},
{
"command": "gitlens.showSettingsPage#line-history-view",
"when": "view =~ /^gitlens\\.views\\.lineHistory:/",
"args": "general",
"group": "9_gitlens"
},
{
"command": "gitlens.showSettingsPage#search-commits-view",
"when": "view =~ /^gitlens\\.views\\.search:/",
"args": "general",
"group": "9_gitlens"
},
{
"command": "gitlens.showSettingsPage#compare-view",
"command": "gitlens.showSettingsPage",
"when": "view =~ /^gitlens\\.views\\.compare:/",
"args": "general",
"group": "9_gitlens"
}
],

+ 885
- 887
src/webviews/apps/scss/main.scss
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 3200
- 2969
src/webviews/apps/settings/index.html
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 47
- 25
src/webviews/apps/settings/index.ts ファイルの表示

@ -35,7 +35,7 @@ export class SettingsApp extends AppWithConfig {
}
let top = 83;
const header = document.querySelector('.page-header--sticky');
const header = document.querySelector('.hero__area--sticky');
if (header != null) {
top = header.clientHeight;
}
@ -54,12 +54,23 @@ export class SettingsApp extends AppWithConfig {
protected onBind(me: this) {
super.onBind(me);
DOM.listenAll('.section__header', 'click', function(this: HTMLInputElement, e: Event) {
DOM.listenAll('.section--collapsible>.section__header', 'click', function(this: HTMLInputElement, e: Event) {
return me.onSectionHeaderClicked(this, e as MouseEvent);
});
DOM.listenAll('.setting--expandable .setting__expander', 'click', function(this: HTMLInputElement, e: Event) {
return me.onSettingExpanderCicked(this, e as MouseEvent);
});
DOM.listenAll('a[data-action="jump"]', 'mousedown', (e: Event) => {
e.stopPropagation();
e.preventDefault();
});
DOM.listenAll('a[data-action="jump"]', 'click', function(this: HTMLAnchorElement, e: Event) {
return me.onJumpToLinkClicked(this, e as MouseEvent);
});
DOM.listenAll('[data-action]', 'mousedown', (e: Event) => {
e.stopPropagation();
e.preventDefault();
});
DOM.listenAll('[data-action]', 'click', function(this: HTMLAnchorElement, e: Event) {
return me.onActionLinkClicked(this, e as MouseEvent);
});
@ -85,31 +96,41 @@ export class SettingsApp extends AppWithConfig {
private onObserver(entries: IntersectionObserverEntry[], observer: IntersectionObserver) {
for (const entry of entries) {
this._sections.set(entry.target.parentElement!.id, entry.isIntersecting);
}
let nextActive: string | undefined;
for (const [id, visible] of this._sections.entries()) {
if (nextActive === undefined) {
nextActive = this._activeSection === 'modes' ? 'modes' : id;
}
if (!visible) continue;
let nextActive: string | undefined;
for (const [id, visible] of this._sections.entries()) {
if (visible) {
nextActive = id;
break;
}
}
if (this._activeSection === nextActive) return;
if (nextActive === undefined) {
if (entries.length !== 1) return;
if (this._activeSection !== undefined) {
this.toggleJumpLink(this._activeSection, false);
}
const entry = entries[0];
if (entry.boundingClientRect == null || entry.rootBounds == null) return;
this._activeSection = nextActive;
nextActive = entry.target.parentElement!.id;
if (entry.boundingClientRect.top >= entry.rootBounds.bottom) {
const keys = [...this._sections.keys()];
const index = keys.indexOf(nextActive);
if (index <= 0) return;
if (this._activeSection !== undefined) {
this.toggleJumpLink(this._activeSection, true);
nextActive = keys[index - 1];
}
}
if (this._activeSection === nextActive) return;
if (this._activeSection !== undefined) {
this.toggleJumpLink(this._activeSection, false);
}
this._activeSection = nextActive;
this.toggleJumpLink(this._activeSection, true);
}
protected getSettingsScope(): 'user' | 'workspace' {
@ -121,7 +142,7 @@ export class SettingsApp extends AppWithConfig {
private onActionLinkClicked(element: HTMLElement, e: MouseEvent) {
switch (element.dataset.action) {
case 'collapse':
for (const el of document.querySelectorAll('.section__header')) {
for (const el of document.querySelectorAll('.section--collapsible')) {
el.classList.add('collapsed');
}
@ -130,7 +151,7 @@ export class SettingsApp extends AppWithConfig {
break;
case 'expand':
for (const el of document.querySelectorAll('.section__header')) {
for (const el of document.querySelectorAll('.section--collapsible')) {
el.classList.remove('collapsed');
}
@ -161,14 +182,15 @@ export class SettingsApp extends AppWithConfig {
}
private onSectionHeaderClicked(element: HTMLElement, e: MouseEvent) {
if (
(e.target as HTMLElement).matches('i.icon__info') ||
(e.target as HTMLElement).matches('a.link__learn-more')
) {
if ((e.target as HTMLElement).matches('a, input, label, i.icon__info')) {
return;
}
element.classList.toggle('collapsed');
element.parentElement!.classList.toggle('collapsed');
}
private onSettingExpanderCicked(element: HTMLElement, e: MouseEvent) {
element.parentElement!.parentElement!.classList.toggle('expanded');
}
private scrollToAnchor(anchor: string) {
@ -177,7 +199,7 @@ export class SettingsApp extends AppWithConfig {
let height = 83;
const header = document.querySelector('.page-header--sticky');
const header = document.querySelector('.hero__area--sticky');
if (header != null) {
height = header.clientHeight;
}

+ 61
- 15
src/webviews/apps/shared/appWithConfigBase.ts ファイルの表示

@ -9,6 +9,9 @@ import {
} from '../../protocol';
import { DOM } from './dom';
import { App } from './appBase';
import { Dates } from '../../../system/date';
const dateFormatter = Dates.getFormatter(new Date('Wed Jul 25 2018 19:18:00 GMT-0400'));
export abstract class AppWithConfig<TState extends AppStateWithConfig> extends App<TState> {
private _changes: { [key: string]: any } = Object.create(null);
@ -23,16 +26,25 @@ export abstract class AppWithConfig extends A
}
protected onBind(me: this) {
DOM.listenAll('input[type=checkbox].setting', 'change', function(this: HTMLInputElement) {
DOM.listenAll('input[type=checkbox][data-setting]', 'change', function(this: HTMLInputElement) {
return me.onInputChecked(this);
});
DOM.listenAll('input[type=text].setting, input:not([type]).setting', 'blur', function(this: HTMLInputElement) {
DOM.listenAll('input[type=text][data-setting], input:not([type])[data-setting]', 'blur', function(
this: HTMLInputElement
) {
return me.onInputBlurred(this);
});
DOM.listenAll('input[type=text].setting, input:not([type]).setting', 'focus', function(this: HTMLInputElement) {
DOM.listenAll('input[type=text][data-setting], input:not([type])[data-setting]', 'focus', function(
this: HTMLInputElement
) {
return me.onInputFocused(this);
});
DOM.listenAll('select.setting', 'change', function(this: HTMLSelectElement) {
DOM.listenAll('input[type=text][data-setting][data-setting-preview]', 'input', function(
this: HTMLInputElement
) {
return me.onInputChanged(this);
});
DOM.listenAll('select[data-setting]', 'change', function(this: HTMLSelectElement) {
return me.onInputSelected(this);
});
DOM.listenAll('.popup', 'mousedown', function(this: HTMLElement, e: Event) {
@ -95,6 +107,14 @@ export abstract class AppWithConfig extends A
this.applyChanges();
}
protected onInputChanged(element: HTMLInputElement) {
if (this._updating) return;
for (const el of document.querySelectorAll<HTMLSpanElement>(`span[data-setting-preview="${element.name}"]`)) {
this.updatePreview(el, element.value);
}
}
protected onInputChecked(element: HTMLInputElement) {
if (this._updating) return;
@ -102,7 +122,7 @@ export abstract class AppWithConfig extends A
`${this.appName}.onInputChecked: name=${element.name}, checked=${element.checked}, value=${element.value}`
);
switch (element.dataset.type) {
switch (element.dataset.settingType) {
case 'object': {
const props = element.name.split('.');
const settingName = props.splice(0, 1)[0];
@ -140,7 +160,7 @@ export abstract class AppWithConfig extends A
if (element.checked) {
this._changes[element.name] = fromCheckboxValue(element.value);
} else {
this._changes[element.name] = false;
this._changes[element.name] = element.dataset.valueOff == null ? false : element.dataset.valueOff;
}
break;
@ -193,7 +213,7 @@ export abstract class AppWithConfig extends A
this.log(`${this.appName}.onTokenClicked: id=${element.id}`);
const setting = element.closest('.settings-group__setting');
const setting = element.closest('.setting');
if (setting == null) return;
const input = setting.querySelector<HTMLInputElement>('input[type=text], input:not([type])');
@ -253,27 +273,34 @@ export abstract class AppWithConfig extends A
this._updating = true;
try {
for (const el of document.querySelectorAll<HTMLInputElement>('input[type=checkbox].setting')) {
const checked =
el.dataset.type === 'array'
? (this.getSettingValue<string[]>(el.name) || []).includes(el.value)
: this.getSettingValue<boolean>(el.name) || false;
el.checked = checked;
for (const el of document.querySelectorAll<HTMLInputElement>('input[type=checkbox][data-setting]')) {
if (el.dataset.settingType === 'array') {
el.checked = (this.getSettingValue<string[]>(el.name) || []).includes(el.value);
} else if (el.dataset.valueOff != null) {
const value = this.getSettingValue<string>(el.name);
el.checked = el.dataset.valueOff !== value;
} else {
el.checked = this.getSettingValue<boolean>(el.name) || false;
}
}
for (const el of document.querySelectorAll<HTMLInputElement>(
'input[type=text].setting, input:not([type]).setting'
'input[type=text][data-setting], input:not([type])[data-setting]'
)) {
el.value = this.getSettingValue<string>(el.name) || '';
}
for (const el of document.querySelectorAll<HTMLSelectElement>('select.setting')) {
for (const el of document.querySelectorAll<HTMLSelectElement>('select[data-setting]')) {
const value = this.getSettingValue<string>(el.name);
const option = el.querySelector<HTMLOptionElement>(`option[value='${value}']`);
if (option != null) {
option.selected = true;
}
}
for (const el of document.querySelectorAll<HTMLSpanElement>('span[data-setting-preview]')) {
this.updatePreview(el);
}
} finally {
this._updating = false;
}
@ -317,6 +344,25 @@ export abstract class AppWithConfig extends A
el.classList.toggle('hidden', !this.evaluateStateExpression(el.dataset.visibility!, state));
}
}
private updatePreview(el: HTMLSpanElement, value?: string) {
switch (el.dataset.settingPreviewType) {
case 'date':
if (value === undefined) {
value = this.getSettingValue<string>(el.dataset.settingPreview!);
}
if (value == null || value.length === 0) {
value = el.dataset.settingPreviewDefault;
}
el.innerText = value == null ? '' : dateFormatter.format(value);
break;
default:
break;
}
}
}
function ensureIfBoolean(value: string | boolean): string | boolean {

+ 1
- 0
src/webviews/apps/shared/theme.ts ファイルの表示

@ -53,6 +53,7 @@ export function initializeAndWatchThemeColors() {
bodyStyle.setProperty('--color-foreground', color);
bodyStyle.setProperty('--color-foreground--85', opacity(color, 85));
bodyStyle.setProperty('--color-foreground--75', opacity(color, 75));
bodyStyle.setProperty('--color-foreground--65', opacity(color, 65));
bodyStyle.setProperty('--color-foreground--50', opacity(color, 50));
color = computedStyle.getPropertyValue('--vscode-focusBorder').trim();

+ 567
- 509
src/webviews/apps/welcome/index.html
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 54
- 0
src/webviews/helpers.ts ファイルの表示

@ -0,0 +1,54 @@
'use strict';
import { ConfigurationTarget } from 'vscode';
import { configuration, ViewLocation } from '../configuration';
export function applyViewLayoutPreset(preset: 'contextual' | 'default' | 'scm') {
let repositories;
let histories;
let compareAndSearch;
switch (preset) {
case 'contextual':
repositories = ViewLocation.SourceControl;
histories = ViewLocation.Explorer;
compareAndSearch = ViewLocation.GitLens;
break;
case 'default':
repositories = ViewLocation.GitLens;
histories = ViewLocation.GitLens;
compareAndSearch = ViewLocation.GitLens;
break;
case 'scm':
repositories = ViewLocation.SourceControl;
histories = ViewLocation.SourceControl;
compareAndSearch = ViewLocation.SourceControl;
break;
default:
return;
}
configuration.update(
configuration.name('views')('repositories')('location').value,
repositories,
ConfigurationTarget.Global
);
configuration.update(
configuration.name('views')('fileHistory')('location').value,
histories,
ConfigurationTarget.Global
);
configuration.update(
configuration.name('views')('lineHistory')('location').value,
histories,
ConfigurationTarget.Global
);
configuration.update(
configuration.name('views')('compare')('location').value,
compareAndSearch,
ConfigurationTarget.Global
);
configuration.update(
configuration.name('views')('search')('location').value,
compareAndSearch,
ConfigurationTarget.Global
);
}

+ 12
- 62
src/webviews/settingsWebview.ts ファイルの表示

@ -1,7 +1,7 @@
'use strict';
import { commands, ConfigurationTarget, Disposable, workspace } from 'vscode';
import { commands, Disposable, workspace } from 'vscode';
import { Commands } from '../commands';
import { Config, configuration, ViewLocation } from '../configuration';
import { Config, configuration } from '../configuration';
import {
IpcMessage,
onIpcCommand,
@ -10,6 +10,7 @@ import {
SettingsState
} from './protocol';
import { WebviewBase } from './webviewBase';
import { applyViewLayoutPreset } from './helpers';
const anchorRegex = /.*?#(.*)/;
@ -35,18 +36,18 @@ export class SettingsWebview extends WebviewBase {
[, anchor] = match;
}
return commands.registerCommand(
c,
() => {
this._pendingJumpToAnchor = anchor;
return this.show();
},
this
);
return commands.registerCommand(c, () => this.onShowCommand(anchor), this);
})
);
}
protected onShowCommand(anchor?: string) {
if (anchor) {
this._pendingJumpToAnchor = anchor;
}
super.onShowCommand();
}
protected onMessageReceived(e: IpcMessage) {
switch (e.method) {
case ReadyCommandType.method:
@ -96,57 +97,6 @@ export class SettingsWebview extends WebviewBase {
}
registerCommands() {
return [commands.registerCommand(`${this.id}.applyViewLayoutPreset`, this.applyViewLayoutPreset, this)];
}
private applyViewLayoutPreset(preset: 'contextual' | 'default' | 'scm') {
let repositories;
let histories;
let compareAndSearch;
switch (preset) {
case 'contextual':
repositories = ViewLocation.SourceControl;
histories = ViewLocation.Explorer;
compareAndSearch = ViewLocation.GitLens;
break;
case 'default':
repositories = ViewLocation.GitLens;
histories = ViewLocation.GitLens;
compareAndSearch = ViewLocation.GitLens;
break;
case 'scm':
repositories = ViewLocation.SourceControl;
histories = ViewLocation.SourceControl;
compareAndSearch = ViewLocation.SourceControl;
break;
default:
return;
}
configuration.update(
configuration.name('views')('repositories')('location').value,
repositories,
ConfigurationTarget.Global
);
configuration.update(
configuration.name('views')('fileHistory')('location').value,
histories,
ConfigurationTarget.Global
);
configuration.update(
configuration.name('views')('lineHistory')('location').value,
histories,
ConfigurationTarget.Global
);
configuration.update(
configuration.name('views')('compare')('location').value,
compareAndSearch,
ConfigurationTarget.Global
);
configuration.update(
configuration.name('views')('search')('location').value,
compareAndSearch,
ConfigurationTarget.Global
);
return [commands.registerCommand(`${this.id}.applyViewLayoutPreset`, applyViewLayoutPreset, this)];
}
}

+ 6
- 2
src/webviews/webviewBase.ts ファイルの表示

@ -41,10 +41,10 @@ export abstract class WebviewBase implements Disposable {
private _disposablePanel: Disposable | undefined;
private _panel: WebviewPanel | undefined;
constructor(showCommand: Commands, column?: ViewColumn) {
constructor(showCommand: Commands, private readonly _column?: ViewColumn) {
this._disposable = Disposable.from(
configuration.onDidChange(this.onConfigurationChanged, this),
commands.registerCommand(showCommand, () => this.show(column), this)
commands.registerCommand(showCommand, this.onShowCommand, this)
);
}
@ -65,6 +65,10 @@ export abstract class WebviewBase implements Disposable {
this._disposablePanel && this._disposablePanel.dispose();
}
protected onShowCommand() {
this.show(this._column);
}
private onConfigurationChanged(e: ConfigurationChangeEvent) {
this.notifyDidChangeConfiguration();
}

+ 6
- 0
src/webviews/welcomeWebview.ts ファイルの表示

@ -1,8 +1,10 @@
'use strict';
import { commands } from 'vscode';
import { Commands } from '../commands';
import { Container } from '../container';
import { WelcomeState } from './protocol';
import { WebviewBase } from './webviewBase';
import { applyViewLayoutPreset } from './helpers';
export class WelcomeWebview extends WebviewBase {
constructor() {
@ -21,6 +23,10 @@ export class WelcomeWebview extends WebviewBase {
return 'Welcome to GitLens';
}
registerCommands() {
return [commands.registerCommand(`${this.id}.applyViewLayoutPreset`, applyViewLayoutPreset, this)];
}
renderEndOfBody() {
const bootstrap: WelcomeState = {
config: Container.config

読み込み中…
キャンセル
保存