Skip to main content
Glama

GridStack MCP Server

gridstack-utils.ts•17.8 kB
import { GridStackOptions, GridStackWidget } from "../types.js"; export interface GridStackCodeResult { operation: string; parameters?: any; code: string; description?: string; example?: string; notes?: string[]; } export class GridStackUtils { /** * Generate standardized GridStack code response */ generateGridStackCode(operation: string, params: any): string { const result: GridStackCodeResult = { operation, parameters: params, code: params.code || "", description: this.getOperationDescription(operation), example: this.getOperationExample(operation), notes: this.getOperationNotes(operation), }; return this.formatResponse(result); } /** * Format GridStack options for display */ formatOptions(options: GridStackOptions): string { const formatted = JSON.stringify(options, null, 2); return formatted; } /** * Validate widget configuration */ validateWidget(widget: GridStackWidget): { valid: boolean; errors: string[]; } { const errors: string[] = []; // Check required properties for positioning if ( widget.x !== undefined && (widget.x < 0 || !Number.isInteger(widget.x)) ) { errors.push("x position must be a non-negative integer"); } if ( widget.y !== undefined && (widget.y < 0 || !Number.isInteger(widget.y)) ) { errors.push("y position must be a non-negative integer"); } if ( widget.w !== undefined && (widget.w <= 0 || !Number.isInteger(widget.w)) ) { errors.push("width must be a positive integer"); } if ( widget.h !== undefined && (widget.h <= 0 || !Number.isInteger(widget.h)) ) { errors.push("height must be a positive integer"); } // Check min/max constraints if ( widget.minW !== undefined && widget.maxW !== undefined && widget.minW > widget.maxW ) { errors.push("minW cannot be greater than maxW"); } if ( widget.minH !== undefined && widget.maxH !== undefined && widget.minH > widget.maxH ) { errors.push("minH cannot be greater than maxH"); } if ( widget.w !== undefined && widget.minW !== undefined && widget.w < widget.minW ) { errors.push("width cannot be less than minW"); } if ( widget.w !== undefined && widget.maxW !== undefined && widget.w > widget.maxW ) { errors.push("width cannot be greater than maxW"); } if ( widget.h !== undefined && widget.minH !== undefined && widget.h < widget.minH ) { errors.push("height cannot be less than minH"); } if ( widget.h !== undefined && widget.maxH !== undefined && widget.h > widget.maxH ) { errors.push("height cannot be greater than maxH"); } return { valid: errors.length === 0, errors, }; } /** * Generate CSS selector validation */ validateSelector(selector: string): { valid: boolean; error?: string } { if (!selector || typeof selector !== "string") { return { valid: false, error: "Selector must be a non-empty string" }; } // Basic CSS selector validation try { document.querySelector(selector); return { valid: true }; } catch (error) { return { valid: false, error: "Invalid CSS selector format" }; } } /** * Generate responsive breakpoint validation */ validateBreakpoints(breakpoints: any[]): { valid: boolean; errors: string[]; } { const errors: string[] = []; if (!Array.isArray(breakpoints)) { errors.push("Breakpoints must be an array"); return { valid: false, errors }; } breakpoints.forEach((bp, index) => { if (typeof bp !== "object" || bp === null) { errors.push(`Breakpoint at index ${index} must be an object`); return; } if (typeof bp.w !== "number" || bp.w <= 0) { errors.push( `Breakpoint at index ${index} must have a positive width (w)` ); } if (typeof bp.c !== "number" || bp.c <= 0 || !Number.isInteger(bp.c)) { errors.push( `Breakpoint at index ${index} must have a positive integer columns (c)` ); } }); // Check for duplicate widths const widths = breakpoints .map((bp) => bp.w) .filter((w) => typeof w === "number"); const uniqueWidths = new Set(widths); if (widths.length !== uniqueWidths.size) { errors.push("Breakpoints cannot have duplicate widths"); } return { valid: errors.length === 0, errors, }; } /** * Generate HTML template for GridStack */ generateHTMLTemplate(options: GridStackOptions = {}): string { const { class: className = "", column = 12, cellHeight = "auto", margin = 10, children = [], } = options; const gridClass = `grid-stack${className ? ` ${className}` : ""}`; let html = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>GridStack Layout</title> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/gridstack@12/dist/gridstack.min.css"> <style> .grid-stack-item-content { background-color: #f8f9fa; border: 1px solid #dee2e6; border-radius: 4px; padding: 10px; } </style> </head> <body> <div class="${gridClass}">`; // Add children widgets children.forEach((widget) => { const attrs = [ `gs-x="${widget.x || 0}"`, `gs-y="${widget.y || 0}"`, `gs-w="${widget.w || 1}"`, `gs-h="${widget.h || 1}"`, ]; if (widget.id) attrs.push(`gs-id="${widget.id}"`); if (widget.minW) attrs.push(`gs-min-w="${widget.minW}"`); if (widget.maxW) attrs.push(`gs-max-w="${widget.maxW}"`); if (widget.minH) attrs.push(`gs-min-h="${widget.minH}"`); if (widget.maxH) attrs.push(`gs-max-h="${widget.maxH}"`); if (widget.locked) attrs.push(`gs-locked="true"`); if (widget.noResize) attrs.push(`gs-no-resize="true"`); if (widget.noMove) attrs.push(`gs-no-move="true"`); html += ` <div class="grid-stack-item" ${attrs.join(" ")}> <div class="grid-stack-item-content"> ${widget.content || `Widget ${widget.id || "Item"}`} </div> </div>`; }); html += ` </div> <script src="https://cdn.jsdelivr.net/npm/gridstack@12/dist/gridstack-all.js"></script> <script> const grid = GridStack.init(${JSON.stringify(options, null, 4)}); </script> </body> </html>`; return html; } /** * Generate CSS for GridStack customization */ generateCustomCSS( options: { cellHeight?: number | string; margin?: number | string; columns?: number; customColors?: { background?: string; border?: string; hover?: string; }; } = {} ): string { const { cellHeight, margin, columns = 12, customColors } = options; let css = `/* GridStack Custom Styles */\n`; // Grid container styles css += `.grid-stack {\n`; if (margin) { css += ` margin: ${ typeof margin === "number" ? `${margin}px` : margin };\n`; } css += `}\n\n`; // Cell height if (cellHeight) { css += `.grid-stack > .grid-stack-item > .grid-stack-item-content {\n`; css += ` min-height: ${ typeof cellHeight === "number" ? `${cellHeight}px` : cellHeight };\n`; css += `}\n\n`; } // Custom colors if (customColors) { css += `.grid-stack-item-content {\n`; if (customColors.background) { css += ` background-color: ${customColors.background};\n`; } if (customColors.border) { css += ` border-color: ${customColors.border};\n`; } css += `}\n\n`; if (customColors.hover) { css += `.grid-stack-item:hover .grid-stack-item-content {\n`; css += ` background-color: ${customColors.hover};\n`; css += `}\n\n`; } } // Responsive columns if (columns !== 12) { css += `/* Custom ${columns}-column grid */\n`; for (let i = 1; i <= columns; i++) { const width = ((100 / columns) * i).toFixed(6); css += `.grid-stack > .grid-stack-item[gs-w="${i}"] { width: ${width}%; }\n`; css += `.grid-stack > .grid-stack-item[gs-x="${i - 1}"] { left: ${( (100 / columns) * (i - 1) ).toFixed(6)}%; }\n`; } css += `\n`; } return css; } private getOperationDescription(operation: string): string { const descriptions: Record<string, string> = { init: "Initialize a new GridStack instance with the specified options", addWidget: "Add a new widget to the grid at the specified position", removeWidget: "Remove a widget from the grid", updateWidget: "Update widget properties (position, size, constraints)", moveWidget: "Move a widget to a new position", resizeWidget: "Resize a widget to new dimensions", compact: "Compact the grid layout to remove gaps", float: "Enable or disable floating widget mode", column: "Change the number of columns in the grid", cellHeight: "Update the height of grid cells", margin: "Update the margin/gap between grid items", batchUpdate: "Enable batch update mode for efficient multiple operations", save: "Serialize the current grid layout to JSON", load: "Load a grid layout from JSON data", enable: "Enable or disable grid interactions", destroy: "Destroy the grid instance and clean up", getGridItems: "Get all grid items (widgets)", setResponsive: "Configure responsive breakpoints", willItFit: "Check if a widget will fit at the specified position", isAreaEmpty: "Check if a grid area is empty", getCellHeight: "Get the current cell height", getCellFromPixel: "Convert pixel coordinates to grid cell position", addEventListener: "Add an event listener for grid events", removeEventListener: "Remove an event listener", makeWidget: "Convert an existing DOM element into a grid widget", removeAll: "Remove all widgets from the grid", getMargin: "Get current margin values", getColumn: "Get current number of columns", getFloat: "Get current float mode state", addGrid: "Create a new grid with options and children (static method)", }; return descriptions[operation] || "GridStack operation"; } private getOperationExample(operation: string): string { const examples: Record<string, string> = { init: `// Initialize with basic options const grid = GridStack.init({ column: 12, cellHeight: 'auto', margin: 10, float: false });`, addWidget: `// Add a widget with content grid.addWidget({ x: 0, y: 0, w: 3, h: 2, content: '<div>My Widget</div>', id: 'widget1' });`, removeWidget: `// Remove widget by selector grid.removeWidget('#widget1');`, updateWidget: `// Update widget properties grid.update('#widget1', { w: 4, h: 3, locked: true });`, moveWidget: `// Move widget to new position grid.move('#widget1', 2, 1);`, resizeWidget: `// Resize widget grid.resize('#widget1', 4, 3);`, save: `// Save layout to JSON const layout = grid.save(true); localStorage.setItem('layout', JSON.stringify(layout));`, load: `// Load layout from JSON const layout = JSON.parse(localStorage.getItem('layout')); grid.load(layout);`, }; return examples[operation] || `// ${operation} example`; } private getOperationNotes(operation: string): string[] { const notes: Record<string, string[]> = { init: [ "Grid container must have 'grid-stack' class", "Include GridStack CSS and JS files", "Options are merged with defaults", ], addWidget: [ "Widget will be auto-positioned if x,y not specified", "Triggers 'added' event by default", "Returns the created DOM element", ], removeWidget: [ "Can accept element, selector, or GridStackNode", "Set removeDOM=false to keep in DOM", "Triggers 'removed' event by default", ], batchUpdate: [ "Use before multiple operations for efficiency", "Call with false to end batch mode", "Only one 'change' event fired at end", ], save: [ "Returns array of widget configurations", "saveContent=true includes HTML content", "saveGridOpt=true includes grid options", ], load: [ "Accepts array of widget configs or JSON string", "addAndRemove=true syncs with current widgets", "Existing widgets not in layout are removed", ], float: [ "Float mode allows widgets to move up to fill gaps", "Disable for more predictable layouts", "Can be toggled at runtime", ], column: [ "Changing columns re-layouts existing widgets", "CSS must support the new column count", "Use 'auto' for nested grids", ], }; return notes[operation] || []; } private formatResponse(result: GridStackCodeResult): string { let response = `## GridStack ${result.operation} ${result.description} ### Generated Code: \`\`\`javascript ${result.code} \`\`\``; if (result.parameters && Object.keys(result.parameters).length > 0) { response += ` ### Parameters: \`\`\`json ${JSON.stringify(result.parameters, null, 2)} \`\`\``; } if (result.example) { response += ` ### Example: \`\`\`javascript ${result.example} \`\`\``; } if (result.notes && result.notes.length > 0) { response += ` ### Notes: ${result.notes.map((note) => `- ${note}`).join("\n")}`; } return response; } /** * Generate framework-specific integration code */ generateFrameworkIntegration(framework: "react" | "vue" | "angular"): string { switch (framework) { case "react": return this.generateReactIntegration(); case "vue": return this.generateVueIntegration(); case "angular": return this.generateAngularIntegration(); default: return "Framework not supported"; } } private generateReactIntegration(): string { return `// React GridStack Component import React, { useEffect, useRef } from 'react'; import { GridStack } from 'gridstack'; import 'gridstack/dist/gridstack.min.css'; const GridStackComponent = ({ options, children, onChange }) => { const gridRef = useRef(null); const gridInstanceRef = useRef(null); useEffect(() => { // Initialize GridStack gridInstanceRef.current = GridStack.init(options, gridRef.current); // Add event listeners if (onChange) { gridInstanceRef.current.on('change', onChange); } return () => { // Cleanup gridInstanceRef.current?.destroy(); }; }, []); return ( <div ref={gridRef} className="grid-stack"> {children} </div> ); }; // Usage const App = () => { const handleChange = (event, items) => { console.log('Grid changed:', items); }; return ( <GridStackComponent options={{ column: 12, cellHeight: 'auto' }} onChange={handleChange} > <div className="grid-stack-item" gs-x="0" gs-y="0" gs-w="3" gs-h="2"> <div className="grid-stack-item-content">Widget 1</div> </div> </GridStackComponent> ); };`; } private generateVueIntegration(): string { return `<!-- Vue GridStack Component --> <template> <div ref="gridRef" class="grid-stack"> <slot /> </div> </template> <script> import { GridStack } from 'gridstack'; import 'gridstack/dist/gridstack.min.css'; export default { name: 'GridStackComponent', props: { options: { type: Object, default: () => ({}) } }, data() { return { grid: null }; }, mounted() { this.grid = GridStack.init(this.options, this.$refs.gridRef); // Add event listeners this.grid.on('change', (event, items) => { this.$emit('change', event, items); }); }, beforeUnmount() { this.grid?.destroy(); } }; </script> <!-- Usage --> <template> <GridStackComponent :options="{ column: 12, cellHeight: 'auto' }" @change="handleChange" > <div class="grid-stack-item" gs-x="0" gs-y="0" gs-w="3" gs-h="2"> <div class="grid-stack-item-content">Widget 1</div> </div> </GridStackComponent> </template>`; } private generateAngularIntegration(): string { return `// Angular GridStack Component import { Component, ElementRef, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core'; import { GridStack } from 'gridstack'; @Component({ selector: 'app-gridstack', template: '<ng-content></ng-content>', styleUrls: ['./gridstack.component.css'] }) export class GridStackComponent implements OnInit, OnDestroy { @Input() options: any = {}; @Output() change = new EventEmitter<any>(); private grid: GridStack | null = null; constructor(private el: ElementRef) {} ngOnInit() { this.el.nativeElement.classList.add('grid-stack'); this.grid = GridStack.init(this.options, this.el.nativeElement); this.grid.on('change', (event, items) => { this.change.emit({ event, items }); }); } ngOnDestroy() { this.grid?.destroy(); } } // Usage in template /* <app-gridstack [options]="{ column: 12, cellHeight: 'auto' }" (change)="onGridChange($event)"> <div class="grid-stack-item" gs-x="0" gs-y="0" gs-w="3" gs-h="2"> <div class="grid-stack-item-content">Widget 1</div> </div> </app-gridstack> */`; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/raghavsharma-simpplr/gridstack-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server