Files
kindle-calendar/dash/docs/theme-switching-plan.zh.md
2026-03-16 10:41:39 +08:00

482 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Kindle Dashboard 多主题方案
## 1. 背景
当前链路已经稳定分成两部分:
- `calendar/` 负责渲染网页和导出背景图
- `dash/` 运行在 Kindle 上,负责拉取背景图并在本地重画时钟
这里先明确一个约束,后续方案都基于它展开:
- Kindle 端仍然保持“拉背景图片 + 绘制时钟”的职责
- 不把整页日历渲染放回 Kindle
- 主题的名称、路径、时钟占位信息都由 `calendar/` 提供
这次多主题方案要同时满足两类需求:
1. 访问 `calendar` 网站时,可以在页面上切换主题预览效果
2. 后续 Kindle 端如果需要切换主题,也只依赖 `calendar` 提供的主题配置,不自己猜路径或布局
## 2. 目标
本方案的目标是:
1. 访问网站时,可以通过顶部可选菜单切换主题样式
2. 网站预览效果应尽量与 Kindle 最终显示效果一致
3. 当前背景生成流程继续可用,不因主题预览被破坏
4. `calendar/` 提供统一的主题清单 JSON作为主题的单一真相源
5. Kindle 只需要读取固定位置的主题清单和主题配置 JSON
6. Kindle 切换主题后,应立即拉取该主题背景和主题级配置并刷新
7. 时钟的位置、尺寸和绘制参数由 `calendar` 的主题配置决定Kindle 不自行决定
非目标:
- 不在 Kindle 上重新渲染整张页面
- 不让 Kindle 维护主题路径规则
- 不要求第一阶段就把现有导出脚本改成强制批量导出
- 不要求第一阶段就做“本地表盘素材包”体系
## 3. 设计原则
### 3.1 `calendar/` 是主题真相源
主题系统的单一真相源放在 `calendar/` 侧。
建议由 `calendar/` 产出:
- 一个主题清单文件,例如 `themes.json`
- 每个主题自己的配置文件,例如 `default.json``paper.json`
- 每个主题自己的背景图
其中 `themes.json` 至少描述:
- 有哪些主题
- 每个主题叫什么名字
- 每个主题的配置 JSON 在哪里
- 默认主题是谁
这样做的好处是:
- 主题路径由 `calendar` 管理,不在 Kindle 端写死
- 新增主题时Kindle 无需改代码里的路径规则
- 网站预览和 Kindle 切换都依赖同一套主题元数据
这里再明确一下边界。
远端主题配置只有两类:
- `themes.json`
- 每个主题自己的 JSON例如 `themes/default.json`
除此之外:
- 背景图属于资源文件,不属于主题配置
- Kindle 本地缓存属于运行时状态,不属于主题配置
### 3.2 网站预览与导出解耦
主题预览只是网站访问时的交互能力,不应破坏当前导出主流程。
建议明确两条链路:
- 预览链路:用户访问 `/?mode=full&theme=<id>`,通过页面菜单切换主题
- 导出链路:现有脚本继续生成默认背景图,保持兼容
也就是说:
- 预览能力先落地
- 当前 `kindlebg.png` 导出链路先不强制变化
- 多主题导出作为后续增量能力接入
### 3.3 Kindle 侧最简化
Kindle 侧只保留最小能力:
- 拉取背景图
- 读取主题 JSON
- 按 JSON 提供的时钟参数绘制时钟
Kindle 不负责:
- 推导主题路径
- 决定时钟区域坐标
- 维护主题布局规则
推荐让 Kindle 只认识一个固定入口,例如:
```text
https://shell.biboer.cn:20001/themes.json
```
然后从这个 JSON 中知道:
- 当前有哪些主题
- 每个主题的配置 JSON 在哪里
- 切换到该主题时该去拉哪个背景图
### 3.4 当前时钟实现已经足够轻量
当前 Kindle 侧时钟是本地几何绘制,不依赖外部表盘或指针素材文件。
这意味着第一阶段主题切换只需要关注:
- 背景图 URL
- 时钟区域位置和尺寸
- 本地绘制时钟所需的参数
不需要先引入复杂的“主题素材包”机制。
## 4. 数据约定
### 4.1 主题清单 `themes.json`
建议由 `calendar/` 在固定位置提供主题清单,例如:
```json
{
"updatedAt": "2026-03-16T10:00:00+08:00",
"defaultThemeId": "default",
"themes": [
{
"id": "default",
"label": "Default",
"configUrl": "https://shell.biboer.cn:20001/themes/default.json",
"previewQuery": "?mode=full&theme=default"
},
{
"id": "paper",
"label": "Paper",
"configUrl": "https://shell.biboer.cn:20001/themes/paper.json",
"previewQuery": "?mode=full&theme=paper"
},
{
"id": "classic",
"label": "Classic",
"configUrl": "https://shell.biboer.cn:20001/themes/classic.json",
"previewQuery": "?mode=full&theme=classic"
}
]
}
```
第一阶段建议先保留当前主题并命名为 `default`,然后新增:
- `paper`
- `classic`
### 4.2 主题配置 `<theme-id>.json`
每个主题再提供一份主题级配置,例如:
```json
{
"id": "default",
"label": "Default",
"background": {
"url": "https://shell.biboer.cn:20001/themes/default/kindlebg.png",
"refreshIntervalMinutes": 120
},
"clock": {
"x": 262,
"y": 55,
"width": 220,
"height": 220,
"faceRadiusRatio": 0.47,
"faceStroke": 3,
"tickOuterInset": 6,
"majorTickLength": 14,
"minorTickLength": 7,
"majorTickThickness": 4,
"minorTickThickness": 2,
"hourLengthRatio": 0.48,
"minuteLengthRatio": 0.72,
"hourThickness": 9,
"minuteThickness": 5,
"centerRadius": 7
}
}
```
说明:
- 背景图真实路径由主题配置提供,不在 Kindle 端拼接猜测
- 时钟坐标和大小来自 `calendar` 中该主题的时钟占位,不允许 Kindle 任意改
- 后续如果某个主题需要不同的时钟绘制参数,也由该 JSON 一起提供
### 4.3 Kindle 本地状态
Kindle 本地只需要保存少量状态:
```text
/mnt/us/dashboard/local/theme.env
/mnt/us/dashboard/local/state/themes.json
/mnt/us/dashboard/local/state/current-theme.json
/mnt/us/dashboard/local/state/background-updated-at
```
其中:
- `theme.env` 只记录当前主题 ID
- `themes.json` 是最近一次同步到本地的主题清单缓存
- `current-theme.json` 是当前主题配置缓存
这几项是 Kindle 本地运行状态,不属于远端主题配置。
## 5. `calendar/` 侧方案
### 5.1 网站访问时增加主题预览菜单
主题预览菜单只出现在网站访问场景,不出现在背景截图结果里。
建议行为:
1. 用户访问 `/?mode=full` 时,在页面顶部显示一个可选主题菜单
2. 切换菜单时更新当前页面的 `theme` 参数
3. 菜单位置放在顶部任意不影响截图的位置即可
4.`mode=background` 下不显示该菜单,避免影响截图导出
建议 URL 形态如下:
- `/?mode=full&theme=default`
- `/?mode=full&theme=paper`
- `/?mode=full&theme=classic`
### 5.2 预览效果与 Kindle 效果对齐
主题预览不能只是“网页版好看”,还需要尽量接近 Kindle 最终效果。
建议做到:
- 预览和导出使用同一套主题配置
- 背景导出与预览使用同一套时钟占位信息
- Kindle 侧按主题 JSON 中的时钟参数绘制,避免预览和实机错位
这样评审主题时,看到的页面效果和 Kindle 最终效果才是一致的。
### 5.3 现有导出流程保持兼容
当前默认导出链路继续保留:
```text
calendar/dist/kindlebg.png
calendar/dist/dashboard-manifest.json
calendar/dist/clock-region.json
```
兼容规则建议如下:
- 如果导出脚本没有显式传入 `theme`,默认导出 `default`
- 现有 `export:background` 的行为保持不变
- 先不强制要求所有主题都参与默认导出
这样可以保证:
- 网站预览菜单先落地
- 当前生产链路不被打断
- Kindle 当前单主题使用方式继续可用
### 5.4 多主题导出作为附加能力
当需要支持 Kindle 端多主题切换时,再增加多主题导出,例如:
```text
calendar/dist/themes.json
calendar/dist/themes/default.json
calendar/dist/themes/default/kindlebg.png
calendar/dist/themes/paper.json
calendar/dist/themes/paper/kindlebg.png
calendar/dist/themes/classic.json
calendar/dist/themes/classic/kindlebg.png
```
这里的关键点是:
- 原有 `calendar/dist/kindlebg.png` 继续保留
- 多主题目录和 `themes.json` 是新增能力,不是替换能力
- 主题路径管理由 `calendar` 决定,不在 Kindle 侧固化为固定模式
## 6. Kindle 侧方案
### 6.1 主题发现方式
Kindle 只需要知道一个固定地址:
```text
themes.json 的 URL
```
推荐两种同步方式并存:
- 被动同步Kindle 每天拉取一次 `themes.json`
- 主动同步:用户进入主题切换流程时,先即时拉取最新 `themes.json`
这样能兼顾:
- 平时低频更新
- 切换主题时拿到最新主题清单
### 6.2 主题切换流程
主题切换时建议执行以下流程:
1. 拉取最新 `themes.json`
2. 根据用户选择的主题 ID 找到对应 `configUrl`
3. 拉取该主题对应的 `<theme-id>.json`
4.`THEME_ID` 写入本地 `theme.env`
5. 将主题 JSON 缓存在本地
6. 立即拉取该主题背景图
7. 立即执行一次全屏刷新
8. 之后继续按当前分钟级时钟刷新逻辑运行
这里有一个关键要求:
- 主题切换后,不等待下一个两小时周期
- 而是立即拉取一次新主题背景
### 6.3 时钟绘制保持现有模型
当前 Kindle 时钟是本地几何绘制,已经满足主题切换第一阶段需求。
因此 Kindle 侧仍然保持:
- 拉背景图片
- 读取主题 JSON
- 用本地 `lua + fbink` 画时钟
不需要在第一阶段增加:
- 表盘图片素材同步
- 指针图片素材同步
- 复杂的本地主题资源目录
如果未来某个主题确实需要独立表盘素材,再在第二阶段扩展。
### 6.4 主题切换入口
入口建议做成“统一主题入口”,而不是在 KUAL 里硬编码每个主题项。
目标交互是:
1. 用户点击“Theme”
2. 设备根据 `themes.json` 获取当前可用主题清单
3. 用户点击具体主题
4. 立即切换到该主题
实现上可以接受两种方式:
- KUAL 能直接承载该交互,则由脚本在入口内完成
- 如果 KUAL 不能直接动态展示,则由脚本先读取 `themes.json`,再生成或更新对应菜单
不管具体 UI 形态如何,数据契约都应保持一致:
- 主题列表来自 `themes.json`
- 切换行为来自所选主题对应的 `<theme-id>.json`
## 7. 分阶段落地
### 第一阶段:网站主题预览
目标:
- 新增 `theme` 参数
- 增加顶部主题预览菜单
- 默认主题命名为 `default`
- 新增 `paper``classic`
结果:
- 用户可以在网站上预览三套主题
- 现有截图导出流程保持不变
### 第二阶段:主题元数据发布
目标:
- `calendar/` 产出 `themes.json`
- 每个主题产出自己的 `<theme-id>.json`
- 每个主题有独立背景图 URL
结果:
- 主题列表、主题路径、时钟配置都由 `calendar/` 统一提供
- Kindle 侧可以开始按 JSON 理解主题系统
### 第三阶段Kindle 端主题切换
目标:
- Kindle 能拉取 `themes.json`
- Kindle 能按选择主题拉取对应的 `<theme-id>.json`
- 切换后立即拉取新背景并全屏刷新
结果:
- Kindle 端正式具备多主题切换能力
- Kindle 侧仍保持最小职责
### 第四阶段:按需扩展主题能力
只有在未来确实出现需求时,再考虑:
- 为某些主题提供独立表盘素材
- 增加主题级本地资源同步
- 增加更复杂的主题切换交互
这一步不是当前必需项。
## 8. 风险与约束
### 8.1 预览和实机效果偏差
如果预览和导出用的不是同一套主题配置,最终会出现:
- 网页看起来正确
- Kindle 上时钟错位或背景不一致
因此必须保证:
- 预览
- 导出
- Kindle 时钟绘制
都依赖同一套主题元数据。
### 8.2 主题切换后的缓存问题
如果主题切换后没有立即拉取新背景Kindle 可能继续显示旧缓存。
因此切换主题时必须同时处理:
- 更新 `THEME_ID`
- 拉取新的 `<theme-id>.json`
- 拉取新的背景图
- 重置背景更新时间戳或直接覆盖缓存背景
### 8.3 主题清单缓存过旧
如果 Kindle 长时间只读本地缓存,可能看不到新主题。
因此建议:
- 日常低频同步一次 `themes.json`
- 用户进入主题切换时再主动刷新一次
## 9. 推荐结论
推荐采用下面这条路线:
1. 先在 `calendar` 网站上增加顶部主题预览菜单
2. 维持当前默认背景导出链路不变
3.`calendar` 增量提供 `themes.json` 和主题级 `<theme-id>.json`
4. Kindle 只读取固定位置的 `themes.json`
5. 切换主题时立即拉取该主题配置和背景图,并继续使用现有本地时钟绘制
这条路线的优点是:
- 不打断现有 `kindlebg.png` 生成流程
- 主题路径和布局都由 `calendar` 统一管理
- Kindle 侧实现最简化
- 预览和实机显示可以基于同一套配置对齐
- 当前时钟实现不依赖表盘和指针素材,第一阶段改造成本低