Skip to content

Commit

Permalink
#40 model singleton and changes required for redux
Browse files Browse the repository at this point in the history
  • Loading branch information
ddelpiano committed Dec 11, 2022
1 parent 80eb5d8 commit 1912490
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 69 deletions.
102 changes: 47 additions & 55 deletions src/components/Main.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,14 @@
import React from 'react';
import MetaDiagram, {
CallbackTypes,
ComponentsMap,
EventTypes,
} from '@metacell/meta-diagram';
import { connect } from "react-redux";
import { withStyles } from '@mui/styles';
import { PNLClasses } from '../constants';
import BG from '../assets/svg/bg-dotted.svg';
import { generateMetaGraph } from '../model/utils';
import ModelInterpreter from '../model/Interpreter';
import { Sidebar } from './views/rightSidebar/Sidebar';
import Composition from './views/compositions/Composition';
import { handlePostUpdates } from './graph/eventsHandler';
import { select, loadModel } from '../redux/actions/general';
import { leftSideBarNodes } from './views/leftSidebar/nodes';
import GenericMechanism from './views/mechanisms/GenericMechanism';
import CustomLinkWidget from './views/projections/CustomLinkWidget';

import { connect } from "react-redux";
import { select } from '../redux/actions/general';
import { handlePostUpdates, handlePreUpdates } from './graph/eventsHandler';



const mockModel = require('../resources/model').mockModel;
import MetaDiagram, { EventTypes } from '@metacell/meta-diagram';
import { mockModel } from '../resources/model';
import ModelSingleton from '../model/ModelSingleton';

const styles = () => ({
root: {
Expand All @@ -36,28 +23,11 @@ const styles = () => ({
class Main extends React.Component {
constructor(props) {
super(props);
// interpreter and model stored in the state will be moved to redux later
this.state = {};

this.mousePos = { x: 0, y: 0 };
this.interpreter = new ModelInterpreter(mockModel);
this.model = this.interpreter.getModel();
this.metaModel = this.interpreter.getMetaModel();
this.modelMap = this.interpreter.getModelElementsMap();
this.componentsMap = new ComponentsMap(new Map(), new Map());
this.componentsMap.nodes.set(PNLClasses.COMPOSITION, Composition);
this.componentsMap.nodes.set(PNLClasses.MECHANISM, GenericMechanism);
this.componentsMap.links.set(PNLClasses.PROJECTION, CustomLinkWidget);

// functions bond to this scope
this.metaCallback = this.metaCallback.bind(this);
this.mouseMoveCallback = this.mouseMoveCallback.bind(this);

this.metaGraph = generateMetaGraph([
...this.metaModel[PNLClasses.COMPOSITION],
...this.metaModel[PNLClasses.MECHANISM],
]);
this.metaGraph.addLinks(this.metaModel[PNLClasses.PROJECTION]);
}

metaCallback(event) {
Expand All @@ -74,6 +44,10 @@ class Main extends React.Component {
}
}

componentDidMount() {
this.props.loadModel(mockModel);
}

mouseMoveCallback(event) {
this.mousePos.x = event.clientX;
this.mousePos.y = event.clientY;
Expand All @@ -86,38 +60,56 @@ class Main extends React.Component {
}

render() {
let modelHandler = undefined;
const { classes } = this.props;

if (this.props.modelState === 'LOADED') {
modelHandler = ModelSingleton.getInstance();
}

return (
<div className={classes.root} onMouseMove={this.mouseMoveCallback}>
<MetaDiagram
metaCallback={this.metaCallback}
componentsMap={this.componentsMap}
metaLinks={this.metaGraph.getLinks()}
metaNodes={this.metaGraph.getNodes()}
sidebarProps={{
sidebarNodes: leftSideBarNodes,
selectedBarNode: 'targetMechanism',
}}
metaTheme={{
customThemeVariables: {
padding: 0,
margin: 0,
},
canvasClassName: classes.canvasBG,
}}
/>
<Sidebar />
{this.props.modelState === 'LOADED'
? <>
<MetaDiagram
metaCallback={this.metaCallback}
componentsMap={modelHandler.getComponentsMap()}
metaLinks={modelHandler.getMetaGraph().getLinks()}
metaNodes={modelHandler.getMetaGraph().getNodes()}
sidebarProps={{
sidebarNodes: leftSideBarNodes,
selectedBarNode: 'targetMechanism',
}}
metaTheme={{
customThemeVariables: {
padding: 0,
margin: 0,
},
canvasClassName: classes.canvasBG,
}}
/>
<Sidebar />
</>
: <>
<Sidebar />
</>
}
</div>
);
}
}

function mapStateToProps (state) {
return {
modelState: state.modelState
}
}

function mapDispatchToProps (dispatch) {
return {
selectInstance: (node) => dispatch(select(node)),
loadModel: (model) => dispatch(loadModel(model))
}
}

export default connect(null, mapDispatchToProps, null)(withStyles(styles)(Main));
export default connect(mapStateToProps, mapDispatchToProps, null, { forwardRef : true } )(withStyles(styles)(Main));
6 changes: 4 additions & 2 deletions src/components/graph/eventsHandler.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { CallbackTypes } from '@metacell/meta-diagram';
import ModelSingleton from '../../model/ModelSingleton';

export function handlePostUpdates(event, context) {
const node = event.entity;
const modelInstance = ModelSingleton.getInstance();
switch (event.function) {
case CallbackTypes.POSITION_CHANGED: {
context.metaGraph.updateGraph(
modelInstance.getMetaGraph().updateGraph(
node,
context.mousePos.x,
context.mousePos.y,
event?.extraCondition === CallbackTypes.CHILD_POSITION_CHANGED
);
context.interpreter.updateModel(node);
modelInstance.getInterpreter().updateModel(node);
break;
}
case CallbackTypes.SELECTION_CHANGED: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/rightSidebar/Sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const COMPOSITION_DTO = {
]
};

const COMPOSITION_EXAMPLE = {
const COMPOSITION_EXAMPLE = {
id: uuidv4(),
label: 'Composition 2',
tooltip: 'Composition 2',
Expand Down
76 changes: 76 additions & 0 deletions src/model/ModelSingleton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { PNLClasses } from '../constants';
import { generateMetaGraph } from './utils';
import ModelInterpreter from './Interpreter';
import { ComponentsMap } from '@metacell/meta-diagram';
import { MetaGraph } from '../components/graph/MetaGraph';
import { MetaLink, MetaNode } from '@metacell/meta-diagram';
import Composition from '../components/views/compositions/Composition';
import GenericMechanism from '../components/views/mechanisms/GenericMechanism';
import CustomLinkWidget from '../components/views/projections/CustomLinkWidget';

export default class ModelSingleton {
private static instance: ModelSingleton;
private static componentsMap: any;
private static interpreter: ModelInterpreter;
private static model: Object;
private static metaModel: { [key: string]: Array<MetaNode|MetaLink> };
private static metaGraph: MetaGraph;

private constructor(inputModel: any) {
ModelSingleton.componentsMap = new ComponentsMap(new Map(), new Map());
ModelSingleton.componentsMap.nodes.set(PNLClasses.COMPOSITION, Composition);
ModelSingleton.componentsMap.nodes.set(PNLClasses.MECHANISM, GenericMechanism);
ModelSingleton.componentsMap.links.set(PNLClasses.PROJECTION, CustomLinkWidget);

ModelSingleton.interpreter = new ModelInterpreter(inputModel);
ModelSingleton.model = ModelSingleton.interpreter.getModel();
ModelSingleton.metaModel = ModelSingleton.interpreter.getMetaModel();

ModelSingleton.metaGraph = generateMetaGraph([
...ModelSingleton.metaModel[PNLClasses.COMPOSITION],
...ModelSingleton.metaModel[PNLClasses.MECHANISM],
]);
// const links = ModelSingleton.metaModel[PNLClasses.PROJECTION].filter((item: any) => {return (item instanceof MetaLink)});
// @ts-ignore
ModelSingleton.metaGraph.addLinks(ModelSingleton.metaModel[PNLClasses.PROJECTION]);
}

static initInstance(initModel: any) {
if (!ModelSingleton.instance) {
ModelSingleton.instance = new ModelSingleton(initModel)
}
return ModelSingleton.instance;
}

static getInstance(): ModelSingleton {
if (!ModelSingleton.instance) {
throw Error("Model Singleton has not been initialised yet.");
}
return ModelSingleton.instance;
}

public updateModel(inputModel: any): ModelSingleton {
ModelSingleton.instance = new ModelSingleton(inputModel);
return ModelSingleton.instance;
}

public getModel(): Object {
return ModelSingleton.model;
}

public getMetaModel(): any {
return ModelSingleton.metaModel;
}

public getMetaGraph(): MetaGraph {
return ModelSingleton.metaGraph;
}

public getComponentsMap(): ComponentsMap {
return ModelSingleton.componentsMap;
}

public getInterpreter(): ModelInterpreter {
return ModelSingleton.interpreter;
}
}
12 changes: 12 additions & 0 deletions src/redux/middleware/pnlmiddleware.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
import { LOAD_MODEL } from "../actions/general";
import ModelSingleton from "../../model/ModelSingleton";

const pnlMiddleware = store => next => action => {
switch (action.type) {
case LOAD_MODEL: {
const modelSing = ModelSingleton.initInstance(action.data);
break;
}
default: {
break;
}
}
next(action);
}

Expand Down
14 changes: 8 additions & 6 deletions src/redux/reducers/general.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { GUIStates } from '../../constants';
import * as Actions from '../actions/general';
import ModelSingleton from '../../model/ModelSingleton';

export const GENERAL_DEFAULT_STATE = {
modelState: 'EMPTY',
error: undefined,
selected: undefined,
original_model: undefined,
model: undefined,
gui_state: GUIStates.EDIT,
composition_opened: undefined
guiState: GUIStates.EDIT,
compositionOpened: undefined
}

const reducer = ( state = {}, action ) => ({
Expand All @@ -22,8 +22,10 @@ function generalReducer (state, action) {
return {...state};
}
case Actions.LOAD_MODEL: {
// TODO: to be implemented
return {...state};
return {
...state,
modelState: 'LOADED',
}
}
case Actions.SAVE_MODEL: {
// TODO: to be implemented
Expand Down
9 changes: 4 additions & 5 deletions src/redux/store.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import _ from 'lodash';
import logger from 'redux-logger';
// import _ from 'lodash';
import reducer from "./reducers/general";
import { configureStore } from '@reduxjs/toolkit'
import pnlMiddleware from "./middleware/pnlmiddleware";
import { GENERAL_DEFAULT_STATE } from "./reducers/general";
// And use redux-batched-subscribe as an example of adding enhancers
import { batchedSubscribe } from 'redux-batched-subscribe';
// import { batchedSubscribe } from 'redux-batched-subscribe';


const INIT_STATE = { generals: GENERAL_DEFAULT_STATE };
const debounceNotify = _.debounce(notify => notify());
// const debounceNotify = _.debounce(notify => notify());

function initStore (state = INIT_STATE) {
return configureStore({
reducer,
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(pnlMiddleware),
devTools: process.env.NODE_ENV !== 'production',
state,
enhancers: [batchedSubscribe(debounceNotify)],
// enhancers: [batchedSubscribe(debounceNotify)],
});
}

Expand Down

0 comments on commit 1912490

Please sign in to comment.