blockparty 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/README.md +102 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +54 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/build.d.ts +2 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +66 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/storybook.d.ts +2 -0
- package/dist/commands/storybook.d.ts.map +1 -0
- package/dist/commands/storybook.js +95 -0
- package/dist/commands/storybook.js.map +1 -0
- package/dist/discoverBlocks.d.ts +9 -0
- package/dist/discoverBlocks.d.ts.map +1 -0
- package/dist/discoverBlocks.js +63 -0
- package/dist/discoverBlocks.js.map +1 -0
- package/dist/extractProps.d.ts +10 -0
- package/dist/extractProps.d.ts.map +1 -0
- package/dist/extractProps.js +251 -0
- package/dist/extractProps.js.map +1 -0
- package/dist/extractProps.test.d.ts +2 -0
- package/dist/extractProps.test.d.ts.map +1 -0
- package/dist/extractProps.test.js +305 -0
- package/dist/extractProps.test.js.map +1 -0
- package/dist/generateBlocksModule.d.ts +3 -0
- package/dist/generateBlocksModule.d.ts.map +1 -0
- package/dist/generateBlocksModule.js +21 -0
- package/dist/generateBlocksModule.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/parseReadme.d.ts +15 -0
- package/dist/parseReadme.d.ts.map +1 -0
- package/dist/parseReadme.js +84 -0
- package/dist/parseReadme.js.map +1 -0
- package/dist/parseReadme.test.d.ts +2 -0
- package/dist/parseReadme.test.d.ts.map +1 -0
- package/dist/parseReadme.test.js +142 -0
- package/dist/parseReadme.test.js.map +1 -0
- package/dist/templates/App.tsx +236 -0
- package/dist/templates/ErrorBoundary.tsx +53 -0
- package/dist/templates/PropsEditor.tsx +707 -0
- package/dist/templates/index.html +27 -0
- package/dist/templates/index.tsx +10 -0
- package/dist/viteConfig.d.ts +12 -0
- package/dist/viteConfig.d.ts.map +1 -0
- package/dist/viteConfig.js +22 -0
- package/dist/viteConfig.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseReadme.d.ts","sourceRoot":"","sources":["../src/parseReadme.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC;AAGD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAyB1G;AAGD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAiCtE;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAgCjF"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
// exported for tests
|
|
5
|
+
export function parseFrontmatter(content) {
|
|
6
|
+
const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
|
|
7
|
+
const match = content.match(frontmatterRegex);
|
|
8
|
+
if (!match) {
|
|
9
|
+
return { frontmatter: {}, content };
|
|
10
|
+
}
|
|
11
|
+
const frontmatterText = match[1];
|
|
12
|
+
const remainingContent = match[2];
|
|
13
|
+
const frontmatter = {};
|
|
14
|
+
const lines = frontmatterText.split('\n');
|
|
15
|
+
for (const line of lines) {
|
|
16
|
+
const colonIndex = line.indexOf(':');
|
|
17
|
+
if (colonIndex > 0) {
|
|
18
|
+
const key = line.slice(0, colonIndex).trim();
|
|
19
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
20
|
+
// Remove quotes if present
|
|
21
|
+
frontmatter[key] = value.replace(/^["']|["']$/g, '');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { frontmatter, content: remainingContent };
|
|
25
|
+
}
|
|
26
|
+
// exported for tests
|
|
27
|
+
export function extractMarkdownMetadata(content) {
|
|
28
|
+
const lines = content.trim().split('\n');
|
|
29
|
+
let name;
|
|
30
|
+
let description;
|
|
31
|
+
// Look for first heading
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const line = lines[i].trim();
|
|
34
|
+
if (line.startsWith('#')) {
|
|
35
|
+
// Found a heading - extract the name
|
|
36
|
+
name = line.replace(/^#+\s*/, '').trim();
|
|
37
|
+
// Look for the first non-empty paragraph after the heading
|
|
38
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
39
|
+
const nextLine = lines[j].trim();
|
|
40
|
+
// Skip empty lines
|
|
41
|
+
if (!nextLine)
|
|
42
|
+
continue;
|
|
43
|
+
// Skip if it's another heading
|
|
44
|
+
if (nextLine.startsWith('#'))
|
|
45
|
+
break;
|
|
46
|
+
// Found a paragraph - this is the description
|
|
47
|
+
description = nextLine;
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return { name, description };
|
|
54
|
+
}
|
|
55
|
+
export async function parseReadmeMetadata(dirPath) {
|
|
56
|
+
const readmePath = join(dirPath, 'README.md');
|
|
57
|
+
if (!existsSync(readmePath)) {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const content = await readFile(readmePath, 'utf-8');
|
|
62
|
+
const { frontmatter, content: remainingContent } = parseFrontmatter(content);
|
|
63
|
+
let name = frontmatter.name;
|
|
64
|
+
let description = frontmatter.description;
|
|
65
|
+
// If name or description is missing from frontmatter, extract from markdown
|
|
66
|
+
if (!name || !description) {
|
|
67
|
+
const markdownMetadata = extractMarkdownMetadata(remainingContent);
|
|
68
|
+
name ?? (name = markdownMetadata.name);
|
|
69
|
+
description ?? (description = markdownMetadata.description);
|
|
70
|
+
}
|
|
71
|
+
const result = {
|
|
72
|
+
name,
|
|
73
|
+
description,
|
|
74
|
+
readme: remainingContent,
|
|
75
|
+
frontmatter
|
|
76
|
+
};
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error(`Error parsing README.md at ${readmePath}:`, error);
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=parseReadme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseReadme.js","sourceRoot":"","sources":["../src/parseReadme.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseReadme.test.d.ts","sourceRoot":"","sources":["../src/parseReadme.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { test, describe } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { parseFrontmatter, extractMarkdownMetadata } from './parseReadme.js';
|
|
4
|
+
describe('parseFrontmatter', () => {
|
|
5
|
+
test('extracts frontmatter and remaining content', () => {
|
|
6
|
+
const content = `---
|
|
7
|
+
name: Big Numbers
|
|
8
|
+
description: Display large numbers
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Heading
|
|
12
|
+
|
|
13
|
+
Content here`;
|
|
14
|
+
const result = parseFrontmatter(content);
|
|
15
|
+
assert.strictEqual(result.frontmatter.name, 'Big Numbers');
|
|
16
|
+
assert.strictEqual(result.frontmatter.description, 'Display large numbers');
|
|
17
|
+
assert.strictEqual(result.content.trim(), '# Heading\n\nContent here');
|
|
18
|
+
});
|
|
19
|
+
test('handles frontmatter with quoted values', () => {
|
|
20
|
+
const content = `---
|
|
21
|
+
name: "My Component"
|
|
22
|
+
description: 'A cool component'
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Content`;
|
|
26
|
+
const result = parseFrontmatter(content);
|
|
27
|
+
assert.strictEqual(result.frontmatter.name, 'My Component');
|
|
28
|
+
assert.strictEqual(result.frontmatter.description, 'A cool component');
|
|
29
|
+
});
|
|
30
|
+
test('returns empty frontmatter when no frontmatter present', () => {
|
|
31
|
+
const content = `# Heading
|
|
32
|
+
|
|
33
|
+
Just some content without frontmatter`;
|
|
34
|
+
const result = parseFrontmatter(content);
|
|
35
|
+
assert.deepStrictEqual(result.frontmatter, {});
|
|
36
|
+
assert.strictEqual(result.content, content);
|
|
37
|
+
});
|
|
38
|
+
test('handles frontmatter with multiple properties', () => {
|
|
39
|
+
const content = `---
|
|
40
|
+
name: Component
|
|
41
|
+
description: A description
|
|
42
|
+
author: John Doe
|
|
43
|
+
version: 1.0.0
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
Content`;
|
|
47
|
+
const result = parseFrontmatter(content);
|
|
48
|
+
assert.strictEqual(result.frontmatter.name, 'Component');
|
|
49
|
+
assert.strictEqual(result.frontmatter.description, 'A description');
|
|
50
|
+
assert.strictEqual(result.frontmatter.author, 'John Doe');
|
|
51
|
+
assert.strictEqual(result.frontmatter.version, '1.0.0');
|
|
52
|
+
});
|
|
53
|
+
test('handles empty frontmatter', () => {
|
|
54
|
+
const content = `---
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
Content`;
|
|
59
|
+
const result = parseFrontmatter(content);
|
|
60
|
+
assert.deepStrictEqual(result.frontmatter, {});
|
|
61
|
+
assert.strictEqual(result.content, 'Content');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe('extractMarkdownMetadata', () => {
|
|
65
|
+
test('extracts name from first heading and description from first paragraph', () => {
|
|
66
|
+
const content = `# Big Numbers
|
|
67
|
+
|
|
68
|
+
Display large numerical values with formatting
|
|
69
|
+
|
|
70
|
+
Additional content here`;
|
|
71
|
+
const result = extractMarkdownMetadata(content);
|
|
72
|
+
assert.strictEqual(result.name, 'Big Numbers');
|
|
73
|
+
assert.strictEqual(result.description, 'Display large numerical values with formatting');
|
|
74
|
+
});
|
|
75
|
+
test('handles h2 headings', () => {
|
|
76
|
+
const content = `## Component Name
|
|
77
|
+
|
|
78
|
+
This is the description`;
|
|
79
|
+
const result = extractMarkdownMetadata(content);
|
|
80
|
+
assert.strictEqual(result.name, 'Component Name');
|
|
81
|
+
assert.strictEqual(result.description, 'This is the description');
|
|
82
|
+
});
|
|
83
|
+
test('handles headings with multiple hash marks', () => {
|
|
84
|
+
const content = `#### Deep Heading
|
|
85
|
+
|
|
86
|
+
Description text`;
|
|
87
|
+
const result = extractMarkdownMetadata(content);
|
|
88
|
+
assert.strictEqual(result.name, 'Deep Heading');
|
|
89
|
+
assert.strictEqual(result.description, 'Description text');
|
|
90
|
+
});
|
|
91
|
+
test('skips empty lines between heading and description', () => {
|
|
92
|
+
const content = `# Component
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
Description after blank lines`;
|
|
96
|
+
const result = extractMarkdownMetadata(content);
|
|
97
|
+
assert.strictEqual(result.name, 'Component');
|
|
98
|
+
assert.strictEqual(result.description, 'Description after blank lines');
|
|
99
|
+
});
|
|
100
|
+
test('returns undefined when no heading present', () => {
|
|
101
|
+
const content = `Just some text without a heading`;
|
|
102
|
+
const result = extractMarkdownMetadata(content);
|
|
103
|
+
assert.strictEqual(result.name, undefined);
|
|
104
|
+
assert.strictEqual(result.description, undefined);
|
|
105
|
+
});
|
|
106
|
+
test('returns undefined description when no paragraph after heading', () => {
|
|
107
|
+
const content = `# Component Name
|
|
108
|
+
|
|
109
|
+
## Another Heading`;
|
|
110
|
+
const result = extractMarkdownMetadata(content);
|
|
111
|
+
assert.strictEqual(result.name, 'Component Name');
|
|
112
|
+
assert.strictEqual(result.description, undefined);
|
|
113
|
+
});
|
|
114
|
+
test('handles heading-only content', () => {
|
|
115
|
+
const content = `# Lonely Heading`;
|
|
116
|
+
const result = extractMarkdownMetadata(content);
|
|
117
|
+
assert.strictEqual(result.name, 'Lonely Heading');
|
|
118
|
+
assert.strictEqual(result.description, undefined);
|
|
119
|
+
});
|
|
120
|
+
test('handles content with leading whitespace', () => {
|
|
121
|
+
const content = `
|
|
122
|
+
|
|
123
|
+
# Heading with Space
|
|
124
|
+
|
|
125
|
+
Description with space
|
|
126
|
+
`;
|
|
127
|
+
const result = extractMarkdownMetadata(content);
|
|
128
|
+
assert.strictEqual(result.name, 'Heading with Space');
|
|
129
|
+
assert.strictEqual(result.description, 'Description with space');
|
|
130
|
+
});
|
|
131
|
+
test('stops at next heading when looking for description', () => {
|
|
132
|
+
const content = `# First Heading
|
|
133
|
+
|
|
134
|
+
## Second Heading
|
|
135
|
+
|
|
136
|
+
This should not be the description`;
|
|
137
|
+
const result = extractMarkdownMetadata(content);
|
|
138
|
+
assert.strictEqual(result.name, 'First Heading');
|
|
139
|
+
assert.strictEqual(result.description, undefined);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
//# sourceMappingURL=parseReadme.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseReadme.test.js","sourceRoot":"","sources":["../src/parseReadme.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
import { ErrorBoundary } from './ErrorBoundary'
|
|
3
|
+
import { PropsEditor } from './PropsEditor'
|
|
4
|
+
import { blocks } from './blocks'
|
|
5
|
+
|
|
6
|
+
interface PropDefinition {
|
|
7
|
+
name: string
|
|
8
|
+
type: string
|
|
9
|
+
optional: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const STORAGE_KEY = 'blockparty-state'
|
|
13
|
+
|
|
14
|
+
const isComplexType = (type: string) => {
|
|
15
|
+
return type.includes('[') || type.includes('{') || type.includes('|')
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const getDefaultValue = (type: string, optional: boolean) => {
|
|
19
|
+
if (optional) return ''
|
|
20
|
+
|
|
21
|
+
if (type.includes('[]')) {
|
|
22
|
+
return '[]'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (type.includes('{') || type.includes('object')) {
|
|
26
|
+
return '{}'
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (type === 'number') {
|
|
30
|
+
return '0'
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (type === 'boolean') {
|
|
34
|
+
return 'false'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return ''
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const parseValue = (value: string, type: string, propDefs: PropDefinition[]) => {
|
|
41
|
+
if (!value) return value
|
|
42
|
+
|
|
43
|
+
// For React.ReactNode, parse and render the block(s) - always an array
|
|
44
|
+
if (type === 'React.ReactNode' || type === 'ReactNode') {
|
|
45
|
+
try {
|
|
46
|
+
const parsed = typeof value === 'string' ? JSON.parse(value) : value
|
|
47
|
+
|
|
48
|
+
if (Array.isArray(parsed)) {
|
|
49
|
+
return parsed.map((item, index) => {
|
|
50
|
+
if (item && typeof item === 'object' && '__block' in item) {
|
|
51
|
+
const blockName = item.__block
|
|
52
|
+
const blockProps = item.__props || {}
|
|
53
|
+
const block = blocks.find(b => b.name === blockName)
|
|
54
|
+
|
|
55
|
+
if (block) {
|
|
56
|
+
const parsedBlockProps = Object.fromEntries(
|
|
57
|
+
Object.entries(blockProps).map(([key, val]) => {
|
|
58
|
+
const propDef = block.propDefinitions.find((p: PropDefinition) => p.name === key)
|
|
59
|
+
return [key, propDef ? parseValue(val as string, propDef.type, block.propDefinitions) : val]
|
|
60
|
+
})
|
|
61
|
+
)
|
|
62
|
+
return <block.Component key={index} {...parsedBlockProps} />
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return null
|
|
66
|
+
}).filter(Boolean)
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Fall through
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// For complex types, try to parse as JSON
|
|
74
|
+
if (isComplexType(type)) {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(value)
|
|
77
|
+
} catch {
|
|
78
|
+
return value
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// For simple number type, parse as number
|
|
83
|
+
if (type === 'number') {
|
|
84
|
+
const num = Number(value)
|
|
85
|
+
return isNaN(num) ? value : num
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// For boolean type
|
|
89
|
+
if (type === 'boolean') {
|
|
90
|
+
return value === 'true'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return value
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const formatValue = (value: any, type: string) => {
|
|
97
|
+
if (value === undefined || value === null) return ''
|
|
98
|
+
if (isComplexType(type)) {
|
|
99
|
+
try {
|
|
100
|
+
return JSON.stringify(value, null, 2)
|
|
101
|
+
} catch {
|
|
102
|
+
return String(value)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return String(value)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function App() {
|
|
109
|
+
const [selectedBlock, setSelectedBlock] = useState(() => {
|
|
110
|
+
try {
|
|
111
|
+
const saved = localStorage.getItem(STORAGE_KEY)
|
|
112
|
+
return saved ? JSON.parse(saved).selectedBlock ?? 0 : 0
|
|
113
|
+
} catch {
|
|
114
|
+
return 0
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
const [props, setProps] = useState<Record<string, string>>(() => {
|
|
119
|
+
try {
|
|
120
|
+
const saved = localStorage.getItem(STORAGE_KEY)
|
|
121
|
+
return saved ? JSON.parse(saved).props ?? {} : {}
|
|
122
|
+
} catch {
|
|
123
|
+
return {}
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const currentBlock = blocks[selectedBlock]
|
|
128
|
+
const CurrentComponent = currentBlock.Component
|
|
129
|
+
const propDefinitions: PropDefinition[] = currentBlock.propDefinitions
|
|
130
|
+
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
try {
|
|
133
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify({ selectedBlock, props }))
|
|
134
|
+
} catch (e) {
|
|
135
|
+
console.error('Failed to save state:', e)
|
|
136
|
+
}
|
|
137
|
+
}, [selectedBlock, props])
|
|
138
|
+
|
|
139
|
+
// Initialize props with defaults when block changes or prop definitions change
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
const validPropNames = new Set(propDefinitions.map(p => p.name))
|
|
142
|
+
|
|
143
|
+
// Remove props that no longer exist in the prop definitions
|
|
144
|
+
const filteredProps = Object.fromEntries(
|
|
145
|
+
Object.entries(props).filter(([key]) => validPropNames.has(key))
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
// Add default values for required props that don't have values
|
|
149
|
+
const defaultProps = propDefinitions.reduce((acc: Record<string, string>, propDef) => {
|
|
150
|
+
if (!propDef.optional && !filteredProps[propDef.name]) {
|
|
151
|
+
acc[propDef.name] = getDefaultValue(propDef.type, propDef.optional)
|
|
152
|
+
}
|
|
153
|
+
return acc
|
|
154
|
+
}, {})
|
|
155
|
+
|
|
156
|
+
const newProps = { ...filteredProps, ...defaultProps }
|
|
157
|
+
|
|
158
|
+
// Only update if props actually changed
|
|
159
|
+
if (JSON.stringify(props) !== JSON.stringify(newProps)) {
|
|
160
|
+
setProps(newProps)
|
|
161
|
+
}
|
|
162
|
+
}, [selectedBlock, propDefinitions])
|
|
163
|
+
|
|
164
|
+
const parsedProps = Object.fromEntries(
|
|
165
|
+
Object.entries(props).map(([key, value]) => {
|
|
166
|
+
const propDef = propDefinitions.find((p: PropDefinition) => p.name === key)
|
|
167
|
+
return [key, propDef ? parseValue(value as string, propDef.type, propDefinitions) : value]
|
|
168
|
+
})
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div style={{ display: 'flex', height: '100vh', fontFamily: 'system-ui, sans-serif' }}>
|
|
173
|
+
{/* Left sidebar - block list */}
|
|
174
|
+
<aside style={{ width: '200px', borderRight: '1px solid #ddd', padding: '20px', overflow: 'auto' }}>
|
|
175
|
+
<h2 style={{ marginTop: 0, fontSize: '18px' }}>🎉 Block Party</h2>
|
|
176
|
+
|
|
177
|
+
<h3 style={{ fontSize: '14px', textTransform: 'uppercase', color: '#666', marginTop: '24px' }}>Blocks</h3>
|
|
178
|
+
<ul style={{ listStyle: 'none', padding: 0 }}>
|
|
179
|
+
{blocks.map((block, idx) => (
|
|
180
|
+
<li key={idx}>
|
|
181
|
+
<button
|
|
182
|
+
onClick={() => {
|
|
183
|
+
setSelectedBlock(idx)
|
|
184
|
+
setProps({})
|
|
185
|
+
}}
|
|
186
|
+
style={{
|
|
187
|
+
width: '100%',
|
|
188
|
+
padding: '8px 12px',
|
|
189
|
+
textAlign: 'left',
|
|
190
|
+
border: 'none',
|
|
191
|
+
background: selectedBlock === idx ? '#0066ff' : 'transparent',
|
|
192
|
+
color: selectedBlock === idx ? 'white' : 'black',
|
|
193
|
+
cursor: 'pointer',
|
|
194
|
+
borderRadius: '4px',
|
|
195
|
+
marginBottom: '4px'
|
|
196
|
+
}}
|
|
197
|
+
>
|
|
198
|
+
{block.name}
|
|
199
|
+
</button>
|
|
200
|
+
</li>
|
|
201
|
+
))}
|
|
202
|
+
</ul>
|
|
203
|
+
</aside>
|
|
204
|
+
|
|
205
|
+
{/* Center - component preview */}
|
|
206
|
+
<main style={{ flex: 1, padding: '40px', overflow: 'auto' }}>
|
|
207
|
+
<div style={{ maxWidth: '1200px', margin: '0 auto' }}>
|
|
208
|
+
<ErrorBoundary key={`${selectedBlock}-${JSON.stringify(props)}`}>
|
|
209
|
+
<CurrentComponent {...parsedProps} />
|
|
210
|
+
</ErrorBoundary>
|
|
211
|
+
</div>
|
|
212
|
+
</main>
|
|
213
|
+
|
|
214
|
+
{/* Right sidebar - description and props */}
|
|
215
|
+
<aside style={{ width: '320px', borderLeft: '1px solid #ddd', padding: '20px', overflow: 'auto', background: '#fafafa' }}>
|
|
216
|
+
<h3 style={{ marginTop: 0, fontSize: '16px' }}>{currentBlock.name}</h3>
|
|
217
|
+
|
|
218
|
+
{currentBlock.description && (
|
|
219
|
+
<div style={{ marginBottom: '24px', padding: '12px', background: '#fff', borderRadius: '4px', border: '1px solid #e0e0e0' }}>
|
|
220
|
+
<p style={{ fontSize: '12px', color: '#666', margin: 0, lineHeight: '1.5' }}>
|
|
221
|
+
{currentBlock.description}
|
|
222
|
+
</p>
|
|
223
|
+
</div>
|
|
224
|
+
)}
|
|
225
|
+
|
|
226
|
+
<h4 style={{ fontSize: '14px', textTransform: 'uppercase', color: '#666', marginBottom: '12px' }}>Props</h4>
|
|
227
|
+
<PropsEditor
|
|
228
|
+
propDefinitions={propDefinitions}
|
|
229
|
+
props={props}
|
|
230
|
+
onPropsChange={setProps}
|
|
231
|
+
availableBlocks={blocks}
|
|
232
|
+
/>
|
|
233
|
+
</aside>
|
|
234
|
+
</div>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Component } from 'react'
|
|
2
|
+
|
|
3
|
+
interface ErrorBoundaryProps {
|
|
4
|
+
children: React.ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface ErrorBoundaryState {
|
|
8
|
+
hasError: boolean
|
|
9
|
+
error: Error | null
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
13
|
+
constructor(props: ErrorBoundaryProps) {
|
|
14
|
+
super(props)
|
|
15
|
+
this.state = { hasError: false, error: null }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static getDerivedStateFromError(error: Error) {
|
|
19
|
+
return { hasError: true, error }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
|
23
|
+
console.error('Component error:', error, errorInfo)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
if (this.state.hasError) {
|
|
28
|
+
return (
|
|
29
|
+
<div style={{
|
|
30
|
+
padding: '20px',
|
|
31
|
+
background: '#fee',
|
|
32
|
+
border: '1px solid #fcc',
|
|
33
|
+
borderRadius: '4px',
|
|
34
|
+
color: '#c33',
|
|
35
|
+
fontFamily: 'monospace',
|
|
36
|
+
fontSize: '12px'
|
|
37
|
+
}}>
|
|
38
|
+
<strong style={{ display: 'block', marginBottom: '8px', fontSize: '14px' }}>
|
|
39
|
+
⚠️ Component Error
|
|
40
|
+
</strong>
|
|
41
|
+
<pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
|
|
42
|
+
{this.state.error?.toString()}
|
|
43
|
+
</pre>
|
|
44
|
+
<p style={{ marginTop: '12px', fontSize: '11px', color: '#666' }}>
|
|
45
|
+
Check your props values. Complex types should be valid JSON.
|
|
46
|
+
</p>
|
|
47
|
+
</div>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return this.props.children
|
|
52
|
+
}
|
|
53
|
+
}
|