calendar-widget/widget.js
2025-05-06 09:52:24 +08:00

163 lines
4.3 KiB
JavaScript

// == 配置 ==
const API_KEY = "change-me";
const API_HOST = "https://calendar-widget-api.tunpok.com";
const GET_URL = `${API_HOST}/tasks`;
const CACHE_FN = "calendar-widget-task-cache.json"; // 本地缓存文件
// == 处理「点击 cell」==
if (args.queryParameters?.action === "schedule" && args.queryParameters?.id) {
await scheduleTask(args.queryParameters.id);
// 小提示:让 Scriptable 返回桌面并刷新 widget
Script.complete();
return;
}
// == 入口:拉取数据 & 渲染 widget ==
const data = await safeFetchTasks(); // 若接口失败则自动读取缓存
const widget = buildWidget(data);
Script.setWidget(widget);
Script.complete();
// == 每分钟自动刷新 ==
widget.refreshAfterDate = new Date(Date.now() + 60 * 1000);
// -----------------------------------------------------------------------------
// ⬇️ 工具函数 ⬇️
// -----------------------------------------------------------------------------
/**
* 从接口安全拉取任务;失败时返回缓存内容
*/
async function safeFetchTasks() {
try {
const req = new Request(GET_URL);
req.method = "GET";
req.headers = { "X-Api-Key": API_KEY };
const json = await req.loadJSON();
cacheJson(json); // 拉取成功即写入缓存
return json;
} catch (e) {
return readCache();
}
}
/**
* 点击 cell 后调度任务
*/
async function scheduleTask(id) {
try {
const url = `${API_HOST}/tasks/${id}/schedule`;
const req = new Request(url);
req.method = "POST";
req.headers = { "X-Api-Key": API_KEY };
await req.load(); // 无需关心返回值
} catch (e) {
// 失败时静默忽略,保持 widget 不变
}
}
/**
* 构建并返回 ListWidget
*/
function buildWidget(tasks) {
// == Widget 初始化 ==
const w = new ListWidget();
w.backgroundColor = new Color("#f5f5f9");
w.setPadding(10, 10, 10, 10);
const main = w.addStack();
main.layoutVertically();
main.centerAlignContent();
main.spacing = 12;
// == 布局判定 ==
let columns = 4, rows = 1;
switch (config.widgetFamily) {
case "medium": columns = 2; rows = 2; break;
case "large": columns = 4; rows = 2; break;
}
let idx = 0;
const total = tasks.length;
for (let r = 0; r < rows; r++) {
const row = main.addStack();
row.layoutHorizontally();
row.centerAlignContent();
row.spacing = 10;
for (let c = 0; c < columns; c++) {
if (idx >= total) break;
const item = tasks[idx++];
const cell = row.addStack();
cell.layoutVertically();
cell.backgroundColor = new Color("#f2f2f2");
cell.cornerRadius = 16;
cell.setPadding(10, 25, 10, 25);
cell.centerAlignContent();
cell.size = new Size(0, 65);
// ===== 标题 =====
const title = cell.addText(item.name);
title.font = Font.mediumSystemFont(14);
title.textColor = Color.black();
title.centerAlignText();
cell.addSpacer(8);
// ===== 色条 =====
const bar = cell.addStack();
bar.backgroundColor = colorMap(item.color);
bar.cornerRadius = 8;
bar.size = new Size(Device.screenSize().width / (columns * 1.8), 10);
// ===== 点击跳转 =====
const url = `scriptable:///run?scriptName=${encodeURIComponent(Script.name())}`
+ `&action=schedule&id=${item.id}`;
cell.url = url; // 整个 cell 可点击
}
}
return w;
}
/**
* 根据接口给出的 color 字段返回 Scriptable 的颜色对象
*/
function colorMap(color) {
switch ((color || "").toLowerCase()) {
case "green": return Color.green();
case "yellow": return Color.yellow();
case "red": return Color.red();
default: return Color.gray();
}
}
/**
* 写缓存
*/
function cacheJson(obj) {
try {
const fm = FileManager.local();
const dir = fm.documentsDirectory();
const path = fm.joinPath(dir, CACHE_FN);
fm.writeString(path, JSON.stringify(obj));
} catch (_) {}
}
/**
* 读缓存,若无缓存则返回空数组
*/
function readCache() {
try {
const fm = FileManager.local();
const dir = fm.documentsDirectory();
const path = fm.joinPath(dir, CACHE_FN);
if (fm.fileExists(path)) {
const str = fm.readString(path);
return JSON.parse(str);
}
} catch (_) {}
return [];
}