@eddyskywalker/dsh-chatgpt-subscription 0.36.2 → 0.38.0
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.
- package/CHANGELOG.md +51 -0
- package/README.md +32 -9
- package/lib/client.js +603 -134
- package/lib/client.js.map +1 -1
- package/lib/index.js +3256 -55
- package/lib/types/client/common/ComposerQuotaBadge.d.ts +7 -0
- package/lib/types/client/common/ComposerQuotaBadge.d.ts.map +1 -1
- package/lib/types/client/kimi-code/KimiCodeSection.d.ts.map +1 -1
- package/lib/types/client/kimi-code/KimiModelCapabilities.d.ts.map +1 -1
- package/lib/types/client/kimi-code/labels.d.ts +66 -0
- package/lib/types/client/kimi-code/labels.d.ts.map +1 -0
- package/lib/types/client/kimi-code/locales.d.ts +52 -11
- package/lib/types/client/kimi-code/locales.d.ts.map +1 -1
- package/lib/types/client/locales-ru/kimi-code.d.ts.map +1 -1
- package/lib/types/client/locales-ru/opendesign.d.ts.map +1 -1
- package/lib/types/client/locales-ru/progress-updates.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +4 -4
- package/lib/types/client/opendesign/OpenDesignComposerQuota.d.ts +6 -0
- package/lib/types/client/opendesign/OpenDesignComposerQuota.d.ts.map +1 -1
- package/lib/types/client/opendesign/OpenDesignSection.d.ts.map +1 -1
- package/lib/types/client/opendesign/locales.d.ts +54 -0
- package/lib/types/client/opendesign/locales.d.ts.map +1 -1
- package/lib/types/client/opendesign/styles.d.ts +5 -0
- package/lib/types/client/opendesign/styles.d.ts.map +1 -1
- package/lib/types/client/progress-updates/locales.d.ts +17 -2
- package/lib/types/client/progress-updates/locales.d.ts.map +1 -1
- package/lib/types/client/progress-updates/settings-binding.d.ts +13 -3
- package/lib/types/client/progress-updates/settings-binding.d.ts.map +1 -1
- package/lib/types/client/progress-updates/settings-section.d.ts +5 -5
- package/lib/types/client/progress-updates/settings-section.d.ts.map +1 -1
- package/lib/types/client/progress-updates/styles.d.ts.map +1 -1
- package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
- package/lib/types/host/kimi-code/client.d.ts.map +1 -1
- package/lib/types/host/kimi-code/mapper.d.ts +89 -1
- package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
- package/lib/types/host/kimi-code/model-catalog.d.ts +20 -4
- package/lib/types/host/kimi-code/model-catalog.d.ts.map +1 -1
- package/lib/types/host/kimi-code/routes.d.ts.map +1 -1
- package/lib/types/host/kimi-code/types.d.ts +50 -6
- package/lib/types/host/kimi-code/types.d.ts.map +1 -1
- package/lib/types/host/opendesign/client.d.ts +103 -0
- package/lib/types/host/opendesign/client.d.ts.map +1 -1
- package/lib/types/host/opendesign/routes.d.ts.map +1 -1
- package/lib/types/host/opendesign/types.d.ts +31 -0
- package/lib/types/host/opendesign/types.d.ts.map +1 -1
- package/lib/types/host/progress-updates/index.d.ts +5 -0
- package/lib/types/host/progress-updates/index.d.ts.map +1 -1
- package/lib/types/host/progress-updates/prompt.d.ts +37 -3
- package/lib/types/host/progress-updates/prompt.d.ts.map +1 -1
- package/lib/types/host/progress-updates/tool.d.ts.map +1 -1
- package/lib/types/host/proxy-manager.d.ts +63 -1
- package/lib/types/host/proxy-manager.d.ts.map +1 -1
- package/lib/types/host/widget-board/line-contracts.d.ts +192 -0
- package/lib/types/host/widget-board/line-contracts.d.ts.map +1 -0
- package/lib/types/host/widget-board/line-quota.d.ts +284 -0
- package/lib/types/host/widget-board/line-quota.d.ts.map +1 -0
- package/lib/types/host/widget-board/line-registry.d.ts +402 -0
- package/lib/types/host/widget-board/line-registry.d.ts.map +1 -0
- package/lib/types/host/widget-board/provider.d.ts +140 -0
- package/lib/types/host/widget-board/provider.d.ts.map +1 -0
- package/lib/types/index.d.ts +10 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/shared/kimi-code-contracts.d.ts +30 -6
- package/lib/types/shared/kimi-code-contracts.d.ts.map +1 -1
- package/lib/types/shared/opendesign-contracts.d.ts +47 -0
- package/lib/types/shared/opendesign-contracts.d.ts.map +1 -1
- package/package.json +2 -1
- package/widgets/line-flip/index.html +58 -0
- package/widgets/line-flip/main.js +1102 -0
- package/widgets/line-flip/style.css +729 -0
- package/widgets/line-flip/widget.json +555 -0
|
@@ -0,0 +1,1102 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 线路额度卡的脚本。
|
|
3
|
+
*
|
|
4
|
+
* 数据来自宿主上的 `dsh-chatgpt-subscription:line-card` 数据源(按 60 秒轮询)。一条线路的全部事实
|
|
5
|
+
* ——额度、账号池、当前账号、轮换策略——都在同一个形状里,正反两面读的是同一份 `widget.data`:
|
|
6
|
+
* 翻面不重读数据,它只是把已经拿到的东西换一面摆出来。
|
|
7
|
+
*
|
|
8
|
+
* 三个能力都按线路调用(参数里带 `line`):刷新额度、切换账号、改轮换策略。成功返回同一份形状时用
|
|
9
|
+
* 它重画,失败只在对应的那一面加一行内联原因——不吞掉,也不把整张卡变成错误页。
|
|
10
|
+
*
|
|
11
|
+
* 翻面用套件的 `t-flip-card`:`[data-t-flip]` 加内层 rotateY,背面 `backface-visibility` 由套件给。
|
|
12
|
+
* 触屏轻点与键盘由运行时切换 `.is-flipped`,桌面靠 `:hover`;两条路径最后都读同一个类名,再写进
|
|
13
|
+
* `widget.setState({ face })`,重载后回到那一面。鼠标点击也切换一次「记住的那一面」——桌面没有悬停
|
|
14
|
+
* 之外的翻面动作,少了这一下,被记住的背面在纯鼠标下就回不到正面。
|
|
15
|
+
*
|
|
16
|
+
* 时间戳一律按毫秒处理,直接交给 `new Date(ms)`。窗口标签优先用数据里的 label,空的时候按
|
|
17
|
+
* 「天 → 小时 → 分钟」逐级推:43200 分钟先命中 30 天,不会落到 720 小时。
|
|
18
|
+
*
|
|
19
|
+
* 组件库缩略画面(placement 为 preview)与没有宿主的场景不调能力,画一份静态样例。
|
|
20
|
+
*/
|
|
21
|
+
;(function () {
|
|
22
|
+
var card = document.querySelector('.line-flip')
|
|
23
|
+
if (!card) return
|
|
24
|
+
|
|
25
|
+
var widget = window.widget
|
|
26
|
+
var REFRESH = 'dsh-chatgpt-subscription:line-refresh'
|
|
27
|
+
var SWITCH = 'dsh-chatgpt-subscription:line-account-switch'
|
|
28
|
+
var STRATEGY = 'dsh-chatgpt-subscription:line-account-strategy'
|
|
29
|
+
|
|
30
|
+
var NAMES = [
|
|
31
|
+
'refresh', 'frontTitle', 'frontMeta', 'frontPlan', 'frontUpdated', 'frontBlank', 'frontError',
|
|
32
|
+
'windows', 'foot', 'credits', 'resets',
|
|
33
|
+
'backKicker', 'backTitle', 'backMeta', 'backCount', 'backStrategy', 'strategy', 'list',
|
|
34
|
+
'backBlank', 'backBlankText', 'backBlankHint', 'backError'
|
|
35
|
+
]
|
|
36
|
+
var fields = {}
|
|
37
|
+
for (var index = 0; index < NAMES.length; index += 1) {
|
|
38
|
+
fields[NAMES[index]] = card.querySelector('[data-field="' + NAMES[index] + '"]')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
var shown = null
|
|
42
|
+
var face = 'front'
|
|
43
|
+
var pendingRefresh = null
|
|
44
|
+
var pendingSwitch = null
|
|
45
|
+
var pendingStrategy = null
|
|
46
|
+
var refreshError = ''
|
|
47
|
+
var accountError = ''
|
|
48
|
+
var SAMPLE = null
|
|
49
|
+
|
|
50
|
+
function t(key, fallback) {
|
|
51
|
+
var value = widget !== undefined && widget !== null && typeof widget.t === 'function' ? widget.t(key) : key
|
|
52
|
+
return value === key ? fallback : value
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** 文案模板里的占位符:{n}、{time}、{date}、{a}、{b} 各换一次(全局)。 */
|
|
56
|
+
function fill(template, values) {
|
|
57
|
+
var text = String(template)
|
|
58
|
+
for (var key in values) {
|
|
59
|
+
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
|
60
|
+
text = text.replace(new RegExp('\\{' + key + '\\}', 'g'), String(values[key]))
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return text
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function put(node, value) {
|
|
67
|
+
if (node !== null && node !== undefined) node.textContent = value === null || value === undefined ? '' : String(value)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function show(node, visible) {
|
|
71
|
+
if (node !== null && node !== undefined) node.hidden = !visible
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** 数字字段可能是数字也可能是数字字符串;其余一律当作没有。 */
|
|
75
|
+
function numberOrNull(value) {
|
|
76
|
+
if (typeof value === 'number' && isFinite(value)) return value
|
|
77
|
+
if (typeof value === 'string' && value.trim() !== '' && isFinite(Number(value))) return Number(value)
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function textOrNull(value) {
|
|
82
|
+
return typeof value === 'string' && value.trim() !== '' ? value.trim() : null
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function clock(stamp) {
|
|
86
|
+
var date = new Date(stamp)
|
|
87
|
+
var hours = date.getHours()
|
|
88
|
+
var minutes = date.getMinutes()
|
|
89
|
+
return (hours < 10 ? '0' : '') + hours + ':' + (minutes < 10 ? '0' : '') + minutes
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function dayStamp(stamp) {
|
|
93
|
+
var date = new Date(stamp)
|
|
94
|
+
return date.getMonth() + 1 + '/' + date.getDate()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** 一位小数、去掉没意义的 .0:30 天、1.5 小时。 */
|
|
98
|
+
function trimDecimal(value) {
|
|
99
|
+
return String(Math.round(value * 10) / 10)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function percentText(value) {
|
|
103
|
+
var used = numberOrNull(value)
|
|
104
|
+
if (used === null) return ''
|
|
105
|
+
return trimDecimal(used) + '%'
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** 这张卡显示哪条线路:props 是第一来源,数据里的 line 兜底。 */
|
|
109
|
+
function lineId() {
|
|
110
|
+
var props = widget !== undefined && widget !== null ? widget.props : null
|
|
111
|
+
var fromProps = props !== null && props !== undefined ? textOrNull(props.line) : null
|
|
112
|
+
if (fromProps !== null) return fromProps
|
|
113
|
+
var fromData = shown !== null && shown !== undefined ? textOrNull(shown.line) : null
|
|
114
|
+
return fromData !== null ? fromData : ''
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** 标题永远是线路的显示名:先按 props.line 取文案,缺了再退回数据里的 label。 */
|
|
118
|
+
function lineName() {
|
|
119
|
+
var id = lineId()
|
|
120
|
+
if (id !== '') {
|
|
121
|
+
var key = 'line.' + id
|
|
122
|
+
var stated = t(key, '')
|
|
123
|
+
if (stated !== '') return stated
|
|
124
|
+
}
|
|
125
|
+
var label = shown !== null && shown !== undefined ? textOrNull(shown.label) : null
|
|
126
|
+
if (label !== null) return label
|
|
127
|
+
return t('title', '线路额度')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function quotaOf(data) {
|
|
131
|
+
if (data === null || data === undefined || typeof data !== 'object') return null
|
|
132
|
+
return data.quota !== null && typeof data.quota === 'object' ? data.quota : null
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function windowsOf(data) {
|
|
136
|
+
var quota = quotaOf(data)
|
|
137
|
+
return quota !== null && Array.isArray(quota.windows) ? quota.windows : []
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function accountsOf(data) {
|
|
141
|
+
return data !== null && data !== undefined && Array.isArray(data.accounts) ? data.accounts : []
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* 一个账号自己那份完整窗口列表。
|
|
146
|
+
*
|
|
147
|
+
* 形状 C 里这是可选字段:没有这个键(这条线路不做按账号读取)与 `[]`(读了,什么都没量到)都不
|
|
148
|
+
* 画窗口行——两种都不补 0% 占位,占位会被读成「用完了」。
|
|
149
|
+
*/
|
|
150
|
+
function accountWindowsOf(account) {
|
|
151
|
+
if (account === null || account === undefined || typeof account !== 'object') return []
|
|
152
|
+
return Array.isArray(account.windows) ? account.windows : []
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** 任何一个账号带着非空窗口:正面就有额度可画,哪怕线路级的 `quota.windows` 是空的。 */
|
|
156
|
+
function hasAccountWindows(data) {
|
|
157
|
+
var accounts = accountsOf(data)
|
|
158
|
+
for (var i = 0; i < accounts.length; i += 1) {
|
|
159
|
+
if (accountWindowsOf(accounts[i]).length > 0) return true
|
|
160
|
+
}
|
|
161
|
+
return false
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 正面的分组方案:**≥2 个账号各自带着非空窗口**时才分组,否则返回 null(保持扁平)。
|
|
166
|
+
*
|
|
167
|
+
* 只有一个账号有窗口时分组只会多出一层没有信息量的标题——那一层由规则二挡掉:退回线路级的
|
|
168
|
+
* `quota.windows`。分组时正面只画各账号自己的窗口,线路级那份不再重复一遍。
|
|
169
|
+
*/
|
|
170
|
+
function windowGroupsOf(data) {
|
|
171
|
+
var accounts = accountsOf(data)
|
|
172
|
+
var groups = []
|
|
173
|
+
for (var i = 0; i < accounts.length; i += 1) {
|
|
174
|
+
var windows = accountWindowsOf(accounts[i])
|
|
175
|
+
if (windows.length > 0) groups.push({ account: accounts[i], windows: windows })
|
|
176
|
+
}
|
|
177
|
+
return groups.length >= 2 ? groups : null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 窗口标签:优先用数据给的 label;空的时候不足一小时按分钟,整天按天,整点按小时,其余一位小数。
|
|
182
|
+
*
|
|
183
|
+
* 43200 分钟(30 天)先命中天,不会落到「720 小时」。
|
|
184
|
+
*/
|
|
185
|
+
function windowText(entry) {
|
|
186
|
+
var item = entry !== null && entry !== undefined && typeof entry === 'object' ? entry : {}
|
|
187
|
+
var stated = textOrNull(item.label)
|
|
188
|
+
if (stated !== null) return stated
|
|
189
|
+
var span = numberOrNull(item.windowMinutes)
|
|
190
|
+
if (span === null || span <= 0) return t('quota', '额度')
|
|
191
|
+
var minutes = Math.round(span)
|
|
192
|
+
if (minutes < 60) return fill(t('minutes', '{n} 分钟'), { n: minutes })
|
|
193
|
+
if (minutes % 1440 === 0) return fill(t('days', '{n} 天'), { n: minutes / 1440 })
|
|
194
|
+
if (minutes % 60 === 0) return fill(t('hours', '{n} 小时'), { n: minutes / 60 })
|
|
195
|
+
return fill(t('hours', '{n} 小时'), { n: trimDecimal(minutes / 60) })
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** 重置倒计时:到点写「即将重置」,否则按天、小时、分钟给一句人话。 */
|
|
199
|
+
function resetText(stamp, now) {
|
|
200
|
+
var at = numberOrNull(stamp)
|
|
201
|
+
if (at === null) return ''
|
|
202
|
+
var diff = at - now
|
|
203
|
+
if (diff <= 0) return t('resetting', '即将重置')
|
|
204
|
+
var minutes = Math.ceil(diff / 60000)
|
|
205
|
+
if (minutes < 60) return fill(t('resetMinutes', '{n} 分钟后重置'), { n: minutes })
|
|
206
|
+
var hours = Math.floor(minutes / 60)
|
|
207
|
+
var rest = minutes % 60
|
|
208
|
+
if (hours < 24) {
|
|
209
|
+
return rest === 0
|
|
210
|
+
? fill(t('resetHoursExact', '{a} 小时后重置'), { a: hours })
|
|
211
|
+
: fill(t('resetHours', '{a} 小时 {b} 分后重置'), { a: hours, b: rest })
|
|
212
|
+
}
|
|
213
|
+
var days = Math.floor(hours / 24)
|
|
214
|
+
var left = hours % 24
|
|
215
|
+
return left === 0
|
|
216
|
+
? fill(t('resetDaysExact', '{a} 天后重置'), { a: days })
|
|
217
|
+
: fill(t('resetDays', '{a} 天 {b} 小时后重置'), { a: days, b: left })
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function creditText(data) {
|
|
221
|
+
var quota = quotaOf(data)
|
|
222
|
+
var credits = quota !== null ? quota.credits : null
|
|
223
|
+
if (credits === null || credits === undefined || typeof credits !== 'object') return ''
|
|
224
|
+
var label = t('credits', '赠额')
|
|
225
|
+
if (credits.unlimited === true) return label + ' ' + t('unlimited', '无限')
|
|
226
|
+
var balance = credits.balance
|
|
227
|
+
if (typeof balance === 'number' && isFinite(balance)) return label + ' ' + String(balance)
|
|
228
|
+
var stated = textOrNull(balance)
|
|
229
|
+
return stated !== null ? label + ' ' + stated : ''
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* 「只有绝对额度、没有百分比窗口」的线路的主事实:原样用数据里的余额串,不自己补币种。
|
|
234
|
+
*
|
|
235
|
+
* 返回空串表示这条线路没有可显示的额度——那时窗口区让位给空态文案。
|
|
236
|
+
*/
|
|
237
|
+
function creditFactText(data) {
|
|
238
|
+
var quota = quotaOf(data)
|
|
239
|
+
var credits = quota !== null ? quota.credits : null
|
|
240
|
+
if (credits === null || credits === undefined || typeof credits !== 'object') return ''
|
|
241
|
+
if (credits.unlimited === true) return t('credits', '赠额') + ' ' + t('unlimited', '无限')
|
|
242
|
+
var balance = credits.balance
|
|
243
|
+
var stated = typeof balance === 'number' && isFinite(balance) ? String(balance) : textOrNull(balance)
|
|
244
|
+
if (stated === null) return ''
|
|
245
|
+
return fill(t('balanceAvailable', '可用余额 {v}'), { v: stated })
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function resetsText(data) {
|
|
249
|
+
var quota = quotaOf(data)
|
|
250
|
+
var reset = quota !== null ? quota.resetCredits : null
|
|
251
|
+
if (reset === null || reset === undefined || typeof reset !== 'object') return ''
|
|
252
|
+
var available = numberOrNull(reset.available)
|
|
253
|
+
if (available === null || available < 0) return ''
|
|
254
|
+
var text = available === 1
|
|
255
|
+
? t('resetsOne', '1 次可用重置')
|
|
256
|
+
: fill(t('resetsAvailable', '{n} 次可用重置'), { n: Math.round(available) })
|
|
257
|
+
var expiresAt = numberOrNull(reset.expiresAt)
|
|
258
|
+
// 0 是「没有过期时间」的占位值,不画日期。
|
|
259
|
+
if (expiresAt !== null && expiresAt > 0) {
|
|
260
|
+
text += ' · ' + fill(t('resetsExpires', '{date} 到期'), { date: dayStamp(expiresAt) })
|
|
261
|
+
}
|
|
262
|
+
return text
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* 正面的右上角事实:套餐名 + 这条线路自己的读取时间。
|
|
267
|
+
*
|
|
268
|
+
* 拆成两段返回:窄盒子里「更新于」由 `.w-hide-narrow` 收掉,只留套餐名,不靠省略号硬撑。
|
|
269
|
+
*
|
|
270
|
+
* 分隔点只在两段都在时才出现,并且跟着第二段一起走:Qoder、Command Code 这些线路没有套餐名,
|
|
271
|
+
* 只剩「更新于」时不会多出一个孤零零的前导点;窄盒子里第二段被收掉,也不会留下尾部的点。
|
|
272
|
+
*/
|
|
273
|
+
function frontMeta(data) {
|
|
274
|
+
if (data === null || data === undefined || typeof data !== 'object') return { plan: '', rest: '' }
|
|
275
|
+
var plan = textOrNull(data.planLabel)
|
|
276
|
+
var quota = quotaOf(data)
|
|
277
|
+
var fetchedAt = quota !== null ? numberOrNull(quota.fetchedAt) : null
|
|
278
|
+
var updated = fetchedAt !== null && fetchedAt > 0 ? fill(t('updatedAt', '更新于 {time}'), { time: clock(fetchedAt) }) : ''
|
|
279
|
+
var stated = plan !== null ? plan : ''
|
|
280
|
+
return {
|
|
281
|
+
plan: stated,
|
|
282
|
+
rest: updated === '' ? '' : stated === '' ? updated : ' · ' + updated
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function strategyLabel(strategy) {
|
|
287
|
+
if (strategy === 'sequential') return t('strategySequential', '顺序使用')
|
|
288
|
+
if (strategy === 'round-robin') return t('strategyRoundRobin', '轮流使用')
|
|
289
|
+
if (strategy === 'sticky') return t('strategySticky', '固定当前')
|
|
290
|
+
return ''
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** 背面的右上角事实:账号数 + 当前策略;策略在窄盒子里收掉——下面那排 chip 已经说明了它。 */
|
|
294
|
+
function backMeta(data) {
|
|
295
|
+
var count = accountsOf(data).length
|
|
296
|
+
if (count === 0) return { count: '', rest: '' }
|
|
297
|
+
var label = data !== null && data !== undefined ? strategyLabel(data.strategy) : ''
|
|
298
|
+
return {
|
|
299
|
+
count: count === 1 ? t('metaAccountOne', '1 个账号') : fill(t('metaAccounts', '{n} 个账号'), { n: count }),
|
|
300
|
+
rest: label !== '' ? ' · ' + label : ''
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function displayLabel(account) {
|
|
305
|
+
var stated = textOrNull(account.label)
|
|
306
|
+
if (stated !== null) return stated
|
|
307
|
+
var email = textOrNull(account.email)
|
|
308
|
+
if (email !== null) return email
|
|
309
|
+
return typeof account.id === 'string' ? account.id : ''
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** 当前账号:先按 activeAccountId 在号池里找,找不到再退回数据里的账号摘要。 */
|
|
313
|
+
function currentAccountOf(data) {
|
|
314
|
+
if (data === null || data === undefined || typeof data !== 'object') return ''
|
|
315
|
+
var activeId = typeof data.activeAccountId === 'string' ? data.activeAccountId : ''
|
|
316
|
+
var accounts = accountsOf(data)
|
|
317
|
+
for (var i = 0; i < accounts.length; i += 1) {
|
|
318
|
+
var account = accounts[i]
|
|
319
|
+
if (account !== null && typeof account === 'object' && activeId !== '' && account.id === activeId) return displayLabel(account)
|
|
320
|
+
}
|
|
321
|
+
var label = textOrNull(data.accountLabel)
|
|
322
|
+
return label !== null ? label : ''
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** 状态点:登录失效优先于冷却与主账号,颜色只在点上看,读屏文字另给一句。 */
|
|
326
|
+
function toneOf(account, activeId, now) {
|
|
327
|
+
var auth = typeof account.authStatus === 'string' ? account.authStatus : ''
|
|
328
|
+
if (auth === 'expired') return { tone: 'danger', sr: 'srExpired', label: '登录已失效' }
|
|
329
|
+
if (auth === 'invalid') return { tone: 'danger', sr: 'srInvalid', label: '登录无效' }
|
|
330
|
+
if (auth !== '' && auth !== 'ok') return { tone: 'danger', sr: 'srBlocked', label: '登录需要处理' }
|
|
331
|
+
var cooldown = numberOrNull(account.cooldownUntil)
|
|
332
|
+
if (cooldown !== null && cooldown > now) return { tone: 'warn', sr: 'srCooling', label: '冷却中' }
|
|
333
|
+
if (account.primary === true || (activeId !== '' && account.id === activeId)) return { tone: 'brand', sr: 'srPrimary', label: '主账号' }
|
|
334
|
+
return { tone: 'idle', sr: 'srReady', label: '可用' }
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* 正面的状态:整卡级别的错误优先,其次是没有登录、没有安装,再往后才是额度本身的事实。
|
|
339
|
+
*
|
|
340
|
+
* 没有百分比窗口不等于没有额度:只有绝对额度的线路(command-code、qoder)走 'credits',
|
|
341
|
+
* 让余额成为正文里那条主事实;'refreshing' 与 'empty' 才交给空态文案。
|
|
342
|
+
*/
|
|
343
|
+
function kindOf(data) {
|
|
344
|
+
if (data === null || data === undefined || typeof data !== 'object') return 'loading'
|
|
345
|
+
if (textOrNull(data.error) !== null) return 'error'
|
|
346
|
+
if (data.signedIn === false) return 'signed-out'
|
|
347
|
+
if (data.installed === false) return 'not-installed'
|
|
348
|
+
var quota = quotaOf(data)
|
|
349
|
+
if (quota === null) return 'no-quota'
|
|
350
|
+
if (textOrNull(quota.error) !== null) return 'error'
|
|
351
|
+
// 线路级窗口与「各账号自己的窗口」任一个给了事实,正面就有东西画。
|
|
352
|
+
if (windowsOf(data).length > 0 || hasAccountWindows(data)) return 'ready'
|
|
353
|
+
if (creditFactText(data) !== '') return 'credits'
|
|
354
|
+
if (quota.refreshing === true) return 'refreshing'
|
|
355
|
+
return 'empty'
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function frontBlankOf(data, kind) {
|
|
359
|
+
if (kind === 'loading') return t('loading', '正在读取额度…')
|
|
360
|
+
if (kind === 'signed-out') return t('signedOut', '未登录')
|
|
361
|
+
if (kind === 'not-installed') return t('notInstalled', '这条线路的插件还没装')
|
|
362
|
+
if (kind === 'no-quota') return t('noQuota', '这条线路还没有额度记录,点刷新或去设置页打开一次')
|
|
363
|
+
if (kind === 'refreshing') return t('refreshing', '正在刷新额度…')
|
|
364
|
+
if (kind === 'empty') return t('empty', '这条线路没有额度窗口')
|
|
365
|
+
if (kind === 'error') {
|
|
366
|
+
var quota = quotaOf(data)
|
|
367
|
+
var reason = data !== null && data !== undefined ? textOrNull(data.error) : null
|
|
368
|
+
if (reason === null && quota !== null) reason = textOrNull(quota.error)
|
|
369
|
+
return reason !== null ? reason : t('failed', '读不到额度')
|
|
370
|
+
}
|
|
371
|
+
return ''
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** 背面的空态:没有账号时说得具体一点,别只留一片空白。 */
|
|
375
|
+
function backBlankOf(data) {
|
|
376
|
+
if (data === null || data === undefined || typeof data !== 'object') {
|
|
377
|
+
return { text: t('loading', '正在读取额度…'), hint: '' }
|
|
378
|
+
}
|
|
379
|
+
if (data.installed === false) return { text: t('notInstalled', '这条线路的插件还没装'), hint: '' }
|
|
380
|
+
if (data.signedIn === false) return { text: t('signedOut', '未登录'), hint: '' }
|
|
381
|
+
var reason = textOrNull(data.error)
|
|
382
|
+
if (reason !== null) return { text: reason, hint: '' }
|
|
383
|
+
return { text: t('emptyAccounts', '账号池是空的'), hint: t('emptyAccountsHint', '添加账号后可以在这里切换') }
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function windowRow(entry, now) {
|
|
387
|
+
var item = entry !== null && entry !== undefined && typeof entry === 'object' ? entry : {}
|
|
388
|
+
var row = document.createElement('div')
|
|
389
|
+
row.className = 'line-flip__win'
|
|
390
|
+
|
|
391
|
+
var head = document.createElement('div')
|
|
392
|
+
head.className = 'line-flip__win-head'
|
|
393
|
+
|
|
394
|
+
var label = document.createElement('span')
|
|
395
|
+
label.className = 'line-flip__win-label'
|
|
396
|
+
label.textContent = windowText(item)
|
|
397
|
+
|
|
398
|
+
var used = document.createElement('span')
|
|
399
|
+
used.className = 'line-flip__win-used'
|
|
400
|
+
var usedText = percentText(item.usedPercent)
|
|
401
|
+
used.textContent = usedText
|
|
402
|
+
if (usedText === '') used.hidden = true
|
|
403
|
+
|
|
404
|
+
var reset = document.createElement('span')
|
|
405
|
+
reset.className = 'line-flip__win-reset w-hide-narrow'
|
|
406
|
+
var resetValue = resetText(item.resetsAt, now)
|
|
407
|
+
reset.textContent = resetValue
|
|
408
|
+
if (resetValue === '') reset.hidden = true
|
|
409
|
+
|
|
410
|
+
head.appendChild(label)
|
|
411
|
+
head.appendChild(used)
|
|
412
|
+
head.appendChild(reset)
|
|
413
|
+
|
|
414
|
+
var meter = document.createElement('span')
|
|
415
|
+
meter.className = 'line-flip__meter'
|
|
416
|
+
meter.setAttribute('aria-hidden', 'true')
|
|
417
|
+
var bar = document.createElement('span')
|
|
418
|
+
bar.className = 'line-flip__fill'
|
|
419
|
+
meter.appendChild(bar)
|
|
420
|
+
|
|
421
|
+
var usedValue = numberOrNull(item.usedPercent)
|
|
422
|
+
if (usedValue !== null) {
|
|
423
|
+
meter.style.setProperty('--line-used', Math.max(0, Math.min(100, usedValue)) + '%')
|
|
424
|
+
// 接近满额换成警告色;颜色之外还有百分比数字,不靠颜色单独表达状态。
|
|
425
|
+
if (usedValue >= 90) row.setAttribute('data-high', 'true')
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
row.appendChild(head)
|
|
429
|
+
row.appendChild(meter)
|
|
430
|
+
return row
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* 分组小标题右侧那句弱化的事实:当前/主账号 + 计划名,有什么写什么,挤不下时由 CSS 在窄盒子里收掉。
|
|
435
|
+
*
|
|
436
|
+
* 它不是标题的一部分:标题是「这是谁的额度」,这一句是「这个号现在什么状况」。
|
|
437
|
+
*/
|
|
438
|
+
function groupNoteOf(account, data) {
|
|
439
|
+
var activeId = data !== null && data !== undefined && typeof data.activeAccountId === 'string' ? data.activeAccountId : ''
|
|
440
|
+
var parts = []
|
|
441
|
+
if (activeId !== '' && account.id === activeId) parts.push(t('current', '当前'))
|
|
442
|
+
else if (account.primary === true) parts.push(t('srPrimary', '主账号'))
|
|
443
|
+
var plan = textOrNull(account.plan)
|
|
444
|
+
if (plan !== null) parts.push(plan)
|
|
445
|
+
return parts.join(' · ')
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* 一个账号一组:caption 级的小标题 + 它自己的窗口行(沿用窗口行本身,标签/已用%/倒计时/进度条)。
|
|
450
|
+
*
|
|
451
|
+
* 标题只写一个身份(email,没有就 label),撞名的处理与背面同一套,区分符跟在标题右边;标题行不
|
|
452
|
+
* 参与「行高预算」——它比一行窗口还矮,仍由窗口区自己滚动。
|
|
453
|
+
*/
|
|
454
|
+
function windowGroupNode(group, data, plan, now) {
|
|
455
|
+
var account = group.account
|
|
456
|
+
var node = document.createElement('div')
|
|
457
|
+
node.className = 'line-flip__group'
|
|
458
|
+
if (typeof account.id === 'string' && account.id !== '') node.setAttribute('data-account', account.id)
|
|
459
|
+
|
|
460
|
+
var head = document.createElement('p')
|
|
461
|
+
head.className = 'line-flip__group-head'
|
|
462
|
+
|
|
463
|
+
var titleText = plan !== null && plan !== undefined && plan.name !== '' ? plan.name : displayLabel(account)
|
|
464
|
+
head.setAttribute('aria-label', fill(t('accountQuota', '{name} 的额度'), {
|
|
465
|
+
name: plan !== null && plan !== undefined && plan.tag !== '' ? titleText + ' · ' + plan.tag : titleText
|
|
466
|
+
}))
|
|
467
|
+
var title = document.createElement('span')
|
|
468
|
+
title.className = 'line-flip__group-title'
|
|
469
|
+
title.textContent = titleText
|
|
470
|
+
title.setAttribute('title', titleText)
|
|
471
|
+
head.appendChild(title)
|
|
472
|
+
|
|
473
|
+
var tag = plan !== null && plan !== undefined ? plan.tag : ''
|
|
474
|
+
if (tag !== '') {
|
|
475
|
+
var tagNode = document.createElement('span')
|
|
476
|
+
tagNode.className = 'line-flip__group-tag'
|
|
477
|
+
tagNode.textContent = tag
|
|
478
|
+
head.appendChild(tagNode)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
var note = groupNoteOf(account, data)
|
|
482
|
+
if (note !== '') {
|
|
483
|
+
var noteNode = document.createElement('span')
|
|
484
|
+
noteNode.className = 'line-flip__group-note w-hide-narrow'
|
|
485
|
+
noteNode.textContent = note
|
|
486
|
+
head.appendChild(noteNode)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
node.appendChild(head)
|
|
490
|
+
for (var i = 0; i < group.windows.length; i += 1) node.appendChild(windowRow(group.windows[i], now))
|
|
491
|
+
return node
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* 「只有绝对额度、没有百分比窗口」的主事实行:主句用窗口标题那一档字号与前景色,说明跟在下面一行小字。
|
|
496
|
+
*
|
|
497
|
+
* 复用窗口行的类名:主句按省略号收在盒子宽度里,说明按 288px 的宽度折行,两者都不会横向溢出。
|
|
498
|
+
*/
|
|
499
|
+
function creditFactRow(data) {
|
|
500
|
+
var row = document.createElement('div')
|
|
501
|
+
row.className = 'line-flip__win'
|
|
502
|
+
var fact = document.createElement('span')
|
|
503
|
+
fact.className = 'line-flip__win-label'
|
|
504
|
+
fact.textContent = creditFactText(data)
|
|
505
|
+
var hint = document.createElement('span')
|
|
506
|
+
hint.className = 'line-flip__fact'
|
|
507
|
+
hint.textContent = t('creditsOnlyHint', '这条线路按额度计量,没有百分比窗口')
|
|
508
|
+
row.appendChild(fact)
|
|
509
|
+
row.appendChild(hint)
|
|
510
|
+
return row
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** 该账号自己最近一次快照的主窗口:迷你进度条加百分比,没有快照时不画。 */
|
|
514
|
+
function gaugeOf(account) {
|
|
515
|
+
var used = numberOrNull(account.usedPercent)
|
|
516
|
+
if (used === null) return null
|
|
517
|
+
var wrap = document.createElement('span')
|
|
518
|
+
wrap.className = 'line-flip__gauge'
|
|
519
|
+
wrap.setAttribute('aria-hidden', 'true')
|
|
520
|
+
var meter = document.createElement('span')
|
|
521
|
+
meter.className = 'line-flip__meter line-flip__meter--mini'
|
|
522
|
+
var bar = document.createElement('span')
|
|
523
|
+
bar.className = 'line-flip__fill'
|
|
524
|
+
meter.appendChild(bar)
|
|
525
|
+
meter.style.setProperty('--line-used', Math.max(0, Math.min(100, used)) + '%')
|
|
526
|
+
var pct = document.createElement('span')
|
|
527
|
+
pct.className = 'line-flip__pct'
|
|
528
|
+
var exact = document.createElement('span')
|
|
529
|
+
exact.className = 'line-flip__pct-full'
|
|
530
|
+
exact.textContent = percentText(used)
|
|
531
|
+
var whole = document.createElement('span')
|
|
532
|
+
whole.className = 'line-flip__pct-int'
|
|
533
|
+
whole.textContent = Math.round(Math.max(0, Math.min(100, used))) + '%'
|
|
534
|
+
pct.appendChild(exact)
|
|
535
|
+
pct.appendChild(whole)
|
|
536
|
+
wrap.appendChild(meter)
|
|
537
|
+
wrap.appendChild(pct)
|
|
538
|
+
return wrap
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* 整行是一个按钮,所以读屏拿到的名字由这里给:动作、别名、邮箱与状态都在一句话里。
|
|
543
|
+
*
|
|
544
|
+
* 只写「切换到 X」会丢掉邮箱与状态,而这两个字段恰恰是同一线路里换号时唯一能区分账号的东西。
|
|
545
|
+
*/
|
|
546
|
+
function rowLabel(name, email, statusText) {
|
|
547
|
+
var label = fill(t('switchTo', '切换到 {name}'), { name: name })
|
|
548
|
+
if (email !== null && email !== name) label += ' · ' + email
|
|
549
|
+
if (statusText !== '') label += ' · ' + statusText
|
|
550
|
+
return label
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/** id 的后 4 位:重名又没邮箱时,这是唯一还能把两行分开的东西。 */
|
|
554
|
+
function tailOf(id) {
|
|
555
|
+
var text = typeof id === 'string' ? id : ''
|
|
556
|
+
if (text === '') return ''
|
|
557
|
+
return text.length > 4 ? text.slice(-4) : text
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* 整卡的「标题方案」:一行一个账号,给出标题、可选的短区分符与副标题。
|
|
562
|
+
*
|
|
563
|
+
* 真实数据里同一个线路的账号常常同名(Qoder 的「For SK · 账号 1」、MiniMax 连着两个「MiniMax Code · cn ·
|
|
564
|
+
* 账号 2」),两行写一样就分不出谁是谁。规则只有一句:**标题撞了才加区分符**,优先用这个账号的邮箱,
|
|
565
|
+
* 邮箱也没有(或者它已经当标题了)就用 id 的后 4 位。用邮箱当区分符时副标题不再重复同一个地址。
|
|
566
|
+
*
|
|
567
|
+
* 区分符之间再撞一次(两个号连邮箱都一样)等于没分,这时退回 id 尾号。
|
|
568
|
+
*
|
|
569
|
+
* 窄盒子放不下「名字 + 区分符」:两半都只会剩几个字,读完谁也不是谁。那里由 CSS 把这一行的标题换成
|
|
570
|
+
* 区分符本身(下面 .line-flip__row[data-tagged] 的规则),名字留在 title 与 aria-label 里备查。
|
|
571
|
+
*/
|
|
572
|
+
function collide(plans) {
|
|
573
|
+
var counts = {}
|
|
574
|
+
var i
|
|
575
|
+
for (i = 0; i < plans.length; i += 1) counts[plans[i].name] = (counts[plans[i].name] || 0) + 1
|
|
576
|
+
var tagCounts = {}
|
|
577
|
+
for (i = 0; i < plans.length; i += 1) {
|
|
578
|
+
var plan = plans[i]
|
|
579
|
+
plan.collided = counts[plan.name] > 1
|
|
580
|
+
if (plan.collided) {
|
|
581
|
+
if (plan.email !== null && plan.email !== plan.name) plan.tag = plan.email
|
|
582
|
+
else {
|
|
583
|
+
var tail = tailOf(plan.id)
|
|
584
|
+
plan.tag = tail === '' ? '' : '…' + tail
|
|
585
|
+
}
|
|
586
|
+
if (plan.tag !== '') tagCounts[plan.tag] = (tagCounts[plan.tag] || 0) + 1
|
|
587
|
+
} else if (plan.email !== null && plan.email !== plan.name) {
|
|
588
|
+
plan.subtitle = plan.email
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
for (i = 0; i < plans.length; i += 1) {
|
|
592
|
+
var current = plans[i]
|
|
593
|
+
var suffix = tailOf(current.id)
|
|
594
|
+
if (current.tag !== '' && tagCounts[current.tag] > 1 && suffix !== '') current.tag = '…' + suffix
|
|
595
|
+
}
|
|
596
|
+
return plans
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function titlePlanOf(data) {
|
|
600
|
+
var accounts = accountsOf(data)
|
|
601
|
+
var plans = []
|
|
602
|
+
for (var i = 0; i < accounts.length; i += 1) {
|
|
603
|
+
var account = accounts[i] !== null && typeof accounts[i] === 'object' ? accounts[i] : {}
|
|
604
|
+
plans.push({
|
|
605
|
+
name: displayLabel(account),
|
|
606
|
+
email: textOrNull(account.email),
|
|
607
|
+
id: typeof account.id === 'string' ? account.id : '',
|
|
608
|
+
collided: false,
|
|
609
|
+
tag: '',
|
|
610
|
+
subtitle: ''
|
|
611
|
+
})
|
|
612
|
+
}
|
|
613
|
+
return collide(plans)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* 正面分组小标题的方案:标题优先用账号的 email,没有 email 才用 label,再没有用 id。
|
|
618
|
+
*
|
|
619
|
+
* 撞名沿用背面那一套({@link collide}):先拿邮箱当区分符,邮箱没有或已经当了标题就用 id 后 4
|
|
620
|
+
* 位。不同的只是**比较的范围**——背面比的是整个号池,正面比的是「这一屏真的画了标题的那几组」,
|
|
621
|
+
* 否则一个没被分组显示的账号也会在别人标题后面挂一个区分符。
|
|
622
|
+
*/
|
|
623
|
+
function captionPlanOf(groups) {
|
|
624
|
+
var plans = []
|
|
625
|
+
for (var i = 0; i < groups.length; i += 1) {
|
|
626
|
+
var account = groups[i].account
|
|
627
|
+
var email = textOrNull(account.email)
|
|
628
|
+
var label = textOrNull(account.label)
|
|
629
|
+
plans.push({
|
|
630
|
+
name: email !== null ? email : label !== null ? label : typeof account.id === 'string' ? account.id : '',
|
|
631
|
+
email: email,
|
|
632
|
+
id: typeof account.id === 'string' ? account.id : '',
|
|
633
|
+
collided: false,
|
|
634
|
+
tag: '',
|
|
635
|
+
subtitle: ''
|
|
636
|
+
})
|
|
637
|
+
}
|
|
638
|
+
return collide(plans)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function accountRow(account, data, now, plan) {
|
|
642
|
+
var activeId = data !== null && data !== undefined && typeof data.activeAccountId === 'string' ? data.activeAccountId : ''
|
|
643
|
+
var isActive = activeId !== '' && account.id === activeId
|
|
644
|
+
var busy = pendingSwitch !== null && pendingSwitch === account.id
|
|
645
|
+
var status = toneOf(account, activeId, now)
|
|
646
|
+
var planned = plan !== null && plan !== undefined ? plan : null
|
|
647
|
+
var name = planned !== null ? planned.name : displayLabel(account)
|
|
648
|
+
var tag = planned !== null ? planned.tag : ''
|
|
649
|
+
var subtitle = planned !== null ? planned.subtitle : ''
|
|
650
|
+
var tagged = tag !== '' && (planned === null || planned.collided === true)
|
|
651
|
+
|
|
652
|
+
var item = document.createElement('li')
|
|
653
|
+
item.className = 'line-flip__item'
|
|
654
|
+
|
|
655
|
+
var row = document.createElement('button')
|
|
656
|
+
row.type = 'button'
|
|
657
|
+
row.className = 'line-flip__row'
|
|
658
|
+
row.setAttribute('data-tone', status.tone)
|
|
659
|
+
if (isActive) {
|
|
660
|
+
row.setAttribute('aria-current', 'true')
|
|
661
|
+
row.disabled = true
|
|
662
|
+
} else {
|
|
663
|
+
// 读屏的名字跟着视觉走:区分符也算进名字里,否则读屏同样分不出同名的两个号。
|
|
664
|
+
row.setAttribute('aria-label', rowLabel(tag !== '' ? name + ' · ' + tag : name, subtitle !== '' ? subtitle : null, t(status.sr, status.label)))
|
|
665
|
+
}
|
|
666
|
+
if (busy) row.setAttribute('data-busy', 'true')
|
|
667
|
+
|
|
668
|
+
var dot = document.createElement('span')
|
|
669
|
+
dot.className = 'line-flip__dot'
|
|
670
|
+
dot.setAttribute('aria-hidden', 'true')
|
|
671
|
+
|
|
672
|
+
var sr = document.createElement('span')
|
|
673
|
+
sr.className = 'w-sr'
|
|
674
|
+
sr.textContent = t(status.sr, status.label)
|
|
675
|
+
|
|
676
|
+
var id = document.createElement('span')
|
|
677
|
+
id.className = 'line-flip__id'
|
|
678
|
+
|
|
679
|
+
// 标题与短区分符同一行:区分符不参与省略,名字先让。
|
|
680
|
+
var titleNode = document.createElement('span')
|
|
681
|
+
titleNode.className = 'line-flip__name'
|
|
682
|
+
var labelNode = document.createElement('span')
|
|
683
|
+
labelNode.className = 'line-flip__label'
|
|
684
|
+
labelNode.textContent = name
|
|
685
|
+
titleNode.appendChild(labelNode)
|
|
686
|
+
if (tag !== '') {
|
|
687
|
+
var tagNode = document.createElement('span')
|
|
688
|
+
tagNode.className = 'line-flip__tag'
|
|
689
|
+
tagNode.textContent = tag
|
|
690
|
+
titleNode.appendChild(tagNode)
|
|
691
|
+
}
|
|
692
|
+
id.appendChild(titleNode)
|
|
693
|
+
|
|
694
|
+
// 撞名的行在窄盒子里只剩区分符当标题:两个半截的名字摆一起,谁也不认得谁。
|
|
695
|
+
if (tagged) {
|
|
696
|
+
row.setAttribute('data-tagged', 'true')
|
|
697
|
+
var aliasNode = document.createElement('span')
|
|
698
|
+
aliasNode.className = 'line-flip__alias'
|
|
699
|
+
aliasNode.textContent = tag
|
|
700
|
+
aliasNode.setAttribute('title', name)
|
|
701
|
+
id.appendChild(aliasNode)
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// 副标题是邮箱;alias 常常就是邮箱,两行写同一个地址没有意义,而且这一行已经用邮箱当区分符时也不再重复。
|
|
705
|
+
if (subtitle !== '') {
|
|
706
|
+
var emailNode = document.createElement('span')
|
|
707
|
+
emailNode.className = 'line-flip__email w-hide-narrow'
|
|
708
|
+
emailNode.textContent = subtitle
|
|
709
|
+
id.appendChild(emailNode)
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
row.appendChild(dot)
|
|
713
|
+
row.appendChild(sr)
|
|
714
|
+
row.appendChild(id)
|
|
715
|
+
var gauge = gaugeOf(account)
|
|
716
|
+
if (gauge !== null) {
|
|
717
|
+
row.appendChild(gauge)
|
|
718
|
+
} else {
|
|
719
|
+
// 这一个号还没有额度快照:不画 0% 的空条(空条会被读成「用完了」),只留一句弱化的小字。
|
|
720
|
+
var unread = document.createElement('span')
|
|
721
|
+
unread.className = 'line-flip__gauge-missing'
|
|
722
|
+
unread.textContent = t('quotaUnread', '额度未读')
|
|
723
|
+
row.appendChild(unread)
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
if (isActive || busy) {
|
|
727
|
+
var marker = document.createElement('span')
|
|
728
|
+
marker.className = 'line-flip__marker'
|
|
729
|
+
marker.textContent = busy ? t('switching', '切换中…') : t('current', '当前')
|
|
730
|
+
row.appendChild(marker)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
var used = numberOrNull(account.usedPercent)
|
|
734
|
+
if (used !== null && used >= 90) row.setAttribute('data-high', 'true')
|
|
735
|
+
|
|
736
|
+
if (!isActive) {
|
|
737
|
+
row.disabled = busy || pendingSwitch !== null || pendingStrategy !== null
|
|
738
|
+
row.addEventListener('click', function (event) {
|
|
739
|
+
stop(event)
|
|
740
|
+
switchTo(account.id)
|
|
741
|
+
})
|
|
742
|
+
// Enter 与空格交给浏览器按按钮的默认动作走;别冒泡到翻转容器的键盘处理上去换面。
|
|
743
|
+
row.addEventListener('keydown', stopKey)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
item.appendChild(row)
|
|
747
|
+
return item
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function renderStrategy(data) {
|
|
751
|
+
var group = fields.strategy
|
|
752
|
+
if (group === null || group === undefined) return
|
|
753
|
+
var current = data !== null && data !== undefined && typeof data.strategy === 'string' ? data.strategy : ''
|
|
754
|
+
var busy = pendingStrategy !== null || pendingSwitch !== null || data === null || data === undefined
|
|
755
|
+
group.setAttribute('data-busy', pendingStrategy !== null ? 'true' : 'false')
|
|
756
|
+
var chips = group.querySelectorAll('[data-strategy]')
|
|
757
|
+
for (var i = 0; i < chips.length; i += 1) {
|
|
758
|
+
var chip = chips[i]
|
|
759
|
+
chip.setAttribute('aria-pressed', chip.getAttribute('data-strategy') === current ? 'true' : 'false')
|
|
760
|
+
chip.disabled = busy
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function setBusy() {
|
|
765
|
+
var quota = quotaOf(shown)
|
|
766
|
+
var busy = pendingRefresh !== null || (quota !== null && quota.refreshing === true)
|
|
767
|
+
card.setAttribute('data-busy', busy ? 'true' : 'false')
|
|
768
|
+
if (fields.refresh !== null && fields.refresh !== undefined) fields.refresh.disabled = pendingRefresh !== null
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function render() {
|
|
772
|
+
var data = shown
|
|
773
|
+
var kind = kindOf(data)
|
|
774
|
+
card.setAttribute('data-state', kind)
|
|
775
|
+
|
|
776
|
+
put(fields.frontTitle, lineName())
|
|
777
|
+
var frontMetaParts = frontMeta(data)
|
|
778
|
+
put(fields.frontPlan, frontMetaParts.plan)
|
|
779
|
+
put(fields.frontUpdated, frontMetaParts.rest)
|
|
780
|
+
show(fields.frontMeta, frontMetaParts.plan !== '' || frontMetaParts.rest !== '')
|
|
781
|
+
|
|
782
|
+
var windows = kind === 'ready' ? windowsOf(data) : []
|
|
783
|
+
// 分组要 ≥2 个账号各自带着窗口;不够时 groups 是 null,正面就还是原来那一层扁平的线路窗口。
|
|
784
|
+
var groups = kind === 'ready' ? windowGroupsOf(data) : null
|
|
785
|
+
var now = Date.now()
|
|
786
|
+
var factOnly = kind === 'credits'
|
|
787
|
+
if (fields.windows !== null && fields.windows !== undefined) {
|
|
788
|
+
fields.windows.textContent = ''
|
|
789
|
+
if (groups !== null) {
|
|
790
|
+
var captions = captionPlanOf(groups)
|
|
791
|
+
for (var g = 0; g < groups.length; g += 1) fields.windows.appendChild(windowGroupNode(groups[g], data, captions[g], now))
|
|
792
|
+
} else {
|
|
793
|
+
for (var i = 0; i < windows.length; i += 1) fields.windows.appendChild(windowRow(windows[i], now))
|
|
794
|
+
}
|
|
795
|
+
// 没有百分比窗口但有绝对额度的线路:窗口区的那一格留给「可用余额 …」,正文不再是空白。
|
|
796
|
+
if (factOnly) fields.windows.appendChild(creditFactRow(data))
|
|
797
|
+
show(fields.windows, groups !== null || windows.length > 0 || factOnly)
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
var blank = kind === 'ready' || factOnly ? '' : frontBlankOf(data, kind)
|
|
801
|
+
put(fields.frontBlank, blank)
|
|
802
|
+
show(fields.frontBlank, blank !== '')
|
|
803
|
+
|
|
804
|
+
// 页脚的赠额对「只有余额」的线路是同一句话的复读:主事实已经写在正文里,页脚只留可用重置。
|
|
805
|
+
var credits = kind === 'ready' ? creditText(data) : ''
|
|
806
|
+
var resets = kind === 'ready' || factOnly ? resetsText(data) : ''
|
|
807
|
+
put(fields.credits, credits)
|
|
808
|
+
show(fields.credits, credits !== '')
|
|
809
|
+
put(fields.resets, resets)
|
|
810
|
+
show(fields.resets, resets !== '')
|
|
811
|
+
show(fields.foot, credits !== '' || resets !== '')
|
|
812
|
+
|
|
813
|
+
put(fields.frontError, refreshError)
|
|
814
|
+
show(fields.frontError, refreshError !== '')
|
|
815
|
+
|
|
816
|
+
var current = currentAccountOf(data)
|
|
817
|
+
put(fields.backKicker, lineName())
|
|
818
|
+
put(fields.backTitle, current !== '' ? current : t('noAccount', '还没有当前账号'))
|
|
819
|
+
var backMetaParts = backMeta(data)
|
|
820
|
+
put(fields.backCount, backMetaParts.count)
|
|
821
|
+
put(fields.backStrategy, backMetaParts.rest)
|
|
822
|
+
show(fields.backMeta, backMetaParts.count !== '' || backMetaParts.rest !== '')
|
|
823
|
+
renderStrategy(data)
|
|
824
|
+
|
|
825
|
+
var accounts = accountsOf(data)
|
|
826
|
+
if (fields.list !== null && fields.list !== undefined) {
|
|
827
|
+
fields.list.textContent = ''
|
|
828
|
+
// 区分符是整卡的事(要跟别的行比),所以先算一遍标题方案,再逐行画。
|
|
829
|
+
var plans = titlePlanOf(data)
|
|
830
|
+
for (var j = 0; j < accounts.length; j += 1) fields.list.appendChild(accountRow(accounts[j], data, now, plans[j]))
|
|
831
|
+
show(fields.list, accounts.length > 0)
|
|
832
|
+
}
|
|
833
|
+
var back = accounts.length === 0 ? backBlankOf(data) : { text: '', hint: '' }
|
|
834
|
+
put(fields.backBlankText, back.text)
|
|
835
|
+
put(fields.backBlankHint, back.hint)
|
|
836
|
+
show(fields.backBlankHint, back.hint !== '')
|
|
837
|
+
show(fields.backBlank, back.text !== '')
|
|
838
|
+
|
|
839
|
+
// 刷新按钮在两面上都点得到,所以它的失败也在两面上都写得出来,不会因为在背面就看不见。
|
|
840
|
+
var backError = accountError !== '' ? accountError : refreshError
|
|
841
|
+
put(fields.backError, backError)
|
|
842
|
+
show(fields.backError, backError !== '')
|
|
843
|
+
|
|
844
|
+
setBusy()
|
|
845
|
+
applyFace()
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/** 翻面状态:类名是唯一的事实来源,`face` 是它在上一次同步时的影子。 */
|
|
849
|
+
function applyFace() {
|
|
850
|
+
var back = face === 'back'
|
|
851
|
+
card.classList.toggle('is-flipped', back)
|
|
852
|
+
card.setAttribute('aria-pressed', back ? 'true' : 'false')
|
|
853
|
+
card.setAttribute('aria-label', back ? t('flipToFront', '翻回正面看额度') : t('flipToBack', '翻到背面看账号'))
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function persistFace() {
|
|
857
|
+
if (widget === undefined || widget === null || typeof widget.setState !== 'function') return
|
|
858
|
+
widget.setState({ face: face })
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/** 运行时的翻面动作先跑完,再读类名记下来;0 毫秒的等待就是「这一轮事件之后」。 */
|
|
862
|
+
function syncFace() {
|
|
863
|
+
var next = card.classList.contains('is-flipped') ? 'back' : 'front'
|
|
864
|
+
if (next === face) return
|
|
865
|
+
face = next
|
|
866
|
+
applyFace()
|
|
867
|
+
persistFace()
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function restoredFace() {
|
|
871
|
+
var state = widget !== undefined && widget !== null ? widget.state : null
|
|
872
|
+
return state !== null && state !== undefined && typeof state === 'object' && state.face === 'back' ? 'back' : 'front'
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function stop(event) {
|
|
876
|
+
if (event !== null && event !== undefined && typeof event.stopPropagation === 'function') event.stopPropagation()
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
function stopKey(event) {
|
|
880
|
+
if (event.key === 'Enter' || event.key === ' ') stop(event)
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
/**
|
|
884
|
+
* 轻点翻面交给运行时;鼠标点一下切换「记住的那一面」。
|
|
885
|
+
*
|
|
886
|
+
* 桌面没有悬停之外的翻面动作(运行时对鼠标直接返回),少了这一下,被记住的背面在纯鼠标下就回不到
|
|
887
|
+
* 正面。两种路径最后都在同一个 0 毫秒之后读类名,所以不会与运行时的切换互相抵消。
|
|
888
|
+
*/
|
|
889
|
+
function toggleFace(event) {
|
|
890
|
+
if (event.pointerType === 'mouse') card.classList.toggle('is-flipped')
|
|
891
|
+
window.setTimeout(syncFace, 0)
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function flipByKey(event) {
|
|
895
|
+
if (event.key !== 'Enter' && event.key !== ' ') return
|
|
896
|
+
window.setTimeout(syncFace, 0)
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function reasonOf(error, fallback) {
|
|
900
|
+
if (error !== null && error !== undefined && typeof error.message === 'string' && error.message !== '') return error.message
|
|
901
|
+
return fallback
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
function canCall() {
|
|
905
|
+
return widget !== undefined && widget !== null && typeof widget.call === 'function'
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
/** 三个能力都按线路调用:线路 id 一直在参数里,切换与策略再多带一个字段。 */
|
|
909
|
+
function callLine(capability, extra) {
|
|
910
|
+
var args = { line: lineId() }
|
|
911
|
+
if (extra !== null && extra !== undefined) {
|
|
912
|
+
for (var key in extra) {
|
|
913
|
+
if (Object.prototype.hasOwnProperty.call(extra, key)) args[key] = extra[key]
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return widget.call(capability, args)
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/** 能力返回同一份形状时用它重画;返回别的东西时留着现有数据,等下一轮数据源刷新。 */
|
|
920
|
+
function looksLikeData(value) {
|
|
921
|
+
if (value === null || value === undefined || typeof value !== 'object' || Array.isArray(value)) return false
|
|
922
|
+
return 'quota' in value || 'accounts' in value || 'signedIn' in value || 'strategy' in value
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/** 一次刷新在途时不发第二次;成功与失败都先放开按钮,再画结果。 */
|
|
926
|
+
function refresh() {
|
|
927
|
+
if (pendingRefresh !== null) return
|
|
928
|
+
if (!canCall()) return
|
|
929
|
+
refreshError = ''
|
|
930
|
+
pendingRefresh = callLine(REFRESH, null)
|
|
931
|
+
render()
|
|
932
|
+
pendingRefresh.then(function (next) {
|
|
933
|
+
pendingRefresh = null
|
|
934
|
+
if (looksLikeData(next)) shown = next
|
|
935
|
+
refreshError = ''
|
|
936
|
+
render()
|
|
937
|
+
}, function (error) {
|
|
938
|
+
pendingRefresh = null
|
|
939
|
+
refreshError = reasonOf(error, t('failed', '读不到额度'))
|
|
940
|
+
render()
|
|
941
|
+
})
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function switchTo(accountId) {
|
|
945
|
+
if (pendingSwitch !== null || pendingStrategy !== null) return
|
|
946
|
+
if (!canCall()) return
|
|
947
|
+
accountError = ''
|
|
948
|
+
var call = callLine(SWITCH, { accountId: accountId })
|
|
949
|
+
pendingSwitch = accountId
|
|
950
|
+
render()
|
|
951
|
+
call.then(function (next) {
|
|
952
|
+
pendingSwitch = null
|
|
953
|
+
if (looksLikeData(next)) shown = next
|
|
954
|
+
accountError = ''
|
|
955
|
+
render()
|
|
956
|
+
}, function (error) {
|
|
957
|
+
pendingSwitch = null
|
|
958
|
+
accountError = reasonOf(error, t('accountsFailed', '读不到账号'))
|
|
959
|
+
render()
|
|
960
|
+
})
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function chooseStrategy(strategy) {
|
|
964
|
+
if (pendingStrategy !== null || pendingSwitch !== null) return
|
|
965
|
+
if (!canCall()) return
|
|
966
|
+
if (shown !== null && shown !== undefined && shown.strategy === strategy) return
|
|
967
|
+
accountError = ''
|
|
968
|
+
pendingStrategy = strategy
|
|
969
|
+
render()
|
|
970
|
+
callLine(STRATEGY, { strategy: strategy }).then(function (next) {
|
|
971
|
+
pendingStrategy = null
|
|
972
|
+
if (looksLikeData(next)) shown = next
|
|
973
|
+
accountError = ''
|
|
974
|
+
render()
|
|
975
|
+
}, function (error) {
|
|
976
|
+
pendingStrategy = null
|
|
977
|
+
accountError = reasonOf(error, t('accountsFailed', '读不到账号'))
|
|
978
|
+
render()
|
|
979
|
+
})
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
function applyData(next) {
|
|
983
|
+
shown = next !== null && next !== undefined && typeof next === 'object' ? next : null
|
|
984
|
+
render()
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function preview() {
|
|
988
|
+
if (SAMPLE === null) {
|
|
989
|
+
var now = Date.now()
|
|
990
|
+
SAMPLE = {
|
|
991
|
+
line: lineId() === '' ? 'codex' : lineId(),
|
|
992
|
+
label: 'ChatGPT',
|
|
993
|
+
signedIn: true,
|
|
994
|
+
installed: true,
|
|
995
|
+
planLabel: 'plus',
|
|
996
|
+
accountLabel: 'a***@gmail.com',
|
|
997
|
+
accounts: [
|
|
998
|
+
{
|
|
999
|
+
id: 'acc_1',
|
|
1000
|
+
label: '主账号',
|
|
1001
|
+
email: 'a@example.com',
|
|
1002
|
+
primary: true,
|
|
1003
|
+
plan: 'plus',
|
|
1004
|
+
cooldownUntil: null,
|
|
1005
|
+
authStatus: 'ok',
|
|
1006
|
+
usedPercent: 34.2,
|
|
1007
|
+
resetsAt: now + 7980000
|
|
1008
|
+
},
|
|
1009
|
+
{
|
|
1010
|
+
id: 'acc_2',
|
|
1011
|
+
label: '备账号',
|
|
1012
|
+
email: 'b@example.com',
|
|
1013
|
+
primary: false,
|
|
1014
|
+
plan: 'plus',
|
|
1015
|
+
cooldownUntil: now + 3600000,
|
|
1016
|
+
authStatus: 'ok',
|
|
1017
|
+
usedPercent: 63,
|
|
1018
|
+
resetsAt: now + 432000000
|
|
1019
|
+
}
|
|
1020
|
+
],
|
|
1021
|
+
activeAccountId: 'acc_1',
|
|
1022
|
+
strategy: 'round-robin',
|
|
1023
|
+
quota: {
|
|
1024
|
+
windows: [
|
|
1025
|
+
{ label: '5 小时', usedPercent: 34.2, windowMinutes: 300, resetsAt: now + 7980000 },
|
|
1026
|
+
{ label: null, usedPercent: 61, windowMinutes: 10080, resetsAt: now + 432000000 }
|
|
1027
|
+
],
|
|
1028
|
+
credits: { unlimited: false, balance: '10.00' },
|
|
1029
|
+
resetCredits: { available: 1, expiresAt: now + 1728000000 },
|
|
1030
|
+
fetchedAt: now,
|
|
1031
|
+
refreshing: false,
|
|
1032
|
+
error: null
|
|
1033
|
+
},
|
|
1034
|
+
error: null
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
shown = SAMPLE
|
|
1038
|
+
render()
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function consider(size) {
|
|
1042
|
+
var current = size !== null && size !== undefined ? size : widget !== undefined && widget !== null ? widget.size : null
|
|
1043
|
+
if (current !== null && current !== undefined && current.placement === 'preview') preview()
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
function restoreFromState(state) {
|
|
1047
|
+
var next = state !== null && state !== undefined && typeof state === 'object' && state.face === 'back' ? 'back' : 'front'
|
|
1048
|
+
if (next === face) return
|
|
1049
|
+
face = next
|
|
1050
|
+
applyFace()
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// 静态文案(策略副标题)从 manifest 的 text 里取;没有宿主时留着标记里写的那份。
|
|
1054
|
+
var labels = card.querySelectorAll('[data-label]')
|
|
1055
|
+
for (var l = 0; l < labels.length; l += 1) {
|
|
1056
|
+
var key = labels[l].getAttribute('data-label')
|
|
1057
|
+
if (key !== null) labels[l].textContent = t(key, labels[l].textContent)
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
if (fields.refresh !== null && fields.refresh !== undefined) {
|
|
1061
|
+
fields.refresh.setAttribute('title', t('refresh', '刷新额度'))
|
|
1062
|
+
fields.refresh.setAttribute('aria-label', t('refresh', '刷新额度'))
|
|
1063
|
+
fields.refresh.addEventListener('click', function (event) {
|
|
1064
|
+
stop(event)
|
|
1065
|
+
refresh()
|
|
1066
|
+
})
|
|
1067
|
+
// 空格落在按钮上时浏览器自己会按一次,别让它顺带冒泡成翻面。
|
|
1068
|
+
fields.refresh.addEventListener('keydown', stopKey)
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
if (fields.strategy !== null && fields.strategy !== undefined) {
|
|
1072
|
+
fields.strategy.setAttribute('aria-label', t('rotation', '轮换策略'))
|
|
1073
|
+
var chips = fields.strategy.querySelectorAll('[data-strategy]')
|
|
1074
|
+
for (var c = 0; c < chips.length; c += 1) {
|
|
1075
|
+
var chip = chips[c]
|
|
1076
|
+
chip.addEventListener('click', function (event) {
|
|
1077
|
+
stop(event)
|
|
1078
|
+
var id = this.getAttribute('data-strategy')
|
|
1079
|
+
if (id !== null) chooseStrategy(id)
|
|
1080
|
+
})
|
|
1081
|
+
chip.addEventListener('keydown', stopKey)
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
card.addEventListener('click', toggleFace)
|
|
1086
|
+
card.addEventListener('keydown', flipByKey)
|
|
1087
|
+
|
|
1088
|
+
face = restoredFace()
|
|
1089
|
+
applyFace()
|
|
1090
|
+
|
|
1091
|
+
if (widget !== undefined && widget !== null) {
|
|
1092
|
+
if (typeof widget.onData === 'function') widget.onData(applyData)
|
|
1093
|
+
if (typeof widget.onSize === 'function') widget.onSize(consider)
|
|
1094
|
+
if (typeof widget.onState === 'function') widget.onState(restoreFromState)
|
|
1095
|
+
if (typeof widget.onProps === 'function') widget.onProps(function () { render() })
|
|
1096
|
+
if (widget.data !== undefined && widget.data !== null) applyData(widget.data)
|
|
1097
|
+
else render()
|
|
1098
|
+
consider(widget.size)
|
|
1099
|
+
} else {
|
|
1100
|
+
preview()
|
|
1101
|
+
}
|
|
1102
|
+
})()
|