blob: 53f0be547441c26b9ec2cf4e64423fa10c53d90e [file] [log] [blame]
/**
* @license
* Copyright 2016 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '../../../styles/shared-styles';
import '../gr-button/gr-button';
import {DiffPreferencesInfo, IgnoreWhitespaceType} from '../../../types/diff';
import {subscribe} from '../../lit/subscription-controller';
import {grFormStyles} from '../../../styles/gr-form-styles';
import {sharedStyles} from '../../../styles/shared-styles';
import {css, html, LitElement} from 'lit';
import {customElement, query, state} from 'lit/decorators.js';
import {convertToString} from '../../../utils/string-util';
import {fire} from '../../../utils/event-util';
import {ValueChangedEvent} from '../../../types/events';
import {resolve} from '../../../models/dependency';
import {userModelToken} from '../../../models/user/user-model';
import '@material/web/textfield/outlined-text-field';
import {materialStyles} from '../../../styles/gr-material-styles';
import '@material/web/checkbox/checkbox';
import {MdCheckbox} from '@material/web/checkbox/checkbox';
import '@material/web/select/outlined-select';
import '@material/web/select/select-option';
import {MdOutlinedSelect} from '@material/web/select/outlined-select';
@customElement('gr-diff-preferences')
export class GrDiffPreferences extends LitElement {
@query('#columnsInput') private columnsInput?: HTMLInputElement;
@query('#tabSizeInput') private tabSizeInput?: HTMLInputElement;
@query('#fontSizeInput') private fontSizeInput?: HTMLInputElement;
@query('#lineWrappingInput') private lineWrappingInput?: MdCheckbox;
@query('#showTabsInput') private showTabsInput?: MdCheckbox;
@query('#showTrailingWhitespaceInput')
private showTrailingWhitespaceInput?: MdCheckbox;
@query('#automaticReviewInput')
private automaticReviewInput?: MdCheckbox;
@query('#syntaxHighlightInput')
private syntaxHighlightInput?: MdCheckbox;
// Used in gr-diff-preferences-dialog
@query('#contextSelect') contextSelect?: MdOutlinedSelect;
@state() diffPrefs?: DiffPreferencesInfo;
@state() private originalDiffPrefs?: DiffPreferencesInfo;
readonly getUserModel = resolve(this, userModelToken);
constructor() {
super();
subscribe(
this,
() => this.getUserModel().diffPreferences$,
diffPreferences => {
if (!diffPreferences) return;
this.originalDiffPrefs = diffPreferences;
this.diffPrefs = {...diffPreferences};
}
);
}
static override get styles() {
return [
materialStyles,
sharedStyles,
grFormStyles,
css`
md-outlined-text-field {
max-width: 25em;
}
`,
];
}
override render() {
return html`
<div id="diffPreferences" class="gr-form-styles">
<section>
<label for="contextLineSelect" class="title">Context</label>
<span class="value">
<md-outlined-select
id="contextSelect"
value=${convertToString(this.diffPrefs?.context)}
@change=${(e: Event) => {
const select = e.target as HTMLSelectElement;
this.diffPrefs!.context = Number(select.value);
this.requestUpdate();
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
}}
>
<md-select-option value="3">
<div slot="headline">3 lines</div>
</md-select-option>
<md-select-option value="10">
<div slot="headline">10 lines</div>
</md-select-option>
<md-select-option value="25">
<div slot="headline">25 lines</div>
</md-select-option>
<md-select-option value="50">
<div slot="headline">50 lines</div>
</md-select-option>
<md-select-option value="75">
<div slot="headline">75 lines</div>
</md-select-option>
<md-select-option value="100">
<div slot="headline">100 lines</div>
</md-select-option>
<md-select-option value="-1">
<div slot="headline">Whole file</div>
</md-select-option>
</md-outlined-select>
</span>
</section>
<section>
<label for="lineWrappingInput" class="title">Fit to screen</label>
<span class="value">
<md-checkbox
id="lineWrappingInput"
?checked=${!!this.diffPrefs?.line_wrapping}
@change=${this.handleLineWrappingTap}
></md-checkbox>
</span>
</section>
<section>
<label for="columnsInput" class="title">Diff width</label>
<span class="value">
<md-outlined-text-field
id="columnsInput"
class="showBlueFocusBorder"
type="number"
step="1"
.value=${convertToString(this.diffPrefs?.line_length)}
@input=${this.handleDiffLineLengthInput}
@beforeinput=${(e: InputEvent) => {
// In iron-input we had allowedPattern, but this is not supported
// in md-outlined-text-field. Which uses native input functionality.
// We workaround this.
const data = e.data;
if (data && !/^[0-9]*$/.test(data)) {
e.preventDefault();
}
}}
>
</md-outlined-text-field>
</span>
</section>
<section>
<label for="tabSizeInput" class="title">Tab width</label>
<span class="value">
<md-outlined-text-field
id="tabSizeInput"
class="showBlueFocusBorder"
type="number"
step="1"
.value=${convertToString(this.diffPrefs?.tab_size)}
@input=${this.handleDiffTabSizeInput}
@beforeinput=${(e: InputEvent) => {
// In iron-input we had allowedPattern, but this is not supported
// in md-outlined-text-field. Which uses native input functionality.
// We workaround this.
const data = e.data;
if (data && !/^[0-9]*$/.test(data)) {
e.preventDefault();
}
}}
>
</md-outlined-text-field>
</span>
</section>
<section>
<label for="fontSizeInput" class="title">Font size</label>
<span class="value">
<md-outlined-text-field
id="fontSizeInput"
class="showBlueFocusBorder"
type="number"
step="1"
.value=${convertToString(this.diffPrefs?.font_size)}
@input=${this.handleDiffFontSizeInput}
@beforeinput=${(e: InputEvent) => {
// In iron-input we had allowedPattern, but this is not supported
// in md-outlined-text-field. Which uses native input functionality.
// We workaround this.
const data = e.data;
if (data && !/^[0-9]*$/.test(data)) {
e.preventDefault();
}
}}
>
</md-outlined-text-field>
</span>
</section>
<section>
<label for="showTabsInput" class="title">Show tabs</label>
<span class="value">
<md-checkbox
id="showTabsInput"
?checked=${!!this.diffPrefs?.show_tabs}
@change=${this.handleShowTabsTap}
></md-checkbox>
</span>
</section>
<section>
<label for="showTrailingWhitespaceInput" class="title"
>Show trailing whitespace</label
>
<span class="value">
<md-checkbox
id="showTrailingWhitespaceInput"
?checked=${!!this.diffPrefs?.show_whitespace_errors}
@change=${this.handleShowTrailingWhitespaceTap}
></md-checkbox>
</span>
</section>
<section>
<label for="syntaxHighlightInput" class="title"
>Syntax highlighting</label
>
<span class="value">
<md-checkbox
id="syntaxHighlightInput"
?checked=${!!this.diffPrefs?.syntax_highlighting}
@change=${this.handleSyntaxHighlightTap}
></md-checkbox>
</span>
</section>
<section>
<label for="automaticReviewInput" class="title"
>Automatically mark viewed files reviewed</label
>
<span class="value">
<md-checkbox
id="automaticReviewInput"
?checked=${!this.diffPrefs?.manual_review}
@change=${this.handleAutomaticReviewTap}
></md-checkbox>
</span>
</section>
<section>
<div class="pref">
<label for="ignoreWhiteSpace" class="title"
>Ignore Whitespace</label
>
<span class="value">
<md-outlined-select
id="contextSelect"
value=${convertToString(this.diffPrefs?.ignore_whitespace)}
@change=${(e: Event) => {
const select = e.target as HTMLSelectElement;
this.diffPrefs!.ignore_whitespace =
select.value as IgnoreWhitespaceType;
this.requestUpdate();
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
}}
>
<md-select-option value="IGNORE_NONE">
<div slot="headline">None</div>
</md-select-option>
<md-select-option value="IGNORE_TRAILING">
<div slot="headline">Trailing</div>
</md-select-option>
<md-select-option value="IGNORE_LEADING_AND_TRAILING">
<div slot="headline">Leading &amp; trailing</div>
</md-select-option>
<md-select-option value="IGNORE_ALL">
<div slot="headline">All</div>
</md-select-option>
</md-outlined-select>
</span>
</div>
</section>
</div>
`;
}
private readonly handleLineWrappingTap = () => {
this.diffPrefs!.line_wrapping = this.lineWrappingInput!.checked;
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
};
private readonly handleDiffLineLengthInput = () => {
this.diffPrefs!.line_length = Number(this.columnsInput!.value);
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
};
private readonly handleDiffTabSizeInput = () => {
this.diffPrefs!.tab_size = Number(this.tabSizeInput!.value);
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
};
private readonly handleDiffFontSizeInput = () => {
this.diffPrefs!.font_size = Number(this.fontSizeInput!.value);
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
};
private readonly handleShowTabsTap = () => {
this.diffPrefs!.show_tabs = this.showTabsInput!.checked;
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
};
// private but used in test
readonly handleShowTrailingWhitespaceTap = () => {
this.diffPrefs!.show_whitespace_errors =
this.showTrailingWhitespaceInput!.checked;
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
};
private readonly handleSyntaxHighlightTap = () => {
this.diffPrefs!.syntax_highlighting = this.syntaxHighlightInput!.checked;
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
};
private readonly handleAutomaticReviewTap = () => {
this.diffPrefs!.manual_review = !this.automaticReviewInput!.checked;
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
};
hasUnsavedChanges() {
// We have to wrap boolean values in Boolean() to ensure undefined values
// use false rather than undefined.
return (
Boolean(this.originalDiffPrefs?.syntax_highlighting) !==
Boolean(this.diffPrefs?.syntax_highlighting) ||
this.originalDiffPrefs?.context !== this.diffPrefs?.context ||
Boolean(this.originalDiffPrefs?.line_wrapping) !==
Boolean(this.diffPrefs?.line_wrapping) ||
this.originalDiffPrefs?.line_length !== this.diffPrefs?.line_length ||
this.originalDiffPrefs?.tab_size !== this.diffPrefs?.tab_size ||
this.originalDiffPrefs?.font_size !== this.diffPrefs?.font_size ||
this.originalDiffPrefs?.ignore_whitespace !==
this.diffPrefs?.ignore_whitespace ||
Boolean(this.originalDiffPrefs?.show_tabs) !==
Boolean(this.diffPrefs?.show_tabs) ||
Boolean(this.originalDiffPrefs?.show_whitespace_errors) !==
Boolean(this.diffPrefs?.show_whitespace_errors) ||
Boolean(this.originalDiffPrefs?.manual_review) !==
Boolean(this.diffPrefs?.manual_review)
);
}
async save() {
if (!this.diffPrefs) return;
await this.getUserModel().updateDiffPreference(this.diffPrefs);
fire(this, 'has-unsaved-changes-changed', {
value: this.hasUnsavedChanges(),
});
}
}
declare global {
interface HTMLElementEventMap {
'has-unsaved-changes-changed': ValueChangedEvent<boolean>;
}
interface HTMLElementTagNameMap {
'gr-diff-preferences': GrDiffPreferences;
}
}