diff --git a/client/app/scripts/actions/app-actions.js b/client/app/scripts/actions/app-actions.js
index ebbfdaebd..4a468988f 100644
--- a/client/app/scripts/actions/app-actions.js
+++ b/client/app/scripts/actions/app-actions.js
@@ -3,7 +3,7 @@ import { fromJS } from 'immutable';
import ActionTypes from '../constants/action-types';
import { saveGraph } from '../utils/file-utils';
-import { updateRoute } from '../utils/router-utils';
+import { clearStoredViewState, updateRoute } from '../utils/router-utils';
import {
doControlRequest,
getAllNodes,
@@ -15,7 +15,6 @@ import {
teardownWebsockets,
getNodes,
} from '../utils/web-api-utils';
-import { storageSet } from '../utils/storage-utils';
import { loadTheme } from '../utils/contrast-utils';
import { isPausedSelector } from '../selectors/time-travel';
import {
@@ -794,7 +793,7 @@ export function route(urlState) {
export function resetLocalViewState() {
return (dispatch) => {
dispatch({type: ActionTypes.RESET_LOCAL_VIEW_STATE});
- storageSet('scopeViewState', '');
+ clearStoredViewState();
// eslint-disable-next-line prefer-destructuring
window.location.href = window.location.href.split('#')[0];
};
@@ -832,3 +831,10 @@ export function setMonitorState(monitor) {
monitor
};
}
+
+export function setStoreViewState(storeViewState) {
+ return {
+ type: ActionTypes.SET_STORE_VIEW_STATE,
+ storeViewState
+ };
+}
diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js
index 233791066..4588b2419 100644
--- a/client/app/scripts/components/app.js
+++ b/client/app/scripts/components/app.js
@@ -31,6 +31,7 @@ import {
setMonitorState,
setTableView,
setResourceView,
+ setStoreViewState,
shutdown,
setViewportDimensions,
getTopologiesWithInitialPoll,
@@ -64,6 +65,7 @@ class App extends React.Component {
super(props, context);
this.props.dispatch(setMonitorState(this.props.monitor));
+ this.props.dispatch(setStoreViewState(!this.props.disableStoreViewState));
this.setViewportDimensions = this.setViewportDimensions.bind(this);
this.handleResize = debounce(this.setViewportDimensions, VIEWPORT_RESIZE_DEBOUNCE_INTERVAL);
@@ -79,7 +81,7 @@ class App extends React.Component {
window.addEventListener('keypress', this.onKeyPress);
window.addEventListener('keyup', this.onKeyUp);
- this.router = getRouter(this.props.dispatch, this.props.urlState);
+ this.router = this.props.dispatch(getRouter(this.props.urlState));
this.router.start({ hashbang: true });
if (!this.props.routeSet || process.env.WEAVE_CLOUD) {
@@ -102,6 +104,9 @@ class App extends React.Component {
if (nextProps.monitor !== this.props.monitor) {
this.props.dispatch(setMonitorState(nextProps.monitor));
}
+ if (nextProps.disableStoreViewState !== this.props.disableStoreViewState) {
+ this.props.dispatch(setStoreViewState(!nextProps.disableStoreViewState));
+ }
}
onKeyUp(ev) {
@@ -267,12 +272,14 @@ App.propTypes = {
renderTimeTravel: PropTypes.func,
renderNodeDetailsExtras: PropTypes.func,
monitor: PropTypes.bool,
+ disableStoreViewState: PropTypes.bool,
};
App.defaultProps = {
renderTimeTravel: () => ,
renderNodeDetailsExtras: () => null,
monitor: false,
+ disableStoreViewState: false,
};
export default connect(mapStateToProps)(App);
diff --git a/client/app/scripts/constants/action-types.js b/client/app/scripts/constants/action-types.js
index 3323c862c..47ae93a39 100644
--- a/client/app/scripts/constants/action-types.js
+++ b/client/app/scripts/constants/action-types.js
@@ -55,6 +55,7 @@ const ACTION_TYPES = [
'SELECT_NETWORK',
'SET_EXPORTING_GRAPH',
'SET_RECEIVED_NODES_DELTA',
+ 'SET_STORE_VIEW_STATE',
'SET_VIEW_MODE',
'SET_VIEWPORT_DIMENSIONS',
'SHOW_HELP',
diff --git a/client/app/scripts/reducers/root.js b/client/app/scripts/reducers/root.js
index 7ebfee1b1..6ab9b007f 100644
--- a/client/app/scripts/reducers/root.js
+++ b/client/app/scripts/reducers/root.js
@@ -70,6 +70,7 @@ export const initialState = makeMap({
plugins: makeList(),
pinnedSearches: makeList(), // list of node filters
routeSet: false,
+ storeViewState: true,
searchFocused: false,
searchQuery: '',
selectedNetwork: null,
@@ -752,6 +753,10 @@ export function rootReducer(state = initialState, action) {
return state.set('monitor', action.monitor);
}
+ case ActionTypes.SET_STORE_VIEW_STATE: {
+ return state.set('storeViewState', action.storeViewState);
+ }
+
default: {
return state;
}
diff --git a/client/app/scripts/utils/router-utils.js b/client/app/scripts/utils/router-utils.js
index 244d61a3a..fc4425327 100644
--- a/client/app/scripts/utils/router-utils.js
+++ b/client/app/scripts/utils/router-utils.js
@@ -37,6 +37,14 @@ export function parseHashState(hash = window.location.hash) {
return JSON.parse(decodeURL(urlStateString));
}
+export function clearStoredViewState() {
+ storageSet(STORAGE_STATE_KEY, '');
+}
+
+function isStoreViewStateEnabled(state) {
+ return state.get('storeViewState');
+}
+
function shouldReplaceState(prevState, nextState) {
// Opening a new terminal while an existing one is open.
const terminalToTerminal = (prevState.controlPipe && nextState.controlPipe);
@@ -116,7 +124,9 @@ export function updateRoute(getState) {
if (stateUrl === prevStateUrl) return;
// back up state in storage as well
- storageSet(STORAGE_STATE_KEY, stateUrl);
+ if (isStoreViewStateEnabled(getState())) {
+ storageSet(STORAGE_STATE_KEY, stateUrl);
+ }
if (shouldReplaceState(prevState, state)) {
// Replace the top of the history rather than pushing on a new item.
@@ -141,38 +151,43 @@ function detectOldOptions(topologyOptions) {
}
-export function getRouter(dispatch, initialState) {
- // strip any trailing '/'s.
- page.base(window.location.pathname.replace(/\/$/, ''));
+export function getRouter(initialState) {
+ return (dispatch, getState) => {
+ // strip any trailing '/'s.
+ page.base(window.location.pathname.replace(/\/$/, ''));
- page('/', () => {
- // recover from storage state on empty URL
- const storageState = storageGet(STORAGE_STATE_KEY);
- if (storageState) {
- const parsedState = JSON.parse(decodeURL(storageState));
- const dirtyOptions = detectOldOptions(parsedState.topologyOptions);
- if (dirtyOptions) {
- dispatch(route(initialState));
+ page('/', () => {
+ // recover from storage state on empty URL
+ const storageState = storageGet(STORAGE_STATE_KEY);
+ if (storageState && isStoreViewStateEnabled(getState())) {
+ const parsedState = JSON.parse(decodeURL(storageState));
+ const dirtyOptions = detectOldOptions(parsedState.topologyOptions);
+ if (dirtyOptions) {
+ dispatch(route(initialState));
+ } else {
+ const mergedState = Object.assign(initialState, parsedState);
+ // push storage state to URL
+ window.location.hash = `!/state/${stableStringify(mergedState)}`;
+ dispatch(route(mergedState));
+ }
} else {
- const mergedState = Object.assign(initialState, parsedState);
- // push storage state to URL
- window.location.hash = `!/state/${stableStringify(mergedState)}`;
- dispatch(route(mergedState));
+ dispatch(route(initialState));
}
- } else {
- dispatch(route(initialState));
- }
- });
+ });
- page('/state/:state', (ctx) => {
- const state = JSON.parse(decodeURL(ctx.params.state));
- const dirtyOptions = detectOldOptions(state.topologyOptions);
- const nextState = dirtyOptions ? initialState : state;
+ page('/state/:state', (ctx) => {
+ const state = JSON.parse(decodeURL(ctx.params.state));
+ const dirtyOptions = detectOldOptions(state.topologyOptions);
+ const nextState = dirtyOptions ? initialState : state;
- // back up state in storage and redirect
- storageSet(STORAGE_STATE_KEY, encodeURL(stableStringify(state)));
- dispatch(route(nextState));
- });
+ // back up state in storage and redirect
+ if (isStoreViewStateEnabled(getState())) {
+ storageSet(STORAGE_STATE_KEY, encodeURL(stableStringify(state)));
+ }
- return page;
+ dispatch(route(nextState));
+ });
+
+ return page;
+ };
}
diff --git a/client/app/scripts/utils/storage-utils.js b/client/app/scripts/utils/storage-utils.js
index 4c6d3ee07..61908660b 100644
--- a/client/app/scripts/utils/storage-utils.js
+++ b/client/app/scripts/utils/storage-utils.js
@@ -2,30 +2,54 @@ import debug from 'debug';
const log = debug('scope:storage-utils');
-// localStorage detection
-const storage = (typeof Storage) !== 'undefined' ? window.localStorage : null;
-
-export function storageGet(key, defaultValue) {
- if (storage && storage.getItem(key) !== undefined) {
- return storage.getItem(key);
+export const localSessionStorage = {
+ getItem(k) {
+ return window.sessionStorage.getItem(k) || window.localStorage.getItem(k);
+ },
+ setItem(k, v) {
+ window.sessionStorage.setItem(k, v);
+ window.localStorage.setItem(k, v);
+ },
+ clear() {
+ window.sessionStorage.clear();
+ window.localStorage.clear();
}
- return defaultValue;
+};
+
+export function storageGet(key, defaultValue, storage = localSessionStorage) {
+ if (!storage) {
+ return defaultValue;
+ }
+
+ const value = storage.getItem(key);
+ if (value == null) {
+ return defaultValue;
+ }
+
+ return value;
}
-export function storageSet(key, value) {
+export function storageSet(key, value, storage = localSessionStorage) {
if (storage) {
try {
storage.setItem(key, value);
return true;
} catch (e) {
- log('Error storing value in storage. Maybe full? Could not store key.', key);
+ log(
+ 'Error storing value in storage. Maybe full? Could not store key.',
+ key
+ );
}
}
return false;
}
-export function storageGetObject(key, defaultValue) {
- const value = storageGet(key);
+export function storageGetObject(
+ key,
+ defaultValue,
+ storage = localSessionStorage
+) {
+ const value = storageGet(key, undefined, storage);
if (value) {
try {
return JSON.parse(value);
@@ -36,9 +60,9 @@ export function storageGetObject(key, defaultValue) {
return defaultValue;
}
-export function storageSetObject(key, obj) {
+export function storageSetObject(key, obj, storage = localSessionStorage) {
try {
- return storageSet(key, JSON.stringify(obj));
+ return storageSet(key, JSON.stringify(obj), storage);
} catch (e) {
log('Error encoding object for key', key);
}
diff --git a/client/package.json b/client/package.json
index adcdf6a52..354de17e9 100644
--- a/client/package.json
+++ b/client/package.json
@@ -118,7 +118,7 @@
},
"setupFiles": [
"/test/support/raf.js",
- "/test/support/localStorage.js"
+ "/test/support/storage.js"
],
"roots": [
"/app/scripts"
diff --git a/client/test/support/localStorage.js b/client/test/support/localStorage.js
deleted file mode 100644
index 50cf4b680..000000000
--- a/client/test/support/localStorage.js
+++ /dev/null
@@ -1,17 +0,0 @@
-const localStorageMock = (function() {
- let store = {};
- return {
- store,
- getItem: function(key) {
- return store[key];
- },
- setItem: function(key, value) {
- store[key] = value;
- },
- clear: function() {
- store = {};
- }
- };
-})();
-Object.defineProperty(window, 'Storage', { value: localStorageMock });
-Object.defineProperty(window, 'localStorage', { value: localStorageMock });
diff --git a/client/test/support/storage.js b/client/test/support/storage.js
new file mode 100644
index 000000000..47af5aeab
--- /dev/null
+++ b/client/test/support/storage.js
@@ -0,0 +1,21 @@
+const makeStorageMock = function () {
+ let store = {};
+ return {
+ store,
+ getItem(key) {
+ return store[key];
+ },
+ setItem(key, value) {
+ store[key] = value;
+ },
+ clear() {
+ store = {};
+ }
+ };
+};
+
+const localStorageMock = makeStorageMock();
+const sessionStorageMock = makeStorageMock();
+
+Object.defineProperty(window, 'localStorage', { value: localStorageMock });
+Object.defineProperty(window, 'sessionStorage', { value: sessionStorageMock });