GSoC 2024 with Sugar Labs | Week 5 & 6 | Music Blocks (v4) project updates


Hi everyone, welcome to the GSoC’24 Blogs.

I am documenting my complete journey as a contributor for Google Summer of Code 2024 under Sugar Labs for the Music Blocks (v4) project – Masonry Framework.
To get more information about the project, you can read all my blogs here.

My last blog mentioned the Dummy stack and the workspace #409.

In this blog, I would like to share what I have done in the last 2 weeks and project updates.

Implementing the Stack Tree Structure

To manage the hierarchical arrangement of bricks, I designed and implemented a stack tree structure. Here’s a breakdown of the key elements in the code(#410) and their functionalities:

Code Explanation

  1. Interfaces and Classes:

The code defines interfaces (IStackNode and IStack) and classes (StackNode and Stack) to represent the hierarchical stack structure of bricks.

/**
 * @interface IStackNode
 * Represents a node in the stack structure.
 */
export interface IStackNode {
    /** The brick model associated with this node */
    brick: BrickModelData | BrickModelExpression | BrickModelStatement | BrickModelBlock;
    /** Child nodes of this node */
    children: IStackNode[];
}

/**
 * @interface IStack
 * Represents the stack structure for managing bricks.
 */
export interface IStack {
    /** Unique identifier for the stack */
    id: string;
    /** Root nodes of the stack */
    rootNodes: IStackNode[];

    /** Methods for stack operations */
    validate(): boolean;
    addNode(node: IStackNode, parentId?: string): void;
    removeNode(id: string): void;
    moveNode(id: string, newParentId: string, newIndex: number): void;
    collapse(id: string): void;
    expand(id: string): void;
    getValidationErrors(): string[];
}
  1. StackNode Class:

StackNode represents a node in the stack, holding a reference to a brick and its children.

/**
 * @class StackNode
 * Implements the IStackNode interface.
 */
class StackNode implements IStackNode {
    brick: BrickModelData | BrickModelExpression | BrickModelStatement | BrickModelBlock;
    children: IStackNode[];

    /**
     * Creates a new StackNode.
     * @param {BrickModelData | BrickModelExpression | BrickModelStatement | BrickModelBlock} brick - The brick model for this node.
     */
    constructor(
        brick: BrickModelData | BrickModelExpression | BrickModelStatement | BrickModelBlock,
    ) {
        this.brick = brick;
        this.children = [];
    }
}
  1. Stack Class:

Stack manages a collection of StackNodes, providing methods to add, remove, move, collapse, expand nodes, and validate the stack structure.

/**
 * @class Stack
 * Implements the IStack interface.
 */
class Stack implements IStack {
    id: string;
    rootNodes: IStackNode[];
    private _validationDisabled = false;

    /**
     * Creates a new Stack.
     * @param {string} id - The unique identifier for this stack.
     */
    constructor(id: string) {
        this.id = id;
        this.rootNodes = [];
    }

    validate(): boolean {
        if (this._validationDisabled) return true;
        return this.getValidationErrors().length === 0;
    }
  1. Adding a Node:

The addNode method allows adding a new node to the stack.

    addNode(node: IStackNode, parentId?: string): void {
        if (!parentId) {
            this.rootNodes.push(node);
        } else {
            const parent = this.findNode(parentId);
            if (parent && (parent.brick instanceof BrickModelBlock || parent.brick instanceof BrickModelExpression)) {
                parent.children.push(node);
                this.updateNestExtent(parent);
            } else {
                throw new Error('Parent node not found or cannot have children');
            }
        }
    }
  1. Removing a Node:

The removeNode method removes a node from the stack by its ID.

    removeNode(id: string): void {
        const remove = (nodes: IStackNode[]): boolean => {
            for (let i = 0; i < nodes.length; i++) {
                if (nodes[i].brick.uuid === id) {
                    nodes.splice(i, 1);
                    return true;
                }
                if (nodes[i].children.length > 0 && remove(nodes[i].children)) {
                    this.updateNestExtent(nodes[i]);
                    return true;
                }
            }
            return false;
        };
        remove(this.rootNodes);
    }
  1. Moving a Node:

The moveNode method moves a node to a new position in the stack.

    moveNode(id: string, newParentId: string, newIndex: number): void {
        const node = this.findNode(id);
        if (!node) throw new Error('Node not found');

        this.removeNode(id);
        const newParent = this.findNode(newParentId);
        if (!newParent) throw new Error('New parent node not found');

        if (!(newParent.brick instanceof BrickModelBlock) && !(newParent.brick instanceof BrickModelExpression)) {
            throw new Error('New parent cannot have children');
        }

        newParent.children.splice(newIndex, 0, node);
        this.updateNestExtent(newParent);
    }
  1. Collapsing and Expanding a Node:

The collapse and expand methods allow collapsing and expanding block nodes. ( I will update these to new names decided : fold and unfold.)

    collapse(id: string): void {
        const node = this.findNode(id);
        if (node && node.brick instanceof BrickModelBlock) {
            node.brick.collapsed = true;
            this.updateNestExtent(node);
        }
    }

    expand(id: string): void {
        const node = this.findNode(id);
        if (node && node.brick instanceof BrickModelBlock) {
            node.brick.collapsed = false;
            this.updateNestExtent(node);
        }
    }
  1. Validation Errors:

The getValidationErrors method returns an array of validation error messages.

Work done:

  1. Tree Structure Implementation: Successfully implemented a tree structure to manage the hierarchical arrangement of bricks in the stack.
  2. Validation Logic: Implemented validation logic to ensure the integrity of connections and data types between bricks.
  3. Stack Rendering: Rendered a stack of bricks by taking reference from #361.

Next Steps

  • Implementing the bricks: Focus on implementing the bricks and completing the brick library and features (ex. argument/label in bricks etc)

Conclusion

These two weeks have been productive, and I am excited about the progress made on the stack tree structure. Stay tuned for more updates as I continue working on enhancing Music Blocks v4.

Also, don’t forget to drop a star on the project GitHub repo! ⭐
Link: https://github.com/sugarlabs/musicblocks-v4

You can connect with me on LinkedIn. Additionally, you can follow me on GitHub and Twitter, it will be appreciated.

Thank you for reading!