Background: I'm trying to create functionality that would display two layouts of the same data. The position and size each data record must be independently saved per layout and each layout will have different functionality and be displayed in a different way (one will display the records as squares, the other circles).
This is my first time using mobx-state-tree, I would appreciate any thoughts on the models and the overall logic. Mostly I'm worried that the models are too tightly coupled and that the structure isn't correct.
This RootStore
holds an array of Records
, a reference to StageStore
and a reference to the selected Record
export const RootStore = types
.model("RootStore", {
records: types.array(Record),
selection: types.maybeNull(types.reference(Record)),
stage: types.optional(StageStore, {})
})
.actions(self => ({
setSelected(selection){
self.selection = selection
},
addRecord(){
const record = Record.create({id: v4()});
record.addLayout(Layout.create({name: 'adjacency', id: 'adjacency'}));
record.addLayout(Layout.create({name: 'plan', id: 'plan'}));
self.records.push(record);
return record;
},
createRecord(x, y){
const record = self.addRecord();
record.setPosition(x, y);
self.setSelected(record);
}
}));
StageStore
holds information about the scale and translate of the SVG element acting as the stage. It also holds the active Layout
key. This part I'm most unsure about because it tightly couples StageStore
with RecordStore
.
const position = types
.model({
x: types.optional(types.number, 0),
y: types.optional(types.number, 0),
})
.actions(self => ({
setPosition(x, y){
self.x = x;
self.y = y;
}
}));
export const StageStore = types
.model("StageStore", {
translate: types.optional(types.compose(position), {}),
scale: types.optional(types.number, 1),
activeLayout: types.optional(types.string, 'adjacency')
})
.actions(self => ({
setActiveLayout(id){
self.activeLayout = id;
},
setScale(scale){
self.scale = scale;
},
}));
Holds information about each record and a reference to the two layouts.
export const Record = types
.model('Record', {
id: types.identifier,
name: types.optional(types.string, ""),
layouts: types.map(Layout),
})
.views(self => ({
get isSelected(){
return getParent(self, 2).selection === self
},
get isDragging(){
return getParent(self, 2).selection === self && getParent(self, 2).stage.dragging
},
get x(){
let activeLayout = getRoot(self).stage.activeLayout;
return self.layouts.get(activeLayout).x;
},
get y(){
let activeLayout = getRoot(self).stage.activeLayout;
return self.layouts.get(activeLayout).y;
},
get width(){
let activeLayout = getRoot(self).stage.activeLayout;
return self.layouts.get(activeLayout).width;
},
get height(){
let activeLayout = getRoot(self).stage.activeLayout;
return self.layouts.get(activeLayout).height;
},
get fill(){
let activeLayout = getRoot(self).stage.activeLayout;
return self.layouts.get(activeLayout).fill;
},
get rx(){
return Math.sqrt(self.height * self.width / 3.14);
}
}))
.volatile(self => ({
SVGElement: null
}))
.actions(self => ({
addLayout(layout){
self.layouts.put(layout);
},
setSVGElement(el){
self.SVGElement = el;
},
setPosition(x, y){
let activeLayout = getRoot(self).stage.activeLayout;
self.layouts.get(activeLayout).setPosition(x, y);
},
setName(newName) {
self.name = newName
}
}));
Holds positional data per each different Layout
.
export const Layout = types
.model('Layout', {
id: types.identifier,
name: types.optional(types.string, ""),
x: types.optional(types.number, 0),
y: types.optional(types.number, 0),
height: types.optional(types.number, 50),
width: types.optional(types.number, 50),
rx: types.optional(types.number, 0),
fill: types.optional(types.string, "transparent")
})
.views(self => ({
}))
.actions(self => ({
setPosition(x, y){
self.x = x;
self.y = y;
},
setName(newName) {
self.name = newName
}
}));