| /** | |
| * @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 & 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; | |
| } | |
| } |