Commit 5ada3680 authored by manuk's avatar manuk Committed by Commit Bot

[omnibox chrome:omnibox]: Add a "copy" button to export to clipboard as JSON.

This button compliments the previously named 'Import clipboard' button. With
this CL, we have 4 buttons to export to or import from the clipboard or a file.

Additionally, this CL renames the buttons and modifies accesskeys for clarity:
- Copy (alt+c)
- Paste (alt+v)
- Download (alt+n)
- Upload (alt+m)
The 'Lock cursor position to end of input' checkbox also has its accesskey
changed from alt+c to alt+x.

Bug: 937321
Change-Id: Ieada086afd7e2d35e7b164679e1087245050ef91
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1497117Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Commit-Queue: manuk hovanesian <manukh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#638248}
parent 50fa4991
......@@ -65,10 +65,10 @@
</div>
<div class="row">
<label class="checkbox-container">
<input id="lock-cursor-position" type="checkbox" accesskey="c">
<input id="lock-cursor-position" type="checkbox" accesskey="x">
<span>
Lock <span class="accesskey">c</span>ursor
position to end of input
Lock cursor position to end of input
[<span class="accesskey">x</span>]
</span>
</label>
</div>
......@@ -166,26 +166,37 @@
autocomplete="off"
placeholder="Enter filter (e.g. 'google', 'is:star', 'not:del') [Alt+G]"
title="Checks each cell individually; i.e. filter text should not span multiple columns. Supports fuzzyness; each character of filter text must be present in the cell, either adjacent to the previous matched character, or at the start of a new word. Words are defined as being delimited by either capital letters, groups of digits, or non alpha characters. E.g. 'abc' matches 'abc', 'a big cat', 'a-bigCat', 'a very big cat', and 'an amBer cat'; but does not match 'abigcat' or 'an amber cat'. 'green rainbow' is matched by 'gre rain', but not by 'gre bow'. One exception is the first character, which may be matched mid-word. E.g. 'een rain' can also match 'green rainbow'. Boolean properties can be searched for via the property name prefixed by 'is:' or 'not:'. Boolean property names are: 'Can Be Default', 'Starred', 'Has Tab Match', 'Del', 'Prev', and 'Done'.">
<div>
<span id="download-json" class="row button" accesskey="n"
title="Download responses in JSON format. This is not affected by the visibility of output and will include responses in their entirety as well as query and display inputs.">
<i class="icon copy-icon"></i>
<span>Dow<span class="accesskey">n</span>load as JSON</span>
</span>
</div>
<div>
<label id="import-json" class="row icon-button button drag-container"
accesskey="m" title="Import JSON">
<input id="import-json-input" type="file" accept=".json">
<i class="icon copy-icon"></i>
<span>I<span class="accesskey">m</span>port JSON</span>
</label>
<label id="import-clipboard"
class="row icon-button button drag-container" accesskey="b"
title="Import JSON from clipboard">
<i class="icon copy-icon"></i>
<span>Import clip<span class="accesskey">b</span>oard</span>
</label>
<div class="buttons-container">
<div class="buttons-column">
<span id="export-clipboard" class="row button" accesskey="c"
tabindex="0"
title="Copy responses in JSON format. This is not affected by the visibility of output and will include responses in their entirety as well as query and display inputs.">
<i class="icon copy-icon"></i>
<span><span class="accesskey">C</span>opy</span>
</span>
<span id="export-file" class="row button" accesskey="n" tabindex="0"
title="Download responses in JSON format. This is not affected by the visibility of output and will include responses in their entirety as well as query and display inputs.">
<i class="icon copy-icon"></i>
<span>Dow<span class="accesskey">n</span>load</span>
</span>
</div>
<div class="buttons-column">
<label id="import-clipboard"
class="row icon-button button drag-container" accesskey="v"
tabindex="0"
title="Import JSON from clipboard. Accepts dragged text and files as well.">
<i class="icon copy-icon"></i>
<span>Paste [<span class="accesskey">v</span>]</span>
</label>
<label id="import-file"
class="row icon-button button drag-container" accesskey="m"
tabindex="0"
title="Upload previously downloaded responses. Accepts dragged text and files as well.">
<input id="import-file-input" type="file" accept=".json">
<i class="icon copy-icon"></i>
<span>Upload [<span class="accesskey">m</span>]</span>
</label>
</div>
</div>
<div id="imported-warning" class="row" hidden>
<span class="warning-text"
......
......@@ -96,10 +96,11 @@ document.addEventListener('DOMContentLoaded', () => {
e => omniboxOutput.updateDisplayInputs(e.detail));
omniboxInput.addEventListener(
'filter-input-changed', e => omniboxOutput.updateFilterText(e.detail));
omniboxInput.addEventListener('import', e => exportDelegate.import(e.detail));
omniboxInput.addEventListener(
'import-json', e => exportDelegate.importJson(e.detail));
'export-clipboard', () => exportDelegate.exportClipboard());
omniboxInput.addEventListener(
'download-json', () => exportDelegate.downloadJson());
'export-file', () => exportDelegate.exportFile());
omniboxInput.addEventListener(
'response-select',
e => omniboxOutput.updateSelectedResponseIndex(e.detail));
......@@ -121,7 +122,7 @@ class ExportDelegate {
}
/** @param {OmniboxExport} importData */
importJson(importData) {
import(importData) {
if (!validateImportData_(importData)) {
return;
}
......@@ -132,16 +133,25 @@ class ExportDelegate {
this.omniboxOutput_.setResponsesHistory(importData.responsesHistory);
}
downloadJson() {
/** @type {OmniboxExport} */
const exportObj = {
exportClipboard() {
navigator.clipboard.writeText(JSON.stringify(this.exportData_)).catch(
error => console.error('unable to export to clipboard:', error));
}
exportFile() {
const exportData = this.exportData_;
const fileName = `omnibox_debug_export_${exportData.queryInputs.inputText}_
${new Date().toISOString()}.json`;
ExportDelegate.download_(exportData, fileName);
}
/** @private @return {OmniboxExport} */
get exportData_() {
return {
queryInputs: this.omniboxInput_.queryInputs,
displayInputs: this.omniboxInput_.displayInputs,
responsesHistory: this.omniboxOutput_.responsesHistory,
};
const fileName = `omnibox_debug_export_${exportObj.queryInputs.inputText}_${
new Date().toISOString()}.json`;
ExportDelegate.download_(exportObj, fileName);
}
/**
......
......@@ -51,6 +51,15 @@ select {
margin-inline-start: var(--input-alignment-indentation);
}
.buttons-container {
display: flex;
}
.buttons-column {
display: flex;
flex-direction: column;
}
/* :hover, :focus, & :active */
input[type=text],
......
......@@ -76,18 +76,18 @@ class OmniboxInput extends OmniboxElement {
this.$$('#filter-text')
.addEventListener('input', this.onFilterInputsChanged_.bind(this));
this.$$('#download-json')
.addEventListener('click', this.onDownloadJson_.bind(this));
['#import-json', '#import-clipboard'].forEach(query => {
this.$$('#export-clipboard')
.addEventListener('click', this.onExportClipboard_.bind(this));
this.$$('#export-file')
.addEventListener('click', this.onExportFile_.bind(this));
this.$$('#import-clipboard')
.addEventListener('click', this.onImportClipboard_.bind(this));
this.$$('#import-file-input')
.addEventListener('input', this.onImportFile_.bind(this));
['#import-clipboard', '#import-file'].forEach(query => {
this.setupDragListeners_(this.$$(query));
this.$$(query).addEventListener('drop', this.onImportDropped_.bind(this));
});
this.$$('#import-json-input')
.addEventListener('input', this.onImportFileSelected_.bind(this));
this.$$('#import-clipboard')
.addEventListener('click', this.onImportClipboard_.bind(this));
}
/**
......@@ -232,8 +232,23 @@ class OmniboxInput extends OmniboxElement {
}
/** @private */
onDownloadJson_() {
this.dispatchEvent(new CustomEvent('download-json'));
onExportClipboard_() {
this.dispatchEvent(new CustomEvent('export-clipboard'));
}
/** @private */
onExportFile_() {
this.dispatchEvent(new CustomEvent('export-file'));
}
/** @private */
async onImportClipboard_() {
this.import_(await navigator.clipboard.readText());
}
/** @private @param {!Event} event */
onImportFile_(event) {
this.importFile_(event.target.files[0]);
}
/** @private @param {!Event} event */
......@@ -246,11 +261,6 @@ class OmniboxInput extends OmniboxElement {
}
}
/** @private @param {!Event} event */
onImportFileSelected_(event) {
this.importFile_(event.target.files[0]);
}
/** @private @param {!File} file */
importFile_(file) {
const reader = new FileReader();
......@@ -264,16 +274,12 @@ class OmniboxInput extends OmniboxElement {
reader.readAsText(file);
}
async onImportClipboard_() {
this.import_(await navigator.clipboard.readText());
}
/** @private @param {string} importString */
import_(importString) {
try {
const importData = JSON.parse(importString);
this.$$('#imported-warning').hidden = false;
this.dispatchEvent(new CustomEvent('import-json', {detail: importData}));
this.dispatchEvent(new CustomEvent('import', {detail: importData}));
} catch (error) {
console.error('error during import, invalid json:', error);
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment