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
- 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[];
}
- 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 = [];
}
}
- Stack Class:
Stack
manages a collection of StackNode
s, 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;
}
- 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');
}
}
}
- 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);
}
- 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);
}
- 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);
}
}
- Validation Errors:
The getValidationErrors
method returns an array of validation error messages.
Work done:
- Tree Structure Implementation: Successfully implemented a tree structure to manage the hierarchical arrangement of bricks in the stack.
- Validation Logic: Implemented validation logic to ensure the integrity of connections and data types between bricks.
- 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!