@kanyun-ai-infra/band-plugin-react 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,107 @@
1
+ import {
2
+ failedBecause,
3
+ isForbidden
4
+ } from "./chunk-6VMUZ4PQ.js";
5
+
6
+ // src/upgrade-text.ts
7
+ import { LOCKED_ROLE_LABELS } from "@kanyun-ai-infra/band-contracts";
8
+ var labels = LOCKED_ROLE_LABELS;
9
+ function upgradeRoleLabel(role) {
10
+ return Object.hasOwn(labels, role) ? labels[role] : role;
11
+ }
12
+ function roleList(roles) {
13
+ return roles.map(upgradeRoleLabel).join(" / ");
14
+ }
15
+ function projectName(p, index) {
16
+ return p.name ?? `\u9879\u76EE ${index + 1}`;
17
+ }
18
+ function namedProjects(projects, total = projects.length) {
19
+ if (total === 0 || projects.length !== total || projects.some((p) => p.name === void 0))
20
+ return "";
21
+ const shown = projects.slice(0, 3).map((p, i) => projectName(p, i)).join("\u3001");
22
+ return total > 3 ? `\uFF1A${shown} \u7B49 ${total} \u4E2A` : `\uFF1A${shown}`;
23
+ }
24
+ var UPGRADE_TEXT = {
25
+ /** 落后但兼容:只提示。 */
26
+ // 不写「升级后生效」:升完页面自动刷新,用户不用再点一次(note 决定第 6 条)。值保持字符串,钉着旧版的插件还按字符串读它。
27
+ outdated: "\u9879\u76EE\u6709\u65B0\u7248\u672C\u53EF\u7528\u3002\u5347\u7EA7\u540E\u9875\u9762\u4F1A\u81EA\u52A8\u5237\u65B0\u3002",
28
+ /** 不兼容:常显,写被挡。 */
29
+ incompatible: (roles) => `\u9700\u8981\u5347\u7EA7\u624D\u80FD\u7EE7\u7EED\u7528${roleList(roles)}`,
30
+ /** 部署回滚后,钉住的比本部署新。 */
31
+ rolledBack: "\u672C\u90E8\u7F72\u5DF2\u56DE\u9000\uFF0C\u5347\u7EA7\u4F1A\u6362\u56DE\u672C\u90E8\u7F72\u7684\u7248\u672C",
32
+ askAdmin: "\u8BF7\u9879\u76EE\u7BA1\u7406\u5458\u5347\u7EA7",
33
+ button: "\u5347\u7EA7",
34
+ busy: "\u6B63\u5728\u5347\u7EA7\uFF0C\u5B8C\u6210\u540E\u9875\u9762\u4F1A\u81EA\u52A8\u5237\u65B0",
35
+ /** 按钮上的短词(`busy` 是整句,给横幅 / 读屏)。 */
36
+ busyShort: "\u6B63\u5728\u5347\u7EA7\u2026",
37
+ upgraded: "\u5347\u7EA7\u6210\u529F\uFF0C\u6B63\u5728\u5237\u65B0\u9875\u9762\u2026",
38
+ /** 别人升级了、打开着的页面先落盘草稿再刷新。 */
39
+ refreshingOthers: "\u8FD9\u4E2A\u9879\u76EE\u521A\u5347\u7EA7\u4E86\uFF0C\u6B63\u5728\u4FDD\u5B58\u4F60\u7684\u8349\u7A3F\u5E76\u5237\u65B0\u9875\u9762\u2026",
40
+ /** 已登记的草稿或未保存内容阻止刷新时显示;旧项目首次升级前的旧组件可能尚未登记。`own` 是自己点的升级。 */
41
+ refreshStuck: (own) => `${own ? "\u5347\u7EA7\u6210\u529F\u4E86" : "\u8FD9\u4E2A\u9879\u76EE\u521A\u5347\u7EA7\u4E86"}\uFF0C\u4F46\u4F60\u6709\u5185\u5BB9\u6CA1\u80FD\u4FDD\u5B58\uFF0C\u6240\u4EE5\u9875\u9762\u6CA1\u6709\u5237\u65B0\u3002\u8BF7\u5148\u590D\u5236\u6216\u63D0\u4EA4\u5185\u5BB9\uFF0C\u518D\u70B9\u300C\u5237\u65B0\u300D\u3002`,
42
+ alreadyLatest: "\u5DF2\u7ECF\u662F\u6700\u65B0",
43
+ /** 实例钉点的阶段结构与当前版本不同,留在原版本;`n` 是几个实例。 */
44
+ keptInstances: (n) => `${n} \u4E2A\u6D41\u7A0B\u5B9E\u4F8B\u7684\u9636\u6BB5\u7ED3\u6784\u4E0E\u5F53\u524D\u7248\u672C\u4E0D\u540C\uFF0C\u7559\u5728\u539F\u7248\u672C`,
45
+ /** 升级检查没过:哪个钉点、什么原因。 */
46
+ checkFailed: (failures) => `\u5347\u7EA7\u6CA1\u505A\u6210\uFF1A${failures.map((f) => `${f.pin}\uFF08${f.problems.length > 0 ? f.problems.join("\uFF1B") : "\u68C0\u67E5\u6CA1\u8FC7"}\uFF09`).join("\uFF1B")}`,
47
+ noPermission: "\u6CA1\u6709\u5347\u7EA7\u7684\u6743\u9650",
48
+ /** 工坊页。 */
49
+ workshop: {
50
+ /** 落后的项目数:协作功能或本工坊自带的页面功能(工坊重新发布换了自己的插件)有新版本。 */
51
+ outdatedProjects: (n) => `${n} \u4E2A\u9879\u76EE\u6709\u65B0\u7248\u672C\u53EF\u7528`,
52
+ incompatibleProjects: (n) => `${n} \u4E2A\u9879\u76EE\u8981\u66F4\u65B0\u534F\u4F5C\u529F\u80FD\uFF0C\u624D\u80FD\u7EE7\u7EED\u63D0\u610F\u89C1\u3001\u6279\u51C6`,
53
+ button: "\u66F4\u65B0\u672C\u5DE5\u574A\u5168\u90E8\u9879\u76EE",
54
+ currentOutdated: "\u5E73\u53F0\u66F4\u65B0\u4E86\u534F\u4F5C\u529F\u80FD\uFF0C\u91CD\u65B0\u53D1\u5E03\u540E\uFF0C\u65B0\u9879\u76EE\u624D\u4F1A\u7528\u4E0A",
55
+ /** 副本里自带的插件和原版某个共享版本内容一致:可以改成引用,之后跟着原版升级。只提示,不挡任何操作。 */
56
+ referenceHint: {
57
+ title: (n) => `${n} \u4E2A\u63D2\u4EF6\u53EF\u4EE5\u6539\u4E3A\u5F15\u7528`,
58
+ /** `spec` 是该写进 `views.plugins` 的引用,如 `@teaching/wall@^1.0.0`。 */
59
+ row: (name, version) => `${name} \u548C\u539F\u7248\u7684 ${version} \u5185\u5BB9\u4E00\u81F4`,
60
+ how: (specs) => `\u628A\u5DE5\u574A\u4ED3\u5E93 band/workshop.yaml \u91CC views.plugins \u7684\u5BF9\u5E94\u6761\u76EE\u6362\u6210 ${specs.join("\u3001")}\uFF0C\u518D\u53D1\u5E03\u65B0\u7248\u3002\u4E4B\u540E\u6BCF\u6B21\u53D1\u5E03\uFF0C\u90FD\u4F1A\u53D6\u5230\u539F\u7248\u533A\u95F4\u5185\u6700\u65B0\u7684\u517C\u5BB9\u7248\u672C\uFF1B\u4E0D\u60F3\u6539\u5C31\u4FDD\u6301\u73B0\u72B6\u3002`
61
+ },
62
+ /** 点按钮后的确认弹窗:改哪些项目、什么不变、哪些不动、能不能撤回,都说清楚。 */
63
+ confirm: {
64
+ title: "\u66F4\u65B0\u672C\u5DE5\u574A\u5168\u90E8\u9879\u76EE",
65
+ /** 会有变化的项目;名字都有时点名。 */
66
+ willUpdate: (projects) => `\u5C06\u66F4\u65B0 ${projects.length} \u4E2A\u9879\u76EE${namedProjects(projects)}\u3002`,
67
+ unchanged: "\u5B83\u4EEC\u7528\u7684\u8BC4\u8BBA\u3001\u6279\u51C6\u3001\u961F\u5217\u3001\u7248\u672C\u5207\u6362\u548C\u672C\u5DE5\u574A\u81EA\u5E26\u7684\u9875\u9762\u529F\u80FD\u4F1A\u6362\u6210\u6700\u65B0\u7248\uFF1B\u9879\u76EE\u91CC\u7684\u4EA7\u7269\u3001\u610F\u89C1\u548C\u6210\u5458\u90FD\u4E0D\u53D8\u3002",
68
+ // 键名沿用(钉着旧版的插件还读它),内容换成现在的事实:升级不再逐个检查,换完正在看的页面自动刷新
69
+ checks: "\u5347\u7EA7\u540E\u9875\u9762\u4F1A\u81EA\u52A8\u5237\u65B0\uFF0C\u804A\u5929\u6846\u8349\u7A3F\u5148\u4FDD\u5B58\u3002\u5DF2\u5347\u7EA7\u5230\u542B\u672A\u4FDD\u5B58\u5185\u5BB9\u4FDD\u62A4\u7248\u672C\u7684\u9879\u76EE\uFF0C\u5199\u7740\u6CA1\u6CD5\u4FDD\u5B58\u7684\u5185\u5BB9\u65F6\u9875\u9762\u4E0D\u4F1A\u5237\u65B0\uFF0C\u4F1A\u63D0\u793A\u5148\u590D\u5236\u6216\u63D0\u4EA4\uFF0C\u518D\u70B9\u300C\u5237\u65B0\u300D\u3002\u65E7\u9879\u76EE\u9996\u6B21\u5347\u7EA7\u5230\u8BE5\u7248\u672C\u524D\uFF0C\u8BF7\u5148\u590D\u5236\u6216\u63D0\u4EA4\u8BC4\u8BBA\u3001\u610F\u89C1\u6D6E\u5C42\u548C\u9636\u6BB5\u53C2\u6570\u8868\u5355\u91CC\u7684\u5185\u5BB9\u3002",
70
+ archived: (n) => `\u5DF2\u5F52\u6863\u7684 ${n} \u4E2A\u9879\u76EE\u4E0D\u52A8\u3002`,
71
+ /** 工坊里已经是最新、这次不会有变化的那些:写出来,免得「将更新 3 个」和这个工坊的项目数对不上。 */
72
+ alreadyLatest: (n) => `\u5176\u4F59 ${n} \u4E2A\u9879\u76EE\u672C\u6765\u5C31\u662F\u6700\u65B0\u3002`,
73
+ noUndo: "\u66F4\u65B0\u540E\u4E0D\u80FD\u64A4\u56DE\u3002",
74
+ submit: "\u66F4\u65B0",
75
+ busy: "\u6B63\u5728\u66F4\u65B0\u2026"
76
+ },
77
+ /** 结果四类,逐类一句;这一类为零时界面不写。 */
78
+ result: {
79
+ /** `n` 是这一类有几个,`projects` 是有名字的那些(条数对不上时不点名)。 */
80
+ upgraded: (n, projects) => `\u5DF2\u66F4\u65B0 ${n} \u4E2A\u9879\u76EE${namedProjects(projects, n)}\u3002`,
81
+ current: (n) => `${n} \u4E2A\u9879\u76EE\u672C\u6765\u5C31\u662F\u6700\u65B0\u3002`,
82
+ archived: (n) => `${n} \u4E2A\u9879\u76EE\u5DF2\u5F52\u6863\uFF0C\u6CA1\u6709\u66F4\u65B0\u3002`,
83
+ /** 项目钉点换了,但有流程实例的阶段结构与当前版本不同、留在原版本;`n` 是实例数。 */
84
+ keptInstances: (n) => `${n} \u4E2A\u6D41\u7A0B\u5B9E\u4F8B\u7684\u9636\u6BB5\u7ED3\u6784\u4E0E\u5F53\u524D\u7248\u672C\u4E0D\u540C\uFF0C\u7559\u5728\u539F\u7248\u672C\u3002`,
85
+ /** 没更新成的项目逐个点名带原因:检查没过的是哪些问题。 */
86
+ failed: (projects) => `\u6CA1\u66F4\u65B0\u6210 ${projects.length} \u4E2A\u9879\u76EE\uFF1A${projects.map(
87
+ (p, i) => `${projectName(p, i)}\uFF08${p.problems.length > 0 ? p.problems.join("\uFF1B") : "\u68C0\u67E5\u6CA1\u8FC7"}\uFF09`
88
+ ).join("\uFF1B")}\u3002`
89
+ }
90
+ }
91
+ };
92
+ function upgradeErrorText(e) {
93
+ if (isForbidden(e)) return UPGRADE_TEXT.askAdmin;
94
+ const details = e?.details;
95
+ if (details?.reason === "UPGRADE_CHECK_FAILED" && Array.isArray(details.failures)) {
96
+ const failures = details.failures.filter(
97
+ (f) => typeof f === "object" && f !== null && typeof f.pin === "string" && Array.isArray(f.problems)
98
+ );
99
+ return UPGRADE_TEXT.checkFailed(failures);
100
+ }
101
+ return failedBecause(e, "\u7A0D\u540E\u518D\u8BD5");
102
+ }
103
+
104
+ export {
105
+ UPGRADE_TEXT,
106
+ upgradeErrorText
107
+ };
@@ -0,0 +1,99 @@
1
+ import * as react from 'react';
2
+ import { ReactNode } from 'react';
3
+ import { MemberView } from '@kanyun-ai-infra/band-plugin-sdk';
4
+
5
+ /**
6
+ * 一行错误提示:操作失败时显示在面板里。`children` 为空(null、undefined、空串、false)时什么也不渲染。
7
+ * 给了 `retry` 是「读失败」:整行是 alert,提示后面跟「重试」。全站读数据失败只走这一个出口,各页不自己拼;
8
+ * 原因里断网那句由宿主统一写(`errors` 的 `NETWORK_LOST`)。
9
+ */
10
+ declare function ErrorNote({ children, retry }: {
11
+ children: ReactNode;
12
+ retry?: () => void;
13
+ }): react.JSX.Element | null;
14
+ /** 不是错误的一句说明(比如「已经交给 Rondo 了」):不用红色,不抢眼。 */
15
+ declare function InfoNote({ children }: {
16
+ children: ReactNode;
17
+ }): react.JSX.Element | null;
18
+ /** 面板头:标题(`h2`)在左,`children`(筛选、按钮、说明)在右。 */
19
+ declare function PaneHeader({ title, children }: {
20
+ title: ReactNode;
21
+ children?: ReactNode;
22
+ }): react.JSX.Element;
23
+ /**
24
+ * 头像的底色(Band Design `--av1`…`--av5`)。给了 `order`(项目里人的 id,按成员列表的顺序)就按位置轮着取,
25
+ * 五人以内互不相同,同一页上对话、成员、@ 候选看到的是同一种颜色;不在里面的按 id 取一个固定的。
26
+ */
27
+ declare function avatarTone(id: string, order?: readonly string[]): number;
28
+ /** 项目里人的 id,按成员列表的顺序(Agent 不占底色):传给 `Avatar` 的 `order`。 */
29
+ declare function memberOrder(members: readonly MemberView[]): string[];
30
+ /** 头像上的字:名字的第一个字(「王敏」写「王」),拉丁字母大写;没有名字写「?」。 */
31
+ declare function avatarInitial(name: string): string;
32
+ /**
33
+ * 成员头像(Band Design `.av`):人是带底色的名字首字;Agent 是标志里的蓝色符头(DESIGN.md · Rondo)。
34
+ * 旁边已经写了名字时 `decorative` 让读屏跳过它;单独出现时读出名字。
35
+ */
36
+ declare function Avatar({ id, name, agent, instrument, size, decorative, order, }: {
37
+ id: string;
38
+ name: string;
39
+ agent?: boolean;
40
+ /** 工坊 Agent 的乐器(`instrumentOf`);不给就画 Rondo 的符头。只对 `agent` 有效。 */
41
+ instrument?: Instrument;
42
+ size?: "xs" | "sm" | "md";
43
+ decorative?: boolean;
44
+ /** 项目里人的 id(`memberOrder(members)`),决定底色;见 `avatarTone`。 */
45
+ order?: readonly string[];
46
+ }): react.JSX.Element;
47
+ /** 状态符号:每行最右边一个 16px 符号,含义全站一致(DESIGN.md「状态符号」,样式在 Band Design)。 */
48
+ type GlyphKind = "working" | "needs" | "new" | "done" | "seen" | "waiting" | "queued" | "failed" | "";
49
+ /** `bg`:后台还有排着的回合(符号底下垫一层)。装饰性,含义由同一行的文字给出。 */
50
+ declare function Glyph({ kind, bg }: {
51
+ kind: GlyphKind;
52
+ bg?: boolean;
53
+ }): react.JSX.Element;
54
+ /** 项目里某个 Agent 的乐器(`instrumentOf`,同伴是项目里全部 Agent);Rondo 没有。 */
55
+ declare function agentInstrument(id: string, members: readonly MemberView[]): Instrument | undefined;
56
+ /** 项目里某个 Agent 的头像:名字取成员显示名,工坊 Agent 画乐器,Rondo 画符头。 */
57
+ declare function AgentAvatar({ id, members, size, decorative, }: {
58
+ id: string;
59
+ members: readonly MemberView[] | undefined;
60
+ size?: "xs" | "sm" | "md";
61
+ decorative?: boolean;
62
+ }): react.JSX.Element;
63
+ /** 工坊 Agent 的乐器(DESIGN.md「乐器:谁在演」),顺序就是 tokens `instruments/index.json` 的顺序。 */
64
+ declare const INSTRUMENTS: readonly ["sax", "keys", "guitar", "drum", "trumpet", "violin"];
65
+ type Instrument = (typeof INSTRUMENTS)[number];
66
+ /**
67
+ * 一个工坊里各 Agent 的缺省乐器:名字的 FNV-1a 对乐器数取模,同工坊里撞了顺延到下一件空闲的;
68
+ * `peers` 是这个工坊的全部 Agent(顺序无关,按名字排序后依次分配,结果不随阶段顺序变)。
69
+ * Rondo 不拿乐器(返回 undefined,画符头)。超过六个时乐器会重复。
70
+ */
71
+ declare function instrumentOf(name: string, peers: readonly string[]): Instrument | undefined;
72
+ /**
73
+ * 工坊 icon(v8 §7「工坊 icon」):`icon` 是工坊卡片上的字段,`genre:<曲风>` 画内置曲风;仓库内图片还读不到、
74
+ * 没写或认不出时画标志简版。画的是只有乐器的简版(见 `GENRE_COMPACT_SRC`),48px 及以上的完整版用
75
+ * `WorkshopEmblem`。装饰性,名称由旁边的文字给出。
76
+ */
77
+ declare function WorkshopIcon({ icon, size, tile, }: {
78
+ icon?: string;
79
+ size?: number;
80
+ /** 画在方底板上(DESIGN.md「工坊 icon」:底板归宿主)。 */
81
+ tile?: boolean;
82
+ }): react.JSX.Element;
83
+ /**
84
+ * 工坊 icon 的完整版(带英文流派名),画在方底板上,给 48px 及以上的大卡与工坊页头用(DESIGN.md「工坊 icon」)。
85
+ * 和 `WorkshopIcon` 分开,是为了让只画小图标的插件打包时摇掉完整版的图。
86
+ */
87
+ declare function WorkshopEmblem({ icon, size }: {
88
+ icon?: string;
89
+ size?: number;
90
+ }): react.JSX.Element;
91
+ /** Band 标志简版(三个连杠音符,中间那个蓝色的代表 Agent)。装饰性。 */
92
+ declare function BandMark({ size, className }: {
93
+ size?: number;
94
+ className?: string;
95
+ }): react.JSX.Element;
96
+ /** Band 字标(五线谱 + 三个音符 + 「Band」,DESIGN.md「标志」)。样式在 Band Design 的 `.logo`。 */
97
+ declare function LogoLockup(): react.JSX.Element;
98
+
99
+ export { AgentAvatar, Avatar, BandMark, ErrorNote, Glyph, type GlyphKind, INSTRUMENTS, InfoNote, type Instrument, LogoLockup, PaneHeader, WorkshopEmblem, WorkshopIcon, agentInstrument, avatarInitial, avatarTone, instrumentOf, memberOrder };
@@ -0,0 +1,48 @@
1
+ /* band:inject-css */ /* @kanyun-ai-infra/band-plugin-react/components 的样式(scripts/tsup-inject-css.ts) */
2
+ if (typeof document !== "undefined" && document.head) {
3
+ const css = "/* src/ui.css */\n.band-plugin-react-error {\n font-size: var(--fs-ui);\n padding: 7px 10px;\n border-radius: 8px;\n background: var(--bad-soft);\n color: var(--bad);\n font-family: var(--mono);\n}\n.band-plugin-react-info {\n font-size: var(--fs-ui);\n padding: 7px 10px;\n border-radius: 8px;\n background: var(--panel);\n color: var(--muted);\n}\n.band-plugin-react-retry {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 10px;\n}\n.band-plugin-react-failed {\n display: grid;\n gap: 6px;\n justify-items: start;\n margin: auto;\n padding: 8px 10px;\n border-radius: var(--r);\n background: var(--bad-soft);\n color: var(--bad);\n font-size: var(--fs-ui);\n}\n.band-plugin-react-failed p {\n margin: 0;\n}\n.band-plugin-react-failed-title {\n font-weight: 600;\n}\n.band-plugin-react-failed-reason {\n overflow-wrap: anywhere;\n}\n.band-plugin-react-pane-head {\n padding: 6px 14px 6px 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n min-height: 36px;\n}\n.band-plugin-react-pane-title {\n margin: 0;\n font-size: var(--fs-dense);\n color: var(--muted);\n font-weight: 500;\n letter-spacing: 0.02em;\n}\n.band-plugin-react-wsicon {\n flex: none;\n display: block;\n}\n.band-plugin-react-wstile {\n --band-wstile: 16px;\n width: var(--band-wstile);\n height: var(--band-wstile);\n display: inline-grid;\n place-items: center;\n flex: none;\n border-radius: calc(var(--band-wstile) * 0.26);\n background: var(--panel);\n box-shadow: var(--shadow-card);\n}\n.band-plugin-react-wstile > :where(.band-plugin-react-wsicon) {\n width: 82%;\n height: 82%;\n}\n.band-plugin-react-wstile:where(.is-large) {\n border-radius: var(--r-xl);\n}\n.band-plugin-react-wstile:where(.is-large) > :where(.band-plugin-react-wsicon) {\n width: 76%;\n height: 76%;\n}\n.band-plugin-react-instrument {\n width: 72%;\n height: 72%;\n background: currentColor;\n -webkit-mask: var(--band-instrument) center / contain no-repeat;\n mask: var(--band-instrument) center / contain no-repeat;\n}\n.band-plugin-react-model-hint {\n margin: 0;\n}\n.band-plugin-react-target-repo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n margin: 0;\n font-size: var(--fs-meta);\n line-height: 1.5;\n color: var(--muted);\n}\n.band-plugin-react-target-repo code {\n font-family: var(--mono);\n color: var(--ink);\n}\n.band-plugin-react-target-repo a {\n color: var(--accent-text);\n}\n.band-plugin-react-target-repo-request {\n align-self: flex-start;\n}\n.band-plugin-react-target-repo-err {\n color: var(--bad);\n}\n.band-plugin-react-first-delivery {\n display: flex;\n flex-direction: column;\n gap: 8px;\n flex-basis: 100%;\n padding: 12px;\n border: 1px solid var(--rule);\n border-radius: var(--r-lg);\n background: var(--panel);\n margin: 0;\n min-width: 0;\n}\n.band-plugin-react-first-delivery-slot {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: var(--fs-meta);\n}\n.band-plugin-react-first-delivery-pick,\n.band-plugin-react-first-delivery-file {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.band-plugin-react-first-delivery-list {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.band-plugin-react-first-delivery-name {\n min-width: 0;\n overflow-wrap: anywhere;\n}\n.band-plugin-react-first-delivery-size {\n color: var(--muted);\n}\n.band-plugin-react-first-delivery-missing {\n margin: 0;\n font-size: var(--fs-meta);\n color: var(--muted);\n}\n.band-plugin-react-first-delivery .btn {\n align-self: flex-start;\n}\n.band-plugin-react-no-key {\n margin: 0;\n font-size: var(--fs-ui);\n color: var(--bad);\n}\n";
4
+ let el = document.head.querySelector("style[data-band-style=\"@kanyun-ai-infra/band-plugin-react/components\"]");
5
+ if (!el) {
6
+ el = document.createElement("style");
7
+ el.setAttribute("data-band-style", "@kanyun-ai-infra/band-plugin-react/components");
8
+ document.head.prepend(el);
9
+ }
10
+ el.textContent = css;
11
+ }
12
+ /* band:inject-css */
13
+ import {
14
+ AgentAvatar,
15
+ Avatar,
16
+ BandMark,
17
+ ErrorNote,
18
+ Glyph,
19
+ INSTRUMENTS,
20
+ InfoNote,
21
+ LogoLockup,
22
+ PaneHeader,
23
+ WorkshopEmblem,
24
+ WorkshopIcon,
25
+ agentInstrument,
26
+ avatarInitial,
27
+ avatarTone,
28
+ instrumentOf,
29
+ memberOrder
30
+ } from "./chunk-ULDBT77D.js";
31
+ export {
32
+ AgentAvatar,
33
+ Avatar,
34
+ BandMark,
35
+ ErrorNote,
36
+ Glyph,
37
+ INSTRUMENTS,
38
+ InfoNote,
39
+ LogoLockup,
40
+ PaneHeader,
41
+ WorkshopEmblem,
42
+ WorkshopIcon,
43
+ agentInstrument,
44
+ avatarInitial,
45
+ avatarTone,
46
+ instrumentOf,
47
+ memberOrder
48
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 错误怎么写给人看。不依赖 React:宿主的数据层(apps/web)也从子路径 `@kanyun-ai-infra/band-plugin-react/errors` 引,
3
+ * 和插件用同一个出口、同一句话。
4
+ */
5
+ declare function errorText(e: unknown): string;
6
+ /**
7
+ * 请求根本没到服务端(断网、DNS、被代理拦下):宿主抛的错误带这个 `code`,插件按它认,不看各家浏览器的报错原文。
8
+ * 全站对这类失败只有这一个出口、这一句话(card-1810 先合定下,card-1794 的「全站 fetch 失败」复用):
9
+ * 原因写 `NETWORK_LOST`,后面跟这一处能做的下一步,见 `failedBecause`。
10
+ */
11
+ declare const NETWORK_LOST_CODE = "NETWORK";
12
+ declare const NETWORK_LOST = "\u7F51\u7EDC\u65AD\u4E86";
13
+ declare function isNetworkLost(e: unknown): boolean;
14
+ /**
15
+ * 写在「…没传上去:」「…没撤成:」后面的原因。网络断了写「网络断了,<下一步>」,下一步写这一处界面上真有的按钮名
16
+ * (附件旁有「重试」写「点重试」,队列行上只有「撤回」写「再点一次撤回」,没有按钮写「稍后再试」;pm 10-29),
17
+ * 其余照错误原文:宿主的话已经写清上限、配额还剩多少这类能照着做的事。
18
+ */
19
+ declare function failedBecause(e: unknown, next: string): string;
20
+ /**
21
+ * 服务端回了 403(没有权限):宿主抛的错误带服务端的错误码 `code` = `FORBIDDEN_CODE`,插件按它认。
22
+ * 读接口的 403 不是「没读出来」,重试也不会好:画「没有权限」说明(谁能看、去找谁),不放「重试」。
23
+ * 写接口的 403 照旧写原因(服务端的话已经说清为什么不让做)。
24
+ */
25
+ declare const FORBIDDEN_CODE = "FORBIDDEN";
26
+ declare function isForbidden(e: unknown): boolean;
27
+
28
+ export { FORBIDDEN_CODE, NETWORK_LOST, NETWORK_LOST_CODE, errorText, failedBecause, isForbidden, isNetworkLost };
package/dist/errors.js ADDED
@@ -0,0 +1,18 @@
1
+ import {
2
+ FORBIDDEN_CODE,
3
+ NETWORK_LOST,
4
+ NETWORK_LOST_CODE,
5
+ errorText,
6
+ failedBecause,
7
+ isForbidden,
8
+ isNetworkLost
9
+ } from "./chunk-6VMUZ4PQ.js";
10
+ export {
11
+ FORBIDDEN_CODE,
12
+ NETWORK_LOST,
13
+ NETWORK_LOST_CODE,
14
+ errorText,
15
+ failedBecause,
16
+ isForbidden,
17
+ isNetworkLost
18
+ };
@@ -0,0 +1,52 @@
1
+ import { StageRunSummary } from '@kanyun-ai-infra/band-plugin-sdk';
2
+
3
+ /**
4
+ * 从没产出过产物的失败阶段「手动交第一版」的唯一说法与判定:按钮名、置灰原因、非 store 说明、还缺哪几项。
5
+ * 失败阶段上能看到的地方(回合栏等)、对话框、端到端用例都读这里,调用处不各自再判。不依赖 React:端到端用例从
6
+ * 子路径 `@kanyun-ai-infra/band-plugin-react/first-delivery` 引。
7
+ */
8
+
9
+ declare const FIRST_DELIVERY_LABEL = "\u624B\u52A8\u4EA4\u7B2C\u4E00\u7248";
10
+ declare const FIRST_DELIVERY_SUBMIT = "\u63D0\u4EA4\u7B2C\u4E00\u7248";
11
+ type Failure = NonNullable<StageRunSummary["failure"]>;
12
+ type FirstDelivery = NonNullable<Failure["firstDelivery"]>;
13
+ /** 目标仓库或外部存放的产物不能在这里手动交,只留「重试」时失败原因下的那一行。 */
14
+ declare function firstDeliveryNotStoreNote(typeLabel: string): string;
15
+ type FirstDeliveryEntry =
16
+ /** 不画入口;`note` 有就写在失败原因下面。 */
17
+ {
18
+ draw: false;
19
+ note?: string;
20
+ }
21
+ /** 画入口;`disabled` 时置灰,`title` 写原因。 */
22
+ | {
23
+ draw: true;
24
+ disabled: boolean;
25
+ title: string;
26
+ };
27
+ /**
28
+ * 该不该画入口、为什么置灰。没有 `submit-change` 不画(不分原因);上游不满足画出来置灰,`title` 写原因;
29
+ * 非 store 不画,只给说明。不是从没产出过的失败阶段(没有 `firstDelivery`)不画。
30
+ */
31
+ declare function firstDeliveryEntry(failure: Failure | undefined, can: {
32
+ submitChange: boolean;
33
+ }): FirstDeliveryEntry;
34
+ /** 清单里要交的一格:产物类型加扇出键(键由提交人定时没有 `key`)。 */
35
+ interface FirstDeliverySlot {
36
+ id: string;
37
+ type: string;
38
+ key?: string;
39
+ label: string;
40
+ }
41
+ declare function firstDeliverySlots(fd: FirstDelivery): FirstDeliverySlot[];
42
+ /** 还缺哪几项:`filled` 是已选了文件的格子 id。 */
43
+ declare function firstDeliveryMissing(slots: readonly FirstDeliverySlot[], filled: ReadonlySet<string>): FirstDeliverySlot[];
44
+ declare function firstDeliveryMissingText(missing: readonly FirstDeliverySlot[]): string;
45
+ declare const FIRST_DELIVERY_PICK = "\u9009\u62E9\u6587\u4EF6";
46
+ declare const FIRST_DELIVERY_REMOVE = "\u79FB\u9664";
47
+ /** 同一格再选同名文件:新的替换旧的,那一行这样标。 */
48
+ declare const FIRST_DELIVERY_REPLACED = "\u5DF2\u66FF\u6362";
49
+ declare const firstDeliveryFilesText: (n: number) => string;
50
+ declare function fileSizeText(bytes: number): string;
51
+
52
+ export { FIRST_DELIVERY_LABEL, FIRST_DELIVERY_PICK, FIRST_DELIVERY_REMOVE, FIRST_DELIVERY_REPLACED, FIRST_DELIVERY_SUBMIT, type FirstDelivery, type FirstDeliveryEntry, type FirstDeliverySlot, fileSizeText, firstDeliveryEntry, firstDeliveryFilesText, firstDeliveryMissing, firstDeliveryMissingText, firstDeliveryNotStoreNote, firstDeliverySlots };
@@ -0,0 +1,28 @@
1
+ import {
2
+ FIRST_DELIVERY_LABEL,
3
+ FIRST_DELIVERY_PICK,
4
+ FIRST_DELIVERY_REMOVE,
5
+ FIRST_DELIVERY_REPLACED,
6
+ FIRST_DELIVERY_SUBMIT,
7
+ fileSizeText,
8
+ firstDeliveryEntry,
9
+ firstDeliveryFilesText,
10
+ firstDeliveryMissing,
11
+ firstDeliveryMissingText,
12
+ firstDeliveryNotStoreNote,
13
+ firstDeliverySlots
14
+ } from "./chunk-IJBJEU3H.js";
15
+ export {
16
+ FIRST_DELIVERY_LABEL,
17
+ FIRST_DELIVERY_PICK,
18
+ FIRST_DELIVERY_REMOVE,
19
+ FIRST_DELIVERY_REPLACED,
20
+ FIRST_DELIVERY_SUBMIT,
21
+ fileSizeText,
22
+ firstDeliveryEntry,
23
+ firstDeliveryFilesText,
24
+ firstDeliveryMissing,
25
+ firstDeliveryMissingText,
26
+ firstDeliveryNotStoreNote,
27
+ firstDeliverySlots
28
+ };
@@ -0,0 +1,153 @@
1
+ import { Anchor, ProjectCtx, AnchorPlace, ThreadView } from '@kanyun-ai-infra/band-plugin-sdk';
2
+
3
+ /** 图片、视频与分镜共用的几何纯函数。由 viewer-image/image 与 viewer-media/media 原样搬入;不依赖 React 或插件实例。 */
4
+ /** 0–1 相对内容本身的矩形;点是 `w = h = 0`。 */
5
+ interface UnitRect {
6
+ x: number;
7
+ y: number;
8
+ w: number;
9
+ h: number;
10
+ }
11
+ /** 读 `rect`:坐标出界、缺字段的不认(schema 同 `region`,标注 §3.2)。 */
12
+ declare function unitRectFrom(value: unknown): UnitRect | null;
13
+ interface Box {
14
+ left: number;
15
+ top: number;
16
+ width: number;
17
+ height: number;
18
+ }
19
+ /** 按下后移动不到这么多像素算点,不算拖(标注 §4.4,各查看器同一个阈值)。 */
20
+ declare const DRAG_PX = 4;
21
+ /**
22
+ * 屏幕上从 `a` 拖到 `b`(或只点了 `a`)换成相对内容的矩形:限制在内容内,取到千分之一。
23
+ * 内容还没显示出来(宽或高为 0)给 null。
24
+ */
25
+ declare function unitRectOf(content: Box, a: {
26
+ x: number;
27
+ y: number;
28
+ }, b?: {
29
+ x: number;
30
+ y: number;
31
+ }): UnitRect | null;
32
+ declare const MIN_SCALE = 1;
33
+ declare const MAX_SCALE = 8;
34
+ /** 缩到这个倍数以下自动回到适应窗口(产物交付 §3「看图手感」)。 */
35
+ declare const SNAP_SCALE = 1.05;
36
+ /** 缩放与平移:`scale` 相对「适应窗口」的大小,`x`、`y` 是图片中心偏离窗口中心的像素。 */
37
+ interface View {
38
+ scale: number;
39
+ x: number;
40
+ y: number;
41
+ }
42
+ declare const FIT: View;
43
+ /**
44
+ * 图片在窗口里适应时的大小:整张放得下,小图不放大(放大会糊,用户自己点放大)。
45
+ */
46
+ declare function fitSize(natural: {
47
+ width: number;
48
+ height: number;
49
+ }, viewport: {
50
+ width: number;
51
+ height: number;
52
+ }): {
53
+ width: number;
54
+ height: number;
55
+ };
56
+ /** 平移不让图片边缘离开窗口:图比窗口小的那一维居中。`max` 默认 `MAX_SCALE`;超大图要放到实际像素时调用方给更大的。 */
57
+ declare function clampView(v: View, base: {
58
+ width: number;
59
+ height: number;
60
+ }, viewport: {
61
+ width: number;
62
+ height: number;
63
+ }, max?: number): View;
64
+ /**
65
+ * 以窗口里的一点(相对窗口中心的像素)为中心缩放:那一点下面的图片内容缩放前后不动。
66
+ * 往小缩到 `SNAP_SCALE` 以下回到适应窗口;往大不吸附,触控板双指每次只放大百分之几,吸附会让它永远放不大。
67
+ */
68
+ declare function zoomAt(v: View, factor: number, at: {
69
+ x: number;
70
+ y: number;
71
+ }, base: {
72
+ width: number;
73
+ height: number;
74
+ }, viewport: {
75
+ width: number;
76
+ height: number;
77
+ }, max?: number): View;
78
+
79
+ declare const LIGHTBOX_MAX_SCALE: number;
80
+ declare const LIGHTBOX_DRAG_PX: number;
81
+ interface Size {
82
+ width: number;
83
+ height: number;
84
+ }
85
+ declare function lightboxSize(natural: Size, viewport: Size, narrow: boolean): Size;
86
+ /** 和原型一样,放大后的边缘允许离舞台边最多 40px;适应时不平移。 */
87
+ declare function lightboxClamp(view: View, base: Size, viewport: Size): View;
88
+ declare function lightboxZoom(view: View, factor: number, at: {
89
+ x: number;
90
+ y: number;
91
+ }, base: Size, viewport: Size): View;
92
+ declare function focusRegion(rect: UnitRect, base: Size, viewport: Size): View;
93
+ /** 首尾不环绕;切图是否真的发生由返回值判断,边界上不收用户的草稿。 */
94
+ declare function nextImage(index: number, delta: number, count: number): number;
95
+
96
+ /** 由分镜大图抽出的共用控制器;业务信息与锚点语义由查看器提供。 */
97
+
98
+ declare const LIGHTBOX_STYLE = "\n.bs-frame { position: relative; min-height: 0; height: 100%; display: grid; }\n.bs-frame .bs-lb [hidden] { display: none; }\n.bs-picture { display: block; position: relative; padding: 0; border: 0; background: none; border-radius: var(--r-sm); cursor: zoom-in; justify-self: start; }\n.bs-picture:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }\n.bs-wall .bs-picture { width: 100%; justify-self: stretch; }\n.bs-wall .bs-thumb { max-width: 100%; }\n.bs-magnify { position: absolute; right: 4px; bottom: 4px; width: 24px; height: 24px; display: grid; place-items: center; border-radius: 6px; background: rgba(0,0,0,.45); color: var(--media-ink); opacity: 0; }\n.bs-magnify svg { width: 14px; height: 14px; }\n.bs-picture:hover .bs-magnify, .bs-picture:focus-visible .bs-magnify { opacity: 1; }\n.bs-count { position: absolute; top: 4px; left: 4px; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 8px; background: var(--accent); color: var(--accent-ink); font: 600 10px var(--mono); }\n.bs-returned { outline: 2px solid var(--accent); outline-offset: -2px; }\n.bs-lb { position: absolute; inset: 0; display: grid; grid-template-rows: auto minmax(0,1fr) auto; background: var(--media); color: var(--media-ink); min-width: 0; }\n/* \u5168\u5C4F\u5C42\u7EA7\u7531\u771F\u5B9E\u6302\u8F7D\u70B9\u7BA1\u7406\uFF1B\u9501\u5B9A\u8BC4\u8BBA\u5BBF\u4E3B\u540C\u6863\u540E\u7F6E\uFF0C\u7EE7\u7EED\u753B\u5728\u5927\u56FE\u4E0A\u9762\u3002 */\n.bs-lb[data-full=\"true\"] { position: fixed; inset: 0; }\n.bs-lb-bar { display: flex; align-items: center; gap: 4px; padding: 8px 12px; min-width: 0; }\n.bs-lb-title { font-size: var(--fs-ui); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; margin: 0 6px; }\n.bs-lb-title span { color: rgba(255,255,255,.6); font: var(--fs-meta) var(--mono); margin-left: 6px; }\n.bs-lb-grow { flex: 1; }\n.bs-lb .bs-lb-button { width: 32px; height: 32px; padding: 0; border: 0; border-radius: 8px; display: inline-grid; place-items: center; background: transparent; color: rgba(255,255,255,.85); cursor: pointer; flex: none; }\n.bs-lb-button svg { width: 18px; height: 18px; }\n.bs-lb .bs-lb-button:hover, .bs-lb-back:hover, .bs-lb-zoom:hover { background: rgba(255,255,255,.12); }\n.bs-lb button:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }\n.bs-lb button:disabled { opacity: .3; cursor: default; }\n.bs-lb-back { display: inline-flex; align-items: center; gap: 4px; border: 0; background: transparent; color: rgba(255,255,255,.85); font: var(--fs-ui) var(--font); padding: 5px 8px 5px 4px; border-radius: var(--r); cursor: pointer; white-space: nowrap; }\n.bs-lb-back svg { width: 16px; height: 16px; }\n.bs-lb-zoom { font: 500 var(--fs-meta) var(--mono); color: rgba(255,255,255,.8); min-width: 48px; text-align: center; border: 0; background: transparent; cursor: pointer; border-radius: var(--r-sm); padding: 4px 2px; font-variant-numeric: tabular-nums; }\n.bs-lb-main { display: grid; grid-template-columns: minmax(0,1fr) 300px; min-height: 0; }\n.bs-lb-stage { position: relative; overflow: hidden; touch-action: none; user-select: none; min-height: 0; outline: none; }\n.bs-lb-stage[data-pan=\"true\"] { cursor: grab; }\n.bs-lb-stage[data-pan=\"true\"]:active { cursor: grabbing; }\n.bs-lb-stage[data-mark=\"true\"] { cursor: crosshair; }\n.bs-lb-image { position: absolute; border-radius: 2px; box-shadow: 0 8px 30px rgba(0,0,0,.5); max-width: none; object-fit: fill; pointer-events: none; }\n.bs-lb-regions { position: absolute; inset: 0; pointer-events: none; }\n.bs-lb-region { position: absolute; border: 2px solid var(--accent); background: color-mix(in srgb,var(--accent) 13%,transparent); border-radius: 2px; }\n.bs-lb-region.old { border-width: 1.5px; border-style: dashed; background: color-mix(in srgb,var(--accent) 8%,transparent); }\n.bs-lb-point { position: absolute; width: 14px; height: 14px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb,var(--accent) 30%,transparent); translate: -50% -50%; }\n.bs-lb-nav { position: absolute; top: 50%; translate: 0 -50%; width: 40px; height: 40px; border-radius: 20px; border: 0; background: rgba(0,0,0,.45); color: var(--media-ink); display: grid; place-items: center; cursor: pointer; }\n.bs-lb-nav:hover { background: rgba(0,0,0,.7); }\n.bs-lb-nav svg { width: 20px; height: 20px; }\n.bs-lb-nav.prev { left: 12px; } .bs-lb-nav.next { right: 12px; }\n.bs-lb-side { background: var(--panel); color: var(--ink); overflow: auto; overscroll-behavior: contain; padding: 16px; display: grid; gap: 12px; align-content: start; border-left: 1px solid var(--rule); min-width: 0; }\n.bs-lb-side h3 { margin: 0; font-size: var(--fs-title); display: flex; align-items: baseline; gap: 8px; }\n.bs-lb-side h3 span { font: var(--fs-meta) var(--mono); color: var(--muted); font-weight: 400; }\n.bs-lb-side dl { margin: 0; display: grid; grid-template-columns: 40px minmax(0,1fr); gap: 6px 8px; font-size: var(--fs-ui); }\n.bs-lb-side dt { color: var(--muted); font-size: var(--fs-meta); padding-top: 1px; }\n.bs-lb-side dd { margin: 0; overflow-wrap: anywhere; }\n.bs-lb-path { font-family: var(--mono); }\n.bs-lb-cm-head { font-size: var(--fs-meta); color: var(--muted); border-top: 1px solid var(--rule); padding-top: 10px; }\n.bs-lb-cm { border: 0; background: none; text-align: left; display: grid; grid-template-columns: 18px minmax(0,1fr); gap: 8px; padding: 6px; border-radius: var(--r); cursor: pointer; font: var(--fs-ui) var(--font); color: var(--ink); }\n.bs-lb-cm:hover { background: var(--fill-2); }\n.bs-lb-excerpt { overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }\n.bs-lb-hint { margin: 0; font-size: var(--fs-meta); color: var(--muted); }\n.bs-lb-stage .bs-lb-hint { padding: 40px; color: var(--media-ink); }\n.bs-lb-strip { display: flex; gap: 6px; padding: 8px 12px 10px; overflow-x: auto; overscroll-behavior: contain; justify-content: safe center; }\n.bs-lb-strip button { flex: none; width: 88px; aspect-ratio: 16/9; border-radius: 4px; border: 2px solid transparent; padding: 0; position: relative; overflow: hidden; cursor: pointer; background: none; opacity: .65; }\n.bs-lb-strip button[aria-current=\"true\"] { border-color: rgba(255,255,255,1); opacity: 1; }\n.bs-lb-strip button:hover { opacity: 1; }\n.bs-lb-strip img { width: 100%; height: 100%; object-fit: cover; }\n.bs-lb-strip span { position: absolute; left: 4px; bottom: 2px; font: 600 10px var(--mono); color: var(--media-ink); text-shadow: 0 1px 2px rgba(0,0,0,.6); }\n@media (max-width: 600px) {\n .bs-lb-main { grid-template-columns: minmax(0,1fr); grid-template-rows: minmax(0,1fr) minmax(0,1fr); }\n .bs-lb-side { border-left: 0; border-top: 1px solid var(--rule); }\n .bs-lb-strip { display: none; }\n .bs-lb-nav { width: 34px; height: 34px; }\n .bs-lb-title { margin: 0; }\n .bs-lb-back { font-size: 0; padding: 5px 4px; }\n}\n";
99
+ declare const ICONS: {
100
+ back: string;
101
+ next: string;
102
+ minus: string;
103
+ plus: string;
104
+ full: string;
105
+ comment: string;
106
+ };
107
+ declare function lightboxIcon(doc: Document, name: keyof typeof ICONS): SVGSVGElement;
108
+ interface ImageLightboxItem {
109
+ title: string;
110
+ path: string | null;
111
+ alt?: string;
112
+ }
113
+ interface ImageLightboxOptions {
114
+ items(): readonly ImageLightboxItem[];
115
+ imageUrl(path: string): Promise<string>;
116
+ canComment(): boolean;
117
+ region(anchor: Anchor): {
118
+ index: number;
119
+ path: string;
120
+ rect: UnitRect;
121
+ } | null;
122
+ makeAnchor?(index: number, rect: UnitRect): Anchor | null;
123
+ changed(anchor: Anchor, path: string): boolean;
124
+ renderSide(side: HTMLElement, index: number): void;
125
+ heading(index: number): {
126
+ title: string;
127
+ meta?: string;
128
+ };
129
+ labels: {
130
+ layer: string;
131
+ back: string;
132
+ stage: string;
133
+ side: string;
134
+ strip: string;
135
+ previous: string;
136
+ next: string;
137
+ item(index: number): string;
138
+ error: string;
139
+ };
140
+ notify(): void;
141
+ returned(index: number): void;
142
+ }
143
+ declare function createImageLightbox(ctx: ProjectCtx, root: HTMLElement, frame: HTMLElement, options: ImageLightboxOptions): {
144
+ open: (next: number) => void;
145
+ locate: (anchor: Anchor) => AnchorPlace;
146
+ reveal: (anchor: Anchor) => Promise<boolean>;
147
+ setThreads(next: ThreadView[]): void;
148
+ close: () => void;
149
+ refresh(): void;
150
+ dispose(): void;
151
+ };
152
+
153
+ export { DRAG_PX, FIT, type ImageLightboxItem, type ImageLightboxOptions, LIGHTBOX_DRAG_PX, LIGHTBOX_MAX_SCALE, LIGHTBOX_STYLE, MAX_SCALE, MIN_SCALE, SNAP_SCALE, type Size, type UnitRect, type View, clampView, createImageLightbox, fitSize, focusRegion, lightboxClamp, lightboxIcon, lightboxSize, lightboxZoom, nextImage, unitRectFrom, unitRectOf, zoomAt };