Skip to content

Commit 43d7bd6

Browse files
committed
feat(arena): Add support for importing and managing arena layouts via JSON
1 parent 64356b3 commit 43d7bd6

4 files changed

Lines changed: 405 additions & 341 deletions

File tree

README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,37 @@ const serverSchema = Schema.intersect([
103103
#### Commands
104104
服务支持通过 `ssh` 执行命令,如您需要执行命令,内置的命令分别为 重启、根据 `config.seatFile` 选手座位绑定文件更新选手机机器名称、显示选手机座位信息。如您需要执行其他命令,请直接在 UI 界面中输入指令,系统会自动向所有选手机发送指令,并返回结果。
105105

106+
#### Arena Layouts
107+
监视大屏的座位图布局可在 Arena View 中通过 **Import JSON** 导入,数据会保存在浏览器 `localStorage` 中,无需重启或重新构建即可生效。
108+
- 顶层字段:`id`(唯一标识,缺省使用文件名)、`name``description`(可选)、`seatKey`(默认匹配 `hostname`)、`normalize`(`none`/`upper`/`lower`/`trim`/`trim-upper`/`trim-lower`)、`default`(可选,用于默认选中)、`sections`
109+
- `sections` 数组中的每个对象需包含 `grid` 二维数组(元素只能是座位号字符串或 `null` 表示空位),可选字段包括 `title``rowLabels``seatSize``gapSize``meta` 等。
110+
- 同一个 JSON 可携带一个布局对象或布局数组,`default: true` 的布局会在导入后默认选中,可随时使用 **Clear Layouts** 清除本地缓存。
111+
112+
```json
113+
{
114+
"id": "sample-layout",
115+
"name": "Sample Venue",
116+
"description": "Short note shown in the selector",
117+
"seatKey": "hostname",
118+
"normalize": "trim-upper",
119+
"default": true,
120+
"sections": [
121+
{
122+
"id": "main-hall",
123+
"title": "Main Hall",
124+
"rowLabels": ["3", "2", "1"],
125+
"seatSize": 40,
126+
"gapSize": 10,
127+
"grid": [
128+
["A0301", "A0302", null],
129+
["A0201", null, "A0203"],
130+
["A0101", "A0102", "A0103"]
131+
]
132+
}
133+
]
134+
}
135+
```
136+
106137
### Client
107138

108139
Client 端分为打印代码和打印小票两个功能,支持 Windows, Linux, macOS 三大平台,支持打印机自动检测,支持自动分散打印机任务,为了方便使用, Server 与 Client 一同打包为单文件,启动时仅需添加 `--client` 参数即可启动 Client 。

packages/ui/app/arena/nuaa2025.layout.ts

Lines changed: 0 additions & 203 deletions
This file was deleted.

packages/ui/app/arena/types.ts

Lines changed: 16 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,28 @@
1-
export type ArenaCell =
2-
| ArenaSeatCell
3-
| ArenaGapCell
4-
| ArenaLabelCell;
1+
export type ArenaNormalizerId =
2+
| 'none'
3+
| 'upper'
4+
| 'lower'
5+
| 'trim'
6+
| 'trim-upper'
7+
| 'trim-lower';
58

6-
export interface ArenaSeatCell {
7-
type: 'seat';
8-
seatId: string;
9-
label?: string;
10-
span?: number;
11-
meta?: Record<string, unknown>;
12-
}
13-
14-
export interface ArenaGapCell {
15-
type: 'gap';
16-
span?: number;
17-
}
18-
19-
export interface ArenaLabelCell {
20-
type: 'label';
21-
label: string;
22-
span?: number;
23-
align?: 'left' | 'center' | 'right';
24-
}
25-
26-
export type ArenaRow = ArenaCell[];
27-
28-
export interface ArenaSectionConfig {
9+
export interface ArenaLayoutSectionDocument {
2910
id: string;
3011
title?: string;
12+
rowLabels?: (string | null)[];
13+
grid: (string | null)[][];
3114
seatSize?: number;
3215
gapSize?: number;
33-
rows: ArenaRow[];
16+
meta?: Record<string, unknown>;
3417
}
3518

36-
export interface ArenaLayoutConfig {
19+
export interface ArenaLayoutDocument {
3720
id: string;
3821
name: string;
3922
description?: string;
40-
/**
41-
* Monitor field to match against the seatId. Supports dotted paths like "extras.seat".
42-
* If undefined, the monitor name is used as fallback.
43-
*/
4423
seatKey?: string;
45-
/**
46-
* Custom seat extractor. Receives the monitor record and should return the logical seat id.
47-
* Overrides seatKey when provided.
48-
*/
49-
resolveMonitorSeat?: (monitor: Record<string, any>) => string | null | undefined;
50-
/**
51-
* Optional seat id normalizer. Useful when seat naming follows custom casing.
52-
*/
53-
normalizeSeatId?: (seatId: string) => string;
54-
sections: ArenaSectionConfig[];
24+
normalize?: ArenaNormalizerId | string;
25+
default?: boolean;
26+
sections: ArenaLayoutSectionDocument[];
27+
meta?: Record<string, unknown>;
5528
}

0 commit comments

Comments
 (0)