import _ from 'lodash' import React from 'react' import { createBrowserHistory } from 'history' 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 users from '@src/js/common/consts/users.js' import cookie from '@src/js/common/cookie.js' import url from '@src/js/common/url.js' import ids from '@src/js/common/consts/ids.js' const AppContext = React.createContext() export class AppController { init(context) { context.initState(this.initialState()) const history = createBrowserHistory({ basename: url.getApplicationPath() + '#' }) history.listen(location => { const route = routes.parse(location.pathname) this.routeChanged(route.path) }) this.context = context this.history = history } initialState() { return { loaded: false, loading: false, session: null, search: null, pages: [], error: null, settings: {}, 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) { const [settings, serverInformation] = await Promise.all([ this._loadSettings(), openbis.getServerInformation() ]) await this.context.setState({ session: { sessionToken: sessionToken, userName: sessionInformation.userName }, settings, serverInformation }) const routeObject = routes.parse(this.getRoute()) await this.routeChanged(routeObject.path) } 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) if (sessionToken !== null) { cookie.create(cookie.OPENBIS_COOKIE, sessionToken, 7) const [settings, serverInformation] = await Promise.all([ this._loadSettings(), openbis.getServerInformation() ]) await this.context.setState({ session: { sessionToken: sessionToken, userName: username }, settings, serverInformation }) const routeObject = routes.parse(this.getRoute()) await this.routeChange(routeObject.path) } else { throw Error('Session token null') } } 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 loadingChange(loading) { await this.context.setState({ loading: loading }) } async errorChange(error) { await this.context.setState({ error: error }) } async routeChanged(path) { const newRoute = routes.parse(path) if (newRoute.type && newRoute.id) { const object = { type: newRoute.type, id: newRoute.id } const openTabs = this.getOpenTabs(newRoute.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(newRoute.page, id, { id, object }) } } } await this.setCurrentRoute(newRoute.page, newRoute.path) } async routeChange(path) { if (path !== this.history.location.pathname) { this.history.push(path) } } async routeReplace(route) { this.history.replace(route) } 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 } getServerInformation(key) { const serverInformation = this.context.getState().serverInformation || {} return serverInformation[key] } getSession() { return this.context.getState().session } getSessionToken() { const session = this.getSession() return session ? session.sessionToken : null } getUser() { const session = this.getSession() return session ? session.userName : null } isSystemUser() { return this.getUser() === users.SYSTEM } getRoute() { return routes.parse(this.history.location.pathname).path } getSearch() { return this.context.getState().search } getError() { return this.context.getState().error } getCurrentPage() { const route = this.getRoute() 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 } } }) } } async getSetting(settingId) { const { settings } = this.context.getState() return settings[settingId] } async setSetting(settingId, settingObject) { const { settings } = this.context.getState() const newSettings = { ...settings, [settingId]: settingObject } this.context.setState({ settings: newSettings }) await this._saveSettings(settingId, settingObject) } async _loadSettings() { const id = new openbis.Me() const fo = new openbis.PersonFetchOptions() fo.withWebAppSettings(ids.WEB_APP_ID).withAllSettings() const map = await openbis.getPersons([id], fo) const person = map[id] const webAppSettings = person.webAppSettings[ids.WEB_APP_ID] if (webAppSettings && webAppSettings.settings) { const map = {} Object.values(webAppSettings.settings).forEach(setting => { if (setting.value !== null && setting.value !== undefined) { map[setting.name] = JSON.parse(setting.value) } }) return map } else { return {} } } async _saveSettings(settingId, settingObject) { const settings = new openbis.WebAppSettingCreation() settings.setName(settingId) settings.setValue(JSON.stringify(settingObject)) const update = new openbis.PersonUpdate() update.setUserId(new openbis.Me()) update.getWebAppSettings(ids.WEB_APP_ID).add(settings) await openbis.updatePersons([update]) } withState(additionalPropertiesFn) { const WithContext = Component => { const WithConsumer = props => { return React.createElement(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 default { AppContext, AppController, getInstance() { return INSTANCE }, setInstance(instance) { INSTANCE = instance } }