import _ from 'lodash' import React from 'react' import openbis from '@src/js/services/openbis.js' import objectType from '@src/js/common/consts/objectType.js' import objectOperation from '@src/js/common/consts/objectOperation.js' import routes from '@src/js/common/consts/routes.js' import cookie from '@src/js/common/cookie.js' import history from '@src/js/store/history.js' export class AppController { constructor() { this.AppContext = React.createContext() } init(context) { this.context = context context.initState(this.initialState()) } initialState() { return { loaded: false, loading: false, session: null, route: null, search: null, pages: [], error: null, lastObjectModifications: {} } } async load() { const { loaded } = this.context.getState() if (!loaded) { try { await this.context.setState({ loading: true }) await openbis.init() const sessionToken = cookie.read(cookie.OPENBIS_COOKIE) if (sessionToken) { try { openbis.useSession(sessionToken) const sessionInformation = await openbis.getSessionInformation() if (sessionInformation) { await this.context.setState({ session: { sessionToken: sessionToken, userName: sessionInformation.userName } }) } else { openbis.useSession(null) } } catch (e) { openbis.useSession(null) } } await this.context.setState({ loaded: true }) } catch (e) { await this.context.setState({ error: e }) } finally { await this.context.setState({ loading: false }) } } } async login(username, password) { try { await this.context.setState({ loading: true }) const sessionToken = await openbis.login(username, password) this.context.setState({ session: { sessionToken: sessionToken, userName: username } }) cookie.create(cookie.OPENBIS_COOKIE, sessionToken, 7) const { route } = this.context.getState() const routeObject = routes.parse(route) await this.routeChange(routeObject.path) } catch (e) { await this.context.setState({ error: 'Incorrect user or password' }) } finally { await this.context.setState({ loading: false }) } } async logout() { try { await this.context.setState({ loading: true }) await openbis.logout() this.context.setState(state => ({ ...this.initialState(), loaded: state.loaded })) cookie.erase(cookie.OPENBIS_COOKIE) await this.routeChange('/') } catch (e) { await this.context.setState({ error: e }) } finally { await this.context.setState({ loading: false }) } } async search(page, text) { if (text.trim().length > 0) { await this.objectOpen(page, objectType.SEARCH, text.trim()) } await this.context.setState({ search: '' }) } async searchChange(text) { await this.context.setState({ search: text }) } async pageChange(page) { const pageRoute = this.getCurrentRoute(page) if (pageRoute) { await this.routeChange(pageRoute) } else { const route = routes.format({ page }) await this.routeChange(route) } } async errorChange(error) { await this.context.setState({ error: error }) } async routeChange(path) { const route = routes.parse(path) if (route.type && route.id) { const object = { type: route.type, id: route.id } const openTabs = this.getOpenTabs(route.page) if (openTabs) { let found = false let id = 1 openTabs.forEach(openTab => { if (_.isMatch(openTab.object, object)) { found = true } if (openTab.id >= id) { id = openTab.id + 1 } }) if (!found) { await this.addOpenTab(route.page, id, { id, object }) } } } await this.context.setState({ route: path }) await this.setCurrentRoute(route.page, path) } async routeReplace(route, state) { history.replace(route, state) } async objectNew(page, type) { let id = 1 const openObjects = this.getOpenObjects(page) openObjects.forEach(openObject => { if (openObject.type === type) { id++ } }) const route = routes.format({ page, type, id }) await this.routeChange(route) } async objectCreate(page, oldType, oldId, newType, newId) { const openTabs = this.getOpenTabs(page) const oldTab = _.find(openTabs, { object: { type: oldType, id: oldId } }) if (oldTab) { const newTab = { ...oldTab, object: { type: newType, id: newId }, changed: false } await this.replaceOpenTab(page, oldTab.id, newTab) await this.setLastObjectModification( newType, objectOperation.CREATE, Date.now() ) const route = routes.format({ page, type: newType, id: newId }) await this.routeReplace(route) } } async objectOpen(page, type, id) { const route = routes.format({ page, type, id }) await this.routeChange(route) } async objectUpdate(type) { await this.setLastObjectModification( type, objectOperation.UPDATE, Date.now() ) } async objectDelete(page, type, id) { await this.objectClose(page, type, id) await this.setLastObjectModification( type, objectOperation.DELETE, Date.now() ) } async objectChange(page, type, id, changed) { const openTabs = this.getOpenTabs(page) const oldTab = _.find(openTabs, { object: { type, id } }) if (oldTab) { const newTab = { ...oldTab, changed } await this.replaceOpenTab(page, oldTab.id, newTab) } } async objectClose(page, type, id) { const openTabs = this.getOpenTabs(page) const objectToClose = { type, id } let selectedObject = this.getSelectedObject(page) if (selectedObject && _.isEqual(selectedObject, objectToClose)) { if (_.size(openTabs) === 1) { selectedObject = null } else { let selectedIndex = _.findIndex(openTabs, { object: selectedObject }) if (selectedIndex === 0) { selectedObject = openTabs[selectedIndex + 1].object } else { selectedObject = openTabs[selectedIndex - 1].object } } } let tabToClose = _.find(openTabs, { object: objectToClose }) if (tabToClose) { await this.removeOpenTab(page, tabToClose.id) } if (selectedObject) { const route = routes.format({ page, type: selectedObject.type, id: selectedObject.id }) await this.routeChange(route) } else { const route = routes.format({ page }) await this.routeChange(route) } } getLoaded() { return this.context.getState().loaded } getLoading() { return this.context.getState().loading } getSession() { return this.context.getState().session } getRoute() { return this.context.getState().route } getSearch() { return this.context.getState().search } getError() { return this.context.getState().error } getCurrentPage() { const { route } = this.context.getState() return routes.parse(route).page } getCurrentRoute(page) { const { pages } = this.context.getState() return pages[page] ? pages[page].currentRoute : null } async setCurrentRoute(page, route) { await this.context.setState(state => ({ pages: { ...state.pages, [page]: { ...state.pages[page], currentRoute: route } } })) } getSelectedObject(page) { const path = this.getCurrentRoute(page) if (path) { const route = routes.parse(path) if (route && route.type && route.id) { return { type: route.type, id: route.id } } } return null } getSelectedTab(page) { const selectedObject = this.getSelectedObject(page) if (selectedObject) { const openTabs = this.getOpenTabs(page) return _.find(openTabs, { object: selectedObject }) } else { return null } } getOpenObjects(page) { const openTabs = this.getOpenTabs(page) return openTabs.map(openTab => { return openTab.object }) } getOpenTabs(page) { const { pages } = this.context.getState() return (pages[page] && pages[page].openTabs) || [] } async setOpenTabs(page, newOpenTabs) { await this.context.setState(state => ({ pages: { ...state.pages, [page]: { ...state.pages[page], openTabs: newOpenTabs } } })) } async addOpenTab(page, id, tab) { const openTabs = this.getOpenTabs(page) const index = _.findIndex(openTabs, { id: id }, _.isMatch) if (index === -1) { const newOpenTabs = Array.from(openTabs) newOpenTabs.push(tab) await this.setOpenTabs(page, newOpenTabs) } } async removeOpenTab(page, id) { const openTabs = this.getOpenTabs(page) const index = _.findIndex(openTabs, { id: id }, _.isMatch) if (index !== -1) { const newOpenTabs = Array.from(openTabs) newOpenTabs.splice(index, 1) await this.setOpenTabs(page, newOpenTabs) } } async replaceOpenTab(page, id, tab) { const openTabs = this.getOpenTabs(page) const index = _.findIndex(openTabs, { id: id }, _.isMatch) if (index !== -1) { const newOpenTabs = Array.from(openTabs) newOpenTabs[index] = tab await this.setOpenTabs(page, newOpenTabs) } } getLastObjectModifications() { return this.context.getState().lastObjectModifications } async setLastObjectModification(type, operation, timestamp) { const { lastObjectModifications } = this.context.getState() if ( !lastObjectModifications[type] || !lastObjectModifications[type][operation] || lastObjectModifications[type][operation] < timestamp ) { await this.context.setState({ lastObjectModifications: { ...lastObjectModifications, [type]: { ...lastObjectModifications[type], [operation]: timestamp } } }) } } withState(additionalPropertiesFn) { const WithContext = Component => { const WithConsumer = props => { return React.createElement(this.AppContext.Consumer, {}, () => { const additionalProperties = additionalPropertiesFn ? additionalPropertiesFn(props) : {} return React.createElement(Component, { ...props, ...additionalProperties }) }) } WithConsumer.displayName = 'WithConsumer' return WithConsumer } WithContext.displayName = 'WithContext' return WithContext } } let INSTANCE = new AppController() export function setInstance(instance) { INSTANCE = instance } export function getInstance() { return INSTANCE } export default INSTANCE