clickgo 3.0.0-dev

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.
Files changed (145) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +75 -0
  3. package/dist/app/demo/config.json +106 -0
  4. package/dist/app/demo/form/control/block/block.css +1 -0
  5. package/dist/app/demo/form/control/block/block.scss +17 -0
  6. package/dist/app/demo/form/control/block/block.xml +7 -0
  7. package/dist/app/demo/form/control/button/button.css +1 -0
  8. package/dist/app/demo/form/control/button/button.js +27 -0
  9. package/dist/app/demo/form/control/button/button.scss +18 -0
  10. package/dist/app/demo/form/control/button/button.xml +126 -0
  11. package/dist/app/demo/form/control/check/check.js +12 -0
  12. package/dist/app/demo/form/control/check/check.xml +13 -0
  13. package/dist/app/demo/form/control/dialog/dialog.js +8 -0
  14. package/dist/app/demo/form/control/dialog/dialog.xml +26 -0
  15. package/dist/app/demo/form/control/file/file.js +23 -0
  16. package/dist/app/demo/form/control/file/file.xml +25 -0
  17. package/dist/app/demo/form/control/form/form.css +1 -0
  18. package/dist/app/demo/form/control/form/form.js +38 -0
  19. package/dist/app/demo/form/control/form/form.scss +9 -0
  20. package/dist/app/demo/form/control/form/form.xml +28 -0
  21. package/dist/app/demo/form/control/greatview/greatview.css +1 -0
  22. package/dist/app/demo/form/control/greatview/greatview.js +92 -0
  23. package/dist/app/demo/form/control/greatview/greatview.scss +22 -0
  24. package/dist/app/demo/form/control/greatview/greatview.xml +89 -0
  25. package/dist/app/demo/form/control/img/img.xml +16 -0
  26. package/dist/app/demo/form/control/label/label.xml +11 -0
  27. package/dist/app/demo/form/control/list/list.css +1 -0
  28. package/dist/app/demo/form/control/list/list.js +194 -0
  29. package/dist/app/demo/form/control/list/list.scss +7 -0
  30. package/dist/app/demo/form/control/list/list.xml +91 -0
  31. package/dist/app/demo/form/control/loading/loading.xml +8 -0
  32. package/dist/app/demo/form/control/marquee/marquee.js +30 -0
  33. package/dist/app/demo/form/control/marquee/marquee.xml +36 -0
  34. package/dist/app/demo/form/control/menu/menu.js +8 -0
  35. package/dist/app/demo/form/control/menu/menu.xml +122 -0
  36. package/dist/app/demo/form/control/monaco/monaco.js +113 -0
  37. package/dist/app/demo/form/control/monaco/monaco.xml +27 -0
  38. package/dist/app/demo/form/control/overflow/overflow.css +1 -0
  39. package/dist/app/demo/form/control/overflow/overflow.js +70 -0
  40. package/dist/app/demo/form/control/overflow/overflow.scss +18 -0
  41. package/dist/app/demo/form/control/overflow/overflow.xml +98 -0
  42. package/dist/app/demo/form/control/property/property.js +129 -0
  43. package/dist/app/demo/form/control/property/property.xml +6 -0
  44. package/dist/app/demo/form/control/radio/radio.js +7 -0
  45. package/dist/app/demo/form/control/radio/radio.xml +12 -0
  46. package/dist/app/demo/form/control/scroll/scroll.js +14 -0
  47. package/dist/app/demo/form/control/scroll/scroll.xml +35 -0
  48. package/dist/app/demo/form/control/select/select.js +91 -0
  49. package/dist/app/demo/form/control/select/select.xml +74 -0
  50. package/dist/app/demo/form/control/tab/tab.js +75 -0
  51. package/dist/app/demo/form/control/tab/tab.xml +22 -0
  52. package/dist/app/demo/form/control/text/text.js +53 -0
  53. package/dist/app/demo/form/control/text/text.xml +37 -0
  54. package/dist/app/demo/form/control/view/view.css +1 -0
  55. package/dist/app/demo/form/control/view/view.js +73 -0
  56. package/dist/app/demo/form/control/view/view.scss +18 -0
  57. package/dist/app/demo/form/control/view/view.xml +94 -0
  58. package/dist/app/demo/form/event/form/form.css +1 -0
  59. package/dist/app/demo/form/event/form/form.js +129 -0
  60. package/dist/app/demo/form/event/form/form.scss +24 -0
  61. package/dist/app/demo/form/event/form/form.xml +16 -0
  62. package/dist/app/demo/form/event/screen/screen.js +51 -0
  63. package/dist/app/demo/form/event/screen/screen.xml +9 -0
  64. package/dist/app/demo/form/event/task/task.js +78 -0
  65. package/dist/app/demo/form/event/task/task.xml +20 -0
  66. package/dist/app/demo/form/main.css +1 -0
  67. package/dist/app/demo/form/main.js +25 -0
  68. package/dist/app/demo/form/main.scss +9 -0
  69. package/dist/app/demo/form/main.xml +49 -0
  70. package/dist/app/demo/form/method/core/core.js +25 -0
  71. package/dist/app/demo/form/method/core/core.xml +7 -0
  72. package/dist/app/demo/form/method/dom/dom.css +1 -0
  73. package/dist/app/demo/form/method/dom/dom.js +163 -0
  74. package/dist/app/demo/form/method/dom/dom.scss +10 -0
  75. package/dist/app/demo/form/method/dom/dom.xml +55 -0
  76. package/dist/app/demo/form/method/form/form.css +1 -0
  77. package/dist/app/demo/form/method/form/form.js +217 -0
  78. package/dist/app/demo/form/method/form/form.scss +3 -0
  79. package/dist/app/demo/form/method/form/form.xml +56 -0
  80. package/dist/app/demo/form/method/form/test.xml +5 -0
  81. package/dist/app/demo/form/method/fs/fs.js +88 -0
  82. package/dist/app/demo/form/method/fs/fs.xml +8 -0
  83. package/dist/app/demo/form/method/fs/text.js +15 -0
  84. package/dist/app/demo/form/method/fs/text.xml +3 -0
  85. package/dist/app/demo/form/method/task/locale1.json +3 -0
  86. package/dist/app/demo/form/method/task/locale2.json +3 -0
  87. package/dist/app/demo/form/method/task/task.js +153 -0
  88. package/dist/app/demo/form/method/task/task.xml +57 -0
  89. package/dist/app/demo/form/method/theme/theme.js +74 -0
  90. package/dist/app/demo/form/method/theme/theme.xml +9 -0
  91. package/dist/app/demo/form/method/tool/tool.js +64 -0
  92. package/dist/app/demo/form/method/tool/tool.xml +26 -0
  93. package/dist/app/demo/form/method/zip/zip.js +99 -0
  94. package/dist/app/demo/form/method/zip/zip.xml +12 -0
  95. package/dist/app/demo/global.css +3 -0
  96. package/dist/app/demo/res/icon.svg +1 -0
  97. package/dist/app/demo/res/img.jpg +0 -0
  98. package/dist/app/demo/res/r-1.svg +1 -0
  99. package/dist/app/demo/res/r-2.svg +1 -0
  100. package/dist/app/demo/res/sql.svg +1 -0
  101. package/dist/app/demo/res/txt.svg +1 -0
  102. package/dist/app/demo/res/zip.svg +1 -0
  103. package/dist/app/task/config.json +29 -0
  104. package/dist/app/task/form/bar/bar.js +299 -0
  105. package/dist/app/task/form/bar/bar.xml +47 -0
  106. package/dist/app/task/form/desktop/desktop.xml +1 -0
  107. package/dist/app/task/locale/en.json +11 -0
  108. package/dist/app/task/locale/ja.json +11 -0
  109. package/dist/app/task/locale/sc.json +11 -0
  110. package/dist/app/task/locale/tc.json +11 -0
  111. package/dist/clickgo.js +41 -0
  112. package/dist/clickgo.ts +51 -0
  113. package/dist/control/common.cgc +0 -0
  114. package/dist/control/form.cgc +0 -0
  115. package/dist/control/monaco.cgc +0 -0
  116. package/dist/control/property.cgc +0 -0
  117. package/dist/control/task.cgc +0 -0
  118. package/dist/global.css +1 -0
  119. package/dist/icon.png +0 -0
  120. package/dist/index.js +88 -0
  121. package/dist/index.ts +92 -0
  122. package/dist/lib/control.js +365 -0
  123. package/dist/lib/control.ts +428 -0
  124. package/dist/lib/core.js +668 -0
  125. package/dist/lib/core.ts +732 -0
  126. package/dist/lib/dom.js +1471 -0
  127. package/dist/lib/dom.ts +1785 -0
  128. package/dist/lib/form.js +2101 -0
  129. package/dist/lib/form.ts +2496 -0
  130. package/dist/lib/fs.js +849 -0
  131. package/dist/lib/fs.ts +995 -0
  132. package/dist/lib/native.js +138 -0
  133. package/dist/lib/native.ts +219 -0
  134. package/dist/lib/task.js +686 -0
  135. package/dist/lib/task.ts +842 -0
  136. package/dist/lib/theme.js +159 -0
  137. package/dist/lib/theme.ts +196 -0
  138. package/dist/lib/tool.js +501 -0
  139. package/dist/lib/tool.ts +620 -0
  140. package/dist/lib/zip.js +352 -0
  141. package/dist/lib/zip.ts +434 -0
  142. package/dist/theme/familiar.cgt +0 -0
  143. package/package.json +27 -0
  144. package/types/dev.d.ts +30 -0
  145. package/types/index.d.ts +673 -0
@@ -0,0 +1,842 @@
1
+ /**
2
+ * Copyright 2022 Han Guoshuai <zohegs@gmail.com>
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+ import * as types from '../../types';
17
+ import * as core from './core';
18
+ import * as control from './control';
19
+ import * as dom from './dom';
20
+ import * as tool from './tool';
21
+ import * as form from './form';
22
+ import * as theme from './theme';
23
+ import * as fs from './fs';
24
+ import * as native from './native';
25
+
26
+ /** --- 当前运行的程序 --- */
27
+ export const list: Record<number, types.ITask> = {};
28
+ /** --- 最后一个 task id --- */
29
+ export let lastId: number = 0;
30
+
31
+ /** --- task lib 用到的语言包 --- */
32
+ const localeData: Record<string, {
33
+ 'loading': string;
34
+ }> = {
35
+ 'en': {
36
+ 'loading': 'Loading...',
37
+ },
38
+ 'sc': {
39
+ 'loading': '加载中……'
40
+ },
41
+ 'tc': {
42
+ 'loading': '載入中……'
43
+ },
44
+ 'ja': {
45
+ 'loading': '読み込み中...'
46
+ }
47
+ };
48
+
49
+ // --- 创建 frame 监听 ---
50
+ let frameTimer: number = 0;
51
+ const frameMaps: Record<string, number> = {};
52
+
53
+ /**
54
+ * --- 创建 frame 监听 ---
55
+ * @param fun 监听回调
56
+ * @param opt 选项, scope:有效范围,count:执行次数,默认无限次,taskId:APP模式下无效,formId:APP模式下无效
57
+ */
58
+ export function onFrame(fun: () => void | Promise<void>, opt: {
59
+ 'scope'?: 'form' | 'task';
60
+ 'count'?: number;
61
+ 'taskId'?: number;
62
+ 'formId'?: number;
63
+ } = {}): number {
64
+ const taskId = opt.taskId;
65
+ const formId = opt.formId;
66
+ if (!taskId || !formId) {
67
+ return 0;
68
+ }
69
+ const ft = ++frameTimer;
70
+ /** --- 作用域 --- */
71
+ const scope = opt.scope ?? 'form';
72
+ /** --- 执行几次,0 代表无限次 --- */
73
+ const count = opt.count ?? 0;
74
+ /** --- 当前已经执行的次数 --- */
75
+ let c: number = 0;
76
+ let timer: number;
77
+ const timerHandler = async (): Promise<void> => {
78
+ ++c;
79
+ if (list[taskId].forms[formId] === undefined) {
80
+ // --- form 已经没了 ---
81
+ if (scope === 'form') {
82
+ delete list[taskId].timers['1x' + ft.toString()];
83
+ delete frameMaps[ft];
84
+ return;
85
+ }
86
+ }
87
+ await fun();
88
+ if (list[taskId].timers['1x' + ft.toString()] == undefined) {
89
+ return;
90
+ }
91
+ if (count > 1) {
92
+ if (c === count) {
93
+ // --- 终止循环 ---
94
+ delete list[taskId].timers['1x' + ft.toString()];
95
+ delete frameMaps[ft];
96
+ return;
97
+ }
98
+ else {
99
+ // --- 接着循环 ---
100
+ timer = requestAnimationFrame(function() {
101
+ timerHandler().catch(function(e) {
102
+ console.log(e);
103
+ });
104
+ });
105
+ frameMaps[ft] = timer;
106
+ }
107
+ }
108
+ else if (count === 1) {
109
+ // --- 不循环 ---
110
+ delete list[taskId].timers['1x' + ft.toString()];
111
+ delete frameMaps[ft];
112
+ }
113
+ else {
114
+ // --- 无限循环 ---
115
+ timer = requestAnimationFrame(function() {
116
+ timerHandler().catch(function(e) {
117
+ console.log(e);
118
+ });
119
+ });
120
+ frameMaps[ft] = timer;
121
+ }
122
+ };
123
+ /** --- timer 对象 number --- */
124
+ timer = requestAnimationFrame(function() {
125
+ timerHandler().catch(function(e) {
126
+ console.log(e);
127
+ });
128
+ });
129
+ frameMaps[ft] = timer;
130
+ list[taskId].timers['1x' + ft.toString()] = formId;
131
+ return ft;
132
+ }
133
+
134
+ /**
135
+ * --- 移除 frame 监听 ---
136
+ * @param ft 监听 ID
137
+ * @param opt 选项,taskId:APP模式下无效
138
+ */
139
+ export function offFrame(ft: number, opt: {
140
+ 'taskId'?: number;
141
+ } = {}): void {
142
+ const taskId = opt.taskId;
143
+ if (!taskId) {
144
+ return;
145
+ }
146
+ const formId = list[taskId].timers['1x' + ft.toString()];
147
+ if (formId === undefined) {
148
+ return;
149
+ }
150
+ cancelAnimationFrame(frameMaps[ft]);
151
+ delete list[taskId].timers['1x' + ft.toString()];
152
+ delete frameMaps[ft];
153
+ }
154
+
155
+ /**
156
+ * --- 获取任务当前信息 ---
157
+ * @param tid 任务 id
158
+ */
159
+ export function get(tid: number): types.ITaskInfo | null {
160
+ if (list[tid] === undefined) {
161
+ return null;
162
+ }
163
+ return {
164
+ 'name': list[tid].app.config.name,
165
+ 'locale': list[tid].locale.lang,
166
+ 'customTheme': list[tid].customTheme,
167
+ 'formCount': Object.keys(list[tid].forms).length,
168
+ 'icon': list[tid].icon,
169
+ 'path': list[tid].path
170
+ };
171
+ }
172
+
173
+ /**
174
+ * --- 获取 task list 的简略情况 ---
175
+ */
176
+ export function getList(): Record<string, types.ITaskInfo> {
177
+ const rtn: Record<string, types.ITaskInfo> = {};
178
+ for (const tid in list) {
179
+ const item = list[tid];
180
+ rtn[tid] = {
181
+ 'name': item.app.config.name,
182
+ 'locale': item.locale.lang,
183
+ 'customTheme': item.customTheme,
184
+ 'formCount': Object.keys(item.forms).length,
185
+ 'icon': item.icon,
186
+ 'path': item.path
187
+ };
188
+ }
189
+ return rtn;
190
+ }
191
+
192
+ /**
193
+ * --- 运行一个应用 ---
194
+ * @param url app 路径(以 / 为结尾的路径或以 .cga 结尾的文件)
195
+ * @param opt 选项,icon:图标,progress:显示进度条,taskId:所属任务,App 模式下无效
196
+ */
197
+ export async function run(url: string, opt: types.ITaskRunOptions = {}): Promise<number> {
198
+ /** --- 是否是在任务当中启动的任务 --- */
199
+ let ntask: types.ITask | null = null;
200
+ if (opt.taskId) {
201
+ ntask = list[opt.taskId];
202
+ }
203
+ // --- 检测 url 是否合法 ---
204
+ if (!url.endsWith('/') && !url.endsWith('.cga')) {
205
+ return 0;
206
+ }
207
+ /** --- 要显示的应用图标 --- */
208
+ let icon = __dirname + '/../icon.png';
209
+ if (opt.icon) {
210
+ icon = opt.icon;
211
+ }
212
+ if (opt.progress === undefined) {
213
+ opt.progress = true;
214
+ }
215
+ const notifyId: number | undefined = opt.progress ? form.notify({
216
+ 'title': localeData[core.config.locale]?.loading ?? localeData['en'].loading,
217
+ 'content': url,
218
+ 'icon': icon,
219
+ 'timeout': 0,
220
+ 'progress': true
221
+ }) : undefined;
222
+ const app: types.IApp | null = await core.fetchApp(url, {
223
+ 'notifyId': notifyId,
224
+ 'current': ntask ? ntask.path : undefined
225
+ });
226
+ if (notifyId) {
227
+ setTimeout(function(): void {
228
+ form.hideNotify(notifyId);
229
+ }, 2000);
230
+ }
231
+ if (!app) {
232
+ return -1;
233
+ }
234
+ // --- app 的内置文件以及运行时文件 ---
235
+ const files: Record<string, Blob | string> = {};
236
+ for (const fpath in app.files) {
237
+ files[fpath] = app.files[fpath];
238
+ }
239
+ // --- 创建任务对象 ITask ---
240
+ const taskId = ++lastId;
241
+ list[taskId] = {
242
+ 'id': taskId,
243
+ 'app': app,
244
+ 'customTheme': false,
245
+ 'locale': Vue.reactive({
246
+ 'lang': '',
247
+ 'data': {}
248
+ }),
249
+ 'icon': app.icon ?? icon,
250
+ 'path': url,
251
+ 'files': files,
252
+
253
+ 'permissions': {},
254
+ 'forms': {},
255
+ 'objectURLs': [],
256
+ 'controls': {
257
+ 'loaded': {},
258
+ 'layout': {},
259
+ 'prep': {}
260
+ },
261
+ 'timers': {}
262
+ };
263
+ const task: types.ITask = list[taskId];
264
+ // --- control ---
265
+ for (let path of app.config.controls) {
266
+ path += '.cgc';
267
+ path = tool.urlResolve('/', path);
268
+ const file = await fs.getContent(path, {
269
+ 'files': task.files
270
+ });
271
+ if (file && typeof file !== 'string') {
272
+ const c = await control.read(file);
273
+ if (c) {
274
+ task.controls.loaded[path] = c;
275
+ }
276
+ else {
277
+ form.notify({
278
+ 'title': 'Control failed to load',
279
+ 'content': path
280
+ });
281
+ }
282
+ }
283
+ }
284
+ // --- theme ---
285
+ if (app.config.themes) {
286
+ for (let path of app.config.themes) {
287
+ path += '.cgt';
288
+ path = tool.urlResolve('/', path);
289
+ const file = await fs.getContent(path, {
290
+ 'files': task.files
291
+ });
292
+ if (file && typeof file !== 'string') {
293
+ const th = await theme.read(file);
294
+ if (th) {
295
+ await theme.load(th, taskId);
296
+ }
297
+ }
298
+ }
299
+ }
300
+ // --- locale ---
301
+ if (app.config.locales) {
302
+ for (let path in app.config.locales) {
303
+ const locale = app.config.locales[path];
304
+ if (!path.endsWith('.json')) {
305
+ path += '.json';
306
+ }
307
+ const lcontent = await fs.getContent(path, {
308
+ 'encoding': 'utf8',
309
+ 'files': task.files,
310
+ 'current': task.path
311
+ });
312
+ if (!lcontent) {
313
+ continue;
314
+ }
315
+ try {
316
+ const data = JSON.parse(lcontent);
317
+ loadLocaleData(locale, data, '', task.id);
318
+ }
319
+ catch {
320
+ // --- 无所谓 ---
321
+ }
322
+ }
323
+ }
324
+ // --- 触发 taskStarted 事件 ---
325
+ core.trigger('taskStarted', task.id);
326
+ // --- 创建 Task 总 style ---
327
+ dom.createToStyleList(task.id);
328
+ // --- 创建 form ---
329
+ const f = await form.create({
330
+ 'taskId': task.id,
331
+ 'file': app.config.main
332
+ });
333
+ if (typeof f === 'number') {
334
+ // --- 结束任务 ---
335
+ delete list[task.id];
336
+ dom.removeFromStyleList(task.id);
337
+ core.trigger('taskEnded', task.id);
338
+ return f - 100;
339
+ }
340
+ // --- 设置 global style(如果 form 创建失败,就不设置 global style 了) ---
341
+ if (app.config.style && app.files[app.config.style + '.css']) {
342
+ const style = app.files[app.config.style + '.css'] as string;
343
+ const r = tool.stylePrepend(style, 'cg-task' + task.id.toString() + '_');
344
+ dom.pushStyle(task.id, await tool.styleUrl2DataUrl(app.config.style, r.style, app.files));
345
+ }
346
+ // --- 是否要加载独立的 theme ---
347
+ if (app.config.themes && app.config.themes.length > 0) {
348
+ task.customTheme = true;
349
+ for (const path of app.config.themes) {
350
+ const blob = await fs.getContent(path, {
351
+ 'files': task.files,
352
+ 'current': task.path
353
+ });
354
+ if (!(blob instanceof Blob)) {
355
+ continue;
356
+ }
357
+ const th = await theme.read(blob);
358
+ if (!th) {
359
+ continue;
360
+ }
361
+ await theme.load(th, task.id);
362
+ }
363
+ }
364
+ else {
365
+ // --- 检测是否加载系统 theme ---
366
+ if (theme.global) {
367
+ await theme.load(undefined, task.id);
368
+ }
369
+ }
370
+ return task.id;
371
+ }
372
+
373
+ /**
374
+ * --- 完全结束任务 ---
375
+ * @param taskId 任务 id
376
+ */
377
+ export function end(taskId: number): boolean {
378
+ const task = list[taskId];
379
+ if (!task) {
380
+ return true;
381
+ }
382
+ // --- 获取最大的 z index 窗体,并让他获取焦点 ---
383
+ const fid = form.getMaxZIndexID({
384
+ 'taskIds': [task.id]
385
+ });
386
+ if (fid) {
387
+ form.changeFocus(fid);
388
+ }
389
+ else {
390
+ form.changeFocus();
391
+ }
392
+ // --- 移除窗体 list ---
393
+ for (const fid in task.forms) {
394
+ const f = task.forms[fid];
395
+ core.trigger('formRemoved', taskId, f.id, f.vroot.$refs.form.title, f.vroot.$refs.form.iconData);
396
+ f.vapp.unmount();
397
+ f.vapp._container.remove();
398
+ }
399
+ // --- 移除 style ---
400
+ dom.removeFromStyleList(taskId);
401
+ // --- 移除本 task 创建的所有 object url ---
402
+ for (const url of task.objectURLs) {
403
+ tool.revokeObjectURL(url, task.id);
404
+ }
405
+ // --- 移除所有 timer ---
406
+ for (const timer in list[taskId].timers) {
407
+ if (timer.startsWith('1x')) {
408
+ const ft = timer.slice(2);
409
+ cancelAnimationFrame(frameMaps[ft]);
410
+ delete frameMaps[ft];
411
+ }
412
+ else {
413
+ clearTimeout(parseFloat(timer));
414
+ }
415
+ }
416
+ // --- 移除各类监听 ---
417
+ native.clearListener(taskId);
418
+ dom.clearWatchSize(taskId);
419
+ // --- 移除 task ---
420
+ delete list[taskId];
421
+ // --- 触发 taskEnded 事件 ---
422
+ core.trigger('taskEnded', taskId);
423
+ // --- 移除 task bar ---
424
+ clearSystem(taskId);
425
+ return true;
426
+ }
427
+
428
+ /**
429
+ * --- 加载 locale data 对象到 task ---
430
+ * @param lang 语言名,如 sc
431
+ * @param data 数据
432
+ * @param pre 前置
433
+ * @param taskId 任务ID,App 模式下无效
434
+ */
435
+ export function loadLocaleData(lang: string, data: Record<string, any>, pre: string = '', taskId?: number): void {
436
+ if (!taskId) {
437
+ return;
438
+ }
439
+ if (!list[taskId].locale.data[lang]) {
440
+ list[taskId].locale.data[lang] = {};
441
+ }
442
+ for (const k in data) {
443
+ const v = data[k];
444
+ if (typeof v === 'object') {
445
+ loadLocaleData(lang, v, pre + k + '.', taskId);
446
+ }
447
+ else {
448
+ list[taskId].locale.data[lang][pre + k] = v;
449
+ }
450
+ }
451
+ }
452
+
453
+ /**
454
+ * --- 加载 locale 文件 json ---
455
+ * @param lang 语言名,如 sc
456
+ * @param path 地址
457
+ * @param taskId 所属的 taskId,App 模式下无效
458
+ * @param formId 所属的 formId,App 模式下无效
459
+ */
460
+ export async function loadLocale(lang: string, path: string, taskId?: number, formId?: number): Promise<boolean> {
461
+ if (!taskId) {
462
+ return false;
463
+ }
464
+ const task = list[taskId];
465
+ if (!task) {
466
+ return false;
467
+ }
468
+ let form: types.IForm | null = null;
469
+ if (formId) {
470
+ if (!task.forms[formId]) {
471
+ return false;
472
+ }
473
+ form = task.forms[formId];
474
+ }
475
+ /** --- 当前父 form 的路径(以 / 结尾)或 /(没有基路径的话) --- */
476
+ const base: string = form ? form.vroot.cgPath : '/';
477
+ path = tool.urlResolve(base, path) + '.json';
478
+ /** --- 获取的语言文件 --- */
479
+ const fcontent = await fs.getContent(path, {
480
+ 'encoding': 'utf8',
481
+ 'files': task.files,
482
+ 'current': task.path
483
+ });
484
+ if (!fcontent) {
485
+ return false;
486
+ }
487
+ try {
488
+ const data = JSON.parse(fcontent);
489
+ loadLocaleData(lang, data, '', task.id);
490
+ return true;
491
+ }
492
+ catch {
493
+ return false;
494
+ }
495
+ }
496
+
497
+ /**
498
+ * --- 清除任务的所有加载的语言包 ---
499
+ * @param taskId 所属的 taskId,App 模式下无效
500
+ */
501
+ export function clearLocale(taskId?: number): void {
502
+ if (!taskId) {
503
+ return;
504
+ }
505
+ const task = list[taskId];
506
+ if (!task) {
507
+ return;
508
+ }
509
+ task.locale.data = {};
510
+ }
511
+
512
+ /**
513
+ * --- 加载全新 locale(老 locale 的所以语言的缓存会被卸载) ---
514
+ * @param lang 语言名,如 sc
515
+ * @param path 路径
516
+ * @param taskId 所属的 taskId,App 模式下无效
517
+ * @param formId 所属的 formId,App 模式下无效
518
+ */
519
+ export function setLocale(lang: string, path: string, taskId?: number, formId?: number): Promise<boolean> {
520
+ clearLocale(taskId);
521
+ return loadLocale(lang, path, taskId, formId);
522
+ }
523
+
524
+ /**
525
+ * --- 设置本 task 的语言 name ---
526
+ * @param lang 语言名,如 sc
527
+ * @param taskId 所属的 taskId,App 模式下无效
528
+ */
529
+ export function setLocaleLang(lang: string, taskId?: number): void {
530
+ if (!taskId) {
531
+ return;
532
+ }
533
+ const task = list[taskId];
534
+ if (!task) {
535
+ return;
536
+ }
537
+ task.locale.lang = lang;
538
+ }
539
+
540
+ /**
541
+ * --- 清除 task 的语言设置 ---
542
+ * @param taskId 所属的 taskId,App 模式下无效
543
+ */
544
+ export function clearLocaleLang(taskId?: number): void {
545
+ if (!taskId) {
546
+ return;
547
+ }
548
+ const task = list[taskId];
549
+ if (!task) {
550
+ return;
551
+ }
552
+ task.locale.lang = '';
553
+ }
554
+
555
+ /**
556
+ * --- 创建 timer ---
557
+ * @param fun 执行函数
558
+ * @param delay 延迟/间隔
559
+ * @param opt 选项, taskId: App 模式下无效, formId: 可省略,App 模式下省略代表当前窗体,immediate: 立即执行
560
+ */
561
+ export function createTimer(
562
+ fun: () => void | Promise<void>,
563
+ delay: number,
564
+ opt: types.ICreateTimerOptions = {}
565
+ ): number {
566
+ const taskId = opt.taskId;
567
+ if (!taskId) {
568
+ return 0;
569
+ }
570
+ const formId = opt.formId;
571
+ if (!formId) {
572
+ return 0;
573
+ }
574
+ /** --- 作用域 --- */
575
+ const scope = opt.scope ?? 'form';
576
+ /** --- 执行几次,0 代表无限次 --- */
577
+ const count = opt.count ?? 0;
578
+ /** --- 当前已经执行的次数 --- */
579
+ let c: number = 0;
580
+ // --- 是否立即执行 ---
581
+ if (opt.immediate) {
582
+ const r = fun();
583
+ if (r instanceof Promise) {
584
+ r.catch(function(e) {
585
+ console.log(e);
586
+ });
587
+ }
588
+ ++c;
589
+ if (count > 0 && c === count) {
590
+ return 0;
591
+ }
592
+ }
593
+ let timer: number;
594
+ const timerHandler = (): void => {
595
+ ++c;
596
+ if (list[taskId].forms[formId] === undefined) {
597
+ // --- form 已经没了 ---
598
+ if (scope === 'form') {
599
+ clearTimeout(timer);
600
+ delete list[taskId].timers[timer];
601
+ return;
602
+ }
603
+ }
604
+ const r = fun();
605
+ if (r instanceof Promise) {
606
+ r.catch(function(e) {
607
+ console.log(e);
608
+ });
609
+ }
610
+ if (count > 0 && c === count) {
611
+ clearTimeout(timer);
612
+ delete list[taskId].timers[timer];
613
+ return;
614
+ }
615
+ };
616
+ /** --- timer 对象 number --- */
617
+ if (count === 1) {
618
+ timer = window.setTimeout(timerHandler, delay);
619
+ }
620
+ else {
621
+ timer = window.setInterval(timerHandler, delay);
622
+ }
623
+ list[taskId].timers[timer] = formId;
624
+ return timer;
625
+ }
626
+
627
+ /**
628
+ * --- 移除 timer ---
629
+ * @param timer ID
630
+ * @param taskId 任务 id,App 模式下无效
631
+ */
632
+ export function removeTimer(timer: number, taskId?: number): void {
633
+ if (!taskId) {
634
+ return;
635
+ }
636
+ if (list[taskId] === undefined) {
637
+ return;
638
+ }
639
+ const formId = list[taskId].timers[timer];
640
+ if (formId === undefined) {
641
+ return;
642
+ }
643
+ // --- 放在这,防止一个 task 能结束 别的 task 的 timer ---
644
+ clearTimeout(timer);
645
+ delete list[taskId].timers[timer];
646
+ }
647
+
648
+ /**
649
+ * --- 暂停一小段时间 ---
650
+ * @param fun 回调函数
651
+ * @param delay 暂停时间
652
+ * @param taskId 任务 id,App 模式下无效
653
+ * @param formId 窗体 id,App 模式下无效
654
+ */
655
+ export function sleep(fun: () => void | Promise<void>, delay: number, taskId?: number, formId?: number): number {
656
+ return createTimer(fun, delay, {
657
+ 'taskId': taskId,
658
+ 'formId': formId,
659
+ 'count': 1
660
+ });
661
+ }
662
+
663
+ /** --- task 的信息 --- */
664
+ export const systemTaskInfo: types.ISystemTaskInfo = Vue.reactive({
665
+ 'taskId': 0,
666
+ 'formId': 0,
667
+ 'length': 0
668
+ });
669
+
670
+ Vue.watch(systemTaskInfo, function(n, o) {
671
+ const originKeys = ['taskId', 'formId', 'length'];
672
+ // --- 检测有没有缺少的 key ---
673
+ for (const key of originKeys) {
674
+ if ((systemTaskInfo as any)[key] !== undefined) {
675
+ continue;
676
+ }
677
+ form.notify({
678
+ 'title': 'Warning',
679
+ 'content': 'There is a software that maliciously removed the system task info item.\nKey: ' + key,
680
+ 'type': 'warning'
681
+ });
682
+ (systemTaskInfo as any)[key] = o[key] ?? 0;
683
+ }
684
+ for (const key in systemTaskInfo) {
685
+ if (!['taskId', 'formId', 'length'].includes(key)) {
686
+ form.notify({
687
+ 'title': 'Warning',
688
+ 'content': 'There is a software that maliciously modifies the system task info item.\nKey: ' + key,
689
+ 'type': 'warning'
690
+ });
691
+ delete (systemTaskInfo as any)[key];
692
+ continue;
693
+ }
694
+ if (typeof (systemTaskInfo as any)[key] === 'number') {
695
+ continue;
696
+ }
697
+ form.notify({
698
+ 'title': 'Warning',
699
+ 'content': 'There is a software that maliciously modifies the system task info item.\nKey: ' + key,
700
+ 'type': 'warning'
701
+ });
702
+ (systemTaskInfo as any)[key] = o[key] ?? 0;
703
+ }
704
+ }, {
705
+ 'deep': true
706
+ });
707
+
708
+ /**
709
+ * --- 将任务注册为系统 task ---
710
+ * @param formId task bar 的 form id,App 模式下留空为当前窗体
711
+ * @param taskId task id,App 模式下无效
712
+ */
713
+ export function setSystem(formId?: number, taskId?: number): boolean {
714
+ if (!formId || !taskId) {
715
+ return false;
716
+ }
717
+ const task = list[taskId];
718
+ if (!task) {
719
+ return false;
720
+ }
721
+ const f = task.forms[formId];
722
+ if (!f) {
723
+ return false;
724
+ }
725
+ if (f.vroot.position === undefined) {
726
+ form.notify({
727
+ 'title': 'Warning',
728
+ 'content': `Task id is "${taskId}" app is not an available task app, position not found.`,
729
+ 'type': 'warning'
730
+ });
731
+ return false;
732
+ }
733
+ if (systemTaskInfo.taskId > 0) {
734
+ form.notify({
735
+ 'title': 'Info',
736
+ 'content': 'More than 1 system-level task application is currently running.',
737
+ 'type': 'info'
738
+ });
739
+ }
740
+ systemTaskInfo.taskId = taskId;
741
+ systemTaskInfo.formId = formId;
742
+ form.simpleSystemTaskRoot.forms = {};
743
+ refreshSystemPosition();
744
+ return true;
745
+ }
746
+
747
+ /**
748
+ * --- 清除系统任务设定 ---
749
+ * @param taskId 清除的 taskid 为 task id 才能清除,App 模式下无效
750
+ */
751
+ export function clearSystem(taskId?: number): boolean {
752
+ if (!taskId) {
753
+ return false;
754
+ }
755
+ if (typeof taskId !== 'number') {
756
+ form.notify({
757
+ 'title': 'Warning',
758
+ 'content': 'The "formId" of "clearTask" must be a number type.',
759
+ 'type': 'warning'
760
+ });
761
+ return false;
762
+ }
763
+ if (systemTaskInfo.taskId !== taskId) {
764
+ return false;
765
+ }
766
+ systemTaskInfo.taskId = 0;
767
+ systemTaskInfo.formId = 0;
768
+ systemTaskInfo.length = 0;
769
+ core.trigger('screenResize');
770
+ // --- 如果此时已经有最小化的窗体,那么他将永远“不见天日”,需要将他们传递给 simpletask ---
771
+ const tasks = getList();
772
+ for (const taskId in tasks) {
773
+ const forms = form.getList(parseInt(taskId));
774
+ for (const formId in forms) {
775
+ const f = forms[formId];
776
+ if (!f.stateMin) {
777
+ continue;
778
+ }
779
+ form.simpleSystemTaskRoot.forms[formId] = {
780
+ 'title': f.title,
781
+ 'icon': f.icon
782
+ };
783
+ }
784
+ }
785
+ return true;
786
+ }
787
+
788
+ /**
789
+ * --- 刷新系统任务的 form 的位置以及 length ---
790
+ */
791
+ export function refreshSystemPosition(): void {
792
+ if (systemTaskInfo.taskId > 0) {
793
+ const form = list[systemTaskInfo.taskId].forms[systemTaskInfo.formId];
794
+ // --- 更新 task bar 的位置 ---
795
+ switch (core.config['task.position']) {
796
+ case 'left':
797
+ case 'right': {
798
+ form.vroot.$refs.form.setPropData('width', 'auto');
799
+ form.vroot.$refs.form.setPropData('height', document.body.clientHeight);
800
+ break;
801
+ }
802
+ case 'top':
803
+ case 'bottom': {
804
+ form.vroot.$refs.form.setPropData('width', document.body.clientWidth);
805
+ form.vroot.$refs.form.setPropData('height', 'auto');
806
+ break;
807
+ }
808
+ }
809
+ setTimeout(function() {
810
+ switch (core.config['task.position']) {
811
+ case 'left': {
812
+ systemTaskInfo.length = form.vroot.$el.offsetWidth;
813
+ form.vroot.$refs.form.setPropData('left', 0);
814
+ form.vroot.$refs.form.setPropData('top', 0);
815
+ break;
816
+ }
817
+ case 'right': {
818
+ systemTaskInfo.length = form.vroot.$el.offsetWidth;
819
+ form.vroot.$refs.form.setPropData('left', document.body.clientWidth - systemTaskInfo.length);
820
+ form.vroot.$refs.form.setPropData('top', 0);
821
+ break;
822
+ }
823
+ case 'top': {
824
+ systemTaskInfo.length = form.vroot.$el.offsetHeight;
825
+ form.vroot.$refs.form.setPropData('left', 0);
826
+ form.vroot.$refs.form.setPropData('top', 0);
827
+ break;
828
+ }
829
+ case 'bottom': {
830
+ systemTaskInfo.length = form.vroot.$el.offsetHeight;
831
+ form.vroot.$refs.form.setPropData('left', 0);
832
+ form.vroot.$refs.form.setPropData('top', document.body.clientHeight - systemTaskInfo.length);
833
+ break;
834
+ }
835
+ }
836
+ core.trigger('screenResize');
837
+ }, 50);
838
+ }
839
+ else {
840
+ core.trigger('screenResize');
841
+ }
842
+ }