| /* | |
| * Copyright (C) 2008 Apple Inc. All Rights Reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR | |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| /** | |
| * @unrestricted | |
| */ | |
| Resources.DatabaseQueryView = class extends UI.VBox { | |
| constructor(database) { | |
| super(); | |
| this.database = database; | |
| this.element.classList.add('storage-view', 'query', 'monospace'); | |
| this.element.addEventListener('selectstart', this._selectStart.bind(this), false); | |
| this._promptContainer = this.element.createChild('div', 'database-query-prompt-container'); | |
| this._promptContainer.appendChild(UI.Icon.create('smallicon-text-prompt', 'prompt-icon')); | |
| this._promptElement = this._promptContainer.createChild('div'); | |
| this._promptElement.className = 'database-query-prompt'; | |
| this._promptElement.addEventListener('keydown', this._promptKeyDown.bind(this)); | |
| this._prompt = new UI.TextPrompt(); | |
| this._prompt.initialize(this.completions.bind(this), ' '); | |
| this._proxyElement = this._prompt.attach(this._promptElement); | |
| this.element.addEventListener('click', this._messagesClicked.bind(this), true); | |
| this.element.tabIndex = 0; | |
| this.element.addEventListener('focus', this._prompt.focus.bind(this._prompt)); | |
| } | |
| _messagesClicked() { | |
| if (!this._prompt.isCaretInsidePrompt() && !this.element.hasSelection()) { | |
| this._prompt.moveCaretToEndOfPrompt(); | |
| } | |
| } | |
| /** | |
| * @param {string} expression | |
| * @param {string} prefix | |
| * @param {boolean=} force | |
| * @return {!Promise<!UI.SuggestBox.Suggestions>} | |
| */ | |
| async completions(expression, prefix, force) { | |
| if (!prefix) { | |
| return []; | |
| } | |
| prefix = prefix.toLowerCase(); | |
| const tableNames = await this.database.tableNames(); | |
| return tableNames.map(name => name + ' ') | |
| .concat(Resources.DatabaseQueryView._SQL_BUILT_INS) | |
| .filter(proposal => proposal.toLowerCase().startsWith(prefix)) | |
| .map(completion => ({text: completion})); | |
| } | |
| _selectStart(event) { | |
| if (this._selectionTimeout) { | |
| clearTimeout(this._selectionTimeout); | |
| } | |
| this._prompt.clearAutocomplete(); | |
| /** | |
| * @this {Resources.DatabaseQueryView} | |
| */ | |
| function moveBackIfOutside() { | |
| delete this._selectionTimeout; | |
| if (!this._prompt.isCaretInsidePrompt() && !this.element.hasSelection()) { | |
| this._prompt.moveCaretToEndOfPrompt(); | |
| } | |
| this._prompt.autoCompleteSoon(); | |
| } | |
| this._selectionTimeout = setTimeout(moveBackIfOutside.bind(this), 100); | |
| } | |
| _promptKeyDown(event) { | |
| if (isEnterKey(event)) { | |
| this._enterKeyPressed(event); | |
| return; | |
| } | |
| } | |
| async _enterKeyPressed(event) { | |
| event.consume(true); | |
| const query = this._prompt.textWithCurrentSuggestion(); | |
| this._prompt.clearAutocomplete(); | |
| if (!query.length) { | |
| return; | |
| } | |
| this._prompt.setEnabled(false); | |
| try { | |
| const result = await new Promise((resolve, reject) => { | |
| this.database.executeSql( | |
| query, (columnNames, values) => resolve({columnNames, values}), errorText => reject(errorText)); | |
| }); | |
| this._queryFinished(query, result.columnNames, result.values); | |
| } catch (e) { | |
| this._appendErrorQueryResult(query, e); | |
| } | |
| this._prompt.setEnabled(true); | |
| this._prompt.setText(''); | |
| this._prompt.focus(); | |
| } | |
| _queryFinished(query, columnNames, values) { | |
| const dataGrid = DataGrid.SortableDataGrid.create(columnNames, values); | |
| const trimmedQuery = query.trim(); | |
| let view = null; | |
| if (dataGrid) { | |
| dataGrid.setStriped(true); | |
| dataGrid.renderInline(); | |
| dataGrid.autoSizeColumns(5); | |
| view = dataGrid.asWidget(); | |
| } | |
| this._appendViewQueryResult(trimmedQuery, view); | |
| if (trimmedQuery.match(/^create /i) || trimmedQuery.match(/^drop table /i)) { | |
| this.dispatchEventToListeners(Resources.DatabaseQueryView.Events.SchemaUpdated, this.database); | |
| } | |
| } | |
| /** | |
| * @param {string} query | |
| * @param {?UI.Widget} view | |
| */ | |
| _appendViewQueryResult(query, view) { | |
| const resultElement = this._appendQueryResult(query); | |
| if (view) { | |
| view.show(resultElement); | |
| } else { | |
| resultElement.remove(); | |
| } | |
| this._promptElement.scrollIntoView(false); | |
| } | |
| /** | |
| * @param {string} query | |
| * @param {string} errorText | |
| */ | |
| _appendErrorQueryResult(query, errorText) { | |
| const resultElement = this._appendQueryResult(query); | |
| resultElement.classList.add('error'); | |
| resultElement.appendChild(UI.Icon.create('smallicon-error', 'prompt-icon')); | |
| resultElement.createTextChild(errorText); | |
| this._promptElement.scrollIntoView(false); | |
| } | |
| /** | |
| * @param {string} query | |
| */ | |
| _appendQueryResult(query) { | |
| const element = createElement('div'); | |
| element.className = 'database-user-query'; | |
| element.appendChild(UI.Icon.create('smallicon-user-command', 'prompt-icon')); | |
| this.element.insertBefore(element, this._promptContainer); | |
| const commandTextElement = createElement('span'); | |
| commandTextElement.className = 'database-query-text'; | |
| commandTextElement.textContent = query; | |
| element.appendChild(commandTextElement); | |
| const resultElement = createElement('div'); | |
| resultElement.className = 'database-query-result'; | |
| element.appendChild(resultElement); | |
| return resultElement; | |
| } | |
| }; | |
| /** @enum {symbol} */ | |
| Resources.DatabaseQueryView.Events = { | |
| SchemaUpdated: Symbol('SchemaUpdated') | |
| }; | |
| Resources.DatabaseQueryView._SQL_BUILT_INS = [ | |
| 'SELECT ', 'FROM ', 'WHERE ', 'LIMIT ', 'DELETE FROM ', 'CREATE ', 'DROP ', 'TABLE ', 'INDEX ', 'UPDATE ', | |
| 'INSERT INTO ', 'VALUES (' | |
| ]; |