@ramme-io/create-app 1.1.8 → 1.1.9
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ramme-io/create-app",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-ramme-app": "./index.js"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"bundle:ai": "repomix --ignore '**/*.lock,**/dist/**,**/template/dist/**,**/template/node_modules/**' --output 'ramme-starter-context.txt'"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ramme-io/ui": "^1.1.
|
|
20
|
+
"@ramme-io/ui": "^1.1.3",
|
|
21
21
|
"ag-grid-community": "^34.1.2",
|
|
22
22
|
"ag-grid-enterprise": "^34.1.2",
|
|
23
23
|
"ag-grid-react": "^34.1.2",
|
package/template/pkg.json
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
"preview": "vite preview"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@ramme-io/ui": "^1.1.
|
|
12
|
+
"@ramme-io/ui": "^1.1.3",
|
|
13
13
|
"mqtt": "^5.3.5",
|
|
14
|
+
"zustand": "^4.5.0",
|
|
14
15
|
"ag-grid-community": "^31.3.1",
|
|
15
16
|
"ag-grid-enterprise": "^31.3.1",
|
|
16
17
|
"ag-grid-react": "^31.3.1",
|
|
@@ -3,12 +3,11 @@ import { getComponent } from '../core/component-registry';
|
|
|
3
3
|
import { useSignal } from '../hooks/useSignal';
|
|
4
4
|
import { getMockData } from '../data/mockData';
|
|
5
5
|
|
|
6
|
-
// 1. THE TRANSLATOR: Maps Data Status ('fresh') to UI Status ('online')
|
|
7
6
|
const mapSignalStatus = (status: string): string => {
|
|
8
7
|
switch (status) {
|
|
9
|
-
case 'fresh': return 'online';
|
|
10
|
-
case 'stale': return 'warning';
|
|
11
|
-
case 'disconnected': return 'offline';
|
|
8
|
+
case 'fresh': return 'online';
|
|
9
|
+
case 'stale': return 'warning';
|
|
10
|
+
case 'disconnected': return 'offline';
|
|
12
11
|
case 'error': return 'error';
|
|
13
12
|
default: return 'offline';
|
|
14
13
|
}
|
|
@@ -24,35 +23,29 @@ interface DynamicBlockProps {
|
|
|
24
23
|
|
|
25
24
|
export const DynamicBlock: React.FC<DynamicBlockProps> = ({ block }) => {
|
|
26
25
|
const Component = getComponent(block.type);
|
|
27
|
-
|
|
28
26
|
const { signalId, dataId, ...staticProps } = block.props;
|
|
29
27
|
|
|
30
|
-
//
|
|
28
|
+
// 1. Resolve Data
|
|
31
29
|
const resolvedData = dataId ? getMockData(dataId) : staticProps.data;
|
|
32
30
|
|
|
33
|
-
//
|
|
34
|
-
// We pass an empty string if undefined, the hook handles the rest.
|
|
31
|
+
// 2. Signal Wiring
|
|
35
32
|
const signalState = useSignal(signalId || '');
|
|
36
33
|
|
|
37
|
-
//
|
|
38
|
-
// We explicitly type this as Record<string, any> so we can inject new props without TS errors
|
|
34
|
+
// 3. Merge Props
|
|
39
35
|
const dynamicProps: Record<string, any> = {
|
|
40
36
|
...staticProps,
|
|
37
|
+
// FIX: Pass data as BOTH 'data' (Charts) and 'rowData' (Tables)
|
|
41
38
|
data: resolvedData || [],
|
|
39
|
+
rowData: resolvedData || [],
|
|
42
40
|
};
|
|
43
41
|
|
|
44
|
-
//
|
|
42
|
+
// 4. Inject Signal Data
|
|
45
43
|
if (signalId && signalState) {
|
|
46
|
-
// Inject the value (formatted with unit)
|
|
47
44
|
dynamicProps.value = `${signalState.value}${signalState.unit || ''}`;
|
|
48
|
-
|
|
49
|
-
// Inject the translated status
|
|
50
45
|
if (signalState.status) {
|
|
51
46
|
dynamicProps.status = mapSignalStatus(signalState.status);
|
|
52
|
-
|
|
53
|
-
// Optional: Auto-map 'variant' for components like Badge that use it
|
|
54
47
|
if (signalState.status === 'error') {
|
|
55
|
-
dynamicProps.variant = 'destructive';
|
|
48
|
+
dynamicProps.variant = 'destructive';
|
|
56
49
|
}
|
|
57
50
|
}
|
|
58
51
|
}
|
|
@@ -12,33 +12,42 @@ import {
|
|
|
12
12
|
} from '@ramme-io/ui';
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* The Registry maps the "string" name of a component (from the
|
|
15
|
+
* The Registry maps the "string" name of a component (from the JSON manifest)
|
|
16
16
|
* to the actual React component implementation.
|
|
17
17
|
*/
|
|
18
18
|
export const COMPONENT_REGISTRY: Record<string, React.FC<any>> = {
|
|
19
|
-
// ---
|
|
19
|
+
// --- Core Components ---
|
|
20
|
+
DeviceCard,
|
|
20
21
|
StatCard,
|
|
21
22
|
BarChart,
|
|
22
23
|
LineChart,
|
|
23
24
|
PieChart,
|
|
24
25
|
DataTable,
|
|
25
|
-
|
|
26
|
-
// --- IoT & Controls ---
|
|
27
|
-
DeviceCard,
|
|
28
|
-
|
|
29
|
-
// MAPPING STRATEGY:
|
|
30
|
-
// The Wizard generates specific UI intents ("Gauge", "Toggle").
|
|
31
|
-
// For now, we map these to our versatile 'DeviceCard' primitive.
|
|
32
|
-
// In the future, we will build dedicated components for each.
|
|
33
|
-
GaugeCard: DeviceCard,
|
|
34
|
-
SparklineCard: DeviceCard,
|
|
35
|
-
ToggleCard: DeviceCard,
|
|
36
|
-
SliderCard: DeviceCard,
|
|
37
|
-
|
|
38
|
-
// --- Layout & Feedback ---
|
|
39
26
|
Card,
|
|
40
27
|
Alert,
|
|
41
28
|
EmptyState,
|
|
29
|
+
|
|
30
|
+
// --- 🛡️ ROBUSTNESS ALIASES ---
|
|
31
|
+
// These mappings allow the manifest to use lowercase or alternate names
|
|
32
|
+
// without crashing the application.
|
|
33
|
+
|
|
34
|
+
// Tables
|
|
35
|
+
'table': DataTable, // Fixes your specific error
|
|
36
|
+
'Table': DataTable,
|
|
37
|
+
'grid': DataTable,
|
|
38
|
+
|
|
39
|
+
// Charts
|
|
40
|
+
'chart': BarChart, // Default generic chart to BarChart
|
|
41
|
+
'line': LineChart,
|
|
42
|
+
'bar': BarChart,
|
|
43
|
+
'pie': PieChart,
|
|
44
|
+
|
|
45
|
+
// IoT Fallbacks
|
|
46
|
+
'DataCard': DeviceCard,
|
|
47
|
+
'GaugeCard': DeviceCard,
|
|
48
|
+
'ToggleCard': DeviceCard,
|
|
49
|
+
'SliderCard': DeviceCard,
|
|
50
|
+
'SparklineCard': DeviceCard,
|
|
42
51
|
};
|
|
43
52
|
|
|
44
53
|
/**
|
|
@@ -46,12 +55,19 @@ export const COMPONENT_REGISTRY: Record<string, React.FC<any>> = {
|
|
|
46
55
|
* Returns a fallback if the component name is unknown.
|
|
47
56
|
*/
|
|
48
57
|
export const getComponent = (name: string) => {
|
|
49
|
-
|
|
58
|
+
// 1. Try direct lookup
|
|
59
|
+
let Component = COMPONENT_REGISTRY[name];
|
|
60
|
+
|
|
61
|
+
// 2. If not found, try PascalCase (e.g. "deviceCard" -> "DeviceCard")
|
|
62
|
+
if (!Component) {
|
|
63
|
+
const pascalName = name.charAt(0).toUpperCase() + name.slice(1);
|
|
64
|
+
Component = COMPONENT_REGISTRY[pascalName];
|
|
65
|
+
}
|
|
50
66
|
|
|
51
67
|
if (!Component) {
|
|
52
68
|
console.warn(`[Registry] Unknown component type: "${name}"`);
|
|
53
69
|
return () => (
|
|
54
|
-
<Alert variant="
|
|
70
|
+
<Alert variant="danger" title="Unknown Component">
|
|
55
71
|
The system tried to render <code>{name}</code> but it was not found in the registry.
|
|
56
72
|
</Alert>
|
|
57
73
|
);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { PageHeader, Alert } from '@ramme-io/ui';
|
|
3
|
+
import { appManifest } from '../config/app.manifest';
|
|
4
|
+
import { getComponent } from '../core/component-registry';
|
|
5
|
+
// Reuse the GhostOverlay for structure visualization
|
|
6
|
+
import { GhostOverlay } from '../components/dev/GhostOverlay';
|
|
7
|
+
// Import the component that renders a single block (you likely have this extracted from Dashboard.tsx)
|
|
8
|
+
import { DynamicBlock } from '../components/DynamicBlock';
|
|
9
|
+
|
|
10
|
+
interface DynamicPageProps {
|
|
11
|
+
pageId: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DynamicPage: React.FC<DynamicPageProps> = ({ pageId }) => {
|
|
15
|
+
// 1. Look up the page definition in the manifest
|
|
16
|
+
const pageConfig = appManifest.pages?.find((p: { id: string; }) => p.id === pageId);
|
|
17
|
+
|
|
18
|
+
if (!pageConfig) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="p-8">
|
|
21
|
+
<Alert variant="danger" title="404: Page Definition Not Found">
|
|
22
|
+
The manifest does not contain a page with ID: <code>{pageId}</code>.
|
|
23
|
+
</Alert>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div className="space-y-8 fade-in">
|
|
30
|
+
<PageHeader
|
|
31
|
+
title={pageConfig.title}
|
|
32
|
+
description={pageConfig.description}
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
{/* Render Sections */}
|
|
36
|
+
{pageConfig.sections.map((section: { id: React.Key | null | undefined; title: string | number | boolean | React.ReactElement<any, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal | null | undefined; layout: { columns: any; }; blocks: any[]; }) => (
|
|
37
|
+
<div key={section.id} className="space-y-4">
|
|
38
|
+
{section.title && <h3 className="text-xl font-semibold">{section.title}</h3>}
|
|
39
|
+
|
|
40
|
+
<div
|
|
41
|
+
className="grid gap-6"
|
|
42
|
+
style={{
|
|
43
|
+
gridTemplateColumns: `repeat(${section.layout?.columns || 3}, minmax(300px, 1fr))`
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
{section.blocks.map(block => (
|
|
47
|
+
<GhostOverlay
|
|
48
|
+
key={block.id}
|
|
49
|
+
componentId={block.id}
|
|
50
|
+
componentType={block.type}
|
|
51
|
+
signalId={block.props.signalId}
|
|
52
|
+
>
|
|
53
|
+
<DynamicBlock block={block} />
|
|
54
|
+
</GhostOverlay>
|
|
55
|
+
))}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export default DynamicPage;
|