clickgo 3.0.6-dev7 → 3.1.0-dev9

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 (41) hide show
  1. package/README.md +1 -1
  2. package/dist/app/demo/app.js +93 -0
  3. package/dist/app/demo/form/control/form/form.js +21 -20
  4. package/dist/app/demo/form/control/form/form.xml +3 -3
  5. package/dist/app/demo/form/main.js +20 -10
  6. package/dist/app/demo/form/main.xml +1 -1
  7. package/dist/app/task/app.js +46 -0
  8. package/dist/app/task/form/bar/bar.js +85 -86
  9. package/dist/app/task/form/bar/bar.xml +5 -6
  10. package/dist/clickgo.js +1 -10
  11. package/dist/clickgo.ts +0 -8
  12. package/dist/control/common.cgc +0 -0
  13. package/dist/control/form.cgc +0 -0
  14. package/dist/control/monaco.cgc +0 -0
  15. package/dist/control/property.cgc +0 -0
  16. package/dist/control/task.cgc +0 -0
  17. package/dist/global.css +1 -1
  18. package/dist/index.js +105 -56
  19. package/dist/index.ts +164 -59
  20. package/dist/lib/control.js +363 -240
  21. package/dist/lib/control.ts +497 -284
  22. package/dist/lib/core.js +331 -217
  23. package/dist/lib/core.ts +418 -244
  24. package/dist/lib/dom.js +6 -3
  25. package/dist/lib/dom.ts +7 -6
  26. package/dist/lib/form.js +635 -980
  27. package/dist/lib/form.ts +817 -1072
  28. package/dist/lib/fs.js +42 -39
  29. package/dist/lib/fs.ts +45 -41
  30. package/dist/lib/native.js +8 -148
  31. package/dist/lib/native.ts +9 -211
  32. package/dist/lib/task.js +707 -191
  33. package/dist/lib/task.ts +778 -210
  34. package/dist/lib/theme.ts +2 -2
  35. package/dist/lib/tool.js +58 -48
  36. package/dist/lib/tool.ts +79 -64
  37. package/dist/theme/familiar.cgt +0 -0
  38. package/package.json +5 -7
  39. package/types/index.d.ts +286 -324
  40. package/dist/app/demo/config.json +0 -106
  41. package/dist/app/task/config.json +0 -32
package/dist/lib/core.ts CHANGED
@@ -19,8 +19,12 @@ import * as fs from './fs';
19
19
  import * as form from './form';
20
20
  import * as task from './task';
21
21
  import * as tool from './tool';
22
+ import * as control from './control';
23
+ import * as theme from './theme';
22
24
  import * as zip from './zip';
25
+ import * as dom from './dom';
23
26
 
27
+ /** --- Config 原始参考对象 --- */
24
28
  const configOrigin: types.IConfig = {
25
29
  'locale': 'en',
26
30
  'task.position': 'bottom',
@@ -28,8 +32,11 @@ const configOrigin: types.IConfig = {
28
32
  'desktop.icon.storage': true,
29
33
  'desktop.icon.recycler': true,
30
34
  'desktop.wallpaper': null,
31
- 'desktop.path': null
35
+ 'desktop.path': null,
36
+ 'launcher.list': []
32
37
  };
38
+
39
+ /** --- Config 配置对象 --- */
33
40
  export const config: types.IConfig = clickgo.vue.reactive({
34
41
  'locale': 'en',
35
42
  'task.position': 'bottom',
@@ -37,10 +44,300 @@ export const config: types.IConfig = clickgo.vue.reactive({
37
44
  'desktop.icon.storage': true,
38
45
  'desktop.icon.recycler': true,
39
46
  'desktop.wallpaper': null,
40
- 'desktop.path': null
47
+ 'desktop.path': null,
48
+ 'launcher.list': []
41
49
  });
42
50
 
43
- export const cdn = '';
51
+ /** --- App 抽象类 --- */
52
+ export abstract class AbstractApp {
53
+
54
+ /** --- 当前 js 文件在包内的完整路径 --- */
55
+ public get filename(): string {
56
+ return '';
57
+ }
58
+
59
+ /** --- invoke 时系统会自动设置本项 --- */
60
+ public get taskId(): number {
61
+ return 0;
62
+ }
63
+
64
+ public set taskId(v: number) {
65
+ form.notify({
66
+ 'title': 'Error',
67
+ 'content': `The software tries to modify the system variable "taskId" to "${v}".\nPath: ${this.filename}`,
68
+ 'type': 'danger'
69
+ });
70
+ }
71
+
72
+ /** --- App 的入口文件 --- */
73
+ public abstract main(): Promise<void>;
74
+
75
+ /**
76
+ * --- 先设置 App 的配置信息 ---
77
+ * @param config 配置对象
78
+ */
79
+ public async config(config: types.IAppConfig): Promise<boolean> {
80
+ const t = task.list[this.taskId];
81
+ if (!t) {
82
+ return false;
83
+ }
84
+ t.config = config;
85
+ // --- 加载余下的 xml 等静态包内资源,仅限 net 的 app 模式 ---
86
+ if (t.app.net) {
87
+ if (!t.config.files) {
88
+ return false;
89
+ }
90
+ const files = t.config.files;
91
+ const net = t.app.net;
92
+ await new Promise<void>(function(resolve) {
93
+ let loaded = 0;
94
+ const total = files.length;
95
+ const beforeTotal = Object.keys(t.app.files).length;
96
+ net.progress?.(loaded + beforeTotal, total + beforeTotal) as unknown;
97
+ for (const file of files) {
98
+ fs.getContent(net.url + file.slice(1), {
99
+ 'current': net.current
100
+ }).then(async function(blob) {
101
+ if (blob === null || typeof blob === 'string') {
102
+ clickgo.form.notify({
103
+ 'title': 'File not found',
104
+ 'content': net.url + file.slice(1),
105
+ 'type': 'danger'
106
+ });
107
+ return;
108
+ }
109
+ const mime = tool.getMimeByPath(file);
110
+ if (['txt', 'json', 'js', 'css', 'xml', 'html'].includes(mime.ext)) {
111
+ t.app.files[file] = (await tool.blob2Text(blob)).replace(/^\ufeff/, '');
112
+ }
113
+ else {
114
+ t.app.files[file] = blob;
115
+ }
116
+ ++loaded;
117
+ net.progress?.(loaded + beforeTotal, total + beforeTotal) as unknown;
118
+ if (net.notify) {
119
+ form.notifyProgress(net.notify, (loaded / total) / 2 + 0.5);
120
+ }
121
+ if (loaded < total) {
122
+ return;
123
+ }
124
+ resolve();
125
+ }).catch(function() {
126
+ ++loaded;
127
+ net.progress?.(loaded + beforeTotal, total + beforeTotal) as unknown;
128
+ if (net.notify) {
129
+ form.notifyProgress(net.notify, (loaded / total) / 2 + 0.5);
130
+ }
131
+ if (loaded < total) {
132
+ return;
133
+ }
134
+ resolve();
135
+ });
136
+ }
137
+ });
138
+ if (net.notify) {
139
+ setTimeout(function(): void {
140
+ form.hideNotify(net.notify!);
141
+ }, 2000);
142
+ }
143
+ }
144
+ // --- 要加载 control ---
145
+ if (!await control.init(this.taskId)) {
146
+ return false;
147
+ }
148
+ // --- theme ---
149
+ if (config.themes?.length) {
150
+ for (let path of config.themes) {
151
+ path += '.cgt';
152
+ path = tool.urlResolve('/', path);
153
+ const file = await fs.getContent(path, {
154
+ 'files': t.app.files,
155
+ 'current': t.current
156
+ });
157
+ if (file && typeof file !== 'string') {
158
+ const th = await theme.read(file);
159
+ if (th) {
160
+ await theme.load(th, t.id);
161
+ }
162
+ }
163
+ }
164
+ }
165
+ else {
166
+ // --- 加载全局主题 ---
167
+ if (theme.global) {
168
+ await theme.load(undefined, this.taskId);
169
+ }
170
+ }
171
+ // --- locale ---
172
+ if (config.locales) {
173
+ for (let path in config.locales) {
174
+ const locale = config.locales[path];
175
+ if (!path.endsWith('.json')) {
176
+ path += '.json';
177
+ }
178
+ const lcontent = await fs.getContent(path, {
179
+ 'encoding': 'utf8',
180
+ 'files': t.app.files,
181
+ 'current': t.current
182
+ });
183
+ if (!lcontent) {
184
+ continue;
185
+ }
186
+ try {
187
+ const data = JSON.parse(lcontent);
188
+ task.loadLocaleData(locale, data, '', t.id);
189
+ }
190
+ catch {
191
+ // --- 无所谓 ---
192
+ }
193
+ }
194
+ }
195
+ // --- 加载任务级全局样式 ---
196
+ if (config.style) {
197
+ const style = await fs.getContent(config.style + '.css', {
198
+ 'encoding': 'utf8',
199
+ 'files': t.app.files,
200
+ 'current': t.current
201
+ });
202
+ if (style) {
203
+ const r = tool.stylePrepend(style, 'cg-task' + this.taskId.toString() + '_');
204
+ dom.pushStyle(this.taskId, await tool.styleUrl2DataUrl(config.style, r.style, t.app.files));
205
+ }
206
+ }
207
+ // --- 加载图标 ---
208
+ if (config.icon) {
209
+ const icon = await fs.getContent(config.icon, {
210
+ 'files': t.app.files,
211
+ 'current': t.current
212
+ });
213
+ if (icon && typeof icon !== 'string') {
214
+ t.app.icon = await tool.blob2DataUrl(icon);
215
+ }
216
+ }
217
+ // --- 全部成功,设置 t.class 为自己 ---
218
+ t.class = this;
219
+ return true;
220
+ }
221
+
222
+ /**
223
+ * --- 以某个窗体进行正式启动这个 app(入口 form),不启动则任务也启动失败 ---
224
+ * @param form 窗体对象
225
+ */
226
+ public run(form: form.AbstractForm | number): void {
227
+ if (typeof form === 'number') {
228
+ // --- 报错 ---
229
+ const msg = 'Application run error, Form creation failed (' + form.toString() + ').';
230
+ trigger('error', this.taskId, 0, new Error(msg), msg);
231
+ return;
232
+ }
233
+ form.show();
234
+ }
235
+
236
+ /** --- 全局错误事件 --- */
237
+ public onError(taskId: number, formId: number, error: Error, info: string): void | Promise<void>;
238
+ public onError(): void {
239
+ return;
240
+ }
241
+
242
+ /** --- 屏幕大小改变事件 --- */
243
+ public onScreenResize(): void | Promise<void>;
244
+ public onScreenResize(): void {
245
+ return;
246
+ }
247
+
248
+ /** --- 系统配置变更事件 --- */
249
+ public onConfigChanged<T extends types.IConfig, TK extends keyof T>(n: TK, v: T[TK]): void | Promise<void>;
250
+ public onConfigChanged(): void {
251
+ return;
252
+ }
253
+
254
+ /** --- 窗体创建事件 --- */
255
+ public onFormCreated(taskId: number, formId: number, title: string, icon: string): void | Promise<void>;
256
+ public onFormCreated(): void {
257
+ return;
258
+ }
259
+
260
+ /** --- 窗体销毁事件 */
261
+ public onFormRemoved(taskId: number, formId: number, title: string, icon: string): void | Promise<void>;
262
+ public onFormRemoved(): void {
263
+ return;
264
+ }
265
+
266
+ /** --- 窗体标题改变事件 */
267
+ public onFormTitleChanged(taskId: number, formId: number, title: string): void | Promise<void>;
268
+ public onFormTitleChanged(): void | Promise<void> {
269
+ return;
270
+ }
271
+
272
+ /** --- 窗体图标改变事件 --- */
273
+ public onFormIconChanged(taskId: number, formId: number, icon: string): void | Promise<void>;
274
+ public onFormIconChanged(): void | Promise<void> {
275
+ return;
276
+ }
277
+
278
+ /** --- 窗体最小化状态改变事件 --- */
279
+ public onFormStateMinChanged(taskId: number, formId: number, state: boolean): void | Promise<void>;
280
+ public onFormStateMinChanged(): void {
281
+ return;
282
+ }
283
+
284
+ /** --- 窗体最大化状态改变事件 --- */
285
+ public onFormStateMaxChanged(taskId: number, formId: number, state: boolean): void | Promise<void>;
286
+ public onFormStateMaxChanged(): void {
287
+ return;
288
+ }
289
+
290
+ /** --- 窗体显示状态改变事件 --- */
291
+ public onFormShowChanged(taskId: number, formId: number, state: boolean): void | Promise<void>;
292
+ public onFormShowChanged(): void {
293
+ return;
294
+ }
295
+
296
+ /** --- 窗体获得焦点事件 --- */
297
+ public onFormFocused(taskId: number, formId: number): void | Promise<void>;
298
+ public onFormFocused(): void {
299
+ return;
300
+ }
301
+
302
+ /** --- 窗体丢失焦点事件 --- */
303
+ public onFormBlurred(taskId: number, formId: number): void | Promise<void>;
304
+ public onFormBlurred(): void {
305
+ return;
306
+ }
307
+
308
+ /** --- 窗体闪烁事件 --- */
309
+ public onFormFlash(taskId: number, formId: number): void | Promise<void>;
310
+ public onFormFlash(): void {
311
+ return;
312
+ }
313
+
314
+ /** --- 任务开始事件 --- */
315
+ public onTaskStarted(taskId: number): void | Promise<void>;
316
+ public onTaskStarted(): void | Promise<void> {
317
+ return;
318
+ }
319
+
320
+ /** --- 任务结束事件 --- */
321
+ public onTaskEnded(taskId: number): void | Promise<void>;
322
+ public onTaskEnded(): void | Promise<void> {
323
+ return;
324
+ }
325
+
326
+ /** --- launcher 文件夹名称修改事件 --- */
327
+ public onLauncherFolderNameChanged(id: string, name: string): void | Promise<void>;
328
+ public onLauncherFolderNameChanged(): void {
329
+ return;
330
+ }
331
+
332
+ }
333
+
334
+ /** --- CDN 地址 --- */
335
+ export function getCdn(): string {
336
+ return loader.cdn;
337
+ }
338
+
339
+ /** --- boot 类 --- */
340
+ export let boot: import('../index').AbstractBoot;
44
341
 
45
342
  clickgo.vue.watch(config, function() {
46
343
  // --- 检测有没有缺少的 config key ---
@@ -99,7 +396,7 @@ const modules: Record<string, { func: () => any | Promise<any>; 'obj': null | an
99
396
  'monaco': {
100
397
  func: async function() {
101
398
  return new Promise(function(resolve, reject) {
102
- fetch(loader.cdn + '/npm/monaco-editor@0.33.0/min/vs/loader.js').then(function(r) {
399
+ fetch(loader.cdn + '/npm/monaco-editor@0.34.1/min/vs/loader.js').then(function(r) {
103
400
  return r.blob();
104
401
  }).then(function(b) {
105
402
  return tool.blob2DataUrl(b);
@@ -116,7 +413,7 @@ const modules: Record<string, { func: () => any | Promise<any>; 'obj': null | an
116
413
  };
117
414
 
118
415
  /**
119
- * --- 注册新的外接模块 ---
416
+ * --- 注册新的外接模块,App 模式下无效 ---
120
417
  * @param name 模块名
121
418
  * @param func 执行加载函数
122
419
  */
@@ -218,33 +515,30 @@ export function getModule(name: string): null | any {
218
515
  return modules[name].obj;
219
516
  }
220
517
 
221
- /** --- 全局响应事件 --- */
222
- export const globalEvents: types.IGlobalEvents = {
223
- errorHandler: null,
224
- screenResizeHandler: function(): void {
518
+ /** --- 系统要处理的全局响应事件 --- */
519
+ const globalEvents = {
520
+ screenResize: function(): void {
225
521
  form.refreshMaxPosition();
226
522
  },
227
- configChangedHandler: null,
228
- formCreatedHandler: null,
229
- formRemovedHandler: function(taskId: number, formId: number): void {
523
+ formRemoved: function(taskId: number, formId: number): void {
230
524
  if (!form.simpleSystemTaskRoot.forms[formId]) {
231
525
  return;
232
526
  }
233
527
  delete form.simpleSystemTaskRoot.forms[formId];
234
528
  },
235
- formTitleChangedHandler: function(taskId: number, formId: number, title: string): void {
529
+ formTitleChanged: function(taskId: number, formId: number, title: string): void {
236
530
  if (!form.simpleSystemTaskRoot.forms[formId]) {
237
531
  return;
238
532
  }
239
533
  form.simpleSystemTaskRoot.forms[formId].title = title;
240
534
  },
241
- formIconChangedHandler: function(taskId: number, formId: number, icon: string): void {
535
+ formIconChanged: function(taskId: number, formId: number, icon: string): void {
242
536
  if (!form.simpleSystemTaskRoot.forms[formId]) {
243
537
  return;
244
538
  }
245
539
  form.simpleSystemTaskRoot.forms[formId].icon = icon;
246
540
  },
247
- formStateMinChangedHandler: function(taskId: number, formId: number, state: boolean): void {
541
+ formStateMinChanged: function(taskId: number, formId: number, state: boolean): void {
248
542
  if (task.systemTaskInfo.taskId > 0) {
249
543
  return;
250
544
  }
@@ -264,118 +558,37 @@ export const globalEvents: types.IGlobalEvents = {
264
558
  }
265
559
  delete form.simpleSystemTaskRoot.forms[formId];
266
560
  }
267
- },
268
- formStateMaxChangedHandler: null,
269
- formShowChangedHandler: null,
270
- formFocusedHandler: null,
271
- formBlurredHandler: null,
272
- formFlashHandler: null,
273
- taskStartedHandler: null,
274
- taskEndedHandler: null
275
- };
276
-
277
- /**
278
- * --- 设置系统事件监听,一个窗体只能设置一个监听 ---
279
- * @param name 系统事件名
280
- * @param func 回调函数
281
- * @param formId 窗体 id,app 模式下留空为当前窗体
282
- * @param taskId 任务 id,app 模式下无效
283
- */
284
- export function setSystemEventListener(
285
- name: types.TGlobalEvent,
286
- func: (...any: any) => void | Promise<void>,
287
- formId?: number,
288
- taskId?: number
289
- ): void {
290
- if (!taskId) {
291
- return;
292
- }
293
- const t = task.list[taskId];
294
- if (!t) {
295
- return;
296
- }
297
- if (!formId) {
298
- return;
299
- }
300
- const f = t.forms[formId];
301
- if (!f) {
302
- return;
303
- }
304
- f.events[name] = func;
305
- }
306
-
307
- /**
308
- * --- 移除系统事件监听,一个窗体只能设置一个监听 ---
309
- * @param name name 系统事件名
310
- * @param formId 窗体 id,app 默认为当前窗体
311
- * @param taskId 任务 id,app 模式下无效
312
- */
313
- export function removeSystemEventListener(
314
- name: types.TGlobalEvent,
315
- formId?: number,
316
- taskId?: number
317
- ): void {
318
- if (!taskId) {
319
- return;
320
561
  }
321
- const t = task.list[taskId];
322
- if (!t) {
323
- return;
324
- }
325
- if (!formId) {
326
- return;
327
- }
328
- const f = t.forms[formId];
329
- if (!f) {
330
- return;
331
- }
332
- delete f.events[name];
333
- }
562
+ };
334
563
 
335
564
  /**
336
- * --- 主动触发系统级事件 ---
565
+ * --- 主动触发系统级事件,App 中无效,用 this.trigger 替代 ---
337
566
  */
338
- export function trigger(name: types.TGlobalEvent, taskId: number | string = 0, formId: number | string | boolean | Record<string, any> | null = 0, param1: boolean | Error | string = '', param2: string = ''): void {
567
+ export function trigger(name: types.TGlobalEvent, taskId: number | string | boolean = 0, formId: number | string | boolean | Record<string, any> | null = 0, param1: boolean | Error | string = '', param2: string = ''): void {
568
+ const eventName = 'on' + name[0].toUpperCase() + name.slice(1);
339
569
  switch (name) {
340
570
  case 'error': {
341
571
  if (typeof taskId !== 'number' || typeof formId !== 'number') {
342
572
  break;
343
573
  }
344
- const r = globalEvents.errorHandler?.(taskId, formId, param1 as Error, param2);
345
- if (r && (r instanceof Promise)) {
346
- r.catch(function(e) {
347
- console.log(e);
348
- });
349
- }
574
+ (boot as any)[eventName](taskId, formId, param1, param2);
350
575
  for (const tid in task.list) {
351
576
  const t = task.list[tid];
577
+ (t.class as any)?.[eventName](taskId, formId, param1, param2);
352
578
  for (const fid in t.forms) {
353
- const r = t.forms[fid].events[name]?.(taskId, formId, param1, param2);
354
- if (r instanceof Promise) {
355
- r.catch(function(e) {
356
- console.log(e);
357
- });
358
- }
579
+ t.forms[fid].vroot[eventName]?.(taskId, formId, param1, param2);
359
580
  }
360
581
  }
361
582
  break;
362
583
  }
363
584
  case 'screenResize': {
364
- const r = globalEvents.screenResizeHandler?.();
365
- if (r && (r instanceof Promise)) {
366
- r.catch(function(e) {
367
- console.log(e);
368
- });
369
- }
585
+ globalEvents.screenResize();
586
+ (boot as any)[eventName]();
370
587
  for (const tid in task.list) {
371
588
  const t = task.list[tid];
589
+ (t.class as any)?.[eventName]();
372
590
  for (const fid in t.forms) {
373
- const r = t.forms[fid].events[name]?.();
374
- if (r instanceof Promise) {
375
- r.catch(function(e) {
376
- console.log(e);
377
- });
378
- }
591
+ t.forms[fid].vroot[eventName]?.();
379
592
  }
380
593
  }
381
594
  break;
@@ -384,53 +597,38 @@ export function trigger(name: types.TGlobalEvent, taskId: number | string = 0, f
384
597
  if ((typeof taskId !== 'string') || (typeof formId === 'number')) {
385
598
  break;
386
599
  }
387
- const r = globalEvents.configChangedHandler?.(taskId as types.TConfigName, formId);
388
- if (r && (r instanceof Promise)) {
389
- r.catch(function(e) {
390
- console.log(e);
391
- });
392
- }
600
+ (boot as any)[eventName]();
393
601
  for (const tid in task.list) {
394
602
  const t = task.list[tid];
603
+ (t.class as any)?.[eventName](taskId, formId);
395
604
  for (const fid in t.forms) {
396
- const r = t.forms[fid].events[name]?.(taskId, formId);
397
- if (r instanceof Promise) {
398
- r.catch(function(e) {
399
- console.log(e);
400
- });
401
- }
605
+ t.forms[fid].vroot[eventName]?.(taskId, formId);
402
606
  }
403
607
  }
404
608
  break;
405
609
  }
406
610
  case 'formCreated':
407
611
  case 'formRemoved': {
408
- (globalEvents as any)[name + 'Handler']?.(taskId, formId, param1, param2);
612
+ (globalEvents as any)[name]?.(taskId, formId, param1, param2);
613
+ (boot as any)[eventName](taskId, formId, param1, param2);
409
614
  for (const tid in task.list) {
410
615
  const t = task.list[tid];
616
+ (t.class as any)?.[eventName](taskId, formId, param1, param2);
411
617
  for (const fid in t.forms) {
412
- const r = t.forms[fid].events[name]?.(taskId, formId, param1, param2);
413
- if (r instanceof Promise) {
414
- r.catch(function(e) {
415
- console.log(e);
416
- });
417
- }
618
+ t.forms[fid].vroot[eventName]?.(taskId, formId, param1, param2);
418
619
  }
419
620
  }
420
621
  break;
421
622
  }
422
623
  case 'formTitleChanged':
423
624
  case 'formIconChanged': {
424
- (globalEvents as any)[name + 'Handler']?.(taskId, formId, param1);
625
+ (globalEvents as any)[name]?.(taskId, formId, param1);
626
+ (boot as any)[eventName](taskId, formId, param1);
425
627
  for (const tid in task.list) {
426
628
  const t = task.list[tid];
629
+ (t.class as any)?.[eventName](taskId, formId, param1);
427
630
  for (const fid in t.forms) {
428
- const r = t.forms[fid].events[name]?.(taskId, formId, param1);
429
- if (r instanceof Promise) {
430
- r.catch(function(e) {
431
- console.log(e);
432
- });
433
- }
631
+ t.forms[fid].vroot[eventName]?.(taskId, formId, param1);
434
632
  }
435
633
  }
436
634
  break;
@@ -438,16 +636,13 @@ export function trigger(name: types.TGlobalEvent, taskId: number | string = 0, f
438
636
  case 'formStateMinChanged':
439
637
  case 'formStateMaxChanged':
440
638
  case 'formShowChanged': {
441
- (globalEvents as any)[name + 'Handler']?.(taskId, formId, param1);
639
+ (globalEvents as any)[name]?.(taskId, formId, param1);
640
+ (boot as any)[eventName](taskId, formId, param1);
442
641
  for (const tid in task.list) {
443
642
  const t = task.list[tid];
643
+ (t.class as any)?.[eventName](taskId, formId, param1);
444
644
  for (const fid in t.forms) {
445
- const r = t.forms[fid].events[name]?.(taskId, formId, param1);
446
- if (r instanceof Promise) {
447
- r.catch(function(e) {
448
- console.log(e);
449
- });
450
- }
645
+ t.forms[fid].vroot[eventName]?.(taskId, formId, param1);
451
646
  }
452
647
  }
453
648
  break;
@@ -455,32 +650,43 @@ export function trigger(name: types.TGlobalEvent, taskId: number | string = 0, f
455
650
  case 'formFocused':
456
651
  case 'formBlurred':
457
652
  case 'formFlash': {
458
- (globalEvents as any)[name + 'Handler']?.(taskId, formId);
653
+ (globalEvents as any)[name]?.(taskId, formId);
654
+ (boot as any)[eventName](taskId, formId);
459
655
  for (const tid in task.list) {
460
656
  const t = task.list[tid];
657
+ (t.class as any)?.[eventName](taskId, formId);
461
658
  for (const fid in t.forms) {
462
- const r = t.forms[fid].events[name]?.(taskId, formId);
463
- if (r instanceof Promise) {
464
- r.catch(function(e) {
465
- console.log(e);
466
- });
467
- }
659
+ t.forms[fid].vroot[eventName]?.(taskId, formId);
468
660
  }
469
661
  }
470
662
  break;
471
663
  }
472
664
  case 'taskStarted':
473
665
  case 'taskEnded': {
474
- (globalEvents as any)[name + 'Handler']?.(taskId, formId);
666
+ (globalEvents as any)[name]?.(taskId, formId);
667
+ (boot as any)[eventName](taskId, formId);
475
668
  for (const tid in task.list) {
476
669
  const t = task.list[tid];
670
+ (t.class as any)?.[eventName](taskId);
477
671
  for (const fid in t.forms) {
478
- const r = t.forms[fid].events[name]?.(taskId);
479
- if (r instanceof Promise) {
480
- r.catch(function(e) {
481
- console.log(e);
482
- });
483
- }
672
+ t.forms[fid].vroot[eventName]?.(taskId);
673
+ }
674
+ }
675
+ break;
676
+ }
677
+ case 'launcherFolderNameChanged': {
678
+ if (typeof formId !== 'string') {
679
+ break;
680
+ }
681
+ if (typeof taskId === 'number') {
682
+ taskId = taskId.toString();
683
+ }
684
+ (boot as any)[eventName](taskId, formId);
685
+ for (const tid in task.list) {
686
+ const t = task.list[tid];
687
+ (t.class as any)?.[eventName](taskId, formId);
688
+ for (const fid in t.forms) {
689
+ t.forms[fid].vroot[eventName]?.(taskId, formId);
484
690
  }
485
691
  }
486
692
  break;
@@ -489,7 +695,7 @@ export function trigger(name: types.TGlobalEvent, taskId: number | string = 0, f
489
695
  }
490
696
 
491
697
  /**
492
- * --- cga 文件 blob IApp 对象 ---
698
+ * --- cga blob 文件解包 ---
493
699
  * @param blob blob 对象
494
700
  */
495
701
  export async function readApp(blob: Blob): Promise<false | types.IApp> {
@@ -501,38 +707,30 @@ export async function readApp(blob: Blob): Promise<false | types.IApp> {
501
707
  }
502
708
  // --- 开始读取文件 ---
503
709
  const files: Record<string, Blob | string> = {};
504
- /** --- 配置文件 --- */
505
- const configContent = await z.getContent('/config.json');
506
- if (!configContent) {
507
- return false;
508
- }
509
- const config: types.IAppConfig = JSON.parse(configContent);
510
- for (const file of config.files) {
511
- const mime = tool.getMimeByPath(file);
710
+ const list = z.readDir('/', {
711
+ 'hasChildren': true
712
+ });
713
+ for (const file of list) {
714
+ const mime = tool.getMimeByPath(file.name);
512
715
  if (['txt', 'json', 'js', 'css', 'xml', 'html'].includes(mime.ext)) {
513
- const fab = await z.getContent(file, 'string');
716
+ const fab = await z.getContent(file.path + file.name, 'string');
514
717
  if (!fab) {
515
718
  continue;
516
719
  }
517
- files[file] = fab.replace(/^\ufeff/, '');
720
+ files[file.path + file.name] = fab.replace(/^\ufeff/, '');
518
721
  }
519
722
  else {
520
- const fab = await z.getContent(file, 'arraybuffer');
723
+ const fab = await z.getContent(file.path + file.name, 'arraybuffer');
521
724
  if (!fab) {
522
725
  continue;
523
726
  }
524
- files[file] = new Blob([fab], {
727
+ files[file.path + file.name] = new Blob([fab], {
525
728
  'type': mime.mime
526
729
  });
527
730
  }
528
731
  }
529
- if (!config) {
530
- return false;
531
- }
532
732
  return {
533
- 'type': 'app',
534
733
  'icon': icon,
535
- 'config': config,
536
734
  'files': files
537
735
  };
538
736
  }
@@ -540,9 +738,12 @@ export async function readApp(blob: Blob): Promise<false | types.IApp> {
540
738
  /**
541
739
  * --- 从网址下载应用,App 模式下本方法不可用 ---
542
740
  * @param url 对于当前网页的相对、绝对路径,以 / 结尾的目录或 .cga 结尾的文件 ---
543
- * @param opt,notifyId:显示进度条的 notify id,current:设置则以设置的为准,以 / 结尾,否则以 location 为准 ---
741
+ * @param opt,notifyId:显示进度条的 notify id,current:设置则以设置的为准,不以 / 结尾,否则以 location 为准 ---
544
742
  */
545
- export async function fetchApp(url: string, opt: types.ICoreFetchAppOptions = {}): Promise<null | types.IApp> {
743
+ export async function fetchApp(
744
+ url: string,
745
+ opt: types.ICoreFetchAppOptions = {}
746
+ ): Promise<null | types.IApp> {
546
747
  /** --- 若是 cga 文件,则是 cga 的文件名,含 .cga --- */
547
748
  let cga: string = '';
548
749
  if (!url.endsWith('/')) {
@@ -555,18 +756,14 @@ export async function fetchApp(url: string, opt: types.ICoreFetchAppOptions = {}
555
756
 
556
757
  let current = '';
557
758
  if (opt.current) {
558
- if (!opt.current.endsWith('/')) {
559
- return null;
560
- }
561
- if (!url.startsWith('/')) {
562
- url = '/current/' + url;
563
- }
759
+ current = opt.current.endsWith('/') ? opt.current.slice(0, -1) : opt.current;
760
+ url = tool.urlResolve('/current/', url);
564
761
  }
565
762
  else {
566
763
  if (!url.startsWith('/clickgo/') && !url.startsWith('/storage/') && !url.startsWith('/mounted/')) {
567
764
  current = tool.urlResolve(window.location.href, url);
568
765
  if (cga) {
569
- current = current.slice(0, -cga.length);
766
+ current = current.slice(0, -cga.length - 1);
570
767
  url = '/current/' + cga;
571
768
  }
572
769
  else {
@@ -601,75 +798,52 @@ export async function fetchApp(url: string, opt: types.ICoreFetchAppOptions = {}
601
798
  return null;
602
799
  }
603
800
  }
604
- // --- 加载目录 ---
605
- // --- 加载 json 文件,并创建 control 信息对象 ---
606
- let config: types.IAppConfig;
607
- // --- 已加载的 files ---
608
- const files: Record<string, Blob | string> = {};
609
- try {
610
- const blob = await fs.getContent(url + 'config.json', {
611
- 'current': current
612
- });
613
- if (blob === null || typeof blob === 'string') {
614
- return null;
615
- }
616
- config = JSON.parse(await tool.blob2Text(blob));
617
- await new Promise<void>(function(resolve) {
618
- const total = config.files.length;
619
- let loaded = 0;
620
- for (const file of config.files) {
621
- fs.getContent(url + file.slice(1), {
622
- 'current': current
623
- }).then(async function(blob) {
624
- if (blob === null || typeof blob === 'string') {
625
- clickgo.form.notify({
626
- 'title': 'File not found',
627
- 'content': url + file.slice(1),
628
- 'type': 'danger'
629
- });
630
- return;
631
- }
632
- const mime = tool.getMimeByPath(file);
633
- if (['txt', 'json', 'js', 'css', 'xml', 'html'].includes(mime.ext)) {
634
- files[file] = (await tool.blob2Text(blob)).replace(/^\ufeff/, '');
635
- }
636
- else {
637
- files[file] = blob;
638
- }
639
- ++loaded;
640
- opt.progress?.(loaded, total) as unknown;
641
- if (opt.notifyId) {
642
- form.notifyProgress(opt.notifyId, loaded / total);
643
- }
644
- if (loaded < total) {
645
- return;
646
- }
647
- resolve();
648
- }).catch(function() {
649
- ++loaded;
650
- opt.progress?.(loaded, total) as unknown;
651
- if (opt.notifyId) {
652
- form.notifyProgress(opt.notifyId, loaded / total);
653
- }
654
- if (loaded < total) {
655
- return;
656
- }
657
- resolve();
658
- });
801
+ // --- 从网络嗅探 ---
802
+ let loaded = 0;
803
+ let total = 30;
804
+ // --- 网络嗅探,不知道文件总数,先暂定 30,不过超出 30 文件的建议打包为 cga ---
805
+ const files = await loader.sniffFiles(url + 'app.js', {
806
+ 'dir': '/',
807
+ adapter: async (url) => {
808
+ const r = await fs.getContent(url, {
809
+ 'encoding': 'utf8',
810
+ 'current': current
811
+ });
812
+ return r;
813
+ },
814
+ 'loaded': () => {
815
+ ++loaded;
816
+ if (loaded === total) {
817
+ ++total;
659
818
  }
660
- });
819
+ if (opt.notifyId) {
820
+ form.notifyProgress(opt.notifyId, (loaded / total) / 2);
821
+ }
822
+ if (opt.progress) {
823
+ opt.progress(loaded, total) as unknown;
824
+ }
825
+ }
826
+ });
827
+ // --- net 模式此处加载完只算到 50%,因为还有 app 类当中 config 中的静态部分,暂定预留 50% ---
828
+ if (opt.notifyId) {
829
+ form.notifyProgress(opt.notifyId, 0.5);
661
830
  }
662
- catch {
831
+ if (Object.keys(files).length === 0) {
663
832
  return null;
664
833
  }
665
- let icon = '/clickgo/icon.png';
666
- if (config.icon && (files[config.icon] instanceof Blob)) {
667
- icon = await tool.blob2DataUrl(files[config.icon] as Blob);
834
+ const ul = url.length - 1;
835
+ for (const fn in files) {
836
+ files[fn.slice(ul)] = files[fn];
837
+ delete files[fn];
668
838
  }
669
839
  return {
670
- 'type': 'app',
671
- 'icon': icon,
672
- 'config': config,
840
+ 'net': {
841
+ 'current': current,
842
+ 'notify': opt.notifyId,
843
+ 'url': url,
844
+ 'progress': opt.progress
845
+ },
846
+ 'icon': '',
673
847
  'files': files
674
848
  };
675
849
  }