Skip to content

Commit

Permalink
GH-1746: Initial changes for the input box with validation support.
Browse files Browse the repository at this point in the history
Signed-off-by: Akos Kitta <kittaakos@gmail.com>
  • Loading branch information
kittaakos committed Apr 25, 2018
1 parent a62d8ea commit 8672e47
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 87 deletions.
4 changes: 3 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dependencies": {
"@phosphor/widgets": "^1.5.0",
"@theia/application-package": "^0.3.8",
"@types/autosize": "3.0.6",
"@types/body-parser": "^1.16.4",
"@types/bunyan": "^1.8.0",
"@types/express": "^4.0.36",
Expand All @@ -16,6 +17,7 @@
"@types/ws": "^3.0.2",
"@types/yargs": "^8.0.2",
"ajv": "^5.2.2",
"autosize": "^4.0.1",
"body-parser": "^1.17.2",
"bunyan": "^1.8.10",
"electron": "1.8.2-beta.5",
Expand Down Expand Up @@ -78,4 +80,4 @@
"nyc": {
"extends": "../../configs/nyc.json"
}
}
}
20 changes: 20 additions & 0 deletions packages/core/src/browser/colors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (C) 2018 TypeFox and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

/**
* A string that could be:
*
* - one of the browser colors, (E.g.: `blue`, `red`, `magenta`),
* - the case insensitive hexadecimal color code, (for instance, `#ee82ee`, `#20B2AA`, `#f09` ), or
* - either the `rgb()` or the `rgba()` functions.
*
* For more details, see: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value.
*
* Note, it is highly recommended to use one of the predefined colors of Theia, so the desired color will
* look nice with both the `light` and the `dark` theme too. For instance: `var(--theia-success-color0)`.
*/
export type Color = string;
15 changes: 1 addition & 14 deletions packages/core/src/browser/tree/tree-decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { injectable } from 'inversify';
import { Tree } from './tree';
import { Color } from '../colors';
import { Event, Emitter } from '../../common/event';

/**
Expand Down Expand Up @@ -167,20 +168,6 @@ export namespace TreeDecoration {
*/
export type FontStyle = 'normal' | 'bold' | 'italic' | 'oblique' | 'underline' | 'line-through';

/**
* A string that could be:
*
* - one of the browser colors, (E.g.: `blue`, `red`, `magenta`),
* - the case insensitive hexadecimal color code, (for instance, `#ee82ee`, `#20B2AA`, `#f09` ), or
* - either the `rgb()` or the `rgba()` functions.
*
* For more details, see: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value.
*
* Note, it is highly recommended to use one of the predefined colors of Theia, so the desired color will
* look nice with both the `light` and the `dark` theme too.
*/
export type Color = string;

/**
* Encapsulates styling information of the font.
*/
Expand Down
69 changes: 69 additions & 0 deletions packages/git/src/browser/git-commit-message-validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright (C) 2018 TypeFox and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import { injectable } from 'inversify';
import { MaybePromise } from '@theia/core/lib/common/types';

@injectable()
export class GitCommitMessageValidator {

static readonly MAX_CHARS_PER_LINE = 72;

/**
* Validates the input and returns with either a validation result with the status and message, or `undefined` if everything went fine.
*/
validate(input: string | undefined): MaybePromise<GitCommitMessageValidator.Result | undefined> {
if (input) {
for (const line of input.split(/\r?\n/)) {
const result = this.isLineValid(line);
if (!!result) {
return result;
}
}
}
return undefined;
}

protected isLineValid(line: string): GitCommitMessageValidator.Result | undefined {
const diff = line.length - this.maxCharsPerLine();
if (diff > 0) {
return {
status: 'warning',
message: `${diff} characters over ${this.maxCharsPerLine()} in current line`
};
}
return undefined;
}

protected maxCharsPerLine(): number {
return GitCommitMessageValidator.MAX_CHARS_PER_LINE;
}

}

export namespace GitCommitMessageValidator {

/**
* Type for the validation result with a status and a corresponding message.
*/
export type Result = Readonly<{ message: string, status: 'info' | 'success' | 'warning' | 'error' }>;

export namespace Result {

/**
* `true` if the `message` and the `status` properties are the same on both `left` and `right`. Or both arguments are `undefined`. Otherwise, `false`.
*/
export function equal(left: Result | undefined, right: Result | undefined): boolean {
if (left && right) {
return left.message === right.message && left.status === right.status;
}
return left === right;
}

}

}
6 changes: 5 additions & 1 deletion packages/git/src/browser/git-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ContainerModule } from 'inversify';
import { ResourceResolver } from "@theia/core/lib/common";
import { WebSocketConnectionProvider, WidgetFactory, bindViewContribution, LabelProviderContribution } from '@theia/core/lib/browser';
import { NavigatorTreeDecorator } from '@theia/navigator/lib/browser';
// import { InputBox, InputBoxFactory, InputBoxProps, InputBoxValidator, InputBoxStyles } from '@theia/core/lib/browser/input-box';
import { Git, GitPath, GitWatcher, GitWatcherPath, GitWatcherServer, GitWatcherServerProxy, ReconnectingGitWatcherServer } from '../common';
import { GitViewContribution, GIT_WIDGET_FACTORY_ID } from './git-view-contribution';
import { bindGitDiffModule } from './diff/git-diff-frontend-module';
Expand All @@ -23,10 +24,11 @@ import { bindGitPreferences } from './git-preferences';
import { bindDirtyDiff } from './dirty-diff/dirty-diff-module';
import { bindBlame } from './blame/blame-module';
import { GitRepositoryTracker } from './git-repository-tracker';
import { GitCommitMessageValidator } from './git-commit-message-validator';

import '../../src/browser/style/index.css';

export default new ContainerModule(bind => {
export default new ContainerModule((bind, unbind, isBound, rebind) => {
bindGitPreferences(bind);
bindGitDiffModule(bind);
bindGitHistoryModule(bind);
Expand All @@ -53,4 +55,6 @@ export default new ContainerModule(bind => {

bind(LabelProviderContribution).to(GitUriLabelProviderContribution).inSingletonScope();
bind(NavigatorTreeDecorator).to(GitDecorator).inSingletonScope();

bind(GitCommitMessageValidator).toSelf().inSingletonScope();
});
116 changes: 62 additions & 54 deletions packages/git/src/browser/git-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,16 @@

import { injectable, inject, postConstruct } from 'inversify';
import { h } from '@phosphor/virtualdom';
import { Message } from '@phosphor/messaging';
import URI from '@theia/core/lib/common/uri';
import { MessageService, ResourceProvider, CommandService, DisposableCollection, MenuPath } from '@theia/core';
import { MessageService, ResourceProvider, CommandService, MenuPath } from '@theia/core';
import { VirtualRenderer, ContextMenuRenderer, VirtualWidget, LabelProvider, DiffUris } from '@theia/core/lib/browser';
import { EditorManager, EditorWidget, EditorOpenerOptions } from '@theia/editor/lib/browser';
import { WorkspaceService, WorkspaceCommands } from '@theia/workspace/lib/browser';
import { Git, GitFileChange, GitFileStatus, Repository, WorkingDirectoryStatus } from '../common';
import { GitWatcher, GitStatusChangeEvent } from '../common/git-watcher';
import { GIT_RESOURCE_SCHEME } from './git-resource';
import { GitRepositoryProvider } from './git-repository-provider';
import { GitCommitMessageValidator } from './git-commit-message-validator';

export interface GitFileChangeNode extends GitFileChange {
readonly icon: string;
Expand All @@ -41,10 +41,9 @@ export class GitWidget extends VirtualWidget {
protected mergeChanges: GitFileChangeNode[] = [];
protected message: string = '';
protected messageInputHighlighted: boolean = false;
protected additionalMessage: string = '';
protected status: WorkingDirectoryStatus | undefined;
protected toDispose = new DisposableCollection();
protected scrollContainer: string;
protected commitMessageValidationResult: GitCommitMessageValidator.Result | undefined;

@inject(EditorManager)
protected readonly editorManager: EditorManager;
Expand All @@ -58,14 +57,14 @@ export class GitWidget extends VirtualWidget {
@inject(CommandService) protected readonly commandService: CommandService,
@inject(GitRepositoryProvider) protected readonly repositoryProvider: GitRepositoryProvider,
@inject(LabelProvider) protected readonly labelProvider: LabelProvider,
@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService) {
@inject(WorkspaceService) protected readonly workspaceService: WorkspaceService,
@inject(GitCommitMessageValidator) protected readonly commitMessageValidator: GitCommitMessageValidator) {

super();
this.id = 'theia-gitContainer';
this.title.label = 'Git';
this.scrollContainer = 'changesOuterContainer';

this.addClass('theia-git');

}

@postConstruct()
Expand All @@ -77,16 +76,6 @@ export class GitWidget extends VirtualWidget {
this.update();
}

protected onActivateRequest(msg: Message): void {
super.onActivateRequest(msg);
const messageInput = document.getElementById('git-messageInput');
if (messageInput) {
messageInput.focus();
} else {
this.node.focus();
}
}

async initialize(repository: Repository | undefined): Promise<void> {
if (repository) {
this.toDispose.dispose();
Expand All @@ -100,7 +89,7 @@ export class GitWidget extends VirtualWidget {
}
}

async commit(repository?: Repository, options?: 'amend' | 'sign-off', message: string = `${this.message}\n\n${this.additionalMessage}`) {
async commit(repository?: Repository, options?: 'amend' | 'sign-off', message: string = `${this.message}`) {
if (repository) {
if (message.trim().length > 0) {
try {
Expand All @@ -116,7 +105,7 @@ export class GitWidget extends VirtualWidget {
}
} else {
// need to access the element, because Phosphor.js is not updating `value`but only `setAttribute('value', ....)` which only sets the default value.
const messageInput = document.getElementById('git-messageInput') as HTMLInputElement;
const messageInput = document.getElementById('theia-git-commit-message') as HTMLInputElement;
if (messageInput) {
this.messageInputHighlighted = true;
this.update();
Expand Down Expand Up @@ -169,13 +158,58 @@ export class GitWidget extends VirtualWidget {
this.update();
}

protected renderCommitMessage(): h.Child {
const oninput = this.onCommitMessageChange.bind(this);
const placeholder = 'Commit message';
const status = this.commitMessageValidationResult ? this.commitMessageValidationResult.status : 'idle';
const classes = [GitWidget.Styles.COMMIT_MESSAGE, `theia-git-commit-message-${status}`];
const className = classes.join(' ');
const autofocus = 'true';
return h.div({ className: GitWidget.Styles.COMMIT_MESSAGE_CONTAINER }, h.textarea({
className,
autofocus,
oninput,
placeholder
}));
}

protected onCommitMessageChange(e: Event): void {
const { target } = e;
if (target instanceof HTMLTextAreaElement) {
// tslint:disable-next-line:no-null-keyword
const fontSize = Number.parseInt(window.getComputedStyle(target, undefined).getPropertyValue('font-size').split('px')[0] || '0', 10);
const { value } = target;
if (Number.isInteger(fontSize) && fontSize > 0) {
const requiredHeight = fontSize * value.split(/\r?\n/).length;
if (requiredHeight < target.scrollHeight) {
target.style.height = `${requiredHeight}px`;
}
}
if (target.clientHeight < target.scrollHeight) {
target.style.height = `${target.scrollHeight}px`;
if (target.clientHeight < target.scrollHeight) {
target.style.height = `${(target.scrollHeight * 2 - target.clientHeight)}px`;
}
}
this.validateCommitMessage(value).then(result => {
if (!GitCommitMessageValidator.Result.equal(this.commitMessageValidationResult, result)) {
this.commitMessageValidationResult = result;
this.update();
}
});
}
}

protected async validateCommitMessage(input: string | undefined): Promise<GitCommitMessageValidator.Result | undefined> {
return this.commitMessageValidator.validate(input);
}

protected render(): h.Child {
const repository = this.repositoryProvider.selectedRepository;

const messageInput = this.renderMessageInput();
const messageTextarea = this.renderMessageTextarea();
const messageInput = this.renderCommitMessage();
const commandBar = this.renderCommandBar(repository);
const headerContainer = h.div({ className: 'headerContainer' }, messageInput, messageTextarea, commandBar);
const headerContainer = h.div({ className: 'headerContainer' }, messageInput, commandBar);

const mergeChanges = this.renderMergeChanges(repository) || '';
const stagedChanges = this.renderStagedChanges(repository) || '';
Expand Down Expand Up @@ -217,35 +251,6 @@ export class GitWidget extends VirtualWidget {
return h.div({ id: 'commandBar', className: 'flexcontainer' }, commandsContainer, placeholder, commitContainer);
}

protected renderMessageInput(): h.Child {
const input = h.input({
id: 'git-messageInput',
oninput: event => {
const inputElement = (event.target as HTMLInputElement);
if (inputElement.value !== '') {
this.messageInputHighlighted = false;
}
this.message = (event.target as HTMLInputElement).value;
},
className: this.messageInputHighlighted ? 'warn' : '',
placeholder: 'Commit message',
value: this.message
});
return h.div({ id: 'messageInputContainer', className: 'flexcontainer row' }, input);
}

protected renderMessageTextarea(): h.Child {
const textarea = h.textarea({
id: 'git-extendedMessageInput',
placeholder: 'Extended commit text',
oninput: event => {
this.additionalMessage = (event.target as HTMLTextAreaElement).value;
},
value: this.additionalMessage
});
return h.div({ id: 'messageTextareaContainer', className: 'flexcontainer row' }, textarea);
}

protected renderGitItemButtons(repository: Repository, change: GitFileChange): h.Child {
const buttons: h.Child[] = [];
if (change.staged) {
Expand Down Expand Up @@ -430,9 +435,7 @@ export class GitWidget extends VirtualWidget {

protected resetCommitMessages(): void {
this.message = '';
this.additionalMessage = '';

const messageInput = document.getElementById('git-messageInput') as HTMLInputElement;
const messageInput = document.getElementById('theia-git-commit-message') as HTMLInputElement;
const extendedMessageInput = document.getElementById('git-extendedMessageInput') as HTMLInputElement;
messageInput.value = '';
extendedMessageInput.value = '';
Expand All @@ -447,4 +450,9 @@ export namespace GitWidget {
export const COMMIT_GROUP: MenuPath = [...PATH, '2_commit'];
}

export namespace Styles {
export const COMMIT_MESSAGE_CONTAINER = 'theia-git-commit-message-container';
export const COMMIT_MESSAGE = 'theia-git-commit-message';
}

}
4 changes: 2 additions & 2 deletions packages/git/src/browser/history/git-history-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class GitHistoryWidget extends GitNavigableListWidget<GitHistoryListNode>
@inject(ApplicationShell) protected readonly shell: ApplicationShell,
@inject(FileSystem) protected readonly fileSystem: FileSystem,
@inject(Git) protected readonly git: Git,
@inject(GitAvatarService) protected readonly avartarService: GitAvatarService,
@inject(GitAvatarService) protected readonly avatarService: GitAvatarService,
@inject(WidgetManager) protected readonly widgetManager: WidgetManager,
@inject(GitDiffContribution) protected readonly diffContribution: GitDiffContribution) {
super();
Expand Down Expand Up @@ -107,7 +107,7 @@ export class GitHistoryWidget extends GitNavigableListWidget<GitHistoryListNode>
const commits: GitCommitNode[] = [];
for (const commit of changes) {
const fileChangeNodes: GitFileChangeNode[] = [];
const avatarUrl = await this.avartarService.getAvatar(commit.author.email);
const avatarUrl = await this.avatarService.getAvatar(commit.author.email);
commits.push({
authorName: commit.author.name,
authorDate: new Date(commit.author.timestamp),
Expand Down
Loading

0 comments on commit 8672e47

Please sign in to comment.