1
\$\begingroup\$

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 , 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
        }
    }));
\$\endgroup\$
1
\$\begingroup\$

A couple of minor remarks

The active layout is used in various locations. Perhaps you should create a getter for it to avoid redundant code.

let activeLayout = getRoot(self).stage.activeLayout;

Writing in a fluent style is a delight for the eye, but it should not come at too much cost in code quality. getParent(self, 2) should have been set in a local variable.

return getParent(self, 2).selection === self && getParent(self, 2).stage.dragging

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.