⌚ 为 Agency-Watch 开发你自己的应用
手表是开放的。服主可以添加一个外观和行为都与内置应用一致的应用,使用相同的组件、相同的配色和相同的翻译。
前提:Pro 版本。只有服务器运行 Agency-Phone 且玩家拥有它时,自定义应用才存在。没有 Pro 时,应用会被注册但永远不会下发:界面不会知道它们,应用商店里不会出现,服务器也会拒绝它们发送的每个操作。这在三个独立的位置进行检查,无法关闭。
| 文件 | 必需 | 作用 |
|---|---|---|
apps/<id>/app.lua | 是 | 注册应用:名称、图标、颜色、分类。 |
apps/<id>/app.js | 是 | 界面:绘制什么以及如何响应。 |
apps/<id>/app.css | 否 | 你自己的样式。仅在 style = true 时加载。 |
apps/<id>/server.lua | 否 | 你的应用可以调用的服务端逻辑。 |
文件夹名称必须与应用 id 完全一致。这样手表无需把路径写两遍就能找到 app.js 和 app.css,而写两遍的路径迟早会不一致。
apps/ 下的所有内容都在 escrow_ignore 中:它们在你的磁盘上保持可读、可编辑,手表更新也永远不会覆盖它们。
1 注册应用
apps/hello/app.lua
AgencyWatchApp({
id = 'hello', -- lowercase, digits and _, must match the folder
name = 'Hello',
icon = 'raster', -- one of the built-in icons, see below
color = '#5ac8fa', -- #rrggbb
category = 'tools', -- tools | games | driving | style | connected
style = true, -- also load app.css
texts = { en = 'Hello', de = 'Hallo' }, -- optional, name per language
})
id 格式错误、没有名称、分类未知或颜色不是 #rrggbb 的应用,会在服务器控制台中被明确拒绝并给出原因。不存在注册一半的情况:否则这类错误要等到玩家点按应用时才会暴露。
添加文件后执行 refresh 和 restart Agency-Watch。启动时手表会报告注册了哪些应用。
2 绘制界面
apps/hello/app.js
AgencyWatch.app('hello', {
render: function (w) { // required, returns HTML
var d = w.data();
return w.card('Hello, ' + w.esc(d.name)) +
w.section('Steps') +
w.row({ icon: 'schritte', static: true,
top: String(d.steps || 0) }) +
w.button('Roll a die', ' data-roll');
},
bind: function (w) { // optional, runs right after
w.click('[data-roll]', function () {
w.toServer('roll', { sides: 6 }).then(function (a) {
if (a.ok) w.notify('Hello', 'You rolled ' + a.data.value);
});
});
},
});
每次构建界面时都会运行 render,因此它在两次调用之间不保留状态。需要在重绘后保留的数据,请放在应用对象之外的变量中。
两个函数都在安全保护中运行。如果你的代码抛出错误,应用会显示一行错误信息,手表继续运行;具体错误会输出到 F8 控制台。
3 工具箱
w 提供手表应用所需的一切,但仅此而已。无所不能的接口以后就再也无法修改,所以这个接口有意保持精简。
| 调用 | 提供的内容 |
|---|---|
w.data() | 一份扁平快照: time, hour, minute, date, weekday, name, job, health, armour, pulse, steps, distance, calories, speed, heading, weather, pro. |
w.row({icon, top, bottom, end, attrs, static}) | 手表自有风格的列表行。 |
w.section(text) | 分区标题。 |
w.card(html) | 一个卡片面。 |
w.button(text, attrs) | 按钮。attrs 是原始属性字符串,例如 ' data-roll'。 |
w.toggle(on) | 开关。 |
w.center(html) | 居中显示,用于单个大数值。 |
w.icon(name) | 内置图标之一。 |
w.esc(text) | 转义文本。凡是不来自你自己的内容,一律使用它。 |
w.text(key, fallback) | 来自手表的翻译。 |
w.click(selector, fn) | 绑定监听器。仅限在 bind 中使用。 |
w.notify(title, text, icon, colour) | 手表上的通知。 |
w.refresh() | 重绘,不带进入动画。 |
w.close() | 返回,与标题栏中的箭头相同。 |
w.toServer(action, data) | 请求服务器。返回一个 promise。 |
w.id, w.version | 你的应用 id 和 AgencyOS 版本。 |
快照是副本,不是引用。手表还保存着联系人、消息和卡片数据,这些都不在快照里。
4 服务端
apps/hello/server.lua
AgencyWatchAction('hello', 'roll', function(src, data)
local sides = tonumber(data and data.sides) or 6
if sides < 2 then sides = 2 end
if sides > 100 then sides = 100 end
return { value = math.random(1, math.floor(sides)) }
end)
w.toServer 总会完成,结果为 { ok: true, data: … } 或 { ok: false, reason: '…' },即使服务器根本没有响应也是如此。你的应用永远不会卡在等待中,而且无论哪一端拒绝,原因都位于顶层。
reason | 含义 |
|---|---|
no_pro | 没有 Pro 版本。没有它就不存在自定义应用。 |
unknown | 没有注册该应用或该操作。 |
too_fast | 频率限制:每位玩家每五秒最多十个请求。 |
error | 你的处理函数抛出了错误。详情见服务器控制台。 |
timeout | 十二秒内没有响应。 |
data | 请求不是一个表。 |
data 中收到的内容来自客户端,是愿望而不是事实。使用前请检查每个值。凡是重要的东西(金钱、物品、结果),都只能在服务端处理。
5 样式
手表的配色以 CSS 变量提供,并跟随佩戴者在 Studio 中的选择。写死颜色会让你的应用在绿色手表上依然是蓝色。
var(--text) var(--text-2) var(--leise)
var(--akzent) var(--gruen) var(--rot) var(--gelb)
var(--karte) var(--karte-hoch) var(--linie)
var(--grund)
给你自己的类名加上前缀。屏幕由所有应用共用,名为 .large 的类很快就会撞车。
6 内置图标
可将以下任意名称传给 app.lua 中的 icon 或 w.icon():
akku auto blitz brief einkauf farben flugzeug gewitter glocke haken helligkeit herz hoch hupe karte karte-pin kein-signal kino kompass kreuz krone lampe laufen lautlos links loeschen minus mobil mond nebel neuladen pause personen pin plus qr raster rechts regen regler runter sanduhr schild schloss schloss-auf schnee schritte signal sonne sp-fall sp-flug sp-ring sp-turm sprechen stoppuhr telefon telefon-aus tempo ton uhr verboten wiedergabe wlan wolke zahnrad
图标名称有意保持原样。它们是 id 而不是标签:玩家永远看不到,而重命名 id 会让所有已在使用它的应用失效。
📦 手表自带一个可运行的示例
资源中的 apps/example/ 是一个完整、可运行的应用:组件、服务端调用、通知以及自己的 CSS。复制该文件夹并重命名,你就有了一个已经能用的起点。
本页的简短版本就在旁边的 apps/README.md 中。
