diff --git a/app/controllers/workspaces_controller.rb b/app/controllers/workspaces_controller.rb index 4f25fe5..32ee33c 100644 --- a/app/controllers/workspaces_controller.rb +++ b/app/controllers/workspaces_controller.rb @@ -55,11 +55,25 @@ def update respond_to do |format| if @workspace.update(workspace_params) format.html { redirect_to @workspace, notice: update_notice } - format.json { render :show, status: :ok, location: @workspace } - format.js { render json: {}, status: :ok } + format.json do + # render :show, status: :ok, location: @workspace + # Handle an XHR request to save a workspace by returning the flash messages pre-rendered + flash.now[:notice] = update_notice + render json: { flash: render_to_string(partial: '/flash_messages', formats: [:html]), persistedState: @workspace.state } + end + format.js do + # Handle an XHR request to save a workspace by returning the flash messages pre-rendered + flash.now[:notice] = update_notice + render json: { flash: render_to_string(partial: '/flash_messages'), state: @workspace.state } + end else format.html { render :edit, status: :unprocessable_entity } format.json { render json: @workspace.errors, status: :unprocessable_entity } + format.js do + # Handle an XHR request to save a workspace by returning the flash messages pre-rendered + flash.now[:alert] = "Your workspace could not be saved: #{@workspace.errors}" + render partial: '/flash_messages' + end end end end diff --git a/app/javascript/components/Viewer.js b/app/javascript/components/Viewer.js index f85b6bd..58d4a0e 100644 --- a/app/javascript/components/Viewer.js +++ b/app/javascript/components/Viewer.js @@ -18,14 +18,15 @@ class Viewer extends React.Component { constructor() { super(); this.state = { - currentState: {}, + viewerState: {}, }; } /** */ componentDidMount() { const { - annototEndpointUrl, config, enabledPlugins, state, updateStateSelector, projectResourcesUrl, + annototEndpointUrl, config, enabledPlugins, initialState, + viewerStateSelector, projectResourcesUrl, } = this.props; delete config.export; @@ -48,11 +49,11 @@ class Viewer extends React.Component { ], ); - if (state) { + if (initialState) { instance.store.dispatch( importMiradorState( { - ...state, + ...initialState, config: instance.store.getState().config, }, ), @@ -60,14 +61,14 @@ class Viewer extends React.Component { } if (projectResourcesUrl) instance.store.dispatch(addResource(projectResourcesUrl)); - if (updateStateSelector) { + if (viewerStateSelector) { instance.store.subscribe(() => { - const currentState = instance.store.getState(); - const exportableState = getExportableState(currentState); - this.setState({ currentState: exportableState }); - const inputElement = document.querySelector(updateStateSelector); + const viewerState = instance.store.getState(); + const exportableState = getExportableState(viewerState); + this.setState({ viewerState: exportableState }); + const inputElement = document.querySelector(viewerStateSelector); const __mise_cache__ = { // eslint-disable-line camelcase - manifests: mapValues(currentState.manifests, m => this.getManifestCache(currentState, m)), + manifests: mapValues(viewerState.manifests, m => this.getManifestCache(viewerState, m)), }; if (inputElement) { inputElement.value = JSON.stringify( @@ -112,22 +113,30 @@ class Viewer extends React.Component { } checkUnsavedChanges = (event) => { - const { currentState } = this.state; - const { state: initialState, saveInProgressSelector } = this.props; - // Skip checking for unsaved changes because a save is in progress - if (document.querySelector(saveInProgressSelector).value === 'true') return true; + const { viewerState } = this.state; + const { persistedStateSelector } = this.props; + // Get current persisted value of DOM element instead of using what is + // stuffed in props since the former may have been updated by a + // user-initiated save operation + const persistedState = document.querySelector(persistedStateSelector).value; - const expectedDiffPaths = ['__mise_cache__', 'config.annotation', 'config.export', 'workspace.id']; // Diff the two Mirador states - const difference = diff(initialState, currentState); + const difference = diff(persistedState, viewerState); // Remove known false positives from diff object - const filtered = omit(difference, expectedDiffPaths); - // Remove empty top-level objects created by prior call to omit + const filtered = omit(difference, ['__mise_cache__', 'config.annotation', 'config.export', 'workspace.id']); + // Remove empty top-level objects created by removing false positives const changes = omitBy(filtered, isEmpty); + if (!isEmpty(changes)) { + console.dir(changes); event.preventDefault(); event.returnValue = 'If you navigate away from the workspace now, changes you have made will be lost. Are you sure you want to navigate away?'; // eslint-disable-line no-param-reassign return event.returnValue; + } else { + console.log('client state:'); + console.dir(viewerState); + console.log('server state:'); + console.dir(persistedState); } return true; } @@ -143,20 +152,20 @@ Viewer.propTypes = { annototEndpointUrl: PropTypes.string, config: PropTypes.object, // eslint-disable-line react/forbid-prop-types enabledPlugins: PropTypes.array, // eslint-disable-line react/forbid-prop-types + initialState: PropTypes.object, // eslint-disable-line react/forbid-prop-types + persistedStateSelector: PropTypes.string, projectResourcesUrl: PropTypes.string, - saveInProgressSelector: PropTypes.string, - state: PropTypes.object, // eslint-disable-line react/forbid-prop-types - updateStateSelector: PropTypes.string, + viewerStateSelector: PropTypes.string, }; Viewer.defaultProps = { annototEndpointUrl: null, config: {}, enabledPlugins: [], + initialState: null, + persistedStateSelector: null, projectResourcesUrl: null, - saveInProgressSelector: null, - state: null, - updateStateSelector: null, + viewerStateSelector: null, }; export default Viewer; diff --git a/app/javascript/controllers/save_workspace_controller.js b/app/javascript/controllers/save_workspace_controller.js deleted file mode 100644 index b218861..0000000 --- a/app/javascript/controllers/save_workspace_controller.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Controller } from 'stimulus'; - -export default class extends Controller { - static targets = ['field']; - - markSaveInProgress() { - this.fieldTarget.value = true; - } -} diff --git a/app/javascript/controllers/workspace_controller.js b/app/javascript/controllers/workspace_controller.js new file mode 100644 index 0000000..1fe9de0 --- /dev/null +++ b/app/javascript/controllers/workspace_controller.js @@ -0,0 +1,11 @@ +import { Controller } from 'stimulus'; + +export default class extends Controller { + static targets = ['flash', 'persistedState']; + + updateForm(event) { + const { flash, persistedState } = event.detail[0]; + this.persistedStateTarget.value = JSON.stringify(persistedState); + this.flashTarget.innerHTML = flash; + } +} diff --git a/app/views/layouts/_flash_messages.html.erb b/app/views/_flash_messages.html.erb similarity index 100% rename from app/views/layouts/_flash_messages.html.erb rename to app/views/_flash_messages.html.erb diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 214dca9..3aa14f4 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -10,7 +10,7 @@ <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> -
+ <%= render 'layouts/navbar' %>