Cache edits made with PolyGerrit
Currently, it is quite easy to lose edits in process, as they are not stored anywhere and there is no prompt before navigation away from editing. With this change, the editor view will cache edits to gr-storage at a debounced rate, similar to gr-editable-content and gr-diff-comment. These edits are keyed with the change number, patch number, and path of the current file. Bug: Issue 8481 Change-Id: I5de55e858225b6518ff5ff37f5b87cebdb84325a
This commit is contained in:
@@ -27,6 +27,7 @@ limitations under the License.
|
||||
<link rel="import" href="../../shared/gr-editable-label/gr-editable-label.html">
|
||||
<link rel="import" href="../../shared/gr-fixed-panel/gr-fixed-panel.html">
|
||||
<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
|
||||
<link rel="import" href="../../shared/gr-storage/gr-storage.html">
|
||||
<link rel="import" href="../gr-default-editor/gr-default-editor.html">
|
||||
<link rel="import" href="../../../styles/shared-styles.html">
|
||||
|
||||
@@ -120,6 +121,7 @@ limitations under the License.
|
||||
</gr-endpoint-decorator>
|
||||
</div>
|
||||
<gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
|
||||
<gr-storage id="storage"></gr-storage>
|
||||
</template>
|
||||
<script src="gr-editor-view.js"></script>
|
||||
</dom-module>
|
||||
|
||||
@@ -17,10 +17,13 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const RESTORED_MESSAGE = 'Content restored from a previous edit.';
|
||||
const SAVING_MESSAGE = 'Saving changes...';
|
||||
const SAVED_MESSAGE = 'All changes saved';
|
||||
const SAVE_FAILED_MSG = 'Failed to save changes';
|
||||
|
||||
const STORAGE_DEBOUNCE_INTERVAL_MS = 100;
|
||||
|
||||
Polymer({
|
||||
is: 'gr-editor-view',
|
||||
|
||||
@@ -87,6 +90,10 @@
|
||||
this._getEditPrefs().then(prefs => { this._prefs = prefs; });
|
||||
},
|
||||
|
||||
get storageKey() {
|
||||
return `c${this._changeNum}_ps${this._patchNum}_${this._path}`;
|
||||
},
|
||||
|
||||
_getLoggedIn() {
|
||||
return this.$.restAPI.getLoggedIn();
|
||||
},
|
||||
@@ -143,9 +150,19 @@
|
||||
},
|
||||
|
||||
_getFileData(changeNum, path, patchNum) {
|
||||
const storedContent =
|
||||
this.$.storage.getEditableContentItem(this.storageKey);
|
||||
|
||||
return this.$.restAPI.getFileContent(changeNum, path, patchNum)
|
||||
.then(res => {
|
||||
this._newContent = res.content || '';
|
||||
if (storedContent && storedContent.message) {
|
||||
this.dispatchEvent(new CustomEvent('show-alert',
|
||||
{detail: {message: RESTORED_MESSAGE}, bubbles: true}));
|
||||
|
||||
this._newContent = storedContent.message;
|
||||
} else {
|
||||
this._newContent = res.content || '';
|
||||
}
|
||||
this._content = res.content || '';
|
||||
|
||||
// A non-ok response may result if the file does not yet exist.
|
||||
@@ -162,6 +179,7 @@
|
||||
_saveEdit() {
|
||||
this._saving = true;
|
||||
this._showAlert(SAVING_MESSAGE);
|
||||
this.$.storage.eraseEditableContentItem(this.storageKey);
|
||||
return this.$.restAPI.saveChangeEdit(this._changeNum, this._path,
|
||||
this._newContent).then(res => {
|
||||
this._saving = false;
|
||||
@@ -191,7 +209,15 @@
|
||||
},
|
||||
|
||||
_handleContentChange(e) {
|
||||
if (e.detail.value) { this.set('_newContent', e.detail.value); }
|
||||
this.debounce('store', () => {
|
||||
const content = e.detail.value;
|
||||
if (content) {
|
||||
this.set('_newContent', e.detail.value);
|
||||
this.$.storage.setEditableContentItem(this.storageKey, content);
|
||||
} else {
|
||||
this.$.storage.eraseEditableContentItem(this.storageKey);
|
||||
}
|
||||
}, STORAGE_DEBOUNCE_INTERVAL_MS);
|
||||
},
|
||||
|
||||
_handleSaveShortcut(e) {
|
||||
|
||||
@@ -118,14 +118,18 @@ suite('gr-editor-view tests', () => {
|
||||
});
|
||||
|
||||
test('reacts to content-change event', () => {
|
||||
const storeStub = sandbox.spy(element.$.storage, 'setEditableContentItem');
|
||||
element._newContent = 'test';
|
||||
element.$.editorEndpoint.dispatchEvent(new CustomEvent('content-change', {
|
||||
bubbles: true,
|
||||
detail: {value: 'new content value'},
|
||||
}));
|
||||
element.flushDebouncer('store');
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.equal(element._newContent, 'new content value');
|
||||
assert.isTrue(storeStub.called);
|
||||
assert.equal(storeStub.lastCall.args[1], 'new content value');
|
||||
});
|
||||
|
||||
suite('edit file content', () => {
|
||||
@@ -147,6 +151,8 @@ suite('gr-editor-view tests', () => {
|
||||
|
||||
test('file modification and save, !ok response', () => {
|
||||
const saveSpy = sandbox.spy(element, '_saveEdit');
|
||||
const eraseStub = sandbox.stub(element.$.storage,
|
||||
'eraseEditableContentItem');
|
||||
const alertStub = sandbox.stub(element, '_showAlert');
|
||||
saveFileStub.returns(Promise.resolve({ok: false}));
|
||||
element._newContent = newText;
|
||||
@@ -163,6 +169,7 @@ suite('gr-editor-view tests', () => {
|
||||
|
||||
return saveSpy.lastCall.returnValue.then(() => {
|
||||
assert.isTrue(saveFileStub.called);
|
||||
assert.isTrue(eraseStub.called);
|
||||
assert.isFalse(element._saving);
|
||||
assert.equal(alertStub.lastCall.args[0], 'Failed to save changes');
|
||||
assert.deepEqual(saveFileStub.lastCall.args,
|
||||
@@ -219,6 +226,7 @@ suite('gr-editor-view tests', () => {
|
||||
element._newContent = 'initial';
|
||||
element._content = 'initial';
|
||||
element._type = 'initial';
|
||||
sandbox.stub(element.$.storage, 'getEditableContentItem').returns(null);
|
||||
});
|
||||
|
||||
test('res.ok', () => {
|
||||
@@ -343,5 +351,37 @@ suite('gr-editor-view tests', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('gr-storage caching', () => {
|
||||
test('local edit exists', () => {
|
||||
sandbox.stub(element.$.storage, 'getEditableContentItem')
|
||||
.returns({message: 'pending edit'});
|
||||
sandbox.stub(element.$.restAPI, 'getFileContent')
|
||||
.returns(Promise.resolve({
|
||||
ok: true,
|
||||
type: 'text/javascript',
|
||||
content: 'old content',
|
||||
}));
|
||||
|
||||
const alertStub = sandbox.stub();
|
||||
element.addEventListener('show-alert', alertStub);
|
||||
|
||||
return element._getFileData(1, 'test', 1).then(() => {
|
||||
flushAsynchronousOperations();
|
||||
|
||||
assert.isTrue(alertStub.called);
|
||||
assert.equal(element._newContent, 'pending edit');
|
||||
assert.equal(element._content, 'old content');
|
||||
assert.equal(element._type, 'text/javascript');
|
||||
});
|
||||
});
|
||||
|
||||
test('storage key computation', () => {
|
||||
element._changeNum = 1;
|
||||
element._patchNum = 1;
|
||||
element._path = 'test';
|
||||
assert.equal(element.storageKey, 'c1_ps1_test');
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user