Skip to content

Commit

Permalink
CELE-17 feat: Add application structure and context
Browse files Browse the repository at this point in the history
  • Loading branch information
afonsobspinto committed Apr 23, 2024
1 parent 5370955 commit 992d242
Show file tree
Hide file tree
Showing 13 changed files with 681 additions and 591 deletions.
2 changes: 1 addition & 1 deletion applications/visualizer/frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
margin: 0 auto;
}

#layout-manager-container {
.layout-manager-container {
display: flex;
position: relative;
width: 100%;
Expand Down
47 changes: 12 additions & 35 deletions applications/visualizer/frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,28 @@
import {useDispatch, useStore} from "react-redux";
import React, {useEffect, useState} from "react";
import {Provider} from "react-redux";
import {ThemeProvider} from '@mui/material/styles';
import {Box, CircularProgress, CssBaseline} from "@mui/material";
import {getLayoutManagerInstance} from "@metacell/geppetto-meta-client/common/layout/LayoutManager";
import {addWidget} from '@metacell/geppetto-meta-client/common/layout/actions';
import {CssBaseline} from "@mui/material";
import '@metacell/geppetto-meta-ui/flex-layout/style/dark.scss';
import {leftComponentWidget, rightComponentWidget} from "./layout-manager/widgets.ts";
import theme from './theme/index.tsx';
import './App.css'

import {useGlobalContext} from "./contexts/GlobalContext.tsx";
import AppLauncher from "./components/AppLauncher.tsx";
import Workspace from "./components/Workspace.tsx";

function App() {
const {workspaces, currentWorkspaceId} = useGlobalContext();

const store = useStore();
const dispatch = useDispatch();
const [LayoutComponent, setLayoutComponent] = useState<React.ComponentType | undefined>(undefined);

useEffect(() => {
if (LayoutComponent === undefined) {
const myManager = getLayoutManagerInstance();
if (myManager) {
setLayoutComponent(myManager.getComponent());
}
}
}, [store, dispatch, LayoutComponent])

useEffect(() => {
dispatch(addWidget(leftComponentWidget()));
dispatch(addWidget(rightComponentWidget()));
}, [LayoutComponent, dispatch])


const isLoading = LayoutComponent === undefined
const hasLaunched = currentWorkspaceId != undefined

return (
<>
<ThemeProvider theme={theme}>
<CssBaseline/>
{isLoading ?
<CircularProgress/> :
<Box id="layout-manager-container">
<LayoutComponent/>
</Box>
}
{hasLaunched ? (
<Provider store={workspaces[currentWorkspaceId].store}>
<Workspace layoutManager={workspaces[currentWorkspaceId].layoutManager}/>
</Provider>
) : <AppLauncher/>}
</ThemeProvider>


</>
)
}
Expand Down
72 changes: 72 additions & 0 deletions applications/visualizer/frontend/src/components/AppLauncher.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import {Typography, Card, CardContent, CardActionArea, Grid, Container} from '@mui/material';
import {createEmptyWorkspace} from "../helpers/initialWorkspacesHelper.ts";
import {useGlobalContext} from "../contexts/GlobalContext.tsx";

function AppLauncher() {

const {workspaces, addWorkspace, switchWorkspace} = useGlobalContext();


const handleTemplateClick = () => {
console.log('Template option clicked');
};

const handleBlankClick = () => {
const workspace =createEmptyWorkspace(`Workspace ${Object.keys(workspaces).length + 1}`)
addWorkspace(workspace)
switchWorkspace(workspace.id)
};

const handlePasteUrlClick = () => {
console.log('Paste URL option clicked');
};

return (
<Container maxWidth="md" style={{marginTop: '50px'}}>
<Typography variant="h3" component="h1" gutterBottom align="center">
Welcome to C. Elegans
</Typography>
<Typography variant="h6" component="p" gutterBottom align="center">
Choose one of the options below to get started.
</Typography>
<Grid container spacing={4} justifyContent="center" style={{marginTop: '20px'}}>
<Grid item xs={12} sm={6} md={4}>
<Card>
<CardActionArea onClick={handleTemplateClick}>
<CardContent>
<Typography variant="h5" component="h2" align="center">
Start from Template
</Typography>
</CardContent>
</CardActionArea>
</Card>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Card>
<CardActionArea onClick={handleBlankClick}>
<CardContent>
<Typography variant="h5" component="h2" align="center">
Start with a Blank Canvas
</Typography>
</CardContent>
</CardActionArea>
</Card>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<Card>
<CardActionArea onClick={handlePasteUrlClick}>
<CardContent>
<Typography variant="h5" component="h2" align="center">
Paste URL
</Typography>
</CardContent>
</CardActionArea>
</Card>
</Grid>
</Grid>
</Container>
);
}

export default AppLauncher;
49 changes: 49 additions & 0 deletions applications/visualizer/frontend/src/components/Workspace.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {useDispatch} from "react-redux";
import React, {useEffect, useState} from "react";
import {ThemeProvider} from '@mui/material/styles';
import {Box, CircularProgress, CssBaseline} from "@mui/material";
import {addWidget} from '@metacell/geppetto-meta-client/common/layout/actions';
import '@metacell/geppetto-meta-ui/flex-layout/style/dark.scss';
import {leftComponentWidget, rightComponentWidget} from "../layout-manager/widgets.ts";
import theme from '../theme';


function Workspace({layoutManager}) {

const dispatch = useDispatch();
const [LayoutComponent, setLayoutComponent] = useState<React.ComponentType | undefined>(undefined);

useEffect(() => {
if (LayoutComponent === undefined) {
if (layoutManager) {
setLayoutComponent(layoutManager.getComponent());
}
}
}, [LayoutComponent])

useEffect(() => {
dispatch(addWidget(leftComponentWidget()));
dispatch(addWidget(rightComponentWidget()));
}, [LayoutComponent, dispatch])


const isLoading = LayoutComponent === undefined

return (
<>
<ThemeProvider theme={theme}>
<CssBaseline/>
{isLoading ?
<CircularProgress/> :
<Box className="layout-manager-container">
<LayoutComponent/>
</Box>
}
</ThemeProvider>


</>
)
}

export default Workspace
51 changes: 51 additions & 0 deletions applications/visualizer/frontend/src/contexts/GlobalContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, {createContext, useState, useContext, ReactNode} from 'react';
import {Workspace} from "../models.ts";

export interface GlobalContextType {
workspaces: Record<string, Workspace>;
currentWorkspaceId: string | undefined;
addWorkspace: (workspace: Workspace) => void;
removeWorkspace: (workspaceId: string) => void;
switchWorkspace: (workspaceId: string) => void;
}

interface GlobalContextProviderProps {
children: ReactNode;
}

const GlobalContext = createContext<GlobalContextType | undefined>(undefined);

export const GlobalContextProvider: React.FC<GlobalContextProviderProps> = ({children}) => {
const [workspaces, setWorkspaces] = useState<Record<string, Workspace>>({});
const [currentWorkspaceId, setCurrentWorkspaceId] = useState<string|undefined>(undefined);

const addWorkspace = (workspace: Workspace) => {
setWorkspaces(prev => ({...prev, [workspace.id]: workspace}));
};

const removeWorkspace = (workspaceId: string) => {
const updatedWorkspaces = {...workspaces};
delete updatedWorkspaces[workspaceId];
setWorkspaces(updatedWorkspaces);
};

const switchWorkspace = (workspaceId: string) => {
setCurrentWorkspaceId(workspaceId);
};

return (
<GlobalContext.Provider
value={{workspaces, currentWorkspaceId, addWorkspace, removeWorkspace, switchWorkspace}}>
{children}
</GlobalContext.Provider>
);
};


export const useGlobalContext = () => {
const context = useContext(GlobalContext);
if (context === undefined) {
throw new Error('useGlobalContext must be used within a GlobalContextProvider');
}
return context;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {Workspace} from "../models.ts";
import getLayoutManagerAndStore from "../layout-manager/layoutManagerFactory.ts";

export const createEmptyWorkspace = (name: string): Workspace => {
// Generate a unique ID for the workspace
const workspaceId = `workspace-${Date.now()}`; // Simple unique ID generation

const {layoutManager, store} = getLayoutManagerAndStore();

return {
id: workspaceId,
name: name,
viewers: [],
datasets: [],
neurons: [],
synchronizations: [],
store: store,
layoutManager: layoutManager,
};

};
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {applyMiddleware, combineReducers, compose, createStore} from "@reduxjs/toolkit";
import {callbacksMiddleware} from '@metacell/geppetto-meta-client/common/middleware/geppettoMiddleware';
import {initLayoutManager} from "@metacell/geppetto-meta-client/common/layout/LayoutManager";
import geppettoClientReducer, {clientInitialState} from '@metacell/geppetto-meta-client/common/reducer/geppettoClient';
import {
layoutInitialState,
layout,
widgets
} from '@metacell/geppetto-meta-client/common/reducer/geppettoLayout';
import {reducerDecorator} from '@metacell/geppetto-meta-client/common/reducer/reducerDecorator';

import componentMap from "./componentMap.ts";
import baseLayout from "./layout.ts";

const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;


const initialState = {
client: clientInitialState,
layout: layoutInitialState,
widgets: {}
};

const staticReducers = {
client: geppettoClientReducer,
layout,
widgets
}

const getLayoutManagerAndStore = () => {
const layoutManager = initLayoutManager(baseLayout, componentMap, undefined, false);
const allMiddlewares = [callbacksMiddleware, layoutManager.middleware];

const store = createStore(
reducerDecorator(combineReducers({...staticReducers})),
{...initialState},
storeEnhancers(applyMiddleware(...allMiddlewares))
);
return {
layoutManager,
store
}

}

export default getLayoutManagerAndStore;
13 changes: 0 additions & 13 deletions applications/visualizer/frontend/src/layout-manager/store.ts

This file was deleted.

7 changes: 3 additions & 4 deletions applications/visualizer/frontend/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import {Provider} from 'react-redux';
import App from './App.tsx'
import './index.css'
import store from "./layout-manager/store.ts";
import {GlobalContextProvider} from "./contexts/GlobalContext.tsx";

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<Provider store={store}>
<GlobalContextProvider>
<App/>
</Provider>
</GlobalContextProvider>
</React.StrictMode>
,
)
Loading

0 comments on commit 992d242

Please sign in to comment.