diff --git a/client/app/scripts/components/app.js b/client/app/scripts/components/app.js index 32abc25d5..278bd6f56 100644 --- a/client/app/scripts/components/app.js +++ b/client/app/scripts/components/app.js @@ -42,7 +42,8 @@ import TimeTravelWrapper from './time-travel-wrapper'; import ViewModeSelector from './view-mode-selector'; import NetworkSelector from './networks-selector'; import DebugToolbar, { showingDebugToolbar, toggleDebugToolbar } from './debug-toolbar'; -import { getRouter, getUrlState } from '../utils/router-utils'; +import { getUrlState } from '../utils/router-utils'; +import { getRouter } from '../router'; import { trackAnalyticsEvent } from '../utils/tracking-utils'; import { availableNetworksSelector } from '../selectors/node-networks'; import { timeTravelSupportedSelector } from '../selectors/time-travel'; diff --git a/client/app/scripts/router.js b/client/app/scripts/router.js new file mode 100644 index 000000000..f1fc9571f --- /dev/null +++ b/client/app/scripts/router.js @@ -0,0 +1,64 @@ +import page from 'page'; +import stableStringify from 'json-stable-stringify'; +import { each } from 'lodash'; + +import { route } from './actions/app-actions'; +import { storageGet, storageSet } from './utils/storage-utils'; +import { + decodeURL, encodeURL, isStoreViewStateEnabled, STORAGE_STATE_KEY +} from './utils/router-utils'; + +// Temporarily detect old topology options to avoid breaking things between releases +// Related to https://github.com/weaveworks/scope/pull/2404 +function detectOldOptions(topologyOptions) { + let bad = false; + each(topologyOptions, (topology) => { + each(topology, (option) => { + if (typeof option === 'string') { + bad = true; + } + }); + }); + return bad; +} + +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 && 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 { + 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; + + // back up state in storage and redirect + if (isStoreViewStateEnabled(getState())) { + storageSet(STORAGE_STATE_KEY, encodeURL(stableStringify(state))); + } + + dispatch(route(nextState)); + }); + + return page; + }; +} diff --git a/client/app/scripts/utils/router-utils.js b/client/app/scripts/utils/router-utils.js index df582cd24..aa339ed52 100644 --- a/client/app/scripts/utils/router-utils.js +++ b/client/app/scripts/utils/router-utils.js @@ -1,13 +1,10 @@ import page from 'page'; import stableStringify from 'json-stable-stringify'; import { fromJS, is as isDeepEqual } from 'immutable'; -import { - each, omit, omitBy, isEmpty -} from 'lodash'; +import { omit, omitBy, isEmpty } from 'lodash'; -import { route } from '../actions/app-actions'; import { hashDifferenceDeep } from './hash-utils'; -import { storageGet, storageSet } from './storage-utils'; +import { storageSet } from './storage-utils'; import { getDefaultTopologyOptions, initialState as initialRootState } from '../reducers/root'; @@ -19,9 +16,9 @@ const SLASH = '/'; const SLASH_REPLACEMENT = ''; const PERCENT = '%'; const PERCENT_REPLACEMENT = ''; -const STORAGE_STATE_KEY = 'scopeViewState'; +export const STORAGE_STATE_KEY = 'scopeViewState'; -function encodeURL(url) { +export function encodeURL(url) { return url .replace(new RegExp(PERCENT, 'g'), PERCENT_REPLACEMENT) .replace(new RegExp(SLASH, 'g'), SLASH_REPLACEMENT); @@ -43,7 +40,7 @@ export function clearStoredViewState() { storageSet(STORAGE_STATE_KEY, ''); } -function isStoreViewStateEnabled(state) { +export function isStoreViewStateEnabled(state) { return state.get('storeViewState'); } @@ -137,59 +134,3 @@ export function updateRoute(getState) { page.show(`/state/${stateUrl}`, state, dispatch); } } - -// Temporarily detect old topology options to avoid breaking things between releases -// Related to https://github.com/weaveworks/scope/pull/2404 -function detectOldOptions(topologyOptions) { - let bad = false; - each(topologyOptions, (topology) => { - each(topology, (option) => { - if (typeof option === 'string') { - bad = true; - } - }); - }); - return bad; -} - - -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 && 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 { - 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; - - // back up state in storage and redirect - if (isStoreViewStateEnabled(getState())) { - storageSet(STORAGE_STATE_KEY, encodeURL(stableStringify(state))); - } - - dispatch(route(nextState)); - }); - - return page; - }; -}