jsbox-cview 1.0.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +19 -0
  3. package/components/alert/input-alert.ts +73 -0
  4. package/components/alert/login-alert.ts +75 -0
  5. package/components/alert/plain-alert.ts +49 -0
  6. package/components/alert/uialert.ts +110 -0
  7. package/components/artificial-flowlayout.ts +321 -0
  8. package/components/base.ts +47 -0
  9. package/components/custom-navigation-bar.ts +570 -0
  10. package/components/dialogs/dialog-sheet.ts +87 -0
  11. package/components/dialogs/form-dialog.ts +23 -0
  12. package/components/dialogs/list-dialog.ts +87 -0
  13. package/components/dialogs/text-dialog.ts +34 -0
  14. package/components/dynamic-itemsize-matrix.ts +190 -0
  15. package/components/dynamic-preference-listview.ts +691 -0
  16. package/components/dynamic-rowheight-list.ts +62 -0
  17. package/components/enhanced-imageview.ts +128 -0
  18. package/components/image-pager.ts +177 -0
  19. package/components/page-control.ts +91 -0
  20. package/components/pageviewer-titlebar.ts +170 -0
  21. package/components/pageviewer.ts +124 -0
  22. package/components/rotating-view.ts +126 -0
  23. package/components/searchbar.ts +373 -0
  24. package/components/sheet.ts +113 -0
  25. package/components/single-views.ts +828 -0
  26. package/components/spinners/loading-double-rings.ts +121 -0
  27. package/components/spinners/loading-dual-ring.ts +90 -0
  28. package/components/spinners/loading-wedges.ts +112 -0
  29. package/components/spinners/spinner-androidstyle.ts +264 -0
  30. package/components/static-preference-listview.ts +991 -0
  31. package/components/symbol-button.ts +105 -0
  32. package/components/tabbar.ts +451 -0
  33. package/controller/base-controller.ts +216 -0
  34. package/controller/controller-router.ts +74 -0
  35. package/controller/pageviewer-controller.ts +86 -0
  36. package/controller/presented-page-controller.ts +57 -0
  37. package/controller/splitview-controller.ts +323 -0
  38. package/controller/tabbar-controller.ts +99 -0
  39. package/package.json +23 -0
  40. package/test.ts +0 -0
  41. package/tsconfig.json +121 -0
  42. package/utils/colors.ts +13 -0
  43. package/utils/cvid.ts +34 -0
  44. package/utils/l10n.ts +42 -0
  45. package/utils/path.ts +100 -0
  46. package/utils/rect.ts +67 -0
  47. package/utils/uitools.ts +117 -0
@@ -0,0 +1,691 @@
1
+ /**
2
+ * # cview PreferenceListView_dynamic
3
+ *
4
+ * 便捷的设置列表实现. 样式以及功能均以 PreferenceListView_static 为准.
5
+ *
6
+ * 优势在于:
7
+ *
8
+ * - 可以实现 sections 重新写入.
9
+ *
10
+ * 劣势在于:
11
+ *
12
+ * - 由于每个 cell 不能单独布局, 因此标题和内容的长度无法动态调整, 在两者都比较短的情况下没有问题, 长了布局可能会重叠.
13
+ * - 不能真正实现 selectable 为 false, 分割线仍然会闪动
14
+ *
15
+ * 为了缓解上面的问题, 让修改布局无需调整源代码, 增加下列 props:
16
+ *
17
+ * - stringLeftInset?: number = 120 将同时作用于 string, number, integer, list, 但是由于后三者内容可控, 可视为只作用于 string
18
+ * - infoAndLinkLeftInset?: number = 120 作用于 info, link
19
+ * - sliderWidth?: number = 200 作用于 slider
20
+ * - tabWidth?: number = 200 作用于 tab
21
+ * 注意以上的修改是应用于 template, 而不是应用于单个 cell 的
22
+ *
23
+ * 独特方法:
24
+ *
25
+ * - cview.sections = sections 可以写入新的 sections
26
+ */
27
+
28
+ import { Base } from "./base";
29
+
30
+ interface PreferenceSection {
31
+ title: string;
32
+ rows: PrefsRow[]
33
+ }
34
+
35
+ type PreferenceCellTypes = "string" | "number" | "integer" | "stepper" | "boolean" | "slider" | "list" | "tab" | "info" | "link" | "action";
36
+
37
+ type PrefsRow = PrefsRowString | PrefsRowNumber | PrefsRowInteger | PrefsRowStepper | PrefsRowBoolean | PrefsRowSlider | PrefsRowList | PrefsRowTab | PrefsRowInfo | PrefsRowLink | PrefsRowAction;
38
+
39
+ interface PrefsRowBase {
40
+ type: PreferenceCellTypes;
41
+ key?: string;
42
+ title?: string;
43
+ titleColor?: UIColor;
44
+ changedEvent?: () => void;
45
+ }
46
+
47
+ interface PrefsRowString extends PrefsRowBase {
48
+ type: "string";
49
+ value?: string;
50
+ placeholder?: string;
51
+ textColor?: UIColor;
52
+ }
53
+
54
+ interface PrefsRowNumber extends PrefsRowBase {
55
+ type: "number";
56
+ value?: number;
57
+ placeholder?: string;
58
+ textColor?: UIColor;
59
+ min?: number;
60
+ max?: number;
61
+ }
62
+
63
+ interface PrefsRowInteger extends PrefsRowBase {
64
+ type: "integer";
65
+ value?: number;
66
+ placeholder?: string;
67
+ textColor?: UIColor;
68
+ min?: number;
69
+ max?: number;
70
+ }
71
+
72
+ interface PrefsRowStepper extends PrefsRowBase {
73
+ type: "stepper";
74
+ value?: number;
75
+ min?: number;
76
+ max?: number;
77
+ }
78
+
79
+ interface PrefsRowBoolean extends PrefsRowBase {
80
+ type: "boolean";
81
+ value?: boolean;
82
+ onColor?: UIColor;
83
+ thumbColor?: UIColor;
84
+ }
85
+
86
+ interface PrefsRowSlider extends PrefsRowBase {
87
+ type: "slider";
88
+ value?: number;
89
+ min?: number;
90
+ max?: number;
91
+ decimal?: number;
92
+ minColor?: UIColor;
93
+ maxColor?: UIColor;
94
+ thumbColor?: UIColor;
95
+ }
96
+
97
+ interface PrefsRowList extends PrefsRowBase {
98
+ type: "list";
99
+ value?: number;
100
+ items: string[];
101
+ }
102
+
103
+ interface PrefsRowTab extends PrefsRowBase {
104
+ type: "tab";
105
+ value?: number;
106
+ items: string[];
107
+ }
108
+
109
+ interface PrefsRowInfo extends PrefsRowBase {
110
+ type: "info";
111
+ value?: string;
112
+ }
113
+
114
+ interface PrefsRowLink extends PrefsRowBase {
115
+ type: "link";
116
+ value?: string;
117
+ }
118
+
119
+ interface PrefsRowAction extends PrefsRowBase {
120
+ type: "action";
121
+ value?: () => void;
122
+ destructive?: boolean;
123
+ }
124
+
125
+ const selectableTypes = [
126
+ "string",
127
+ "number",
128
+ "integer",
129
+ "stepper",
130
+ "list",
131
+ "link",
132
+ "action"
133
+ ];
134
+
135
+ interface CunstomProps extends UiTypes.ListProps {
136
+ stringLeftInset?: number;
137
+ infoAndLinkLeftInset?: number;
138
+ sliderWidth?: number;
139
+ tabWidth?: number;
140
+ }
141
+
142
+ interface RequiredCunstomProps extends UiTypes.ListProps {
143
+ stringLeftInset: number;
144
+ infoAndLinkLeftInset: number;
145
+ sliderWidth: number;
146
+ tabWidth: number;
147
+ }
148
+
149
+
150
+ export class DynamicPreferenceListView extends Base<UIListView, UiTypes.ListOptions>{
151
+ _defineView: () => UiTypes.ListOptions;
152
+ private _sections: PreferenceSection[];
153
+ private _props: RequiredCunstomProps;
154
+ constructor({ sections, props, layout, events = {} }: {
155
+ sections: PreferenceSection[];
156
+ props: CunstomProps;
157
+ layout?: (make: MASConstraintMaker, view: UIListView) => void;
158
+ events?: {
159
+ changed?: (values: any) => void;
160
+ };
161
+
162
+ }) {
163
+ super();
164
+ this._sections = sections.map(n => ({
165
+ title: n.title,
166
+ rows: n.rows.map(r => ({ ...r }))
167
+ }));
168
+ this._props = {
169
+ stringLeftInset: 120,
170
+ infoAndLinkLeftInset: 120,
171
+ sliderWidth: 200,
172
+ tabWidth: 200,
173
+ ...props
174
+ };
175
+ this._layout = layout;
176
+ this._defineView = () => {
177
+ return {
178
+ type: "list",
179
+ props: {
180
+ style: 2,
181
+ ...this._props,
182
+ id: this.id,
183
+ template: {
184
+ views: [
185
+ {
186
+ type: "view",
187
+ props: {
188
+ id: "bgview",
189
+ bgcolor: $color("secondarySurface")
190
+ },
191
+ layout: $layout.fill
192
+ },
193
+ {
194
+ type: "label",
195
+ props: {
196
+ id: "title",
197
+ font: $font(17)
198
+ },
199
+ layout: (make, view) => {
200
+ make.top.bottom.inset(0);
201
+ make.left.right.inset(15);
202
+ }
203
+ },
204
+ {
205
+ type: "view",
206
+ props: {},
207
+ layout: (make, view) => {
208
+ make.top.bottom.inset(0);
209
+ make.left.right.inset(15);
210
+ },
211
+ views: [
212
+ {
213
+ type: "view",
214
+ props: {
215
+ id: "label_and_chevron"
216
+ },
217
+ layout: $layout.fill,
218
+ views: [
219
+ {
220
+ type: "image",
221
+ props: {
222
+ symbol: "chevron.right",
223
+ tintColor: $color("lightGray", "darkGray"),
224
+ contentMode: 1
225
+ },
226
+ layout: (make, view) => {
227
+ make.centerY.equalTo(view.super);
228
+ make.size.equalTo($size(17, 17));
229
+ make.right.inset(0);
230
+ }
231
+ },
232
+ {
233
+ type: "label",
234
+ props: {
235
+ id: "label_before_chevron",
236
+ align: $align.right,
237
+ font: $font(17)
238
+ },
239
+ layout: (make, view) => {
240
+ make.centerY.equalTo(view.super);
241
+ make.left.inset(this._props.stringLeftInset - 15);
242
+ make.right.equalTo(view.prev.left).inset(5);
243
+ }
244
+ }
245
+ ]
246
+ },
247
+ {
248
+ type: "view",
249
+ props: {
250
+ id: "number_and_stepper"
251
+ },
252
+ layout: $layout.fill,
253
+ views: [
254
+ {
255
+ type: "stepper",
256
+ props: {
257
+ id: "stepper"
258
+ },
259
+ layout: (make, view) => {
260
+ make.centerY.equalTo(view.super);
261
+ make.right.inset(0);
262
+ },
263
+ events: {
264
+ changed: sender => {
265
+ const { section, row } = sender.info;
266
+ this._sections[section].rows[row].value =
267
+ sender.value;
268
+ this.view.data = this._map(this._sections);
269
+ if (events.changed)
270
+ events.changed(this.values);
271
+ }
272
+ }
273
+ },
274
+ {
275
+ type: "label",
276
+ props: {
277
+ id: "label_stepper",
278
+ align: $align.right
279
+ },
280
+ layout: (make, view) => {
281
+ make.top.bottom.inset(0);
282
+ make.right.equalTo(view.prev.left).inset(10);
283
+ make.width.equalTo(100);
284
+ }
285
+ }
286
+ ]
287
+ },
288
+ {
289
+ type: "view",
290
+ props: {
291
+ id: "slider_and_number"
292
+ },
293
+ layout: $layout.fill,
294
+ views: [
295
+ {
296
+ type: "slider",
297
+ props: {
298
+ id: "slider"
299
+ },
300
+ layout: (make, view) => {
301
+ make.centerY.equalTo(view.super);
302
+ make.right.inset(40);
303
+ make.width.equalTo(this._props.sliderWidth - 40);
304
+ },
305
+ events: {
306
+ changed: sender => {
307
+ const { section, row } = sender.info;
308
+ const options = this._sections[section].rows[row] as PrefsRowSlider;
309
+ const label = sender.next as UILabelView;
310
+ label.text = this._handleSliderValue(
311
+ sender.value,
312
+ options.decimal,
313
+ options.min,
314
+ options.max
315
+ ).toString();
316
+ },
317
+ touchesEnded: sender => {
318
+ const { section, row } = sender.info;
319
+ const options = this._sections[section].rows[row] as PrefsRowSlider;
320
+ this._sections[section].rows[
321
+ row
322
+ ].value = this._handleSliderValue(
323
+ sender.value,
324
+ options.decimal,
325
+ options.min,
326
+ options.max
327
+ );
328
+ this.view.data = this._map(this._sections);
329
+ if (events.changed)
330
+ events.changed(this.values);
331
+ }
332
+ }
333
+ },
334
+ {
335
+ type: "label",
336
+ props: {
337
+ id: "label_slider",
338
+ align: $align.center
339
+ },
340
+ layout: (make, view) => {
341
+ make.top.bottom.inset(0);
342
+ make.right.inset(0);
343
+ make.width.equalTo(44);
344
+ }
345
+ }
346
+ ]
347
+ },
348
+ {
349
+ type: "switch",
350
+ props: {
351
+ id: "switch"
352
+ },
353
+ layout: (make, view) => {
354
+ make.centerY.equalTo(view.super);
355
+ make.right.inset(0);
356
+ },
357
+ events: {
358
+ changed: sender => {
359
+ const { section, row } = sender.info;
360
+ this._sections[section].rows[row].value = sender.on;
361
+ $delay(0.2, () => {
362
+ this.view.data = this._map(this._sections);
363
+ if (events.changed)
364
+ events.changed(this.values);
365
+ });
366
+ }
367
+ }
368
+ },
369
+ {
370
+ type: "tab",
371
+ props: {
372
+ id: "tab"
373
+ },
374
+ layout: (make, view) => {
375
+ make.centerY.equalTo(view.super);
376
+ make.height.equalTo(32);
377
+ make.width.equalTo(this._props.tabWidth);
378
+ make.right.inset(0);
379
+ },
380
+ events: {
381
+ changed: sender => {
382
+ const { section, row } = sender.info;
383
+ this._sections[section].rows[row].value = sender.index;
384
+ $delay(0.3, () => {
385
+ this.view.data = this._map(this._sections);
386
+ if (events.changed)
387
+ events.changed(this.values);
388
+ });
389
+ }
390
+ }
391
+ },
392
+ {
393
+ type: "label",
394
+ props: {
395
+ id: "label_info_link",
396
+ align: $align.right
397
+ },
398
+ layout: (make, view) => {
399
+ make.top.bottom.inset(0);
400
+ make.left.inset(this._props.infoAndLinkLeftInset);
401
+ make.right.inset(0);
402
+ }
403
+ }
404
+ ]
405
+ }
406
+ ]
407
+ },
408
+ data: this._map(this._sections)
409
+ },
410
+ layout: this._layout,
411
+ events: {
412
+ didSelect: (sender, indexPath, data) => {
413
+ const row = this._sections[indexPath.section].rows[indexPath.row];
414
+ if (!selectableTypes.includes(row.type)) return;
415
+ switch (row.type) {
416
+ case "string": {
417
+ $input.text({
418
+ type: $kbType.default,
419
+ placeholder: row.placeholder,
420
+ handler: text => {
421
+ row.value = text;
422
+ sender.data = this._map(this._sections);
423
+ if (events.changed) events.changed(this.values);
424
+ }
425
+ });
426
+ break;
427
+ }
428
+ case "number": {
429
+ $input.text({
430
+ type: $kbType.decimal,
431
+ placeholder: row.placeholder,
432
+ handler: text => {
433
+ let num = this._handleText(text, row.type);
434
+ if (num === undefined) return;
435
+ if (row.min !== undefined && num < row.min) num = row.min;
436
+ if (row.max !== undefined && num > row.max) num = row.max;
437
+ row.value = num;
438
+ sender.data = this._map(this._sections);
439
+ if (events.changed) events.changed(this.values);
440
+ }
441
+ });
442
+ break;
443
+ }
444
+ case "integer": {
445
+ $input.text({
446
+ type: $kbType.number,
447
+ placeholder: row.placeholder,
448
+ handler: text => {
449
+ let num = this._handleText(text, row.type);
450
+ if (num === undefined) return;
451
+ if (row.min !== undefined && num < row.min) num = row.min;
452
+ if (row.max !== undefined && num > row.max) num = row.max;
453
+ row.value = num;
454
+ sender.data = this._map(this._sections);
455
+ if (events.changed) events.changed(this.values);
456
+ }
457
+ });
458
+ break;
459
+ }
460
+ case "list": {
461
+ $ui.menu({
462
+ items: row.items,
463
+ handler: (title, index) => {
464
+ row.value = index;
465
+ sender.data = this._map(this._sections);
466
+ if (events.changed) events.changed(this.values);
467
+ }
468
+ });
469
+ break;
470
+ }
471
+ case "link": {
472
+ if (row.value) $safari.open({ url: row.value });
473
+ break;
474
+ }
475
+ case "action": {
476
+ if (row.value) row.value();
477
+ break;
478
+ }
479
+ default:
480
+ break;
481
+ }
482
+ }
483
+ }
484
+ };
485
+ }
486
+ }
487
+
488
+ _handleText(text: string, type: string) {
489
+ switch (type) {
490
+ case "number": {
491
+ const number = parseFloat(text);
492
+ if (isNaN(number)) return;
493
+ return number;
494
+ }
495
+ case "integer": {
496
+ const number = parseInt(text);
497
+ if (isNaN(number)) return;
498
+ return number;
499
+ }
500
+ case "stepper": {
501
+ const number = parseInt(text);
502
+ if (isNaN(number)) return;
503
+ return number;
504
+ }
505
+ default:
506
+ throw new Error("Invalid type");
507
+ }
508
+ }
509
+
510
+ _handleSliderValue(num?: number, decimal?: number, min?: number, max?: number): number {
511
+ if (num === undefined) return min || 0;
512
+ if (decimal === undefined) decimal = 1;
513
+ if (isNaN(num)) num = min || 0;
514
+ if (min !== undefined && num < min) num = min;
515
+ if (max !== undefined && num > max) num = max;
516
+ const adjustedValue = parseFloat(num.toFixed(decimal));
517
+ return adjustedValue;
518
+ }
519
+
520
+ _map(sections: PreferenceSection[]) {
521
+ function generateDefaultRow(options: PrefsRow): any {
522
+ return {
523
+ bgview: { hidden: selectableTypes.includes(options.type) }, // bgview其实是用于调整selectable, 显示此视图就没有highlight效果
524
+ title: {
525
+ text: options.title,
526
+ textColor: options.titleColor || $color("primaryText")
527
+ }, // 标题, 同时用于action
528
+ label_and_chevron: { hidden: true }, // 用于string, number, integer, list
529
+ number_and_stepper: { hidden: true }, // 用于stepper
530
+ slider_and_number: { hidden: true }, // 用于slider
531
+ switch: { hidden: true }, // 用于boolean
532
+ tab: { hidden: true }, // 用于tab
533
+ label_info_link: { hidden: true } // 用于info, link
534
+ };
535
+ }
536
+ return sections.map((section, sectionIndex) => ({
537
+ title: section.title,
538
+ rows: section.rows.map((n, rowIndex) => {
539
+ const data = generateDefaultRow(n);
540
+ switch (n.type) {
541
+ case "string": {
542
+ data.label_and_chevron.hidden = false;
543
+ data.label_before_chevron = {
544
+ textColor: n.textColor || $color("primaryText"),
545
+ text: n.value === undefined ? "" : n.value
546
+ };
547
+ break;
548
+ }
549
+ case "number": {
550
+ data.label_and_chevron.hidden = false;
551
+ data.label_before_chevron = {
552
+ textColor: n.textColor || $color("primaryText"),
553
+ text: n.value === undefined ? "" : n.value
554
+ };
555
+ break;
556
+ }
557
+ case "integer": {
558
+ data.label_and_chevron.hidden = false;
559
+ data.label_before_chevron = {
560
+ textColor: n.textColor || $color("primaryText"),
561
+ text: n.value === undefined ? "" : n.value
562
+ };
563
+ break;
564
+ }
565
+ case "stepper": {
566
+ data.number_and_stepper.hidden = false;
567
+ data.label_stepper = {
568
+ textColor: $color("primaryText"),
569
+ text: n.value === undefined ? "" : n.value
570
+ };
571
+ data.stepper = {
572
+ min: n.min,
573
+ max: n.max,
574
+ value: n.value,
575
+ info: { section: sectionIndex, row: rowIndex, key: n.key }
576
+ };
577
+ break;
578
+ }
579
+ case "boolean": {
580
+ data.switch = {
581
+ hidden: false,
582
+ on: n.value,
583
+ onColor: n.onColor || $color("#34C85A"),
584
+ thumbColor: n.thumbColor,
585
+ info: { section: sectionIndex, row: rowIndex, key: n.key }
586
+ };
587
+ break;
588
+ }
589
+ case "slider": {
590
+ data.slider_and_number.hidden = false;
591
+ const adjustedValue = this._handleSliderValue(
592
+ n.value,
593
+ n.decimal,
594
+ n.min,
595
+ n.max
596
+ );
597
+ data.label_slider = {
598
+ textColor: $color("primaryText"),
599
+ text: adjustedValue
600
+ };
601
+ data.slider = {
602
+ value: adjustedValue,
603
+ info: { section: sectionIndex, row: rowIndex, key: n.key },
604
+ min: n.min,
605
+ max: n.max,
606
+ minColor: n.minColor || $color("systemLink"),
607
+ maxColor: n.maxColor,
608
+ thumbColor: n.thumbColor
609
+ };
610
+ break;
611
+ }
612
+ case "list": {
613
+ data.label_and_chevron.hidden = false;
614
+ data.label_before_chevron = {
615
+ textColor: $color("secondaryText"),
616
+ text: n.items[n.value || 0]
617
+ };
618
+ break;
619
+ }
620
+ case "tab": {
621
+ data.tab = {
622
+ hidden: false,
623
+ items: n.items,
624
+ index: n.value,
625
+ info: { section: sectionIndex, row: rowIndex, key: n.key }
626
+ };
627
+ break;
628
+ }
629
+ case "info": {
630
+ data.label_info_link = {
631
+ hidden: false,
632
+ textColor: $color("secondaryText"),
633
+ text: n.value
634
+ };
635
+ break;
636
+ }
637
+ case "link": {
638
+ data.label_info_link = {
639
+ hidden: false,
640
+ styledText: `[${n.value}]()`
641
+ };
642
+ break;
643
+ }
644
+ case "action": {
645
+ data.title.textColor = n.destructive
646
+ ? $color("red")
647
+ : $color("systemLink");
648
+ break;
649
+ }
650
+ default:
651
+ break;
652
+ }
653
+ return data;
654
+ })
655
+ }));
656
+ }
657
+
658
+ get sections() {
659
+ return this._sections;
660
+ }
661
+
662
+ set sections(sections) {
663
+ this._sections = sections.map(n => ({
664
+ title: n.title,
665
+ rows: n.rows.map(r => ({ ...r }))
666
+ }));
667
+ this.view.data = this._map(this._sections);
668
+ }
669
+
670
+ get values() {
671
+ const values: { [key: string]: any } = {};
672
+ const excludedTypes = ["action", "info", "link"];
673
+ this._sections.forEach(section => {
674
+ section.rows.forEach(row => {
675
+ if (row.key && !excludedTypes.includes(row.type)) {
676
+ values[row.key] = row.value;
677
+ }
678
+ });
679
+ });
680
+ return values;
681
+ }
682
+
683
+ set(key: string, value: any) {
684
+ this._sections.forEach(section => {
685
+ section.rows.forEach(row => {
686
+ if (row.key === key) row.value = value;
687
+ });
688
+ });
689
+ this.view.data = this._map(this._sections);
690
+ }
691
+ }