Skip to content

Commit 88ab8b0

Browse files
committed
refactor: simplify imports and enhance ListView data handling in registry
1 parent 609d7bb commit 88ab8b0

File tree

3 files changed

+55
-101
lines changed

3 files changed

+55
-101
lines changed

examples/crm-app/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useState, useEffect } from 'react';
1+
import { useState, useEffect } from 'react';
22
import { BrowserRouter, Routes, Route, Outlet, useParams } from 'react-router-dom';
33
import { SidebarProvider, SidebarInset, SidebarTrigger, Separator } from '@object-ui/components';
44
import { SchemaRendererProvider, SchemaRenderer } from '@object-ui/react';
Lines changed: 51 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,23 @@
1-
import React from 'react';
2-
import { registerComponent } from '@object-ui/core';
1+
import { ComponentRegistry } from '@object-ui/core';
32
import { useDataScope } from '@object-ui/react';
43
import { cn } from '@object-ui/components';
54

6-
// ... existing code ...
7-
8-
// 4. List View (Simple)
9-
const ListView = ({ schema }: any) => {
10-
// If 'bind' is present, useDataScope will resolve the data relative to the current scope
11-
// But useDataScope returns the *entire* scope usually?
12-
// Actually, usually SchemaRenderer handles the `bind` resolution and passes `data` prop?
13-
// Let's assume looking at the architecture: "SchemaRenderer ... Bridges Core and Components."
14-
// If I use `useDataScope`, I should be able to get the list.
15-
16-
// However, if the protocol says:
17-
// "Data binding path (e.g., 'user.address.city')"
18-
// usually the architecture allows the component to access data.
19-
20-
const { scope } = useDataScope();
21-
let data = schema.data; // direct data
22-
23-
if (schema.bind) {
24-
// Simple binding resolution for this demo
25-
// In a real implementation this would be more robust
26-
data = scope ? scope[schema.bind] : [];
27-
}
28-
29-
// Fallback or empty
30-
if (!data || !Array.isArray(data)) return <div className="p-4 text-muted-foreground">No data</div>;
31-
32-
return (
33-
<div className="space-y-2">
34-
{data.slice(0, schema.props?.limit || 10).map((item: any, i: number) => (
35-
<div key={i} className="flex items-center justify-between p-3 border rounded bg-card text-card-foreground">
36-
<div className="flex flex-col">
37-
<span className="font-medium text-sm">{renderTemplate(schema.props?.render?.title, item) || item.name}</span>
38-
<span className="text-xs text-muted-foreground">{renderTemplate(schema.props?.render?.description, item)}</span>
39-
</div>
40-
{schema.props?.render?.extra && (
41-
<div className="text-xs font-semibold px-2 py-1 bg-secondary rounded">
42-
{renderTemplate(schema.props?.render?.extra, item)}
43-
</div>
44-
)}
45-
</div>
46-
))}
47-
</div>
48-
);
49-
};
50-
51-
// Simple string template helper: "Hello ${name}" -> "Hello World"
52-
function renderTemplate(template: string, data: any) {
53-
if (!template) return "";
54-
return template.replace(/\$\{(.*?)\}/g, (match, key) => {
55-
return data[key] !== undefined ? data[key] : match;
56-
});
57-
}
58-
59-
export function registerCustomWidgets() {
60-
registerComponent("widget:metric", MetricWidget);
61-
registerComponent("widget:chart", ChartWidget);
62-
registerComponent("view:timeline", TimelineView);
63-
registerComponent("view:list", ListView);
64-
}
5+
// 1. Metric Widget
656
const MetricWidget = ({ schema }: any) => {
667
const { value, label, trend, format } = schema.props || {};
678
const isPositive = trend?.startsWith('+');
689

10+
// Simple resolution logic if needed, or assume pre-resolved
11+
const { scope } = useDataScope();
12+
const resolvedValue = resolveExpression(value, scope);
13+
6914
return (
7015
<div className="flex flex-col gap-1">
71-
<div className="text-2xl font-bold">{value}</div>
16+
<div className="text-2xl font-bold">
17+
{format === 'currency' && typeof resolvedValue === 'number'
18+
? new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(resolvedValue)
19+
: resolvedValue}
20+
</div>
7221
<div className="text-xs text-muted-foreground">
7322
{trend && (
7423
<span className={cn("mr-1 font-medium", isPositive ? "text-green-600" : "text-red-600")}>
@@ -84,14 +33,14 @@ const MetricWidget = ({ schema }: any) => {
8433
// 2. Simple CSS Bar Chart
8534
const ChartWidget = ({ schema }: any) => {
8635
const { title, data, height = 200 } = schema.props || {};
87-
// Extract max value for scaling
88-
const maxValue = Math.max(...(data?.map((d: any) => d.value) || [100]));
36+
const safeData = Array.isArray(data) ? data : [];
37+
const maxValue = Math.max(...(safeData.map((d: any) => d.value) || [100]));
8938

9039
return (
9140
<div className="flex flex-col h-full w-full">
9241
{title && <h3 className="text-sm font-medium mb-4">{title}</h3>}
9342
<div className="flex items-end justify-around w-full" style={{ height: `${height}px` }}>
94-
{data?.map((item: any, i: number) => {
43+
{safeData.map((item: any, i: number) => {
9544
const percent = (item.value / maxValue) * 100;
9645
return (
9746
<div key={i} className="flex flex-col items-center gap-2 group w-full px-1">
@@ -127,8 +76,7 @@ const TimelineView = ({ schema }: any) => {
12776
<div className="relative mt-1">
12877
<div className="absolute top-8 left-1/2 h-full w-px -translate-x-1/2 bg-gray-200" />
12978
<div className="relative flex h-8 w-8 items-center justify-center rounded-full border bg-background shadow-sm">
130-
{/* Simple icon mapping or fallback */}
131-
<span className="text-xs"></span>
79+
<div className="w-2 h-2 rounded-full bg-blue-500" />
13280
</div>
13381
</div>
13482
<div className="flex flex-col gap-1 pb-8">
@@ -143,45 +91,51 @@ const TimelineView = ({ schema }: any) => {
14391
};
14492

14593
// 4. List View (Simple)
146-
const ListView = ({ schema, data }: any) => {
147-
// If bind is used, the renderer might pass resolved data, but here we assume rudimentary binding access
148-
// In a real scenario, useDataScope or similar hook would be used.
149-
// For this simple registry, let's assume the parent resolved it or we just render props.
150-
// Actually, bind logic is handled by the SchemaRenderer usually?
151-
// If this component supports binding, it should receive `data` if the parent handles it
152-
// OR it should use `useDataScope`.
153-
154-
// Simplification: We will just render what is passed in props for now or handle simple binding manualy if needed.
155-
// But wait, the schema used `bind: "opportunities"`.
156-
// The SchemaRenderer logic should resolve this if wrapped correctly?
157-
// Let's assume standard prop passing for now.
158-
159-
// For the bound data, we need to access `schema.data` if the engine injects it.
160-
// Or we use a hook.
94+
const ListView = ({ schema }: any) => {
95+
const { scope } = useDataScope();
96+
let data = schema.data;
97+
98+
if (schema.bind) {
99+
// Resolve simple property path from scope
100+
data = scope ? scope[schema.bind] : [];
101+
}
161102

162-
// NOTE: Since I cannot easily import `useDataScope` without checking where it is exported from (likely @object-ui/react),
163-
// I will try to import it.
103+
if (!data || !Array.isArray(data)) return <div className="p-4 text-muted-foreground">No data found</div>;
164104

165105
return (
166-
<div className="space-y-4">
167-
{(schema.data || []).map((item: any, i: number) => (
168-
<div key={i} className="flex items-center justify-between p-4 border rounded-lg">
169-
<div>
170-
<div className="font-medium">{item.name || item.title || "Unknown"}</div>
171-
<div className="text-sm text-gray-500">{item.description}</div>
106+
<div className="space-y-2">
107+
{data.slice(0, schema.props?.limit || 10).map((item: any, i: number) => (
108+
<div key={i} className="flex items-center justify-between p-3 border rounded bg-card text-card-foreground">
109+
<div className="flex flex-col">
110+
<span className="font-medium text-sm">{resolveExpression(schema.props?.render?.title, item) || item.name}</span>
111+
<span className="text-xs text-muted-foreground">{resolveExpression(schema.props?.render?.description, item)}</span>
172112
</div>
173-
<div className="text-sm font-bold">{item.amount || item.status}</div>
113+
{schema.props?.render?.extra && (
114+
<div className="text-xs font-semibold px-2 py-1 bg-secondary rounded">
115+
{resolveExpression(schema.props?.render?.extra, item)}
116+
</div>
117+
)}
174118
</div>
175119
))}
176120
</div>
177121
);
122+
};
123+
124+
function resolveExpression(expr: string, context: any) {
125+
if (typeof expr !== 'string') return expr;
126+
// Handle simplified ${prop}
127+
return expr.replace(/\$\{(.*?)\}/g, (match, key) => {
128+
// Strip data. prefix if simple context binding
129+
const cleanKey = key.replace(/^data\./, '');
130+
// Traverse object properties
131+
const val = cleanKey.split('.').reduce((o: any, i: string) => (o ? o[i] : null), context);
132+
return val !== undefined && val !== null ? val : match;
133+
});
178134
}
179135

180136
export function registerCustomWidgets() {
181-
registerComponent("widget:metric", MetricWidget);
182-
registerComponent("widget:chart", ChartWidget);
183-
registerComponent("view:timeline", TimelineView);
184-
// Note: view:list might already exist or handled by plugin-view.
185-
// If I want to override or add it:
186-
registerComponent("view:list", ListView);
137+
ComponentRegistry.register("widget:metric", MetricWidget);
138+
ComponentRegistry.register("widget:chart", ChartWidget);
139+
ComponentRegistry.register("view:timeline", TimelineView);
140+
ComponentRegistry.register("view:list", ListView);
187141
}

examples/crm-app/src/mocks/browser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { protocol } from './protocol';
55
import { mockData } from '../data';
66

77
// 1. Initialize the mock server with our protocol definition
8-
ObjectStackServer.init(protocol);
8+
// Casting as any to bypass strict protocol interface checks for this mock implementation
9+
ObjectStackServer.init(protocol as any);
910

1011
// 2. Seed Data
1112
// We intentionally perform this side-effect here to ensure data exists when the app starts
@@ -122,9 +123,8 @@ const handlers = [
122123

123124
const standardHandlers = [
124125
// Data Query
125-
http.get('/api/v1/data/:object', async ({ params, request }) => {
126+
http.get('/api/v1/data/:object', async ({ params }) => {
126127
const { object } = params;
127-
const url = new URL(request.url);
128128
// We could implement filtering from url.searchParams if ObjectStackServer supports it
129129
const result = await ObjectStackServer.findData(object as string); // findData(obj, params?)
130130
return HttpResponse.json({ value: result.data }); // OData style usually { value: [] }? Or ObjectStack style?

0 commit comments

Comments
 (0)