diff --git a/platform/viewer/src/routes/ModeRoute.js b/platform/viewer/src/routes/ModeRoute.js
new file mode 100644
index 000000000..d206ef440
--- /dev/null
+++ b/platform/viewer/src/routes/ModeRoute.js
@@ -0,0 +1,68 @@
+export default function ModeRoute(mode, dataSourceId) {
+ const [displaySets, setDisplaySets] = useState([]);
+ const { routes, sopClassHandlers, extensions } = getMode(modeId);
+
+ // Deal with toolbar.
+
+ // Only handling one route per mode for now
+ const LayoutComponent = routes[0].layoutTemplate;
+
+ // Add SOPClassHandlers to a new SOPClassManager.
+ const manager = new SOPClassHandlerManager(sopClassHandlers);
+
+ const dataSource = getDataSource(dataSourceId);
+ const queryParams = location.search;
+
+ // Call the data source to start building the view model?
+ dataSource(queryParams);
+
+ metadataStore.onModified();
+
+ const onUpdatedCallback = () => {
+ // TODO: This should append, not create from scratch so we don't nuke existing display sets
+ // when e.g. a new series arrives
+ manager.createDisplaySets.then(setDisplaySets);
+ };
+
+ // TODO: Should extensions provide an array of these or one nested context?
+ const contextModules = extensions.getContextModules();
+ const ExtensionContexts = contextModules => {};
+
+ /*
+ TODO: How are contexts provided by extensions passed into the mode?
+
+ return (
+
+
+
+ );*/
+
+ return ;
+}
+
+/*const ViewModelContext = React.createContext(
+ displaySets: [],
+ setDisplaySets: () => {}
+);
+
+
+class ViewModelProvider extends Component {
+ state = {
+ displaySets: []
+ };
+
+ const setDisplaySets = displaySets => {
+ this.setState({displaySets});
+ };
+
+ render() {
+ return (
+
+ {this.props.children}
+
+ );
+ }
+}*/
diff --git a/platform/viewer/src/routes/SOPClassHandlerManager.js b/platform/viewer/src/routes/SOPClassHandlerManager.js
new file mode 100644
index 000000000..359551f6f
--- /dev/null
+++ b/platform/viewer/src/routes/SOPClassHandlerManager.js
@@ -0,0 +1,27 @@
+export default class SOPClassHandlerManager {
+ constructor(sopClassHandlerIds) {
+ this.SOPClassHandlers = sopClassHandlerIds.map(getSOPClassHandler);
+ }
+
+ async createDisplaySets() {
+ const SOPClassHandlers = this.SOPClassHandlers;
+
+ const displaySets = [];
+
+ // TODO
+ StudyMetadata.forEachSeries(series => {
+ for (let i = 0; i < SOPClassHandlers.length; i++) {
+ const handler = SOPClassHandlers[i];
+
+ // TODO: This step is still unclear to me
+ const displaySet = handler(series);
+
+ if (displaySet) {
+ displaySets.push(displaySet);
+ }
+ }
+ });
+
+ return displaySets;
+ }
+}
diff --git a/platform/viewer/src/routes/buildModeRoutes.js b/platform/viewer/src/routes/buildModeRoutes.js
new file mode 100644
index 000000000..c43476bc8
--- /dev/null
+++ b/platform/viewer/src/routes/buildModeRoutes.js
@@ -0,0 +1,46 @@
+import { React } from 'react';
+import ModeRoute from './ModeRoute';
+
+/*
+ Routes uniquely define an entry point to:
+ - A mode
+ - Linked to a data source
+ - With a specified data set.
+
+ The full route template is:
+
+ /:modeId/:modeRoute/:sourceType/?queryParameters=example
+
+ Where:
+ :modeId - Is the mode selected.
+ :modeRoute - Is the route within the mode to select.
+ :sourceType - Is the data source identifier, which specifies which DataSource to use.
+ ?queryParameters - Are query parameters as defined by data source.
+
+ A default source can be specified at the app level configuration, and then that source is used if :sourceType is omitted:
+
+ /:modeId/:modeRoute/?queryParameters=example
+ */
+export default function buildModeRoutes(modes, extensionManager) {
+ const routes = [];
+
+ // TODO: Build api for this.
+ const dataSources = extensionManager.getDataSources();
+
+ modes.forEach(mode => {
+ dataSources.forEach(dataSource => {
+ const dataSourceId = dataSource.id;
+ const path = `/${mode.id}/${dataSourceId}`;
+
+ const component = ;
+
+ routes.push({
+ path,
+ component,
+ exact: true,
+ });
+ });
+ });
+
+ return routes;
+}