Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
1d6fa11
fix(masonry): path generation for compound bricks is fixed
saumyashahi Jun 24, 2025
8ff5887
feat(masonry): add text measurement utility and dynamic label width c…
saumyashahi Jun 27, 2025
400dba7
feat(masonry): add origin calculation logic for nested and argumnent …
saumyashahi Jun 27, 2025
829bcf9
feat(masonry): add brick tree rendereing system for connection types
saumyashahi Jun 27, 2025
68bd13a
feat(masonry): add test story for argument positioning with different…
saumyashahi Jun 27, 2025
062f33b
feat(masonry): add argument brick management with dynamic positioning
saumyashahi Jun 27, 2025
a95bf7b
feat(masonry): add brick factory for creating different brick types w…
saumyashahi Jun 27, 2025
f2b309d
feat(masonry): add TreeView stories for different brick layouts and c…
saumyashahi Jun 27, 2025
6ffaf38
chore(masonry): remove debug statements and clean up code comments
saumyashahi Jun 27, 2025
6461ec7
fix(masonry): all linting errors and warnings across masonry module
saumyashahi Jun 28, 2025
2271109
refactor(masonry): [brick] seperate text measurement utility
Karan-Palan Jun 29, 2025
0f51edf
chore(masonry): [tree] fix story filename typo
Karan-Palan Jun 29, 2025
84ba115
feat(masonry): [brick] create wrapper compoenent
Karan-Palan Jun 29, 2025
723bea0
feat(masonry): [brick] update other compoenents to reuse wrapper comp…
Karan-Palan Jun 29, 2025
9321df1
refactor(masonry): move @types to /src and update imports
Karan-Palan Jul 2, 2025
702bed0
refactor(masonry): [tower] refactor tree into tower and workspace man…
Karan-Palan Jul 2, 2025
051d754
feat(masonry): [tower] seperate tower logic (one connected stack), si…
Karan-Palan Jul 2, 2025
648fbb3
feat(masonry): [workspace] create Workspace manager which perfroms CR…
Karan-Palan Jul 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type TBrickRenderProps = {
colorBg: TColor;
colorFg: TColor;
strokeColor: TColor;
strokeWidth: number; //remove
strokeWidth: number;
scale: number;
shadow: boolean;
tooltip?: string;
Expand Down
23 changes: 23 additions & 0 deletions modules/masonry/src/@types/tower.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/** A simple Cartesian point */
export type TPoint = { x: number; y: number };

/** All supported physical notch pairings */
export type TNotchType = 'top-bottom' | 'right-left' | 'left-right' | 'nested';

/** A single connection‑point centroid */
export type TConnectionPoint = { x: number; y: number };

/** One logical connection between two bricks */
export type TBrickConnection = {
from: string; // uuid of source brick
to: string; // uuid of destination brick
fromNotchId: string; // e.g. "right_0"
toNotchId: string; // e.g. "left"
type: TNotchType;
};

/** Validation result for attempted connections */
export type TConnectionValidation = {
isValid: boolean;
reason?: string;
};
54 changes: 48 additions & 6 deletions modules/masonry/src/brick/model/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import type {
TBrickRenderPropsExpression,
IBrickCompound,
TBrickRenderPropsCompound,
} from '../@types/brick';
} from '../../@types/brick';
import type { TConnectionPoints as TCP } from '../../tree/model/model';
import { generateBrickData } from '../utils/path';
import type { TInputUnion } from '../utils/path';
import { getLabelWidth } from '../utils/textMeasurement';

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is temporary to render stuff right now, will be updated later, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I figured the previous method to determine the label width in justin's model wasn't rendering correct, so I used this one, we can enhance this further in time as required. Right now for rendering in storybooks this method works fine.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The model does not decide the label length, the component does

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll update the code

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was no function in the model to determine the label width. That was done by the react component itself. The model is only responsible to determine and pass down the props and has no connection(functions related to) to rendering the brick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had temporarily added estimateTextWidth(label) inside the model just to visually unblock layout issues in Storybook for now. But yes, this is something we can definitely clean up by moving all layout-related measurements (like label width) to the React view layer

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was no function in the model to determine the label width. That was done by the react component itself. The model is only responsible to determine and pass down the props and has no connection(functions related to) to rendering the brick.

image

WhatsApp Image 2025-06-27 at 22 37 54_f25bdca2

initially the label widths were being calculated in a fixed way for all label width, no function in the model to determine the label width hence it did not return correct bounding boxes and the position for the origin of the argument bricks for short and long labels did not render correctly. So I made a function to calculate label width

image

Now it is working correctly .

export abstract class BrickModel implements IBrick {
protected _uuid: string;
Expand Down Expand Up @@ -134,6 +135,22 @@ export abstract class BrickModel implements IBrick {

/** Must assemble the full render props for this brick. */
public abstract get renderProps(): TBrickRenderProps;

/** Must update geometry when label or other properties change. */
public abstract updateGeometry(): void;

get label(): string {
return this._label;
}

set label(value: string) {
this._label = value;
this.updateGeometry();
}

get labelType(): 'text' | 'glyph' | 'icon' | 'thumbnail' {
return this._labelType;
}
}

/**
Expand Down Expand Up @@ -184,14 +201,12 @@ export class SimpleBrick extends BrickModel implements IBrickSimple {
type: 'type1',
strokeWidth: this._strokeWidth,
scaleFactor: this._scale,
bBoxLabel: { w: this._label.length * 8, h: 20 },
bBoxLabel: { w: getLabelWidth(this._label), h: 20 },
bBoxArgs: this._bboxArgs,
hasNotchAbove: this._topNotch,
hasNotchBelow: this._bottomNotch,
};
const data = generateBrickData(config);

// use the public setters
this.connectionPoints = data.connectionPoints;
this.boundingBox = data.boundingBox;
}
Expand Down Expand Up @@ -260,7 +275,7 @@ export class ExpressionBrick extends BrickModel implements IBrickExpression {
type: 'type2',
strokeWidth: this._strokeWidth,
scaleFactor: this._scale,
bBoxLabel: { w: this._label.length * 8, h: 20 },
bBoxLabel: { w: getLabelWidth(this._label), h: 20 },
bBoxArgs: this._bboxArgs,
};
const data = generateBrickData(config);
Expand Down Expand Up @@ -341,7 +356,7 @@ export default class CompoundBrick extends BrickModel implements IBrickCompound
type: 'type3',
strokeWidth: this._strokeWidth,
scaleFactor: this._scale,
bBoxLabel: { w: this._label.length * 8, h: 20 },
bBoxLabel: { w: getLabelWidth(this._label), h: 20 },
bBoxArgs: this._bboxArgs,
hasNotchAbove: this._topNotch,
hasNotchBelow: this._bottomNotch,
Expand Down Expand Up @@ -372,6 +387,33 @@ export default class CompoundBrick extends BrickModel implements IBrickCompound
this._bboxNest = extents;
}

/**
* Recursively update bounding box and connection points to fit nested children.
* Call this after all children are attached, before rendering.
*/
public updateLayoutWithChildren(nestedChildren: BrickModel[]): void {
// If there are nested children, calculate the total bounding box
if (nestedChildren && nestedChildren.length > 0) {
// Calculate the bounding box that fits all nested children
let _minX = 0,
_minY = 0,
maxX = 0,
maxY = 0;
nestedChildren.forEach((child) => {
const bbox = child.boundingBox;
// For simplicity, assume children are stacked vertically for now
maxY += bbox.h;
maxX = Math.max(maxX, bbox.w);
});
// Expand this brick's bboxNest to fit the children
this._bboxNest = [{ w: maxX, h: maxY }];
} else {
this._bboxNest = [];
}
// Update geometry with new bboxNest
this.updateGeometry();
}

public override get renderProps(): TBrickRenderPropsCompound {
return {
...this.getCommonRenderProps(),
Expand Down
133 changes: 133 additions & 0 deletions modules/masonry/src/brick/utils/brickFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// brickFactory.ts
import { SimpleBrick, ExpressionBrick } from '../model/model';
import CompoundBrick from '../model/model';
import type { TBrickType, TColor, TExtent } from '../../@types/brick';

let idCounter = 0;
function generateUUID(prefix: string): string {
return `${prefix}_${++idCounter}`;
}

// Default colors
const defaultColors = {
simple: {
colorBg: '#bbdefb' as TColor,
colorFg: '#222' as TColor,
strokeColor: '#1976d2' as TColor,
},
expression: {
colorBg: '#b2fab4' as TColor,
colorFg: '#222' as TColor,
strokeColor: '#2e7d32' as TColor,
},
compound: {
colorBg: '#b9f6ca' as TColor,
colorFg: '#222' as TColor,
strokeColor: '#43a047' as TColor,
},
};

const defaultLabelType = 'text' as const;
const defaultScale = 1;
const defaultBBoxArgs: TExtent[] = [{ w: 40, h: 20 }];

export function createSimpleBrick(
overrides: Partial<ConstructorParameters<typeof SimpleBrick>[0]> = {},
) {
const idx = idCounter + 1;
// By default, SimpleBrick has two argument slots (for arguments/inputs)
return new SimpleBrick({
uuid: generateUUID('simple'),
name: overrides.name ?? `Simple${idx}`,
label: overrides.label ?? `Simple${idx}`,
labelType: overrides.labelType ?? defaultLabelType,
colorBg: overrides.colorBg ?? defaultColors.simple.colorBg,
colorFg: overrides.colorFg ?? defaultColors.simple.colorFg,
strokeColor: overrides.strokeColor ?? defaultColors.simple.strokeColor,
shadow: overrides.shadow ?? false,
scale: overrides.scale ?? defaultScale,
bboxArgs: overrides.bboxArgs ?? [
{ w: 40, h: 20 },
{ w: 40, h: 20 },
],
topNotch: overrides.topNotch ?? true,
bottomNotch: overrides.bottomNotch ?? true,
tooltip: overrides.tooltip,
...overrides,
});
}

// ExpressionBrick is used as an argument value, not as an argument-receiving brick
export function createExpressionBrick(
overrides: Partial<ConstructorParameters<typeof ExpressionBrick>[0]> = {},
) {
const idx = idCounter + 1;
return new ExpressionBrick({
uuid: generateUUID('expr'),
name: overrides.name ?? `Expr${idx}`,
label: overrides.label ?? `Expr${idx}`,
labelType: overrides.labelType ?? defaultLabelType,
colorBg: overrides.colorBg ?? defaultColors.expression.colorBg,
colorFg: overrides.colorFg ?? defaultColors.expression.colorFg,
strokeColor: overrides.strokeColor ?? defaultColors.expression.strokeColor,
shadow: overrides.shadow ?? false,
scale: overrides.scale ?? defaultScale,
bboxArgs: overrides.bboxArgs ?? [{ w: 40, h: 20 }],
value: overrides.value,
isValueSelectOpen: overrides.isValueSelectOpen ?? false,
tooltip: overrides.tooltip,
...overrides,
});
}

export function createCompoundBrick(
overrides: Partial<ConstructorParameters<typeof CompoundBrick>[0]> = {},
) {
const idx = idCounter + 1;
return new CompoundBrick({
uuid: generateUUID('compound'),
name: overrides.name ?? `Compound${idx}`,
label: overrides.label ?? `Compound${idx}`,
labelType: overrides.labelType ?? defaultLabelType,
colorBg: overrides.colorBg ?? defaultColors.compound.colorBg,
colorFg: overrides.colorFg ?? defaultColors.compound.colorFg,
strokeColor: overrides.strokeColor ?? defaultColors.compound.strokeColor,
shadow: overrides.shadow ?? false,
scale: overrides.scale ?? defaultScale,
bboxArgs: overrides.bboxArgs ?? defaultBBoxArgs,
bboxNest: overrides.bboxNest ?? [],
topNotch: overrides.topNotch ?? true,
bottomNotch: overrides.bottomNotch ?? true,
isFolded: overrides.isFolded ?? false,
tooltip: overrides.tooltip,
...overrides,
});
}

export function resetFactoryCounter() {
idCounter = 0;
}

export function getFactoryCounter() {
return idCounter;
}

export function createBrick(
type: TBrickType,
overrides: Partial<
| ConstructorParameters<typeof SimpleBrick>[0]
| ConstructorParameters<typeof ExpressionBrick>[0]
| ConstructorParameters<typeof CompoundBrick>[0]
> = {},
) {
switch (type) {
case 'Simple':
return createSimpleBrick(overrides);
case 'Expression':
return createExpressionBrick(overrides);
case 'Compound':
return createCompoundBrick(overrides);
default:
throw new Error(`Unsupported brick type: ${type}`);
}
}
16 changes: 16 additions & 0 deletions modules/masonry/src/brick/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export {
createSimpleBrick,
createExpressionBrick,
createCompoundBrick,
createBrick,
resetFactoryCounter,
getFactoryCounter,
} from './brickFactory';
export { generateBrickData } from './path';
export type { TInputUnion } from './path';
export {
measureTextWidth,
estimateTextWidth,
getLabelWidth,
measureLabel,
} from './textMeasurement';
Loading