jsbox-cview 1.6.4 → 1.6.6

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 (50) hide show
  1. package/components/dynamic-itemsize-section-matrix.ts +363 -0
  2. package/components/oc-webview.ts +50 -0
  3. package/dist/components/alert/input-alert.js +1 -2
  4. package/dist/components/alert/login-alert.js +1 -2
  5. package/dist/components/alert/plain-alert.js +1 -2
  6. package/dist/components/custom-navigation-bar.js +7 -1
  7. package/dist/components/dialogs/form-dialog.js +1 -2
  8. package/dist/components/dialogs/list-dialog.js +1 -2
  9. package/dist/components/dialogs/text-dialog.js +1 -2
  10. package/dist/components/dynamic-contextmenu-view.js +5 -1
  11. package/dist/components/dynamic-itemsize-matrix.js +17 -15
  12. package/dist/components/dynamic-itemsize-section-matrix.js +293 -0
  13. package/dist/components/dynamic-preference-listview.js +25 -16
  14. package/dist/components/dynamic-rowheight-list.js +10 -3
  15. package/dist/components/enhanced-imageview.js +1 -1
  16. package/dist/components/flowlayout.js +10 -13
  17. package/dist/components/image-pager.js +6 -1
  18. package/dist/components/oc-webview.js +37 -0
  19. package/dist/components/page-control.js +2 -13
  20. package/dist/components/pageviewer-titlebar.js +7 -13
  21. package/dist/components/pageviewer.js +4 -1
  22. package/dist/components/refresh-button.js +3 -4
  23. package/dist/components/rotating-view.js +10 -2
  24. package/dist/components/searchbar.js +8 -1
  25. package/dist/components/single-views.js +11 -4
  26. package/dist/components/spinners/loading-dual-ring.js +3 -12
  27. package/dist/components/spinners/loading-wedges.js +3 -12
  28. package/dist/components/spinners/spinner-androidstyle.js +7 -1
  29. package/dist/components/static-preference-listview.js +13 -10
  30. package/dist/components/symbol-button.js +8 -1
  31. package/dist/components/tabbar.js +8 -1
  32. package/dist/controller/pageviewer-controller.js +4 -1
  33. package/dist/controller/presented-page-controller.js +7 -9
  34. package/dist/controller/splitview-controller.js +23 -11
  35. package/dist/controller/tabbar-controller.js +13 -14
  36. package/dist/index.js +1 -0
  37. package/dist/test/dialog-sheet.js +3 -12
  38. package/dist/test/dynamic-itemsize-section-matrix.js +138 -0
  39. package/dist/test/form-dialog.js +3 -12
  40. package/dist/test/oc-webview.js +195 -0
  41. package/dist/test/refresh-button.js +3 -12
  42. package/dist/utils/l10n.js +1 -2
  43. package/dist/utils/path.js +8 -9
  44. package/dist/utils/rect.js +8 -9
  45. package/dist/utils/uitools.js +6 -6
  46. package/index.ts +1 -0
  47. package/package.json +4 -3
  48. package/test/dynamic-itemsize-section-matrix.ts +142 -0
  49. package/test/oc-webview.ts +197 -0
  50. package/tsconfig.json +1 -1
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  const form_dialog_1 = require("../components/dialogs/form-dialog");
13
4
  $ui.render({
@@ -19,8 +10,8 @@ $ui.render({
19
10
  },
20
11
  layout: $layout.center,
21
12
  events: {
22
- tapped: () => __awaiter(void 0, void 0, void 0, function* () {
23
- const values = yield (0, form_dialog_1.formDialog)({
13
+ tapped: async () => {
14
+ const values = await (0, form_dialog_1.formDialog)({
24
15
  sections: [
25
16
  {
26
17
  title: "Section 1",
@@ -53,7 +44,7 @@ $ui.render({
53
44
  title: "Values",
54
45
  message: JSON.stringify(values, null, 2),
55
46
  });
56
- }),
47
+ },
57
48
  },
58
49
  },
59
50
  ],
@@ -0,0 +1,195 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const oc_webview_1 = require("../components/oc-webview");
4
+ const initialURL = "https://www.example.com";
5
+ const normalizeURL = (value) => {
6
+ const url = value.trim();
7
+ if (!url)
8
+ return initialURL;
9
+ if (/^[a-z][a-z\d+\-.]*:\/\//i.test(url))
10
+ return url;
11
+ return `https://${url}`;
12
+ };
13
+ const currentURL = (sender) => {
14
+ const nsurl = sender.invoke("URL");
15
+ return nsurl ? nsurl.invoke("absoluteString").rawValue() : "";
16
+ };
17
+ const currentTitle = (sender) => {
18
+ const title = sender.invoke("title");
19
+ return title ? title.rawValue() : "";
20
+ };
21
+ const syncAddress = (sender) => {
22
+ const input = $("oc-webview-url");
23
+ if (!input)
24
+ return;
25
+ const url = currentURL(sender);
26
+ if (url)
27
+ input.text = url;
28
+ };
29
+ const logState = (event, sender, error) => {
30
+ const url = currentURL(sender);
31
+ const title = currentTitle(sender);
32
+ const message = error ? ` error=${error.localizedDescription}` : "";
33
+ console.log(`[${event}] url=${url} title=${title}${message}`);
34
+ };
35
+ const localStorageScript = `
36
+ (() => {
37
+ const data = {};
38
+ for (let i = 0; i < localStorage.length; i++) {
39
+ const key = localStorage.key(i);
40
+ if (key !== null) data[key] = localStorage.getItem(key);
41
+ }
42
+ return JSON.stringify(data);
43
+ })()
44
+ `;
45
+ const webView = new oc_webview_1.OCWebView({
46
+ props: {
47
+ url: initialURL,
48
+ },
49
+ layout: (make, view) => {
50
+ make.top.equalTo($("oc-webview-toolbar").bottom);
51
+ make.left.right.bottom.inset(0);
52
+ },
53
+ events: {
54
+ didStart: (sender) => {
55
+ syncAddress(sender);
56
+ logState("didStart", sender);
57
+ },
58
+ didFinish: async (sender) => {
59
+ syncAddress(sender);
60
+ logState("didFinish", sender);
61
+ try {
62
+ const localStorage = await webView.exec(localStorageScript);
63
+ console.log(`[localStorage] ${localStorage || "{}"}`);
64
+ }
65
+ catch (error) {
66
+ console.log(`[localStorage] error=${error?.localizedDescription || error}`);
67
+ }
68
+ },
69
+ didFail: (sender, error) => {
70
+ syncAddress(sender);
71
+ logState("didFail", sender, error);
72
+ },
73
+ },
74
+ });
75
+ const loadURL = (value) => {
76
+ const url = normalizeURL(value);
77
+ const input = $("oc-webview-url");
78
+ if (input)
79
+ input.text = url;
80
+ const nsurl = $objc("NSURL").invoke("URLWithString:", url);
81
+ const request = $objc("NSURLRequest").invoke("requestWithURL:", nsurl);
82
+ webView.webView.invoke("loadRequest:", request);
83
+ };
84
+ $ui.render({
85
+ views: [
86
+ {
87
+ type: "view",
88
+ props: {
89
+ id: "oc-webview-toolbar",
90
+ },
91
+ layout: (make, view) => {
92
+ make.top.equalTo(view.super.safeAreaTop);
93
+ make.left.right.inset(0);
94
+ make.height.equalTo(88);
95
+ },
96
+ views: [
97
+ {
98
+ type: "input",
99
+ props: {
100
+ id: "oc-webview-url",
101
+ text: initialURL,
102
+ type: $kbType.url,
103
+ bgcolor: $color("secondarySurface"),
104
+ radius: 8,
105
+ placeholder: "输入网址后回车",
106
+ },
107
+ layout: (make, view) => {
108
+ make.top.inset(12);
109
+ make.left.right.inset(12);
110
+ make.height.equalTo(36);
111
+ },
112
+ events: {
113
+ returned: (sender) => {
114
+ loadURL(sender.text);
115
+ },
116
+ },
117
+ },
118
+ {
119
+ type: "view",
120
+ props: {},
121
+ layout: (make, view) => {
122
+ make.top.equalTo(view.prev.bottom).offset(8);
123
+ make.left.right.bottom.inset(12);
124
+ },
125
+ views: [
126
+ {
127
+ type: "button",
128
+ props: {
129
+ title: "后退",
130
+ },
131
+ layout: (make, view) => {
132
+ make.left.top.bottom.inset(0);
133
+ make.width.equalTo(72);
134
+ },
135
+ events: {
136
+ tapped: () => {
137
+ webView.goBack();
138
+ },
139
+ },
140
+ },
141
+ {
142
+ type: "button",
143
+ props: {
144
+ title: "前进",
145
+ },
146
+ layout: (make, view) => {
147
+ make.left.equalTo(view.prev.right).offset(8);
148
+ make.top.bottom.equalTo(view.prev);
149
+ make.width.equalTo(72);
150
+ },
151
+ events: {
152
+ tapped: () => {
153
+ webView.goForward();
154
+ },
155
+ },
156
+ },
157
+ {
158
+ type: "button",
159
+ props: {
160
+ title: "刷新",
161
+ },
162
+ layout: (make, view) => {
163
+ make.left.equalTo(view.prev.right).offset(8);
164
+ make.top.bottom.equalTo(view.prev);
165
+ make.width.equalTo(72);
166
+ },
167
+ events: {
168
+ tapped: () => {
169
+ webView.reload();
170
+ },
171
+ },
172
+ },
173
+ {
174
+ type: "button",
175
+ props: {
176
+ title: "分享",
177
+ },
178
+ layout: (make, view) => {
179
+ make.left.equalTo(view.prev.right).offset(8);
180
+ make.top.bottom.equalTo(view.prev);
181
+ make.width.equalTo(72);
182
+ },
183
+ events: {
184
+ tapped: () => {
185
+ $share.sheet(currentURL(webView.webView) || initialURL);
186
+ },
187
+ },
188
+ },
189
+ ],
190
+ },
191
+ ],
192
+ },
193
+ webView.definition,
194
+ ],
195
+ });
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
- return new (P || (P = Promise))(function (resolve, reject) {
5
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9
- });
10
- };
11
2
  Object.defineProperty(exports, "__esModule", { value: true });
12
3
  const refresh_button_1 = require("../components/refresh-button");
13
4
  const refreshButton = new refresh_button_1.RefreshButton({
@@ -23,11 +14,11 @@ const refreshButton = new refresh_button_1.RefreshButton({
23
14
  make.centerX.equalTo(view.super);
24
15
  },
25
16
  events: {
26
- tapped: () => __awaiter(void 0, void 0, void 0, function* () {
17
+ tapped: async () => {
27
18
  refreshButton.loading = true;
28
- yield $wait(2);
19
+ await $wait(2);
29
20
  refreshButton.loading = false;
30
- }),
21
+ },
31
22
  },
32
23
  });
33
24
  $ui.render({
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // 用于自定义的国际化支持
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.l10n = void 0;
4
+ exports.l10n = l10n;
5
5
  const language = $device.info.language;
6
6
  const strings = {
7
7
  "zh-Hans": {
@@ -41,4 +41,3 @@ function l10n(key) {
41
41
  const value = strings[language][key];
42
42
  return value || key;
43
43
  }
44
- exports.l10n = l10n;
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
2
  // 用于处理路径的工具函数
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.getFileSize = exports.getModificationDate = exports.getCreationDate = exports.join = exports.extname = exports.basename = exports.dirname = exports.split = void 0;
4
+ exports.split = split;
5
+ exports.dirname = dirname;
6
+ exports.basename = basename;
7
+ exports.extname = extname;
8
+ exports.join = join;
9
+ exports.getCreationDate = getCreationDate;
10
+ exports.getModificationDate = getModificationDate;
11
+ exports.getFileSize = getFileSize;
5
12
  function _splitProtocol(path) {
6
13
  const regex = /^\w+:\/\//;
7
14
  const result = regex.exec(path);
@@ -37,15 +44,12 @@ function split(path) {
37
44
  return [protocol + remainingPath.slice(0, lastIndex), remainingPath.slice(lastIndex + 1)];
38
45
  }
39
46
  }
40
- exports.split = split;
41
47
  function dirname(path) {
42
48
  return split(path)[0];
43
49
  }
44
- exports.dirname = dirname;
45
50
  function basename(path) {
46
51
  return split(path)[1];
47
52
  }
48
- exports.basename = basename;
49
53
  function extname(path) {
50
54
  const _basename = basename(path);
51
55
  if (!_basename)
@@ -58,7 +62,6 @@ function extname(path) {
58
62
  return "." + components.slice(-1)[0];
59
63
  }
60
64
  }
61
- exports.extname = extname;
62
65
  // 拼接目录
63
66
  function join(...args) {
64
67
  return args
@@ -73,7 +76,6 @@ function join(...args) {
73
76
  .filter((x) => x.length)
74
77
  .join("/");
75
78
  }
76
- exports.join = join;
77
79
  function _getAttributes(path) {
78
80
  if (!$file.exists(path))
79
81
  throw new Error("invalid path");
@@ -89,16 +91,13 @@ function getCreationDate(path) {
89
91
  return 0;
90
92
  return NSFileCreationDate.getTime();
91
93
  }
92
- exports.getCreationDate = getCreationDate;
93
94
  function getModificationDate(path) {
94
95
  const { NSFileModificationDate } = _getAttributes(path);
95
96
  if (!NSFileModificationDate)
96
97
  return 0;
97
98
  return NSFileModificationDate.getTime();
98
99
  }
99
- exports.getModificationDate = getModificationDate;
100
100
  function getFileSize(path) {
101
101
  const { NSFileSize } = _getAttributes(path);
102
102
  return NSFileSize || 0;
103
103
  }
104
- exports.getFileSize = getFileSize;
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
2
  // 用于处理矩形的工具函数
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.inset = exports.translate = exports.union = exports.intersection = exports.intersects = exports.containsRect = exports.containsPoint = exports.center = void 0;
4
+ exports.center = center;
5
+ exports.containsPoint = containsPoint;
6
+ exports.containsRect = containsRect;
7
+ exports.intersects = intersects;
8
+ exports.intersection = intersection;
9
+ exports.union = union;
10
+ exports.translate = translate;
11
+ exports.inset = inset;
5
12
  /**
6
13
  * When called without arguments, return the center of the rectangle.
7
14
  * When a Point is passed as an argument, the rectangle’s x and y values
@@ -16,7 +23,6 @@ function center(rect, point) {
16
23
  rect.y = py - h / 2;
17
24
  return point;
18
25
  }
19
- exports.center = center;
20
26
  /**
21
27
  * Return true if the given point lies within the bounds of the rectangle,
22
28
  * false otherwise.
@@ -26,7 +32,6 @@ function containsPoint(rect, point) {
26
32
  const { x: px, y: py } = point;
27
33
  return x <= px && px <= x + w && y <= py && py <= y + h;
28
34
  }
29
- exports.containsPoint = containsPoint;
30
35
  /**
31
36
  * Return true if the given rectangle lies entirely within the bounds of
32
37
  * this rectangle, false otherwise.
@@ -36,7 +41,6 @@ function containsRect(rect, otherRect) {
36
41
  const { x: x1, y: y1, width: w1, height: h1 } = otherRect;
37
42
  return x <= x1 && y <= y1 && x1 + w1 <= x + w && y1 + h1 <= y + h;
38
43
  }
39
- exports.containsRect = containsRect;
40
44
  /**
41
45
  * Return true if this rectangle intersects with the other rectangle,
42
46
  * false otherwise.
@@ -46,7 +50,6 @@ function intersects(rect, otherRect) {
46
50
  const { x: x1, y: y1, width: w1, height: h1 } = otherRect;
47
51
  return x < x1 + w1 && x1 < x + w && y < y1 + h1 && y1 < y + h;
48
52
  }
49
- exports.intersects = intersects;
50
53
  /**
51
54
  * Return a $rect that corresponds to the intersection of this rectangle with
52
55
  * the other one.
@@ -60,7 +63,6 @@ function intersection(rect, otherRect) {
60
63
  const nh = Math.min(y + h, y1 + h1) - ny;
61
64
  return $rect(nx, ny, nw, nh);
62
65
  }
63
- exports.intersection = intersection;
64
66
  /**
65
67
  * Return the smallest $rect that encloses both rectangles.
66
68
  */
@@ -73,7 +75,6 @@ function union(rect, otherRect) {
73
75
  const nh = Math.max(y + h, y1 + h1) - ny;
74
76
  return $rect(nx, ny, nw, nh);
75
77
  }
76
- exports.union = union;
77
78
  /**
78
79
  * Equivalent to $rect(r.x + x, r.y + y, r.w, r.h)
79
80
  */
@@ -82,7 +83,6 @@ function translate(rect, point) {
82
83
  const { x: x1, y: y1 } = point;
83
84
  return $rect(x + x1, y + y1, width, height);
84
85
  }
85
- exports.translate = translate;
86
86
  /**
87
87
  * Return a $rect that is adjusted by the given edge insets.
88
88
  */
@@ -91,4 +91,3 @@ function inset(rect, insets) {
91
91
  const { top, left, bottom, right } = insets;
92
92
  return $rect(x + left, y + top, width - left - right, height - top - bottom);
93
93
  }
94
- exports.inset = inset;
@@ -1,7 +1,12 @@
1
1
  "use strict";
2
2
  // 用于UI相关的工具函数
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.setLayer = exports.layerCommonOptions = exports.absoluteFrame = exports.getTextHeight = exports.getTextWidth = exports.getWindowSize = void 0;
4
+ exports.layerCommonOptions = void 0;
5
+ exports.getWindowSize = getWindowSize;
6
+ exports.getTextWidth = getTextWidth;
7
+ exports.getTextHeight = getTextHeight;
8
+ exports.absoluteFrame = absoluteFrame;
9
+ exports.setLayer = setLayer;
5
10
  /**
6
11
  * 立即获得window size
7
12
  */
@@ -9,7 +14,6 @@ function getWindowSize() {
9
14
  const window = $objc("UIWindow").$keyWindow().jsValue();
10
15
  return window.size;
11
16
  }
12
- exports.getWindowSize = getWindowSize;
13
17
  /**
14
18
  * 获取单行字符串应有的宽度
15
19
  * 默认额外添加3 inset
@@ -22,7 +26,6 @@ function getTextWidth(text, { font = $font(17), inset = 3 } = {}) {
22
26
  lineSpacing: 0,
23
27
  }).width) + inset);
24
28
  }
25
- exports.getTextWidth = getTextWidth;
26
29
  /**
27
30
  * 获取字符串指定宽度后应有的高度
28
31
  * 默认额外添加3 inset
@@ -35,7 +38,6 @@ function getTextHeight(text, { width = 300, font = $font(17), inset = 3, lineSpa
35
38
  lineSpacing,
36
39
  }).height) + inset);
37
40
  }
38
- exports.getTextHeight = getTextHeight;
39
41
  /**
40
42
  * 计算某个view在某个上级view(若不指定则为UIWindow)上的绝对frame
41
43
  * 此方法不考虑旋转变形等特殊情况
@@ -52,7 +54,6 @@ function absoluteFrame(view, endView) {
52
54
  }
53
55
  return frame;
54
56
  }
55
- exports.absoluteFrame = absoluteFrame;
56
57
  exports.layerCommonOptions = {
57
58
  none: {
58
59
  cornerRadius: 0,
@@ -102,4 +103,3 @@ function setLayer(view, { cornerRadius = 0, shadowRadius = 0, shadowOpacity = 0,
102
103
  layer.invoke("setShadowOffset", shadowOffset);
103
104
  layer.invoke("setShadowColor", shadowColor.ocValue().invoke("CGColor"));
104
105
  }
105
- exports.setLayer = setLayer;
package/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from "./components/base";
2
2
  export * from "./components/custom-navigation-bar";
3
3
  export * from "./components/dynamic-contextmenu-view";
4
4
  export * from "./components/dynamic-itemsize-matrix";
5
+ export * from "./components/dynamic-itemsize-section-matrix";
5
6
  export * from "./components/dynamic-preference-listview";
6
7
  export * from "./components/dynamic-rowheight-list";
7
8
  export * from "./components/enhanced-imageview";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsbox-cview",
3
- "version": "1.6.4",
3
+ "version": "1.6.6",
4
4
  "description": "为 JSBox 设计的微型框架",
5
5
  "repository": {
6
6
  "type": "git",
@@ -10,8 +10,9 @@
10
10
  "bugs": "https://github.com/Gandum2077/JSBox-CView/issues",
11
11
  "main": "dist/index.js",
12
12
  "scripts": {
13
- "test": "tsc && node ./dist/test.js",
14
- "build": "tsc"
13
+ "build:debug": "tsc && npx browserify ${npm_config_entry:-./dist/index.js} > test.js",
14
+ "build": "tsc",
15
+ "format": "prettier --write ."
15
16
  },
16
17
  "keywords": [],
17
18
  "author": "Gandum2077",
@@ -0,0 +1,142 @@
1
+ import {
2
+ DynamicItemSizeSectionMatrix,
3
+ DynamicItemSizeSectionMatrixSection,
4
+ } from "../components/dynamic-itemsize-section-matrix";
5
+
6
+ const makeItem = (sectionName: string, index: number) => {
7
+ return {
8
+ symbol: { symbol: index % 2 === 0 ? "sparkles" : "square.grid.2x2" },
9
+ title: { text: `${sectionName} ${index}` },
10
+ subtitle: { text: `Item index ${index}` },
11
+ badge: { text: `${index}` },
12
+ };
13
+ };
14
+
15
+ const sections: DynamicItemSizeSectionMatrixSection[] = [
16
+ {
17
+ title: "Pinned",
18
+ items: [makeItem("Pinned", 1), makeItem("Pinned", 2), makeItem("Pinned", 3)],
19
+ },
20
+ {
21
+ title: "Long Section Title To Verify The Custom Title Cell Uses Dynamic Height\nLine 2\nLine 3",
22
+ items: [makeItem("Recent", 1), makeItem("Recent", 2), makeItem("Recent", 3), makeItem("Recent", 4)],
23
+ },
24
+ {
25
+ title: "",
26
+ items: [makeItem("Untitled", 1), makeItem("Untitled", 2)],
27
+ },
28
+ ];
29
+
30
+ const matrix = new DynamicItemSizeSectionMatrix({
31
+ props: {
32
+ spacing: 8,
33
+ minItemWidth: $device.isIpad ? 180 : 142,
34
+ maxColumns: $device.isIpad ? 4 : 2,
35
+ data: sections,
36
+ template: {
37
+ views: [
38
+ {
39
+ type: "view",
40
+ props: {
41
+ bgcolor: $color("yellow"),
42
+ cornerRadius: 8,
43
+ },
44
+ layout: $layout.fill,
45
+ views: [
46
+ {
47
+ type: "image",
48
+ props: {
49
+ id: "symbol",
50
+ contentMode: $contentMode.scaleAspectFit,
51
+ tintColor: $color("tint"),
52
+ },
53
+ layout: (make, view) => {
54
+ make.left.top.inset(12);
55
+ make.size.equalTo($size(32, 32));
56
+ },
57
+ },
58
+ {
59
+ type: "label",
60
+ props: {
61
+ id: "badge",
62
+ align: $align.center,
63
+ font: $font(12),
64
+ textColor: $color("white"),
65
+ bgcolor: $color("tint"),
66
+ cornerRadius: 10,
67
+ },
68
+ layout: (make, view) => {
69
+ make.top.right.inset(12);
70
+ make.size.equalTo($size(28, 20));
71
+ },
72
+ },
73
+ {
74
+ type: "label",
75
+ props: {
76
+ id: "title",
77
+ font: $font("bold", 15),
78
+ textColor: $color("primaryText"),
79
+ },
80
+ layout: (make, view) => {
81
+ make.left.right.inset(12);
82
+ make.top.equalTo(view.prev.prev.bottom).offset(10);
83
+ make.height.equalTo(20);
84
+ },
85
+ },
86
+ {
87
+ type: "label",
88
+ props: {
89
+ id: "subtitle",
90
+ font: $font(12),
91
+ textColor: $color("secondaryText"),
92
+ },
93
+ layout: (make, view) => {
94
+ make.left.right.inset(12);
95
+ make.top.equalTo(view.prev.bottom).offset(4);
96
+ make.bottom.inset(12);
97
+ },
98
+ },
99
+ ],
100
+ },
101
+ ],
102
+ },
103
+ },
104
+ layout: $layout.fill,
105
+ events: {
106
+ itemHeight: (width) => Math.max(112, width * 0.72),
107
+ didSelect: (sender, indexPath, data) => {
108
+ const section = matrix.data[indexPath.section];
109
+ const title = (data.title as { text: string }).text;
110
+ $ui.toast(`${section.title || "Untitled"} / ${title}`);
111
+ },
112
+ didLongPress: (sender, indexPath, data) => {
113
+ const title = (data.title as { text: string }).text;
114
+ $ui.alert(`Long pressed ${title}`);
115
+ },
116
+ },
117
+ });
118
+
119
+ $ui.render({
120
+ props: {
121
+ navButtons: [
122
+ {
123
+ symbol: "plus",
124
+ handler: () => {
125
+ const nextIndex = matrix.data[0].items.length + 1;
126
+ matrix.insert({
127
+ indexPath: $indexPath(0, matrix.data[0].items.length),
128
+ value: makeItem("Pinned", nextIndex),
129
+ });
130
+ },
131
+ },
132
+ {
133
+ symbol: "trash",
134
+ handler: () => {
135
+ if (matrix.data[0].items.length === 0) return;
136
+ matrix.delete($indexPath(0, matrix.data[0].items.length - 1));
137
+ },
138
+ },
139
+ ],
140
+ },
141
+ views: [matrix.definition],
142
+ });