mcp-dndgrid 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PROJECT_SUMMARY.md +482 -0
- package/QUICKSTART.md +223 -0
- package/README.md +365 -0
- package/STATUS.md +315 -0
- package/USAGE_GUIDE.md +547 -0
- package/dist/chunk-CMGEAPA5.js +157 -0
- package/dist/chunk-CMGEAPA5.js.map +1 -0
- package/dist/chunk-QZHBI6ZI.js +5281 -0
- package/dist/chunk-QZHBI6ZI.js.map +1 -0
- package/dist/chunk-SEGVTWSK.js +44 -0
- package/dist/chunk-SEGVTWSK.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +248012 -0
- package/dist/index.js.map +1 -0
- package/dist/stdio-FWYJXSU7.js +101 -0
- package/dist/stdio-FWYJXSU7.js.map +1 -0
- package/dist/template-JDMAVVX7.js +9 -0
- package/dist/template-JDMAVVX7.js.map +1 -0
- package/examples/claude_desktop_config.example.json +12 -0
- package/examples/example-complex-editor.tsx +107 -0
- package/examples/example-dashboard.tsx +65 -0
- package/examples/example-ide-layout.tsx +53 -0
- package/examples/test-generator.ts +37 -0
- package/examples/test-parser.ts +121 -0
- package/examples/test-scenarios.md +496 -0
- package/package.json +42 -0
- package/src/index.ts +16 -0
- package/src/server.ts +314 -0
- package/src/tools/analyze-layout.ts +193 -0
- package/src/tools/apply-template.ts +125 -0
- package/src/tools/generate-layout.ts +235 -0
- package/src/tools/interactive-builder.ts +100 -0
- package/src/tools/validate-layout.ts +113 -0
- package/src/types/layout.ts +48 -0
- package/src/types/template.ts +181 -0
- package/src/utils/ast-parser.ts +264 -0
- package/src/utils/code-generator.ts +123 -0
- package/src/utils/layout-analyzer.ts +105 -0
- package/src/utils/layout-builder.ts +127 -0
- package/src/utils/validator.ts +263 -0
- package/stderr.log +1 -0
- package/stdout.log +0 -0
- package/test-mcp.js +27 -0
- package/tsconfig.json +29 -0
- package/tsup.config.ts +16 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { LayoutBuilder, L } from '../utils/layout-builder.js';
|
|
3
|
+
import { CodeGenerator } from '../utils/code-generator.js';
|
|
4
|
+
import { LayoutAnalyzer } from '../utils/layout-analyzer.js';
|
|
5
|
+
import { Validator } from '../utils/validator.js';
|
|
6
|
+
import type { LayoutNode } from '../types/layout.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Input schema for generate-layout tool
|
|
10
|
+
*/
|
|
11
|
+
export const GenerateLayoutInputSchema = z.object({
|
|
12
|
+
description: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe(
|
|
15
|
+
'Natural language description of desired layout (e.g., "3-panel IDE layout with sidebar, editor, and terminal")'
|
|
16
|
+
),
|
|
17
|
+
components: z.array(z.string()).describe('List of component names to place in the layout'),
|
|
18
|
+
containerWidth: z.number().optional().default(1200).describe('Container width in pixels'),
|
|
19
|
+
containerHeight: z.number().optional().default(800).describe('Container height in pixels'),
|
|
20
|
+
framework: z
|
|
21
|
+
.enum(['react', 'nextjs-app', 'nextjs-pages'])
|
|
22
|
+
.optional()
|
|
23
|
+
.default('nextjs-app')
|
|
24
|
+
.describe('Target framework (affects "use client" directive)'),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type GenerateLayoutInput = z.infer<typeof GenerateLayoutInputSchema>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* generate-layout tool implementation
|
|
31
|
+
*/
|
|
32
|
+
export async function generateLayout(args: GenerateLayoutInput) {
|
|
33
|
+
const { description, components, containerWidth, containerHeight, framework } = args;
|
|
34
|
+
|
|
35
|
+
// Analyze description to determine layout pattern
|
|
36
|
+
const pattern = analyzeDescription(description, components);
|
|
37
|
+
|
|
38
|
+
// Build layout based on pattern
|
|
39
|
+
let layoutNode: LayoutNode;
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
layoutNode = buildLayoutFromPattern(pattern, components);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: 'text' as const,
|
|
48
|
+
text: `❌ Failed to generate layout: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Create LayoutTree
|
|
55
|
+
const layout = new LayoutBuilder(containerWidth, containerHeight)
|
|
56
|
+
.setRoot(layoutNode)
|
|
57
|
+
.build();
|
|
58
|
+
|
|
59
|
+
// Generate code
|
|
60
|
+
const generator = new CodeGenerator({
|
|
61
|
+
framework,
|
|
62
|
+
width: containerWidth,
|
|
63
|
+
height: containerHeight,
|
|
64
|
+
});
|
|
65
|
+
const code = generator.generate(layout);
|
|
66
|
+
|
|
67
|
+
// Analyze metadata
|
|
68
|
+
const metadata = LayoutAnalyzer.calculateMetadata(layout);
|
|
69
|
+
|
|
70
|
+
// Validate
|
|
71
|
+
const validator = new Validator();
|
|
72
|
+
const validation = validator.validate(layout, false);
|
|
73
|
+
|
|
74
|
+
// Format output
|
|
75
|
+
let output = `# Generated DndGrid Layout\n\n`;
|
|
76
|
+
output += `**Description:** ${description}\n\n`;
|
|
77
|
+
|
|
78
|
+
output += `## Layout Metadata\n\n`;
|
|
79
|
+
output += `- **Items:** ${metadata.itemCount}\n`;
|
|
80
|
+
output += `- **Splits:** ${metadata.splitCount}\n`;
|
|
81
|
+
output += `- **Max Depth:** ${metadata.maxDepth}\n`;
|
|
82
|
+
output += `- **Performance:** ${metadata.estimatedPerformance}\n`;
|
|
83
|
+
output += `- **Pattern:** ${pattern.type}\n\n`;
|
|
84
|
+
|
|
85
|
+
if (validation.warnings.length > 0) {
|
|
86
|
+
output += `## Warnings\n\n`;
|
|
87
|
+
validation.warnings.forEach((warn, i) => {
|
|
88
|
+
output += `${i + 1}. ${warn.message}\n`;
|
|
89
|
+
});
|
|
90
|
+
output += '\n';
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (validation.suggestions.length > 0) {
|
|
94
|
+
output += `## Suggestions\n\n`;
|
|
95
|
+
validation.suggestions.forEach((sugg, i) => {
|
|
96
|
+
output += `${i + 1}. ${sugg}\n`;
|
|
97
|
+
});
|
|
98
|
+
output += '\n';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
output += `## Generated Code\n\n\`\`\`tsx\n${code}\`\`\`\n`;
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: 'text' as const, text: output }],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Layout patterns
|
|
110
|
+
*/
|
|
111
|
+
type LayoutPattern =
|
|
112
|
+
| { type: 'ide'; sidebar: string; main: string; bottom: string }
|
|
113
|
+
| { type: 'dashboard'; items: string[] }
|
|
114
|
+
| { type: 'three-column'; left: string; center: string; right: string }
|
|
115
|
+
| { type: 'split'; left: string; right: string; direction: 'horizontal' | 'vertical' }
|
|
116
|
+
| { type: 'custom'; items: string[] };
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Analyze description to determine layout pattern
|
|
120
|
+
*/
|
|
121
|
+
function analyzeDescription(description: string, components: string[]): LayoutPattern {
|
|
122
|
+
const lower = description.toLowerCase();
|
|
123
|
+
|
|
124
|
+
// IDE pattern
|
|
125
|
+
if (
|
|
126
|
+
(lower.includes('ide') ||
|
|
127
|
+
(lower.includes('sidebar') && lower.includes('editor')) ||
|
|
128
|
+
lower.includes('3-panel')) &&
|
|
129
|
+
components.length === 3
|
|
130
|
+
) {
|
|
131
|
+
return {
|
|
132
|
+
type: 'ide',
|
|
133
|
+
sidebar: components[0],
|
|
134
|
+
main: components[1],
|
|
135
|
+
bottom: components[2],
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Dashboard pattern (2x2 or grid)
|
|
140
|
+
if (
|
|
141
|
+
(lower.includes('dashboard') || lower.includes('grid') || lower.includes('2x2')) &&
|
|
142
|
+
components.length === 4
|
|
143
|
+
) {
|
|
144
|
+
return {
|
|
145
|
+
type: 'dashboard',
|
|
146
|
+
items: components,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Three column
|
|
151
|
+
if (
|
|
152
|
+
(lower.includes('three') || lower.includes('3')) &&
|
|
153
|
+
lower.includes('column') &&
|
|
154
|
+
components.length === 3
|
|
155
|
+
) {
|
|
156
|
+
return {
|
|
157
|
+
type: 'three-column',
|
|
158
|
+
left: components[0],
|
|
159
|
+
center: components[1],
|
|
160
|
+
right: components[2],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Split view
|
|
165
|
+
if (components.length === 2 && (lower.includes('split') || lower.includes('two'))) {
|
|
166
|
+
const direction = lower.includes('horizontal') || lower.includes('top')
|
|
167
|
+
? 'horizontal'
|
|
168
|
+
: 'vertical';
|
|
169
|
+
return {
|
|
170
|
+
type: 'split',
|
|
171
|
+
left: components[0],
|
|
172
|
+
right: components[1],
|
|
173
|
+
direction,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Custom/fallback
|
|
178
|
+
return {
|
|
179
|
+
type: 'custom',
|
|
180
|
+
items: components,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Build layout node from pattern
|
|
186
|
+
*/
|
|
187
|
+
function buildLayoutFromPattern(pattern: LayoutPattern, components: string[]): LayoutNode {
|
|
188
|
+
switch (pattern.type) {
|
|
189
|
+
case 'ide':
|
|
190
|
+
return L.v(
|
|
191
|
+
0.2,
|
|
192
|
+
L.item(pattern.sidebar),
|
|
193
|
+
L.h(0.7, L.item(pattern.main), L.item(pattern.bottom))
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
case 'dashboard':
|
|
197
|
+
return L.h(
|
|
198
|
+
0.5,
|
|
199
|
+
L.v(0.5, L.item(pattern.items[0]), L.item(pattern.items[1])),
|
|
200
|
+
L.v(0.5, L.item(pattern.items[2]), L.item(pattern.items[3]))
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
case 'three-column':
|
|
204
|
+
return L.v(
|
|
205
|
+
0.2,
|
|
206
|
+
L.item(pattern.left),
|
|
207
|
+
L.v(0.75, L.item(pattern.center), L.item(pattern.right))
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
case 'split':
|
|
211
|
+
if (pattern.direction === 'horizontal') {
|
|
212
|
+
return L.h(0.5, L.item(pattern.left), L.item(pattern.right));
|
|
213
|
+
} else {
|
|
214
|
+
return L.v(0.5, L.item(pattern.left), L.item(pattern.right));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
case 'custom':
|
|
218
|
+
// Simple fallback: vertical splits for all components
|
|
219
|
+
if (components.length === 1) {
|
|
220
|
+
return L.item(components[0]);
|
|
221
|
+
}
|
|
222
|
+
if (components.length === 2) {
|
|
223
|
+
return L.v(0.5, L.item(components[0]), L.item(components[1]));
|
|
224
|
+
}
|
|
225
|
+
// For 3+ components, create nested vertical splits
|
|
226
|
+
let node: LayoutNode = L.item(components[components.length - 1]);
|
|
227
|
+
for (let i = components.length - 2; i >= 0; i--) {
|
|
228
|
+
node = L.v(1 / (i + 2), L.item(components[i]), node);
|
|
229
|
+
}
|
|
230
|
+
return node;
|
|
231
|
+
|
|
232
|
+
default:
|
|
233
|
+
throw new Error(`Unknown pattern type: ${(pattern as any).type}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BUILTIN_TEMPLATES } from '../types/template.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Input schema for interactive-builder tool
|
|
6
|
+
*/
|
|
7
|
+
export const InteractiveBuilderInputSchema = z.object({
|
|
8
|
+
action: z
|
|
9
|
+
.enum(['list-templates', 'select-template', 'help'])
|
|
10
|
+
.describe('Action to perform'),
|
|
11
|
+
templateName: z.string().optional().describe('Template name (for select-template action)'),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export type InteractiveBuilderInput = z.infer<typeof InteractiveBuilderInputSchema>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* interactive-builder tool implementation
|
|
18
|
+
*
|
|
19
|
+
* Simplified version that guides users through template selection
|
|
20
|
+
*/
|
|
21
|
+
export async function interactiveBuilder(args: InteractiveBuilderInput) {
|
|
22
|
+
const { action, templateName } = args;
|
|
23
|
+
|
|
24
|
+
let output = '';
|
|
25
|
+
|
|
26
|
+
switch (action) {
|
|
27
|
+
case 'list-templates':
|
|
28
|
+
output = `# Available Templates\n\n`;
|
|
29
|
+
Object.entries(BUILTIN_TEMPLATES).forEach(([key, template]) => {
|
|
30
|
+
output += `## ${template.name} (\`${key}\`)\n\n`;
|
|
31
|
+
output += `${template.description}\n\n`;
|
|
32
|
+
output += `**Preview:**\n\`\`\`\n${template.preview}\n\`\`\`\n\n`;
|
|
33
|
+
output += `**Required Components:**\n`;
|
|
34
|
+
template.slots.forEach((slot) => {
|
|
35
|
+
output += `- \`${slot}\`\n`;
|
|
36
|
+
});
|
|
37
|
+
output += `\n---\n\n`;
|
|
38
|
+
});
|
|
39
|
+
output += `## Next Steps\n\n`;
|
|
40
|
+
output += `1. Choose a template that matches your needs\n`;
|
|
41
|
+
output += `2. Use the \`apply-template\` tool with your component names\n`;
|
|
42
|
+
output += `\nExample:\n\`\`\`\napply-template --templateName ide-layout --components '{"sidebar": "Sidebar", "editor": "CodeEditor", "terminal": "Terminal"}'\n\`\`\`\n`;
|
|
43
|
+
break;
|
|
44
|
+
|
|
45
|
+
case 'select-template':
|
|
46
|
+
if (!templateName) {
|
|
47
|
+
output = `❌ Please provide a template name.\n\nUse action="list-templates" to see available templates.`;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const template = BUILTIN_TEMPLATES[templateName];
|
|
52
|
+
if (!template) {
|
|
53
|
+
output = `❌ Template "${templateName}" not found.\n\nAvailable: ${Object.keys(BUILTIN_TEMPLATES).join(', ')}`;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
output = `# Selected Template: ${template.name}\n\n`;
|
|
58
|
+
output += `${template.description}\n\n`;
|
|
59
|
+
output += `**Preview:**\n\`\`\`\n${template.preview}\n\`\`\`\n\n`;
|
|
60
|
+
output += `## Required Components\n\n`;
|
|
61
|
+
output += `You need to provide ${template.slots.length} components:\n\n`;
|
|
62
|
+
template.slots.forEach((slot, i) => {
|
|
63
|
+
output += `${i + 1}. **${slot}**: Your component name here\n`;
|
|
64
|
+
});
|
|
65
|
+
output += `\n## Apply This Template\n\n`;
|
|
66
|
+
output += `Use the \`apply-template\` tool:\n\n`;
|
|
67
|
+
output += `\`\`\`\n`;
|
|
68
|
+
output += `templateName: "${templateName}"\n`;
|
|
69
|
+
output += `components:\n`;
|
|
70
|
+
template.slots.forEach((slot) => {
|
|
71
|
+
output += ` ${slot}: "YourComponentName"\n`;
|
|
72
|
+
});
|
|
73
|
+
output += `\`\`\`\n`;
|
|
74
|
+
break;
|
|
75
|
+
|
|
76
|
+
case 'help':
|
|
77
|
+
output = `# Interactive Layout Builder - Help\n\n`;
|
|
78
|
+
output += `This tool helps you build DndGrid layouts interactively.\n\n`;
|
|
79
|
+
output += `## Available Actions\n\n`;
|
|
80
|
+
output += `1. **list-templates** - Show all available templates\n`;
|
|
81
|
+
output += `2. **select-template** - Get details about a specific template\n`;
|
|
82
|
+
output += `3. **help** - Show this help message\n\n`;
|
|
83
|
+
output += `## Workflow\n\n`;
|
|
84
|
+
output += `1. List templates to see what's available\n`;
|
|
85
|
+
output += `2. Select a template that fits your needs\n`;
|
|
86
|
+
output += `3. Use \`apply-template\` with your component names\n\n`;
|
|
87
|
+
output += `## Other Tools\n\n`;
|
|
88
|
+
output += `- **generate-layout** - Generate from natural language description\n`;
|
|
89
|
+
output += `- **validate-layout** - Validate existing code\n`;
|
|
90
|
+
output += `- **analyze-layout** - Analyze existing layout\n`;
|
|
91
|
+
break;
|
|
92
|
+
|
|
93
|
+
default:
|
|
94
|
+
output = `❌ Unknown action: ${action}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
content: [{ type: 'text' as const, text: output }],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Validator } from '../utils/validator.js';
|
|
3
|
+
import { ASTParser } from '../utils/ast-parser.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Input schema for validate-layout tool
|
|
7
|
+
*/
|
|
8
|
+
export const ValidateLayoutInputSchema = z.object({
|
|
9
|
+
code: z.string().describe('DndGrid code to validate'),
|
|
10
|
+
strict: z.boolean().optional().default(false).describe('Enable strict validation (includes performance checks)'),
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
export type ValidateLayoutInput = z.infer<typeof ValidateLayoutInputSchema>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* validate-layout tool implementation
|
|
17
|
+
*/
|
|
18
|
+
export async function validateLayout(args: ValidateLayoutInput) {
|
|
19
|
+
const { code, strict } = args;
|
|
20
|
+
|
|
21
|
+
// First, validate code syntax
|
|
22
|
+
const validator = new Validator();
|
|
23
|
+
const codeValidation = validator.validateCode(code, strict);
|
|
24
|
+
|
|
25
|
+
// Try to parse the code to get layout tree
|
|
26
|
+
const parser = new ASTParser();
|
|
27
|
+
const layout = parser.parse(code);
|
|
28
|
+
|
|
29
|
+
let treeValidation = null;
|
|
30
|
+
if (layout) {
|
|
31
|
+
treeValidation = validator.validate(layout, strict);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Combine results
|
|
35
|
+
const allErrors = [...codeValidation.errors, ...(treeValidation?.errors || [])];
|
|
36
|
+
const allWarnings = [...codeValidation.warnings, ...(treeValidation?.warnings || [])];
|
|
37
|
+
const allSuggestions = [...codeValidation.suggestions, ...(treeValidation?.suggestions || [])];
|
|
38
|
+
|
|
39
|
+
const valid = allErrors.length === 0;
|
|
40
|
+
|
|
41
|
+
// Format output
|
|
42
|
+
let output = `# DndGrid Layout Validation\n\n`;
|
|
43
|
+
|
|
44
|
+
output += `**Status:** ${valid ? '✅ Valid' : '❌ Invalid'}\n\n`;
|
|
45
|
+
|
|
46
|
+
if (allErrors.length > 0) {
|
|
47
|
+
output += `## Errors (${allErrors.length})\n\n`;
|
|
48
|
+
allErrors.forEach((err, i) => {
|
|
49
|
+
output += `${i + 1}. **[${err.type}]** ${err.message}\n`;
|
|
50
|
+
if (err.fix) {
|
|
51
|
+
output += ` - Fix: \`${err.fix}\`\n`;
|
|
52
|
+
}
|
|
53
|
+
if (err.line) {
|
|
54
|
+
output += ` - Line: ${err.line}\n`;
|
|
55
|
+
}
|
|
56
|
+
output += '\n';
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (allWarnings.length > 0) {
|
|
61
|
+
output += `## Warnings (${allWarnings.length})\n\n`;
|
|
62
|
+
allWarnings.forEach((warn, i) => {
|
|
63
|
+
output += `${i + 1}. ${warn.message}\n`;
|
|
64
|
+
if (warn.suggestion) {
|
|
65
|
+
output += ` - Suggestion: ${warn.suggestion}\n`;
|
|
66
|
+
}
|
|
67
|
+
output += '\n';
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (allSuggestions.length > 0) {
|
|
72
|
+
output += `## Suggestions\n\n`;
|
|
73
|
+
allSuggestions.forEach((sugg, i) => {
|
|
74
|
+
output += `${i + 1}. ${sugg}\n`;
|
|
75
|
+
});
|
|
76
|
+
output += '\n';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (layout) {
|
|
80
|
+
output += `## Layout Structure\n\n`;
|
|
81
|
+
output += `- Items: ${countItems(layout.child)}\n`;
|
|
82
|
+
output += `- Splits: ${countSplits(layout.child)}\n`;
|
|
83
|
+
output += `- Max Depth: ${getMaxDepth(layout.child)}\n`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: 'text' as const,
|
|
90
|
+
text: output,
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Helper functions
|
|
97
|
+
function countItems(node: any): number {
|
|
98
|
+
if (node.type === 'item') return 1;
|
|
99
|
+
return countItems(node.primary) + countItems(node.secondary);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function countSplits(node: any): number {
|
|
103
|
+
if (node.type === 'item') return 0;
|
|
104
|
+
return 1 + countSplits(node.primary) + countSplits(node.secondary);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getMaxDepth(node: any, depth: number = 1): number {
|
|
108
|
+
if (node.type === 'item') return depth;
|
|
109
|
+
return Math.max(
|
|
110
|
+
getMaxDepth(node.primary, depth + 1),
|
|
111
|
+
getMaxDepth(node.secondary, depth + 1)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout type definitions for DndGrid MCP server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type DndSplitDirection = 'horizontal' | 'vertical';
|
|
6
|
+
|
|
7
|
+
export interface LayoutItem {
|
|
8
|
+
type: 'item';
|
|
9
|
+
component: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LayoutSplit {
|
|
13
|
+
type: 'split';
|
|
14
|
+
direction: DndSplitDirection;
|
|
15
|
+
ratio: number; // 0 < ratio < 1
|
|
16
|
+
primary: LayoutNode;
|
|
17
|
+
secondary: LayoutNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type LayoutNode = LayoutItem | LayoutSplit;
|
|
21
|
+
|
|
22
|
+
export interface LayoutTree {
|
|
23
|
+
type: 'container';
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
child: LayoutNode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LayoutMetadata {
|
|
30
|
+
splitCount: number;
|
|
31
|
+
itemCount: number;
|
|
32
|
+
maxDepth: number;
|
|
33
|
+
estimatedPerformance: 'excellent' | 'good' | 'fair' | 'poor';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ValidationError {
|
|
37
|
+
type: 'syntax' | 'structure' | 'constraint';
|
|
38
|
+
message: string;
|
|
39
|
+
line?: number;
|
|
40
|
+
column?: number;
|
|
41
|
+
fix?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface ValidationWarning {
|
|
45
|
+
message: string;
|
|
46
|
+
line?: number;
|
|
47
|
+
suggestion?: string;
|
|
48
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { LayoutNode } from './layout.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Template node with slots for components
|
|
5
|
+
*/
|
|
6
|
+
export interface TemplateNode {
|
|
7
|
+
type: 'split' | 'item';
|
|
8
|
+
direction?: 'horizontal' | 'vertical';
|
|
9
|
+
ratio?: number;
|
|
10
|
+
slot?: string; // For item nodes
|
|
11
|
+
primary?: TemplateNode;
|
|
12
|
+
secondary?: TemplateNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Layout template definition
|
|
17
|
+
*/
|
|
18
|
+
export interface LayoutTemplate {
|
|
19
|
+
name: string;
|
|
20
|
+
description: string;
|
|
21
|
+
preview: string; // ASCII art preview
|
|
22
|
+
slots: string[]; // Required component slots
|
|
23
|
+
defaultRatios: Record<string, number>;
|
|
24
|
+
tree: TemplateNode;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Built-in templates
|
|
29
|
+
*/
|
|
30
|
+
export const BUILTIN_TEMPLATES: Record<string, LayoutTemplate> = {
|
|
31
|
+
'ide-layout': {
|
|
32
|
+
name: 'IDE Layout',
|
|
33
|
+
description: '3-panel layout: sidebar (20%), editor (56%), terminal (24%)',
|
|
34
|
+
preview: `┌────┬────────────┐
|
|
35
|
+
│ │ │
|
|
36
|
+
│ S │ Editor │
|
|
37
|
+
│ I │ │
|
|
38
|
+
│ D ├────────────┤
|
|
39
|
+
│ E │ Terminal │
|
|
40
|
+
└────┴────────────┘`,
|
|
41
|
+
slots: ['sidebar', 'editor', 'terminal'],
|
|
42
|
+
defaultRatios: {
|
|
43
|
+
'vertical-main': 0.2,
|
|
44
|
+
'horizontal-content': 0.7,
|
|
45
|
+
},
|
|
46
|
+
tree: {
|
|
47
|
+
type: 'split',
|
|
48
|
+
direction: 'vertical',
|
|
49
|
+
ratio: 0.2,
|
|
50
|
+
primary: {
|
|
51
|
+
type: 'item',
|
|
52
|
+
slot: 'sidebar',
|
|
53
|
+
},
|
|
54
|
+
secondary: {
|
|
55
|
+
type: 'split',
|
|
56
|
+
direction: 'horizontal',
|
|
57
|
+
ratio: 0.7,
|
|
58
|
+
primary: {
|
|
59
|
+
type: 'item',
|
|
60
|
+
slot: 'editor',
|
|
61
|
+
},
|
|
62
|
+
secondary: {
|
|
63
|
+
type: 'item',
|
|
64
|
+
slot: 'terminal',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
'dashboard-2x2': {
|
|
71
|
+
name: 'Dashboard 2x2',
|
|
72
|
+
description: '2x2 grid layout for dashboard widgets',
|
|
73
|
+
preview: `┌──────┬──────┐
|
|
74
|
+
│ W1 │ W2 │
|
|
75
|
+
├──────┼──────┤
|
|
76
|
+
│ W3 │ W4 │
|
|
77
|
+
└──────┴──────┘`,
|
|
78
|
+
slots: ['widget1', 'widget2', 'widget3', 'widget4'],
|
|
79
|
+
defaultRatios: {
|
|
80
|
+
'horizontal-top': 0.5,
|
|
81
|
+
'horizontal-bottom': 0.5,
|
|
82
|
+
'vertical-left': 0.5,
|
|
83
|
+
'vertical-right': 0.5,
|
|
84
|
+
},
|
|
85
|
+
tree: {
|
|
86
|
+
type: 'split',
|
|
87
|
+
direction: 'horizontal',
|
|
88
|
+
ratio: 0.5,
|
|
89
|
+
primary: {
|
|
90
|
+
type: 'split',
|
|
91
|
+
direction: 'vertical',
|
|
92
|
+
ratio: 0.5,
|
|
93
|
+
primary: {
|
|
94
|
+
type: 'item',
|
|
95
|
+
slot: 'widget1',
|
|
96
|
+
},
|
|
97
|
+
secondary: {
|
|
98
|
+
type: 'item',
|
|
99
|
+
slot: 'widget2',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
secondary: {
|
|
103
|
+
type: 'split',
|
|
104
|
+
direction: 'vertical',
|
|
105
|
+
ratio: 0.5,
|
|
106
|
+
primary: {
|
|
107
|
+
type: 'item',
|
|
108
|
+
slot: 'widget3',
|
|
109
|
+
},
|
|
110
|
+
secondary: {
|
|
111
|
+
type: 'item',
|
|
112
|
+
slot: 'widget4',
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
'three-column': {
|
|
119
|
+
name: 'Three Column',
|
|
120
|
+
description: '3-column layout (20% / 60% / 20%)',
|
|
121
|
+
preview: `┌───┬────────┬───┐
|
|
122
|
+
│ │ │ │
|
|
123
|
+
│ L │ Center │ R │
|
|
124
|
+
│ │ │ │
|
|
125
|
+
└───┴────────┴───┘`,
|
|
126
|
+
slots: ['left', 'center', 'right'],
|
|
127
|
+
defaultRatios: {
|
|
128
|
+
'vertical-left': 0.2,
|
|
129
|
+
'vertical-right': 0.75,
|
|
130
|
+
},
|
|
131
|
+
tree: {
|
|
132
|
+
type: 'split',
|
|
133
|
+
direction: 'vertical',
|
|
134
|
+
ratio: 0.2,
|
|
135
|
+
primary: {
|
|
136
|
+
type: 'item',
|
|
137
|
+
slot: 'left',
|
|
138
|
+
},
|
|
139
|
+
secondary: {
|
|
140
|
+
type: 'split',
|
|
141
|
+
direction: 'vertical',
|
|
142
|
+
ratio: 0.75,
|
|
143
|
+
primary: {
|
|
144
|
+
type: 'item',
|
|
145
|
+
slot: 'center',
|
|
146
|
+
},
|
|
147
|
+
secondary: {
|
|
148
|
+
type: 'item',
|
|
149
|
+
slot: 'right',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
'split-view': {
|
|
156
|
+
name: 'Split View',
|
|
157
|
+
description: 'Simple 50/50 split (horizontal or vertical)',
|
|
158
|
+
preview: `┌──────────┬──────────┐
|
|
159
|
+
│ │ │
|
|
160
|
+
│ Left │ Right │
|
|
161
|
+
│ │ │
|
|
162
|
+
└──────────┴──────────┘`,
|
|
163
|
+
slots: ['left', 'right'],
|
|
164
|
+
defaultRatios: {
|
|
165
|
+
'vertical-main': 0.5,
|
|
166
|
+
},
|
|
167
|
+
tree: {
|
|
168
|
+
type: 'split',
|
|
169
|
+
direction: 'vertical',
|
|
170
|
+
ratio: 0.5,
|
|
171
|
+
primary: {
|
|
172
|
+
type: 'item',
|
|
173
|
+
slot: 'left',
|
|
174
|
+
},
|
|
175
|
+
secondary: {
|
|
176
|
+
type: 'item',
|
|
177
|
+
slot: 'right',
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
};
|