Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 35 additions & 15 deletions src/frontend/components/form-group/autosave/lib/modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,73 +11,93 @@ class AutosaveModal extends AutosaveBase {
async initAutosave() {
const $modal = $(this.element);
const $form = $('.form-edit');

$modal.find('.btn-js-restore-values').on('click', async (e) => {
this.storage.setItem('recovering', true);
let curvalCount = $form.find('.linkspace-field[data-column-type="curval"]').length;

e.preventDefault();
e.stopPropagation();

// This need awaiting or it returns before the value is fully set meaning if the recovery is "fast" it will not clear
await this.storage.setItem('recovering', true);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was causing a race condition that was borken

// Count the curvals so we don't return too early
let curvalCount = $form.find('.linkspace-field[data-column-type="curval"]').length;

let errored = false;

let $list = $("<ul></ul>");
const $body = $modal.find(".modal-body");
$body.html("<p>Restoring values...</p>").append($list);
await Promise.all($form.find('.linkspace-field').map(async (_,field) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't need this to be awaited because the finally (after this) encapsulates what would happen at the end either way.

// Convert the fields to promise functions (using the fields) that are run in parallel
// This is only done because various parts of the codebase use the fields in different ways dependent on types (i.e. curval)
Promise.all($form.find('.linkspace-field').map(async (_, field) => {
const $field = $(field);
await this.storage.getItem(this.columnKey($field)).then(json => {
// This was originally a bunch of promises, but as the code is async, we can await things here
try {
const json = await this.storage.getItem(this.columnKey($field))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was being a little overzealous with my usage of Promise objects here - IMO this is "cleaner" now; we still need the Promise.all because the field mappings need to run decryption asynchronously.

let values = json ? JSON.parse(json) : undefined;
return values && Array.isArray(values) ? values : undefined;
}).then(values => {
// If the value can't be parsed, ignore it
if (!values) return;
// If we are in view mode and we need to switch to edit mode, do that
const $editButton = $field.closest('.card--topic').find('.btn-js-edit');
if ($editButton && $editButton.length) $editButton.trigger('click');
if (Array.isArray(values)) {
const name = $field.data("name");
const type = $field.data("column-type");
if (type === "curval") {
// Curvals need to work event-driven - this is because the modal doesn't always load fully,
// meaning the setvalue doesn't work correctly for dropdowns (mainly)
$field.off("validationFailed");
$field.off("validationPassed");
$field.on("validationFailed", (e) => {
// Decrement the curval count
curvalCount--;
const $li = $(`<li class="li-error">Error restoring ${name}, please check these values before submission<ul><li class="warning">${e.message}</li></ul></li>`);
$list.append($li);
if(!curvalCount) this.storage.removeItem('recovering');
// If we've done all fields, turn off the recovery flag
if (!curvalCount) this.storage.removeItem('recovering');
});
$field.on("validationPassed", () => {
// Decrement the curval count
curvalCount--;
const $li = $(`<li class="li-success">Restored ${name}</li>`);
$list.append($li);
if(!curvalCount) this.storage.removeItem('recovering');
// If we've done all fields, turn off the recovery flag
if (!curvalCount) this.storage.removeItem('recovering');
});
}
setFieldValues($field, values);
if(type !== "curval") {
if (type !== "curval") {
const $li = $(`<li class="li-success">Restored ${name}</li>`);
$list.append($li);
}
$field.addClass("field--changed");
}
}).catch(e => {
} catch (e) {
// Catch anything within the mapped promises
const name = $field.data("name");
const $li = $(`<li class="li-error">Failed to restore ${name}<ul><li class="warning">${e.message}</li></ul></li>`);
console.error(e);
$list.append($li);
errored = true;
});
}
})).then(() => {
// If there are errors, show an appropriate message, otherwise show a success message
$body.append(`<p>${errored ? "Values restored with errors." : "All values restored."} Please check that all field values are as expected.</p>`);
}).catch(e => {
// If there are any errors that can't be handled in the mapped promises, show a critical error message
$body.append(`<div class="alert alert-danger"><h4>Critical error restoring values</h4><p>${e}</p></div>`);
}).finally(() => {
// Hide the restore button and show the close button
$modal.find(".modal-footer").find("button:not(.btn-cancel)").hide();
$modal.find(".modal-footer").find(".btn-cancel").text("Close");
if(!curvalCount) this.storage.removeItem('recovering');
// If we've done all fields, turn off the recovery flag
if (!curvalCount) this.storage.removeItem('recovering');
});
});

// Do we need to run an autorecover?
const item = await this.storage.getItem(this.table_key);

if (item){
if (item) {
$modal.modal('show');
$modal.find('.btn-js-delete-values').attr('disabled', 'disabled').hide();
}
Expand Down
Loading