mirror of
https://github.com/SikongJueluo/cc-utils.git
synced 2025-12-20 13:37:49 +08:00
fix render bug and add todos demo
This commit is contained in:
@@ -5,7 +5,7 @@
|
|||||||
import { UIObject } from "./UIObject";
|
import { UIObject } from "./UIObject";
|
||||||
import { calculateLayout } from "./layout";
|
import { calculateLayout } from "./layout";
|
||||||
import { render as renderTree, clearScreen } from "./renderer";
|
import { render as renderTree, clearScreen } from "./renderer";
|
||||||
import { createEffect } from "./reactivity";
|
import { CCLog } from "../ccLog";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main application class
|
* Main application class
|
||||||
@@ -18,11 +18,14 @@ export class Application {
|
|||||||
private focusedNode?: UIObject;
|
private focusedNode?: UIObject;
|
||||||
private termWidth: number;
|
private termWidth: number;
|
||||||
private termHeight: number;
|
private termHeight: number;
|
||||||
|
private logger: CCLog;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const [width, height] = term.getSize();
|
const [width, height] = term.getSize();
|
||||||
this.termWidth = width;
|
this.termWidth = width;
|
||||||
this.termHeight = height;
|
this.termHeight = height;
|
||||||
|
this.logger = new CCLog("tui_debug.log", false);
|
||||||
|
this.logger.debug("Application constructed.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,6 +66,7 @@ export class Application {
|
|||||||
clearScreen();
|
clearScreen();
|
||||||
|
|
||||||
// Initial render
|
// Initial render
|
||||||
|
this.logger.debug("Initial renderFrame call.");
|
||||||
this.renderFrame();
|
this.renderFrame();
|
||||||
|
|
||||||
// Main event loop
|
// Main event loop
|
||||||
@@ -76,12 +80,14 @@ export class Application {
|
|||||||
* Stop the application
|
* Stop the application
|
||||||
*/
|
*/
|
||||||
stop(): void {
|
stop(): void {
|
||||||
|
this.logger.debug("Application stopping.");
|
||||||
this.running = false;
|
this.running = false;
|
||||||
|
|
||||||
if (this.root !== undefined) {
|
if (this.root !== undefined) {
|
||||||
this.root.unmount();
|
this.root.unmount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.close();
|
||||||
clearScreen();
|
clearScreen();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,13 +95,14 @@ export class Application {
|
|||||||
* Render loop - continuously renders when needed
|
* Render loop - continuously renders when needed
|
||||||
*/
|
*/
|
||||||
private renderLoop(): void {
|
private renderLoop(): void {
|
||||||
// Set up reactive rendering - re-render whenever any signal changes
|
|
||||||
createEffect(() => {
|
|
||||||
this.renderFrame();
|
|
||||||
});
|
|
||||||
|
|
||||||
while (this.running) {
|
while (this.running) {
|
||||||
// Keep the loop alive
|
if (this.needsRender) {
|
||||||
|
this.logger.debug(
|
||||||
|
"renderLoop: needsRender is true, calling renderFrame.",
|
||||||
|
);
|
||||||
|
this.needsRender = false;
|
||||||
|
this.renderFrame();
|
||||||
|
}
|
||||||
os.sleep(0.05);
|
os.sleep(0.05);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,7 +112,7 @@ export class Application {
|
|||||||
*/
|
*/
|
||||||
private renderFrame(): void {
|
private renderFrame(): void {
|
||||||
if (this.root === undefined) return;
|
if (this.root === undefined) return;
|
||||||
|
this.logger.debug("renderFrame: Calculating layout.");
|
||||||
// Calculate layout
|
// Calculate layout
|
||||||
calculateLayout(this.root, this.termWidth, this.termHeight, 1, 1);
|
calculateLayout(this.root, this.termWidth, this.termHeight, 1, 1);
|
||||||
|
|
||||||
@@ -113,7 +120,9 @@ export class Application {
|
|||||||
clearScreen();
|
clearScreen();
|
||||||
|
|
||||||
// Render the tree
|
// Render the tree
|
||||||
|
this.logger.debug("renderFrame: Rendering tree.");
|
||||||
renderTree(this.root, this.focusedNode);
|
renderTree(this.root, this.focusedNode);
|
||||||
|
this.logger.debug("renderFrame: Finished rendering tree.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -121,15 +130,20 @@ export class Application {
|
|||||||
*/
|
*/
|
||||||
private eventLoop(): void {
|
private eventLoop(): void {
|
||||||
while (this.running) {
|
while (this.running) {
|
||||||
const [eventType, ...eventData] = os.pullEvent() as LuaMultiReturn<
|
const [eventType, ...eventData] = os.pullEvent();
|
||||||
unknown[]
|
|
||||||
>;
|
|
||||||
|
|
||||||
if (eventType === "key") {
|
if (eventType === "key") {
|
||||||
this.handleKeyEvent(eventData[0] as number);
|
this.handleKeyEvent(eventData[0] as number);
|
||||||
} else if (eventType === "char") {
|
} else if (eventType === "char") {
|
||||||
this.handleCharEvent(eventData[0] as string);
|
this.handleCharEvent(eventData[0] as string);
|
||||||
} else if (eventType === "mouse_click") {
|
} else if (eventType === "mouse_click") {
|
||||||
|
this.logger.debug(
|
||||||
|
string.format(
|
||||||
|
"eventLoop: Mouse click detected at (%d, %d)",
|
||||||
|
eventData[1],
|
||||||
|
eventData[2],
|
||||||
|
),
|
||||||
|
);
|
||||||
this.handleMouseClick(
|
this.handleMouseClick(
|
||||||
eventData[0] as number,
|
eventData[0] as number,
|
||||||
eventData[1] as number,
|
eventData[1] as number,
|
||||||
@@ -204,10 +218,14 @@ export class Application {
|
|||||||
private handleMouseClick(button: number, x: number, y: number): void {
|
private handleMouseClick(button: number, x: number, y: number): void {
|
||||||
if (button !== 1 || this.root === undefined) return;
|
if (button !== 1 || this.root === undefined) return;
|
||||||
|
|
||||||
|
this.logger.debug("handleMouseClick: Finding node.");
|
||||||
// Find which element was clicked
|
// Find which element was clicked
|
||||||
const clicked = this.findNodeAt(this.root, x, y);
|
const clicked = this.findNodeAt(this.root, x, y);
|
||||||
|
|
||||||
if (clicked !== undefined) {
|
if (clicked !== undefined) {
|
||||||
|
this.logger.debug(
|
||||||
|
string.format("handleMouseClick: Found node of type %s.", clicked.type),
|
||||||
|
);
|
||||||
// Set focus
|
// Set focus
|
||||||
this.focusedNode = clicked;
|
this.focusedNode = clicked;
|
||||||
|
|
||||||
@@ -215,7 +233,12 @@ export class Application {
|
|||||||
if (clicked.type === "button") {
|
if (clicked.type === "button") {
|
||||||
const onClick = clicked.handlers.onClick;
|
const onClick = clicked.handlers.onClick;
|
||||||
if (onClick) {
|
if (onClick) {
|
||||||
|
this.logger.debug(
|
||||||
|
"handleMouseClick: onClick handler found, executing.",
|
||||||
|
);
|
||||||
(onClick as () => void)();
|
(onClick as () => void)();
|
||||||
|
this.logger.debug("handleMouseClick: onClick handler finished.");
|
||||||
|
this.needsRender = true;
|
||||||
}
|
}
|
||||||
} else if (clicked.type === "input") {
|
} else if (clicked.type === "input") {
|
||||||
const type = clicked.props.type as string | undefined;
|
const type = clicked.props.type as string | undefined;
|
||||||
@@ -229,13 +252,14 @@ export class Application {
|
|||||||
) {
|
) {
|
||||||
const currentValue = (checkedProp as () => boolean)();
|
const currentValue = (checkedProp as () => boolean)();
|
||||||
(onChangeProp as (v: boolean) => void)(!currentValue);
|
(onChangeProp as (v: boolean) => void)(!currentValue);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.needsRender = true;
|
this.needsRender = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.logger.debug("handleMouseClick: No node found at click position.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the UI node at a specific screen position
|
* Find the UI node at a specific screen position
|
||||||
@@ -256,9 +280,19 @@ export class Application {
|
|||||||
// Check this node
|
// Check this node
|
||||||
if (node.layout !== undefined) {
|
if (node.layout !== undefined) {
|
||||||
const { x: nx, y: ny, width, height } = node.layout;
|
const { x: nx, y: ny, width, height } = node.layout;
|
||||||
if (x >= nx && x < nx + width && y >= ny && y < ny + height) {
|
const hit = x >= nx && x < nx + width && y >= ny && y < ny + height;
|
||||||
|
if (hit) {
|
||||||
|
this.logger.debug(
|
||||||
|
string.format(
|
||||||
|
"findNodeAt: Hit test TRUE for %s at (%d, %d)",
|
||||||
|
node.type,
|
||||||
|
nx,
|
||||||
|
ny,
|
||||||
|
),
|
||||||
|
);
|
||||||
// Only return interactive elements
|
// Only return interactive elements
|
||||||
if (node.type === "button" || node.type === "input") {
|
if (node.type === "button" || node.type === "input") {
|
||||||
|
this.logger.debug("findNodeAt: Node is interactive, returning.");
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -318,22 +318,22 @@ Effect 用于将响应式系统与外部世界(如日志、计时器、手动A
|
|||||||
### `src/lib/ccTUI/`
|
### `src/lib/ccTUI/`
|
||||||
|
|
||||||
- **`index.ts`**: 框架的公共 API 入口。所有可供外部使用的组件(如 `div`, `button`)和函数(如 `createSignal`)都应由此文件导出。
|
- **`index.ts`**: 框架的公共 API 入口。所有可供外部使用的组件(如 `div`, `button`)和函数(如 `createSignal`)都应由此文件导出。
|
||||||
- **`Signal.ts`**: 包含框架的响应式系统核心,即 `createSignal`, `createEffect`, `batch` 等的实现。
|
- **`reactivity.ts`**: 包含框架的响应式系统核心,即 `createSignal`, `createEffect`, `batch`, `createMemo` 等的实现。
|
||||||
- **`UIObject.ts`**: 所有 UI 元素的基类或基础类型。定义了如位置、尺寸、父子关系、绘制(draw)和更新(update)等通用接口。
|
- **`store.ts`**: 包含 `createStore` 的实现,用于管理对象和数组等复杂状态。
|
||||||
- **`TUIApplication.ts`**: 应用程序的根实例。负责管理主窗口、事件循环(event loop)、焦点管理和全局重绘。
|
- **`UIObject.ts`**: 定义了所有 UI 元素的基类或基础类型 `UIObject`,包括位置、尺寸、父子关系、绘制(draw)和更新(update)等通用接口。
|
||||||
- **`UIWindow.ts`**: 代表一个独立的窗口(通常是整个终端屏幕),作为所有 UI 元素的根容器和绘制表面。
|
- **`application.ts`**: 包含 `Application` 类,负责管理主窗口、事件循环(event loop)、焦点管理和全局重绘。`render` 函数也在这里。
|
||||||
- **`TextLabel.ts`, `Button.ts`, `InputField.ts`**: 具体的基础组件实现。
|
- **`renderer.ts`**: 负责将 `UIObject` 树解析并绘制到 ComputerCraft 终端屏幕上。
|
||||||
|
- **`layout.ts`**: Flexbox 布局引擎的实现。解析 `class` 属性并计算组件的布局。
|
||||||
|
- **`components.ts`**: 包含所有基础 UI 组件的实现,如 `div`, `label`, `button`, `input`, `form` 等。
|
||||||
|
- **`controlFlow.ts`**: 包含控制流组件,如 `For` 和 `Show`,用于处理列表渲染和条件渲染。
|
||||||
- **`framework.md`**: (本文档) 框架的设计指南、API 参考和代码规范。
|
- **`framework.md`**: (本文档) 框架的设计指南、API 参考和代码规范。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. 框架示例
|
## 7. 框架示例
|
||||||
|
|
||||||
- **`src/tuiExample/main.ts`**
|
- **`src/tuiExample/main.ts`**: 此文件将作为新的响应式框架示例,用于展示和验证所有 SolidJS 风格的 API。
|
||||||
- 此文件是 `ccTUI` 框架的功能示例和测试场(使用旧的 API)。
|
- 在对框架进行任何修改或添加新功能后,都应在此文件中创建或更新相应的示例来验证其正确性。
|
||||||
- **`src/tuiExample/main.new.ts`**
|
|
||||||
- 新的响应式框架示例,展示 SolidJS 风格的 API。
|
|
||||||
- 在对框架进行任何修改或添加新功能后,都应在此文件中创建相应的示例来验证其正确性并进行展示。
|
|
||||||
- 使用 `just build-example sync` 命令可以编译此示例并将其同步到游戏内的 `computer` 目录中,以便在 Minecraft 环境中实际运行和查看效果。
|
- 使用 `just build-example sync` 命令可以编译此示例并将其同步到游戏内的 `computer` 目录中,以便在 Minecraft 环境中实际运行和查看效果。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -435,4 +435,3 @@ pnpm tstl -p ./tsconfig.tuiExample.json
|
|||||||
# 运行 ESLint 检查
|
# 运行 ESLint 检查
|
||||||
pnpm dlx eslint src/lib/ccTUI/reactivity.ts
|
pnpm dlx eslint src/lib/ccTUI/reactivity.ts
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,20 @@ function measureNode(node: UIObject): { width: number; height: number } {
|
|||||||
const getTextContent = (): string => {
|
const getTextContent = (): string => {
|
||||||
if (node.textContent !== undefined) {
|
if (node.textContent !== undefined) {
|
||||||
if (typeof node.textContent === "function") {
|
if (typeof node.textContent === "function") {
|
||||||
return node.textContent();
|
return (node.textContent)();
|
||||||
}
|
}
|
||||||
return node.textContent;
|
return node.textContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For nodes with text children, get their content
|
||||||
|
if (node.children.length > 0 && node.children[0].textContent !== undefined) {
|
||||||
|
const child = node.children[0];
|
||||||
|
if (typeof child.textContent === "function") {
|
||||||
|
return (child.textContent)();
|
||||||
|
}
|
||||||
|
return child.textContent!;
|
||||||
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -63,6 +73,8 @@ function measureNode(node: UIObject): { width: number; height: number } {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const direction = node.layoutProps.flexDirection ?? "row";
|
const direction = node.layoutProps.flexDirection ?? "row";
|
||||||
|
const isFlex = node.type === "div" || node.type === "form";
|
||||||
|
const gap = isFlex ? 1 : 0;
|
||||||
|
|
||||||
if (direction === "row") {
|
if (direction === "row") {
|
||||||
// In row direction, width is sum of children, height is max
|
// In row direction, width is sum of children, height is max
|
||||||
@@ -71,6 +83,9 @@ function measureNode(node: UIObject): { width: number; height: number } {
|
|||||||
totalWidth += childSize.width;
|
totalWidth += childSize.width;
|
||||||
totalHeight = math.max(totalHeight, childSize.height);
|
totalHeight = math.max(totalHeight, childSize.height);
|
||||||
}
|
}
|
||||||
|
if (node.children.length > 1) {
|
||||||
|
totalWidth += gap * (node.children.length - 1);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// In column direction, height is sum of children, width is max
|
// In column direction, height is sum of children, width is max
|
||||||
for (const child of node.children) {
|
for (const child of node.children) {
|
||||||
@@ -78,6 +93,9 @@ function measureNode(node: UIObject): { width: number; height: number } {
|
|||||||
totalWidth = math.max(totalWidth, childSize.width);
|
totalWidth = math.max(totalWidth, childSize.width);
|
||||||
totalHeight += childSize.height;
|
totalHeight += childSize.height;
|
||||||
}
|
}
|
||||||
|
if (node.children.length > 1) {
|
||||||
|
totalHeight += gap * (node.children.length - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { width: totalWidth, height: totalHeight };
|
return { width: totalWidth, height: totalHeight };
|
||||||
@@ -120,6 +138,9 @@ export function calculateLayout(
|
|||||||
const justify = node.layoutProps.justifyContent ?? "start";
|
const justify = node.layoutProps.justifyContent ?? "start";
|
||||||
const align = node.layoutProps.alignItems ?? "start";
|
const align = node.layoutProps.alignItems ?? "start";
|
||||||
|
|
||||||
|
const isFlex = node.type === "div" || node.type === "form";
|
||||||
|
const gap = isFlex ? 1 : 0;
|
||||||
|
|
||||||
// Measure all children
|
// Measure all children
|
||||||
const childMeasurements = node.children.map((child: UIObject) => measureNode(child));
|
const childMeasurements = node.children.map((child: UIObject) => measureNode(child));
|
||||||
|
|
||||||
@@ -139,6 +160,11 @@ export function calculateLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add gaps to total size
|
||||||
|
if (node.children.length > 1) {
|
||||||
|
totalMainAxisSize += gap * (node.children.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
// Calculate starting position based on justify-content
|
// Calculate starting position based on justify-content
|
||||||
let mainAxisPos = 0;
|
let mainAxisPos = 0;
|
||||||
let spacing = 0;
|
let spacing = 0;
|
||||||
@@ -187,6 +213,9 @@ export function calculateLayout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
mainAxisPos += measure.width + spacing;
|
mainAxisPos += measure.width + spacing;
|
||||||
|
if (i < node.children.length - 1) {
|
||||||
|
mainAxisPos += gap;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Main axis is vertical
|
// Main axis is vertical
|
||||||
childY = startY + math.floor(mainAxisPos);
|
childY = startY + math.floor(mainAxisPos);
|
||||||
@@ -201,6 +230,9 @@ export function calculateLayout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
mainAxisPos += measure.height + spacing;
|
mainAxisPos += measure.height + spacing;
|
||||||
|
if (i < node.children.length - 1) {
|
||||||
|
mainAxisPos += gap;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively calculate layout for child
|
// Recursively calculate layout for child
|
||||||
|
|||||||
@@ -10,21 +10,110 @@ import {
|
|||||||
label,
|
label,
|
||||||
button,
|
button,
|
||||||
render,
|
render,
|
||||||
|
Show,
|
||||||
|
input,
|
||||||
|
For,
|
||||||
|
createStore,
|
||||||
|
removeIndex,
|
||||||
} from "../lib/ccTUI";
|
} from "../lib/ccTUI";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simple counter example
|
* Simple counter example
|
||||||
*/
|
*/
|
||||||
const CounterApp = () => {
|
const Counter = () => {
|
||||||
const [count, setCount] = createSignal(0);
|
const [count, setCount] = createSignal(0);
|
||||||
|
|
||||||
return div({ class: "flex flex-col" },
|
return div(
|
||||||
|
{ class: "flex flex-col" },
|
||||||
h3("Counter Example"),
|
h3("Counter Example"),
|
||||||
label({}, () => `Count: ${count()}`),
|
label({}, () => `Count: ${count()}`),
|
||||||
div({ class: "flex flex-row" },
|
div(
|
||||||
|
{ class: "flex flex-row" },
|
||||||
button({ onClick: () => setCount(count() - 1) }, "-"),
|
button({ onClick: () => setCount(count() - 1) }, "-"),
|
||||||
button({ onClick: () => setCount(count() + 1) }, "+")
|
button({ onClick: () => setCount(count() + 1) }, "+"),
|
||||||
)
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Todo list example
|
||||||
|
*/
|
||||||
|
let nextTodoId = 0;
|
||||||
|
const TodosApp = () => {
|
||||||
|
const [todos, setTodos] = createStore<
|
||||||
|
{ id: number; title: string; completed: boolean }[]
|
||||||
|
>([]);
|
||||||
|
const [newTitle, setNewTitle] = createSignal("");
|
||||||
|
|
||||||
|
const addTodo = () => {
|
||||||
|
const title = newTitle().trim();
|
||||||
|
if (title != undefined) {
|
||||||
|
setTodos((prev) => [
|
||||||
|
...prev,
|
||||||
|
{ id: nextTodoId++, title, completed: false },
|
||||||
|
]);
|
||||||
|
setNewTitle("");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return div(
|
||||||
|
{ class: "flex flex-col" },
|
||||||
|
h3("Todos Example"),
|
||||||
|
div(
|
||||||
|
{ class: "flex flex-row" },
|
||||||
|
input({
|
||||||
|
type: "text",
|
||||||
|
value: newTitle,
|
||||||
|
onInput: setNewTitle,
|
||||||
|
placeholder: "Enter new todo",
|
||||||
|
}),
|
||||||
|
button({ onClick: addTodo }, "Add"),
|
||||||
|
),
|
||||||
|
For({ each: todos, class: "flex flex-col" }, (todo, index) =>
|
||||||
|
div(
|
||||||
|
{ class: "flex flex-row items-center justify-between" },
|
||||||
|
input({
|
||||||
|
type: "checkbox",
|
||||||
|
checked: () => todo.completed,
|
||||||
|
onChange: (checked) => {
|
||||||
|
setTodos(index(), "completed", checked);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
label({ class: "ml-1" }, () => todo.title),
|
||||||
|
button(
|
||||||
|
{
|
||||||
|
class: "ml-1",
|
||||||
|
onClick: () => {
|
||||||
|
setTodos((t) => removeIndex(t, index()));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"X",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main application component with tabs
|
||||||
|
*/
|
||||||
|
const App = () => {
|
||||||
|
const [tabIndex, setTabIndex] = createSignal(0);
|
||||||
|
|
||||||
|
return div(
|
||||||
|
{ class: "flex flex-col" },
|
||||||
|
div(
|
||||||
|
{ class: "flex flex-row" },
|
||||||
|
button({ onClick: () => setTabIndex(0) }, "CountDemo"),
|
||||||
|
button({ onClick: () => setTabIndex(1) }, "TodosDemo"),
|
||||||
|
),
|
||||||
|
Show(
|
||||||
|
{
|
||||||
|
when: () => tabIndex() === 0,
|
||||||
|
fallback: Show({ when: () => tabIndex() === 1 }, TodosApp()),
|
||||||
|
},
|
||||||
|
Counter(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -35,8 +124,7 @@ try {
|
|||||||
print("Starting ccTUI Reactive Demo. Press Ctrl+T to quit.");
|
print("Starting ccTUI Reactive Demo. Press Ctrl+T to quit.");
|
||||||
|
|
||||||
// Render the application
|
// Render the application
|
||||||
render(CounterApp);
|
render(App);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e === "Terminated") {
|
if (e === "Terminated") {
|
||||||
print("Application terminated by user.");
|
print("Application terminated by user.");
|
||||||
|
|||||||
Reference in New Issue
Block a user