Skip to content

Commit a8aee26

Browse files
Milestone feedback tasks (#23)
* feat(version): add version on client * add version bump on github worflow * add version on footer * add update version script * chore(livePreview): add toggle button * chore(livePreview): allow more lib on livePreview * bump(package): bump versions * chore(livePreview): - enable components on simple examples - live component (Balance Checker) - improve live Preview * feat(console): add console output toggle * chore(codeEditor): update live preview responsive * chore(editor): update editor types * chore(playground): fix build vercel * chore(editor): update height size editor * chore(vercel): fix build * chore: update AccountBalanceChecker * chore(playground): fix overflow block of code * chore(playground): update main code
1 parent d0cb7fe commit a8aee26

11 files changed

Lines changed: 1222 additions & 161 deletions

File tree

package.json

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,46 +22,46 @@
2222
"@radix-ui/react-slot": "^1.2.0",
2323
"@radix-ui/react-toggle": "^1.1.6",
2424
"@types/babel__standalone": "^7.1.9",
25-
"@types/uuid": "^9.0.7",
25+
"@types/uuid": "^9.0.8",
2626
"@types/webpack": "^5.28.5",
2727
"biome": "^0.3.3",
28-
"bn.js": "^5.2.1",
28+
"bn.js": "^5.2.2",
2929
"class-variance-authority": "^0.7.1",
3030
"classnames": "^2.5.1",
3131
"clsx": "^2.1.1",
32-
"date-fns": "^3.3.1",
33-
"framer-motion": "^12.7.4",
34-
"immer": "^10.0.4",
32+
"date-fns": "^3.6.0",
33+
"framer-motion": "^12.9.2",
34+
"immer": "^10.1.1",
3535
"lucide-react": "^0.501.0",
3636
"monaco-editor": "^0.52.2",
3737
"monaco-editor-webpack-plugin": "^7.1.0",
3838
"next": "15.2.4",
39-
"polkadot-api": "^1.9.13",
39+
"polkadot-api": "^1.10.0",
4040
"prettier": "^3.5.3",
41-
"react": "^19.0.0",
42-
"react-dom": "^19.0.0",
41+
"react": "^19.1.0",
42+
"react-dom": "^19.1.0",
4343
"react-error-boundary": "^5.0.0",
4444
"react-icons": "^5.5.0",
4545
"react-jsx": "^1.0.0",
4646
"recharts": "^2.15.3",
4747
"rxjs": "^7.8.2",
4848
"tailwind-merge": "^3.2.0",
49-
"tw-animate-css": "^1.2.5",
49+
"tw-animate-css": "^1.2.8",
5050
"uuid": "^9.0.1",
51-
"webpack": "^5.99.6",
51+
"webpack": "^5.99.7",
5252
"zustand": "^5.0.3"
5353
},
5454
"devDependencies": {
5555
"@biomejs/biome": "^1.9.4",
56-
"@eslint/eslintrc": "^3",
57-
"@tailwindcss/postcss": "^4",
58-
"@types/node": "^20",
59-
"@types/react": "^19",
60-
"@types/react-dom": "^19",
61-
"eslint": "^9",
56+
"@eslint/eslintrc": "^3.3.1",
57+
"@tailwindcss/postcss": "^4.1.4",
58+
"@types/node": "^20.17.32",
59+
"@types/react": "^19.1.2",
60+
"@types/react-dom": "^19.1.2",
61+
"eslint": "^9.25.1",
6262
"eslint-config-next": "15.2.4",
6363
"i": "^0.3.7",
64-
"tailwindcss": "^4",
65-
"typescript": "^5"
64+
"tailwindcss": "^4.1.4",
65+
"typescript": "^5.8.3"
6666
}
6767
}

src/components/ConsoleOutput.tsx

Lines changed: 71 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,62 @@
1-
import React, { useRef, useEffect } from "react";
2-
import { Trash2, Copy, CheckCircle2 } from "lucide-react";
3-
import { ConsoleOutput as ConsoleOutputType } from "@/lib/types/example";
1+
"use client";
2+
3+
import type React from "react";
4+
import { useRef, useEffect, useState } from "react";
5+
import { Trash2, Copy, CheckCircle2, MinimizeIcon, Maximize2, Code } from "lucide-react";
6+
import type { ConsoleOutput as ConsoleOutputType } from "@/lib/types/example";
47

58
interface ConsoleOutputProps {
69
outputs: ConsoleOutputType[];
710
onClear: () => void;
11+
isLivePreviewActive?: boolean;
812
}
913

1014
/**
11-
* Component that displays console output from code execution
15+
* Component that displays console output from code execution with responsive layout
16+
* and toggle capability when live preview is active
1217
*/
13-
const ConsoleOutput: React.FC<ConsoleOutputProps> = ({ outputs, onClear }) => {
18+
const ConsoleOutput: React.FC<ConsoleOutputProps> = ({
19+
outputs,
20+
onClear,
21+
isLivePreviewActive = false
22+
}) => {
1423
const consoleRef = useRef<HTMLDivElement>(null);
15-
const [copied, setCopied] = React.useState(false);
24+
const [copied, setCopied] = useState(false);
25+
const [isMinimized, setIsMinimized] = useState(false);
26+
const [unreadOutputs, setUnreadOutputs] = useState(0);
1627

17-
// Auto-scroll to bottom when new output is added
1828
useEffect(() => {
1929
if (consoleRef.current) {
2030
consoleRef.current.scrollTop = consoleRef.current.scrollHeight;
2131
}
22-
}, [outputs]);
2332

24-
// Copy console output to clipboard
33+
if (isMinimized && outputs.length > 0) {
34+
setUnreadOutputs(prev => prev + 1);
35+
}
36+
}, [outputs, isMinimized]); // Adicionado isMinimized como dependência
37+
38+
39+
useEffect(() => {
40+
if (!isMinimized) {
41+
setUnreadOutputs(0);
42+
}
43+
}, [isMinimized]);
44+
45+
2546
const copyToClipboard = () => {
2647
const text = outputs.map((output) => output.content).join("\n");
2748
navigator.clipboard.writeText(text);
2849
setCopied(true);
2950
setTimeout(() => setCopied(false), 2000);
3051
};
3152

32-
// Render a specific output item based on its type
53+
54+
const toggleMinimize = () => {
55+
setIsMinimized(!isMinimized);
56+
};
57+
58+
3359
const renderOutput = (output: ConsoleOutputType, index: number) => {
34-
// Determine text color based on output type
3560
const textColorClass =
3661
output.type === "error"
3762
? "text-red-400"
@@ -46,9 +71,32 @@ const ConsoleOutput: React.FC<ConsoleOutputProps> = ({ outputs, onClear }) => {
4671
);
4772
};
4873

74+
75+
if (isLivePreviewActive && isMinimized) {
76+
return (
77+
<div className="fixed bottom-4 right-4 z-10">
78+
<button
79+
onClick={toggleMinimize}
80+
className="flex items-center gap-2 px-3 py-2 bg-gray-800 text-white rounded-md shadow-lg hover:bg-gray-700 transition-all duration-200"
81+
>
82+
<Code size={16} />
83+
<span>Console</span>
84+
{unreadOutputs > 0 && (
85+
<span className="flex items-center justify-center w-5 h-5 bg-red-500 text-white text-xs font-bold rounded-full">
86+
{unreadOutputs > 9 ? '9+' : unreadOutputs}
87+
</span>
88+
)}
89+
</button>
90+
</div>
91+
);
92+
}
93+
94+
const consoleHeight = isLivePreviewActive ? "h-64" : "h-full";
95+
const consolePosition = isLivePreviewActive ? "fixed bottom-0 left-0 right-0 z-10 border-t border-gray-200 shadow-lg" : "";
96+
4997
return (
50-
<div className="flex flex-col h-full">
51-
<div className="flex items-center justify-between p-2 border-b border-gray-200 bg-gray-50">
98+
<div className={`flex flex-col ${consoleHeight} ${consolePosition} bg-gray-50 transition-all duration-300`}>
99+
<div className="flex items-center justify-between p-2 border-b border-gray-200 bg-gray-100">
52100
<div className="text-sm font-medium">Console Output</div>
53101
<div className="flex items-center space-x-2">
54102
<button
@@ -71,6 +119,15 @@ const ConsoleOutput: React.FC<ConsoleOutputProps> = ({ outputs, onClear }) => {
71119
>
72120
<Trash2 size={16} />
73121
</button>
122+
{isLivePreviewActive && (
123+
<button
124+
className="p-1 text-gray-500 hover:text-gray-700 transition-colors"
125+
onClick={toggleMinimize}
126+
title={isMinimized ? "Maximize console" : "Minimize console"}
127+
>
128+
{isMinimized ? <Maximize2 size={16} /> : <MinimizeIcon size={16} />}
129+
</button>
130+
)}
74131
</div>
75132
</div>
76133
<div
@@ -89,4 +146,4 @@ const ConsoleOutput: React.FC<ConsoleOutputProps> = ({ outputs, onClear }) => {
89146
);
90147
};
91148

92-
export default ConsoleOutput;
149+
export default ConsoleOutput;

src/components/LivePreview/index.tsx

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -130,11 +130,29 @@ const analyzeExports = (exports: Record<string, unknown>): string => {
130130
}
131131
};
132132

133-
const createModuleEnvironment = () => {
133+
134+
const createModuleEnvironment = (network?: Network) => {
134135
const exports: Record<string, unknown> = {};
136+
137+
138+
const global = {
139+
network,
140+
networkData: {
141+
id: network?.id || '',
142+
name: network?.name || '',
143+
tokenSymbol: network?.tokenSymbol || '',
144+
tokenDecimals: network?.tokenDecimals || 0,
145+
isTest: network?.isTest || false,
146+
endpoint: network?.endpoint || '',
147+
explorer: network?.explorer || '',
148+
faucet: network?.faucet || '',
149+
},
150+
};
151+
135152
return {
136153
module: { exports, id: "user-component", loaded: true },
137154
exports,
155+
global,
138156
require: (path: string) => {
139157
if (ALLOWED_MODULES[path]) return ALLOWED_MODULES[path];
140158
throw new Error(
@@ -197,7 +215,7 @@ const normalizeComponent = (
197215
typeof v === "function" &&
198216
((v as { prototype?: unknown }).prototype === undefined ||
199217
(v as { prototype: { render?: () => void } }).prototype.render ===
200-
undefined);
218+
undefined);
201219

202220
const isSpecialReactComponent = (v: unknown): v is { $$typeof: symbol } =>
203221
typeof v === "object" && v !== null && "$$typeof" in v;
@@ -251,15 +269,17 @@ const normalizeComponent = (
251269
return null;
252270
};
253271

254-
const evaluateComponent = (code: string) => {
272+
// Updated to include network parameter
273+
const evaluateComponent = (code: string, network?: Network) => {
255274
try {
256275
const { code: transpiled, errors, originalCode } = transpileCode(code);
257276
if (errors.length > 0) {
258277
throw new Error(
259278
`Compilation errors:\n${errors.map((e) => `${e.line ? `Line ${e.line}: ` : ""}${e.message}`).join("\n")}`,
260279
);
261280
}
262-
const { module, exports, require } = createModuleEnvironment();
281+
282+
const { module, exports, require, global } = createModuleEnvironment(network);
263283
const reactHooks = {
264284
useState,
265285
useEffect,
@@ -271,25 +291,75 @@ const evaluateComponent = (code: string) => {
271291
useLayoutEffect,
272292
useImperativeHandle,
273293
};
294+
295+
// Inject network data into the global scope
296+
const networkCode = network ? `
297+
// Inject network data from props
298+
const __network__ = ${JSON.stringify(network)};
299+
300+
// Create helper functions for accessing network data
301+
const useNetworkInfo = () => __network__;
302+
const getChainToken = () => ({
303+
name: __network__.tokenSymbol,
304+
decimals: __network__.tokenDecimals,
305+
existentialDeposit: BigInt(1),
306+
metadataTypes: {}
307+
});
308+
309+
// Create typedApi mock
310+
const typedApi = {
311+
tx: {
312+
Balances: {
313+
transfer_keep_alive: async (params) => {
314+
return {
315+
getEncodedData: async () => ({
316+
asHex: () => "0x" + Array(64).fill('0').join('')
317+
})
318+
};
319+
}
320+
}
321+
}
322+
};
323+
` : '';
324+
274325
const evaluationCode = `
275326
try {
276327
const { ${Object.keys(reactHooks).join(", ")} } = __hooks__;
328+
329+
// Set up global context
330+
const window = {
331+
...global,
332+
// Add mock fs functionality for file reading in components
333+
fs: {
334+
readFile: async (path, options) => {
335+
console.log("Mock file reading:", path);
336+
return "mocked file content";
337+
}
338+
}
339+
};
340+
341+
${networkCode}
342+
277343
${transpiled}
278344
return module.exports;
279345
} catch(error) {
280346
throw new Error('Runtime error: ' + error.message + '\\n' + error.stack);
281347
}
282348
`;
349+
283350
const evaluator = new Function(
284351
"React",
285352
"exports",
286353
"module",
287354
"require",
288355
"__hooks__",
356+
"global",
289357
evaluationCode,
290358
);
291-
const result = evaluator(React, exports, module, require, reactHooks);
359+
360+
const result = evaluator(React, exports, module, require, reactHooks, global);
292361
const component = normalizeComponent(result as Record<string, unknown>);
362+
293363
if (!component) {
294364
throw new Error(
295365
`No valid React component found. Please ensure your code exports a valid React component.\n\nExport analysis:\n${analyzeExports(result as Record<string, unknown>)}\n\nA valid React component can be:\n- A function component: const MyComponent = () => <div>Hello</div>\n- A class component: class MyComponent extends React.Component { ... }\n- Exported as default: export default MyComponent\n- A memo, forwardRef, or lazy component`,
@@ -406,6 +476,7 @@ const LivePreview: React.FC<LivePreviewProps> = ({
406476
width = "100%",
407477
height = "auto",
408478
fallbackMessage = "Loading component...",
479+
network,
409480
}) => {
410481
const [componentData, setComponentData] = useState<{
411482
Component: ComponentType;
@@ -414,12 +485,15 @@ const LivePreview: React.FC<LivePreviewProps> = ({
414485
const [error, setError] = useState<Error | null>(null);
415486
const [loading, setLoading] = useState(true);
416487

488+
const networkData = useMemo(() => network, [network]);
489+
417490
useEffect(() => {
418491
let isMounted = true;
419492
setLoading(true);
420493
const timer = setTimeout(async () => {
421494
try {
422-
const { component, originalCode } = evaluateComponent(code);
495+
// Pass network data to evaluateComponent
496+
const { component, originalCode } = evaluateComponent(code, networkData);
423497
if (isMounted) {
424498
setComponentData({ Component: component, originalCode });
425499
setError(null);
@@ -434,7 +508,7 @@ const LivePreview: React.FC<LivePreviewProps> = ({
434508
isMounted = false;
435509
clearTimeout(timer);
436510
};
437-
}, [code]);
511+
}, [code, networkData]);
438512

439513
const containerStyle: React.CSSProperties = {
440514
width,
@@ -452,7 +526,7 @@ const LivePreview: React.FC<LivePreviewProps> = ({
452526
return (
453527
<div style={containerStyle}>
454528
<ErrorBoundary
455-
resetKeys={[code]}
529+
resetKeys={[code, network?.id]}
456530
FallbackComponent={({ error }) => (
457531
<ErrorFallback
458532
error={error}
@@ -461,8 +535,8 @@ const LivePreview: React.FC<LivePreviewProps> = ({
461535
)}
462536
>
463537
{loading ? (
464-
<div style={{
465-
textAlign: "center",
538+
<div style={{
539+
textAlign: "center",
466540
color: "#666",
467541
padding: "20px",
468542
fontFamily: "system-ui, -apple-system, sans-serif",
@@ -476,8 +550,9 @@ const LivePreview: React.FC<LivePreviewProps> = ({
476550
/>
477551
) : componentData?.Component ? (
478552
<Suspense fallback={
479-
<div style={{
480-
textAlign: "center",
553+
554+
<div style={{
555+
textAlign: "center",
481556
color: "#666",
482557
padding: "20px",
483558
fontFamily: "system-ui, -apple-system, sans-serif",

0 commit comments

Comments
 (0)