@arronqzy/react-view 1.0.32 → 1.0.35

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/styles.css CHANGED
@@ -637,6 +637,10 @@ video {
637
637
  bottom: 0.375rem;
638
638
  }
639
639
 
640
+ .bottom-3 {
641
+ bottom: 0.75rem;
642
+ }
643
+
640
644
  .bottom-\[var\(--rv-ruler-size\)\] {
641
645
  bottom: var(--rv-ruler-size);
642
646
  }
@@ -673,6 +677,10 @@ video {
673
677
  right: 0px;
674
678
  }
675
679
 
680
+ .right-1\.5 {
681
+ right: 0.375rem;
682
+ }
683
+
676
684
  .right-2 {
677
685
  right: 0.5rem;
678
686
  }
@@ -693,6 +701,10 @@ video {
693
701
  top: 0.25rem;
694
702
  }
695
703
 
704
+ .top-1\.5 {
705
+ top: 0.375rem;
706
+ }
707
+
696
708
  .top-2 {
697
709
  top: 0.5rem;
698
710
  }
@@ -701,6 +713,10 @@ video {
701
713
  top: 1rem;
702
714
  }
703
715
 
716
+ .top-\[calc\(100\%\+4px\)\] {
717
+ top: calc(100% + 4px);
718
+ }
719
+
704
720
  .top-\[calc\(100\%\+6px\)\] {
705
721
  top: calc(100% + 6px);
706
722
  }
@@ -713,6 +729,10 @@ video {
713
729
  isolation: isolate;
714
730
  }
715
731
 
732
+ .z-10 {
733
+ z-index: 10;
734
+ }
735
+
716
736
  .z-20 {
717
737
  z-index: 20;
718
738
  }
@@ -725,6 +745,10 @@ video {
725
745
  z-index: 10000;
726
746
  }
727
747
 
748
+ .z-\[10090\] {
749
+ z-index: 10090;
750
+ }
751
+
728
752
  .z-\[10100\] {
729
753
  z-index: 10100;
730
754
  }
@@ -737,6 +761,14 @@ video {
737
761
  z-index: 10150;
738
762
  }
739
763
 
764
+ .z-\[10200\] {
765
+ z-index: 10200;
766
+ }
767
+
768
+ .z-\[10300\] {
769
+ z-index: 10300;
770
+ }
771
+
740
772
  .z-\[70\] {
741
773
  z-index: 70;
742
774
  }
@@ -978,10 +1010,26 @@ video {
978
1010
  height: 100%;
979
1011
  }
980
1012
 
1013
+ .h-px {
1014
+ height: 1px;
1015
+ }
1016
+
1017
+ .max-h-24 {
1018
+ max-height: 6rem;
1019
+ }
1020
+
981
1021
  .max-h-28 {
982
1022
  max-height: 7rem;
983
1023
  }
984
1024
 
1025
+ .max-h-32 {
1026
+ max-height: 8rem;
1027
+ }
1028
+
1029
+ .max-h-40 {
1030
+ max-height: 10rem;
1031
+ }
1032
+
985
1033
  .max-h-44 {
986
1034
  max-height: 11rem;
987
1035
  }
@@ -990,10 +1038,18 @@ video {
990
1038
  max-height: 13rem;
991
1039
  }
992
1040
 
1041
+ .max-h-56 {
1042
+ max-height: 14rem;
1043
+ }
1044
+
993
1045
  .max-h-\[160px\] {
994
1046
  max-height: 160px;
995
1047
  }
996
1048
 
1049
+ .max-h-\[180px\] {
1050
+ max-height: 180px;
1051
+ }
1052
+
997
1053
  .max-h-\[200px\] {
998
1054
  max-height: 200px;
999
1055
  }
@@ -1002,6 +1058,10 @@ video {
1002
1058
  max-height: 78vh;
1003
1059
  }
1004
1060
 
1061
+ .max-h-\[90vh\] {
1062
+ max-height: 90vh;
1063
+ }
1064
+
1005
1065
  .max-h-\[min\(78vh\2c 560px\)\] {
1006
1066
  max-height: min(78vh, 560px);
1007
1067
  }
@@ -1034,6 +1094,10 @@ video {
1034
1094
  min-height: 68px;
1035
1095
  }
1036
1096
 
1097
+ .min-h-\[min\(62vh\2c 640px\)\] {
1098
+ min-height: min(62vh, 640px);
1099
+ }
1100
+
1037
1101
  .min-h-full {
1038
1102
  min-height: 100%;
1039
1103
  }
@@ -1122,10 +1186,22 @@ video {
1122
1186
  width: 108px;
1123
1187
  }
1124
1188
 
1189
+ .w-\[148px\] {
1190
+ width: 148px;
1191
+ }
1192
+
1193
+ .w-\[168px\] {
1194
+ width: 168px;
1195
+ }
1196
+
1125
1197
  .w-\[220px\] {
1126
1198
  width: 220px;
1127
1199
  }
1128
1200
 
1201
+ .w-\[240px\] {
1202
+ width: 240px;
1203
+ }
1204
+
1129
1205
  .w-\[280px\] {
1130
1206
  width: 280px;
1131
1207
  }
@@ -1138,6 +1214,18 @@ video {
1138
1214
  width: min(94vw, 680px);
1139
1215
  }
1140
1216
 
1217
+ .w-\[min\(96vw\2c 1100px\)\] {
1218
+ width: min(96vw, 1100px);
1219
+ }
1220
+
1221
+ .w-\[min\(96vw\2c 960px\)\] {
1222
+ width: min(96vw, 960px);
1223
+ }
1224
+
1225
+ .w-\[var\(--radix-popover-trigger-width\)\] {
1226
+ width: var(--radix-popover-trigger-width);
1227
+ }
1228
+
1141
1229
  .w-\[var\(--rv-ruler-size\)\] {
1142
1230
  width: var(--rv-ruler-size);
1143
1231
  }
@@ -1174,10 +1262,18 @@ video {
1174
1262
  max-width: 140px;
1175
1263
  }
1176
1264
 
1265
+ .max-w-\[180px\] {
1266
+ max-width: 180px;
1267
+ }
1268
+
1177
1269
  .max-w-\[200px\] {
1178
1270
  max-width: 200px;
1179
1271
  }
1180
1272
 
1273
+ .max-w-\[260px\] {
1274
+ max-width: 260px;
1275
+ }
1276
+
1181
1277
  .max-w-\[360px\] {
1182
1278
  max-width: 360px;
1183
1279
  }
@@ -1210,6 +1306,10 @@ video {
1210
1306
  max-width: 90%;
1211
1307
  }
1212
1308
 
1309
+ .max-w-full {
1310
+ max-width: 100%;
1311
+ }
1312
+
1213
1313
  .max-w-lg {
1214
1314
  max-width: 32rem;
1215
1315
  }
@@ -1253,6 +1353,11 @@ video {
1253
1353
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1254
1354
  }
1255
1355
 
1356
+ .rotate-180 {
1357
+ --tw-rotate: 180deg;
1358
+ transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1359
+ }
1360
+
1256
1361
  .transform {
1257
1362
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
1258
1363
  }
@@ -1320,6 +1425,10 @@ video {
1320
1425
  resize: none;
1321
1426
  }
1322
1427
 
1428
+ .resize-y {
1429
+ resize: vertical;
1430
+ }
1431
+
1323
1432
  .resize {
1324
1433
  resize: both;
1325
1434
  }
@@ -1436,6 +1545,11 @@ video {
1436
1545
  gap: 1.25rem;
1437
1546
  }
1438
1547
 
1548
+ .gap-x-2 {
1549
+ -moz-column-gap: 0.5rem;
1550
+ column-gap: 0.5rem;
1551
+ }
1552
+
1439
1553
  .gap-x-4 {
1440
1554
  -moz-column-gap: 1rem;
1441
1555
  column-gap: 1rem;
@@ -1672,10 +1786,18 @@ video {
1672
1786
  border-color: hsl(var(--border) / 0.8);
1673
1787
  }
1674
1788
 
1789
+ .border-destructive {
1790
+ border-color: hsl(var(--destructive));
1791
+ }
1792
+
1675
1793
  .border-destructive\/20 {
1676
1794
  border-color: hsl(var(--destructive) / 0.2);
1677
1795
  }
1678
1796
 
1797
+ .border-destructive\/30 {
1798
+ border-color: hsl(var(--destructive) / 0.3);
1799
+ }
1800
+
1679
1801
  .border-destructive\/40 {
1680
1802
  border-color: hsl(var(--destructive) / 0.4);
1681
1803
  }
@@ -1684,6 +1806,10 @@ video {
1684
1806
  border-color: hsl(var(--destructive) / 0.5);
1685
1807
  }
1686
1808
 
1809
+ .border-destructive\/60 {
1810
+ border-color: hsl(var(--destructive) / 0.6);
1811
+ }
1812
+
1687
1813
  .border-emerald-500\/30 {
1688
1814
  border-color: rgb(16 185 129 / 0.3);
1689
1815
  }
@@ -1701,10 +1827,18 @@ video {
1701
1827
  border-color: rgb(209 213 219 / var(--tw-border-opacity, 1));
1702
1828
  }
1703
1829
 
1830
+ .border-input {
1831
+ border-color: hsl(var(--input));
1832
+ }
1833
+
1704
1834
  .border-primary\/25 {
1705
1835
  border-color: hsl(var(--primary) / 0.25);
1706
1836
  }
1707
1837
 
1838
+ .border-primary\/30 {
1839
+ border-color: hsl(var(--primary) / 0.3);
1840
+ }
1841
+
1708
1842
  .border-primary\/40 {
1709
1843
  border-color: hsl(var(--primary) / 0.4);
1710
1844
  }
@@ -1725,6 +1859,10 @@ video {
1725
1859
  border-color: hsl(var(--primary) / 0.8);
1726
1860
  }
1727
1861
 
1862
+ .border-rose-500\/50 {
1863
+ border-color: rgb(244 63 94 / 0.5);
1864
+ }
1865
+
1728
1866
  .border-sky-500\/40 {
1729
1867
  border-color: rgb(14 165 233 / 0.4);
1730
1868
  }
@@ -1872,6 +2010,10 @@ video {
1872
2010
  background-color: hsl(var(--destructive) / 0.15);
1873
2011
  }
1874
2012
 
2013
+ .bg-destructive\/5 {
2014
+ background-color: hsl(var(--destructive) / 0.05);
2015
+ }
2016
+
1875
2017
  .bg-emerald-500 {
1876
2018
  --tw-bg-opacity: 1;
1877
2019
  background-color: rgb(16 185 129 / var(--tw-bg-opacity, 1));
@@ -1881,6 +2023,10 @@ video {
1881
2023
  background-color: rgb(16 185 129 / 0.1);
1882
2024
  }
1883
2025
 
2026
+ .bg-emerald-500\/15 {
2027
+ background-color: rgb(16 185 129 / 0.15);
2028
+ }
2029
+
1884
2030
  .bg-muted {
1885
2031
  background-color: hsl(var(--muted));
1886
2032
  }
@@ -1993,6 +2139,10 @@ video {
1993
2139
  background-color: hsl(var(--primary) / 0.9);
1994
2140
  }
1995
2141
 
2142
+ .bg-rose-500\/15 {
2143
+ background-color: rgb(244 63 94 / 0.15);
2144
+ }
2145
+
1996
2146
  .bg-secondary {
1997
2147
  background-color: hsl(var(--secondary));
1998
2148
  }
@@ -2139,6 +2289,10 @@ video {
2139
2289
  padding: 0px;
2140
2290
  }
2141
2291
 
2292
+ .p-0\.5 {
2293
+ padding: 0.125rem;
2294
+ }
2295
+
2142
2296
  .p-1 {
2143
2297
  padding: 0.25rem;
2144
2298
  }
@@ -2259,6 +2413,11 @@ video {
2259
2413
  padding-bottom: 1.75rem;
2260
2414
  }
2261
2415
 
2416
+ .py-8 {
2417
+ padding-top: 2rem;
2418
+ padding-bottom: 2rem;
2419
+ }
2420
+
2262
2421
  .py-px {
2263
2422
  padding-top: 1px;
2264
2423
  padding-bottom: 1px;
@@ -2308,6 +2467,10 @@ video {
2308
2467
  padding-left: 1.25rem;
2309
2468
  }
2310
2469
 
2470
+ .pr-0\.5 {
2471
+ padding-right: 0.125rem;
2472
+ }
2473
+
2311
2474
  .pr-1 {
2312
2475
  padding-right: 0.25rem;
2313
2476
  }
@@ -2478,6 +2641,11 @@ video {
2478
2641
  color: rgb(4 120 87 / var(--tw-text-opacity, 1));
2479
2642
  }
2480
2643
 
2644
+ .text-emerald-800 {
2645
+ --tw-text-opacity: 1;
2646
+ color: rgb(6 95 70 / var(--tw-text-opacity, 1));
2647
+ }
2648
+
2481
2649
  .text-foreground {
2482
2650
  color: hsl(var(--foreground));
2483
2651
  }
@@ -2533,6 +2701,11 @@ video {
2533
2701
  color: hsl(var(--primary) / 0.8);
2534
2702
  }
2535
2703
 
2704
+ .text-rose-600 {
2705
+ --tw-text-opacity: 1;
2706
+ color: rgb(225 29 72 / var(--tw-text-opacity, 1));
2707
+ }
2708
+
2536
2709
  .text-secondary-foreground {
2537
2710
  color: hsl(var(--secondary-foreground));
2538
2711
  }
@@ -2617,6 +2790,10 @@ video {
2617
2790
  opacity: 0.6;
2618
2791
  }
2619
2792
 
2793
+ .opacity-90 {
2794
+ opacity: 0.9;
2795
+ }
2796
+
2620
2797
  .shadow {
2621
2798
  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
2622
2799
  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
@@ -2962,6 +3139,10 @@ video {
2962
3139
  background-color: rgb(245 158 11 / 0.15);
2963
3140
  }
2964
3141
 
3142
+ .hover\:bg-destructive\/10:hover {
3143
+ background-color: hsl(var(--destructive) / 0.1);
3144
+ }
3145
+
2965
3146
  .hover\:bg-destructive\/90:hover {
2966
3147
  background-color: hsl(var(--destructive) / 0.9);
2967
3148
  }
@@ -2986,6 +3167,10 @@ video {
2986
3167
  background-color: hsl(var(--primary) / 0.9);
2987
3168
  }
2988
3169
 
3170
+ .hover\:bg-rose-500\/25:hover {
3171
+ background-color: rgb(244 63 94 / 0.25);
3172
+ }
3173
+
2989
3174
  .hover\:bg-secondary\/80:hover {
2990
3175
  background-color: hsl(var(--secondary) / 0.8);
2991
3176
  }
@@ -3146,11 +3331,21 @@ video {
3146
3331
  color: rgb(252 211 77 / var(--tw-text-opacity, 1));
3147
3332
  }
3148
3333
 
3334
+ .dark\:text-emerald-200:is(.dark *) {
3335
+ --tw-text-opacity: 1;
3336
+ color: rgb(167 243 208 / var(--tw-text-opacity, 1));
3337
+ }
3338
+
3149
3339
  .dark\:text-emerald-300:is(.dark *) {
3150
3340
  --tw-text-opacity: 1;
3151
3341
  color: rgb(110 231 183 / var(--tw-text-opacity, 1));
3152
3342
  }
3153
3343
 
3344
+ .dark\:text-rose-300:is(.dark *) {
3345
+ --tw-text-opacity: 1;
3346
+ color: rgb(253 164 175 / var(--tw-text-opacity, 1));
3347
+ }
3348
+
3154
3349
  .dark\:text-sky-300:is(.dark *) {
3155
3350
  --tw-text-opacity: 1;
3156
3351
  color: rgb(125 211 252 / var(--tw-text-opacity, 1));
@@ -3184,6 +3379,10 @@ video {
3184
3379
  display: flex;
3185
3380
  }
3186
3381
 
3382
+ .sm\:max-w-\[1100px\] {
3383
+ max-width: 1100px;
3384
+ }
3385
+
3187
3386
  .sm\:max-w-\[520px\] {
3188
3387
  max-width: 520px;
3189
3388
  }
@@ -3192,6 +3391,18 @@ video {
3192
3391
  max-width: 860px;
3193
3392
  }
3194
3393
 
3394
+ .sm\:max-w-\[960px\] {
3395
+ max-width: 960px;
3396
+ }
3397
+
3398
+ .sm\:max-w-md {
3399
+ max-width: 28rem;
3400
+ }
3401
+
3402
+ .sm\:max-w-sm {
3403
+ max-width: 24rem;
3404
+ }
3405
+
3195
3406
  .sm\:grid-cols-2 {
3196
3407
  grid-template-columns: repeat(2, minmax(0, 1fr));
3197
3408
  }
@@ -3200,6 +3411,10 @@ video {
3200
3411
  grid-template-columns: repeat(3, minmax(0, 1fr));
3201
3412
  }
3202
3413
 
3414
+ .sm\:gap-0 {
3415
+ gap: 0px;
3416
+ }
3417
+
3203
3418
  .sm\:space-x-0 > :not([hidden]) ~ :not([hidden]) {
3204
3419
  --tw-space-x-reverse: 0;
3205
3420
  margin-right: calc(0px * var(--tw-space-x-reverse));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arronqzy/react-view",
3
- "version": "1.0.32",
3
+ "version": "1.0.35",
4
4
  "files": [
5
5
  "src",
6
6
  "dist",
@@ -31,13 +31,13 @@
31
31
  "@codemirror/theme-one-dark": "^6.1.2",
32
32
  "@codemirror/view": "^6.36.2",
33
33
  "@arronqzy/ui": "1.0.9",
34
- "@arronqzy/i18n": "0.1.14",
35
- "@arronqzy/blueprint-dsl": "1.0.15",
36
- "@arronqzy/react-blueprint": "1.0.18",
34
+ "@arronqzy/i18n": "0.1.16",
35
+ "@arronqzy/react-blueprint": "1.0.21",
36
+ "@arronqzy/blueprint-dsl": "1.0.16",
37
37
  "@arronqzy/view-scene3d": "0.1.5",
38
38
  "@arronqzy/rx-store": "1.0.8",
39
- "@arronqzy/webllm-assistant": "0.1.16",
40
- "@arronqzy/view-table": "0.1.8"
39
+ "@arronqzy/view-table": "0.1.8",
40
+ "@arronqzy/webllm-assistant": "0.1.16"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/react": "^19.0.1",
@@ -46,8 +46,8 @@
46
46
  "eslint": "^8.57.0",
47
47
  "typescript": "^5.5.4",
48
48
  "@arronqzy/eslint-config": "0.0.0",
49
- "@arronqzy/typescript-config": "1.0.0",
50
- "@arronqzy/tailwind": "1.0.2"
49
+ "@arronqzy/tailwind": "1.0.2",
50
+ "@arronqzy/typescript-config": "1.0.0"
51
51
  },
52
52
  "scripts": {
53
53
  "lint": "eslint \"src/**/*.{ts,tsx}\"",
package/readme.md CHANGED
@@ -1,16 +1,27 @@
1
1
  # @arronqzy/react-view
2
2
 
3
- Abuilder 的**视图编辑器** React 包,提供可视化画布、图层、物料、在线预览与工作区管理能力。
3
+ 视图编辑器。物料、无限画布、图层、配置侧栏、工作区和在线预览都在这个包。蓝图分屏通过依赖的 `@arronqzy/react-blueprint` 嵌进来。
4
4
 
5
- ## 功能概览
5
+ 大多数 React 宿主应直接用 [`@arronqzy/abuilder`](../abuilder/README.md) 的 `<App />`。只有要把面板拆进已有布局时才引本包。
6
6
 
7
- - **无限画布**:平移、缩放、标尺、框选与 Moveable 拖拽/缩放/旋转
8
- - **图层系统**:多图层、映射图层、主图层、锁定与合并
9
- - **物料节点**:图表(ECharts)、文本、图片、音视频、几何、网格、引用节点等
10
- - **Scope 模版**:节点属性支持 `{scope.xxx}` 表达式与解析预览
11
- - **工作区**:IndexedDB 持久化、多项目切换、同步与在线预览
12
- - **蓝图联动**:与 `@arronqzy/react-blueprint` 分屏编辑,运行时 Scope 回写视图
13
- - **宿主集成**:工作区事件订阅、预览快照、外部工作区数据加载
7
+ ![左侧物料、中间画布、右侧配置、底部图层](https://github.com/qianzhuoyao/APanel5/raw/v5/docs/guide-assets/live-zh/01-overview.png)
8
+
9
+ ## 画布上有什么
10
+
11
+ - **无限画布**:平移、缩放、标尺、框选,Moveable 拖拽 / 缩放 / 旋转
12
+ - **物料**:ECharts 图表、可视化表格、文本、图片、音视频、几何、网格、视口、三维、引用节点
13
+ - **图层**:多图层、映射图层、主图层、锁定、显示、合并
14
+ - **配置**:按类型展开;可搜字段;展开状态按节点记住
15
+ - **Scope**:属性可写 `{scope.xxx}`,蓝图执行后展开
16
+ - **工作区**:IndexedDB 多项目、导入导出、跨标签同步
17
+
18
+ 表格是物料里配置最多的一类,列、条件样式和外观都在右侧:
19
+
20
+ ![表格节点与配置侧栏](https://github.com/qianzhuoyao/APanel5/raw/v5/docs/guide-assets/live-zh/09-table-full.png)
21
+
22
+ 打开蓝图后,本包把画布拆成上下两块,下面交给蓝图编辑器:
23
+
24
+ ![视图与蓝图分屏](https://github.com/qianzhuoyao/APanel5/raw/v5/docs/guide-assets/live-zh/17-blueprint.png)
14
25
 
15
26
  ## 安装
16
27
 
@@ -18,15 +29,13 @@ Abuilder 的**视图编辑器** React 包,提供可视化画布、图层、物
18
29
  pnpm add @arronqzy/react-view
19
30
  ```
20
31
 
21
- 依赖 React 19,并需引入样式:
22
-
23
32
  ```ts
24
33
  import "@arronqzy/react-view/styles.css";
25
34
  ```
26
35
 
27
- ## 使用
36
+ Vite 宿主还要加 `@arronqzy/abuilder/vite` 的 `webllmAssistant()`,见 abuilder 的 README。否则打包 WebLLM 会失败。
28
37
 
29
- ### 完整编辑器面板
38
+ ## 编辑面板
30
39
 
31
40
  ```tsx
32
41
  import { ReactViewPanel } from "@arronqzy/react-view";
@@ -44,15 +53,15 @@ export function Editor({ workspace }: { workspace?: WorkspaceProjectRecord | nul
44
53
  }
45
54
  ```
46
55
 
47
- | `ReactViewPanel` 参数 | 说明 | 默认值 |
48
- |----------------------|------|--------|
56
+ | 参数 | 说明 | 默认 |
57
+ |------|------|------|
49
58
  | `className` | 根节点 class | — |
50
59
  | `initialZoom` | 初始缩放 | `1` |
51
- | `initialWorkspace` | 外部传入的完整工作区,挂载后自动加载 | — |
60
+ | `initialWorkspace` | 挂载后加载的完整工作区。空则空白画布 | — |
52
61
 
53
- ### 在线预览
62
+ ## 在线预览
54
63
 
55
- 按工作区 ID 从 IndexedDB / 缓存加载:
64
+ 按 ID 从 IndexedDB / 缓存加载:
56
65
 
57
66
  ```tsx
58
67
  import { ReactViewOnlinePreview } from "@arronqzy/react-view";
@@ -60,65 +69,62 @@ import { ReactViewOnlinePreview } from "@arronqzy/react-view";
60
69
  <ReactViewOnlinePreview projectId="your-project-id" />
61
70
  ```
62
71
 
63
- 直接传入工作区数据(无需 IndexedDB):
72
+ 直接给数据(优先于 `projectId`):
64
73
 
65
74
  ```tsx
66
75
  <ReactViewOnlinePreview workspace={savedWorkspace} />
67
76
  ```
68
77
 
69
- URL 参数解析:
70
-
71
78
  ```ts
72
79
  import { parseOnlinePreviewSearchParams } from "@arronqzy/react-view";
73
80
 
74
- const params = parseOnlinePreviewSearchParams("?preview=online&projectId=xxx");
81
+ parseOnlinePreviewSearchParams("?preview=online&projectId=xxx");
75
82
  ```
76
83
 
77
- | `ReactViewOnlinePreview` 参数 | 说明 |
78
- |------------------------------|------|
79
- | `projectId` | 从 IndexedDB 加载的工作区 ID |
80
- | `workspace` | 直接传入的完整工作区数据(优先于 `projectId`) |
84
+ | 参数 | 说明 |
85
+ |------|------|
86
+ | `projectId` | 从 IndexedDB 加载 |
87
+ | `workspace` | 宿主传入的完整工作区,优先 |
81
88
  | `previewInstanceId` | 预览实例 ID(可选) |
82
89
 
83
- ### 工作区事件订阅
90
+ ## 工作区事件
84
91
 
85
- 监听工作区创建与同步,回调携带**完整工作区数据**(面板 + 蓝图),便于持久化到自有后端:
92
+ 回调带完整工作区,用来写自己的后端。IndexedDB 的增删改查不导出。
86
93
 
87
94
  ```ts
88
95
  import {
89
96
  addEventSubscription,
90
97
  AbuilderEvents,
98
+ parseWorkspaceData,
91
99
  type WorkspaceData,
92
100
  } from "@arronqzy/react-view";
93
101
 
94
- const addSub = addEventSubscription(
95
- AbuilderEvents.workspaceAdd,
96
- async (workspace: WorkspaceData) => {
97
- await saveToServer(workspace);
98
- }
99
- );
100
-
101
102
  const syncSub = addEventSubscription(
102
103
  AbuilderEvents.workspaceSync,
103
- async (workspace) => {
104
+ async (workspace: WorkspaceData) => {
104
105
  await saveToServer(workspace);
105
106
  }
106
107
  );
107
-
108
- addSub.unsubscribe();
109
108
  syncSub.unsubscribe();
110
109
  ```
111
110
 
112
- | 事件 | 常量 | 触发时机 |
111
+ | 事件 | 常量 | 何时触发 |
113
112
  |------|------|----------|
114
- | `workspace:add` | `AbuilderEvents.workspaceAdd` | 创建工作区成功 |
115
- | `workspace:sync` | `AbuilderEvents.workspaceSync` | 同步工作区成功 |
113
+ | `workspace:add` | `AbuilderEvents.workspaceAdd` | 创建成功 |
114
+ | `workspace:sync` | `AbuilderEvents.workspaceSync` | 同步成功 |
115
+
116
+ 字段:`id`、`name`、`createdAt`、`updatedAt`、`panelState`、`blueprintDocument`、`blueprintMeta`、`productName`、`titleIconDataUrl`。
117
+
118
+ 校验:
116
119
 
117
- 回调参数类型为 `WorkspaceData`(即 `WorkspaceProjectRecord`),字段包括:`id`、`name`、`createdAt`、`updatedAt`、`panelState`、`blueprintDocument`、`blueprintMeta`、`productName`、`titleIconDataUrl`。
120
+ - `validateViewData` / `parseViewData` — 视图 `panelState`
121
+ - `validateBlueprintData` / `parseBlueprintData` — 蓝图文档
122
+ - `validateWorkspaceData` / `parseWorkspaceData` — 两边都要合法;`parse` 的 `value` 可作 `initialWorkspace`
123
+ - `createEmptyWorkspace` / `createWorkspaceProjectId` — 空白记录和 ID
118
124
 
119
- ### 获取预览快照
125
+ ## 预览缩略图
120
126
 
121
- 在编辑器或在线预览页已挂载时,截取当前预览画面为 base64(data URL),可用于缩略图:
127
+ 编辑器或预览页已挂载且画布有内容时:
122
128
 
123
129
  ```ts
124
130
  import { getPreviewSnapshot } from "@arronqzy/react-view";
@@ -131,56 +137,38 @@ const dataUrl = await getPreviewSnapshot({
131
137
  });
132
138
  ```
133
139
 
134
- | 参数 | 说明 | 默认值 |
135
- |------|------|--------|
136
- | `maxWidth` / `maxHeight` | 等比缩放上限 | 不限制 |
137
- | `mimeType` | `"image/png"` 或 `"image/jpeg"` | `"image/png"` |
138
- | `quality` | JPEG 质量 0–1 | `0.92` |
140
+ | 参数 | 说明 | 默认 |
141
+ |------|------|------|
142
+ | `maxWidth` / `maxHeight` | 等比上限 | 不限制 |
143
+ | `mimeType` | png 或 jpeg | `"image/png"` |
144
+ | `quality` | JPEG 0–1 | `0.92` |
139
145
  | `backgroundColor` | 背景色 | `"#ffffff"` |
140
146
 
141
- ## 核心导出
147
+ ## 主要导出
142
148
 
143
149
  | 导出 | 说明 |
144
150
  |------|------|
145
- | `ReactViewPanel` | 主编辑面板(画布 + 配置侧栏 + 蓝图分屏) |
146
- | `ReactViewOnlinePreview` | 只读在线预览页 |
147
- | `parseOnlinePreviewSearchParams` | 解析预览 URL 参数 |
148
- | `addEventSubscription` | 订阅工作区事件,返回 `{ unsubscribe }` |
149
- | `AbuilderEvents` | 工作区事件名常量 |
150
- | `getPreviewSnapshot` | 获取预览画面 base64 缩略图(仅本包 / `@arronqzy/abuilder`) |
151
- | `parseWorkspaceData` / `validateWorkspaceData` | 解析或校验完整工作区;`parse` 的 `value` 可作 `initialWorkspace` |
152
- | `parseViewData` / `validateViewData` | 解析或校验视图 `panelState` |
153
- | `parseBlueprintData` / `validateBlueprintData` | 解析或校验蓝图文档 |
154
- | `createEmptyWorkspace` | 构造空工作区记录 |
155
- | `createWorkspaceProjectId` | 生成工作区 ID |
156
- | `WorkspaceData` / `WorkspaceProjectRecord` | 完整工作区数据类型 |
157
- | `createView` / `createAction` 等 | 视图 DSL 与指令扩展(`src/core`) |
158
-
159
- 相关类型:`AbuilderEventName`、`AbuilderEventPayloadMap`、`WorkspaceAddEventPayload`、`WorkspaceSyncEventPayload`、`GetPreviewSnapshotOptions`。
160
-
161
- ## 样式与 Tailwind
162
-
163
- 本包内置 Tailwind 配置,构建产物为 `dist/styles.css`:
151
+ | `ReactViewPanel` | 主面板(画布 + 配置 + 蓝图分屏) |
152
+ | `ReactViewOnlinePreview` | 只读预览 |
153
+ | `parseOnlinePreviewSearchParams` | 解析预览 URL |
154
+ | `addEventSubscription` / `AbuilderEvents` | 工作区事件 |
155
+ | `getPreviewSnapshot` | 预览截图 data URL |
156
+ | `parseWorkspaceData` 等 | 校验 / 解析 / 空工作区 |
157
+ | `createView` / `createAction` | 视图 DSL 扩展点(`src/core`) |
158
+
159
+ ## 样式
160
+
161
+ 产物是 `dist/styles.css`。本仓库构建:
164
162
 
165
163
  ```bash
166
164
  pnpm -C packages/react-view build:css
167
165
  ```
168
166
 
169
- 若由应用侧 Tailwind 扫描源码,请将本包加入 `content`:
170
-
171
- ```ts
172
- content: [
173
- "./src/**/*.{ts,tsx}",
174
- "./node_modules/@arronqzy/react-view/src/**/*.{ts,tsx}",
175
- ]
176
- ```
167
+ 若应用自己的 Tailwind 要扫描本包源码,把 `node_modules/@arronqzy/react-view/src/**/*.{ts,tsx}` 加进 `content`。
177
168
 
178
- ## 依赖关系
169
+ ## 依赖
179
170
 
180
- - `@arronqzy/ui` — 基础 UI 组件
181
- - `@arronqzy/rx-store` — 画布状态与历史
182
- - `@arronqzy/react-blueprint` — 蓝图编辑与执行
183
- - `@arronqzy/blueprint-dsl` — 蓝图运行时
171
+ `@arronqzy/ui`、`@arronqzy/rx-store`、`@arronqzy/react-blueprint`、`@arronqzy/blueprint-dsl`、`@arronqzy/view-table`、`@arronqzy/view-scene3d`。
184
172
 
185
173
  ## 许可证
186
174
 
package/src/index.ts CHANGED
@@ -14,6 +14,8 @@ export type {
14
14
  AbuilderEventPayloadMap,
15
15
  WorkspaceAddEventPayload,
16
16
  WorkspaceSyncEventPayload,
17
+ ApiCollectionUploadStartEventPayload,
18
+ ApiCollectionUploadSuccessEventPayload,
17
19
  WorkspaceData,
18
20
  } from "./panel/library/event-subscription";
19
21
  export type { WorkspaceProjectRecord } from "./panel/library/workspace-project-db";
@@ -46,6 +46,7 @@ import { revealPanelConfigFromPatch } from "./ai/revealConfigField";
46
46
  import {
47
47
  BlueprintGraph,
48
48
  BlueprintMetaDialog,
49
+ ApiCollectionManagerDialog,
49
50
  BlueprintNodeSwitchTaskDialog,
50
51
  blueprintDocumentsEqual,
51
52
  buildBlueprintExportPayload,
@@ -55,6 +56,7 @@ import {
55
56
  downloadBlueprintExport,
56
57
  documentToRunnableGraph,
57
58
  getBlueprintLibraryRecord,
59
+ hydrateApiCollections,
58
60
  libraryRecordFromImport,
59
61
  listBlueprintLibrary,
60
62
  parseBlueprintImportFile,
@@ -72,12 +74,16 @@ import {
72
74
  type BlueprintMetaDraft,
73
75
  } from "@arronqzy/react-blueprint";
74
76
  import type { LibraryBlueprintResolver } from "@arronqzy/blueprint-dsl";
75
- import { abortClockNode, appStorageKey, collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE } from "@arronqzy/blueprint-dsl";
77
+ import { abortClockNode, appStorageKey, buildApiCollectionSyncPatches, toApiCollectionSyncResult, collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE } from "@arronqzy/blueprint-dsl";
76
78
  import { WorkspaceStageSplit } from "./components/WorkspaceStageSplit";
77
79
  import {
78
80
  WorkspaceConfigSidebar,
79
81
  type WorkspaceConfigFocus,
80
82
  } from "./components/WorkspaceConfigSidebar";
83
+ import {
84
+ AbuilderEvents,
85
+ emitAbuilderEvent,
86
+ } from "./library/event-subscription";
81
87
  import {
82
88
  getViewElementScope,
83
89
  setViewElementScopes,
@@ -295,6 +301,7 @@ function ReactViewPanelInner({
295
301
  const [blueprintMetaDialogMode, setBlueprintMetaDialogMode] = useState<
296
302
  "export" | "save"
297
303
  >("save");
304
+ const [apiCollectionManagerOpen, setApiCollectionManagerOpen] = useState(false);
298
305
  const [selectedBlueprintNodeId, setSelectedBlueprintNodeId] = useState<string | null>(
299
306
  null
300
307
  );
@@ -655,6 +662,12 @@ function ReactViewPanelInner({
655
662
  [nameSpace]
656
663
  );
657
664
 
665
+ useEffect(() => {
666
+ void hydrateApiCollections(nameSpace).catch((error) => {
667
+ console.error("[abuilder] hydrate api collections failed", error);
668
+ });
669
+ }, [nameSpace]);
670
+
658
671
  const handleBlueprintExecutionBlocked = useCallback((message: string) => {
659
672
  toast({ title: message });
660
673
  }, []);
@@ -1904,6 +1917,7 @@ function ReactViewPanelInner({
1904
1917
  applyTheme={applyTheme}
1905
1918
  isDark={isDark}
1906
1919
  openBlueprintExportDialog={openBlueprintExportDialog}
1920
+ openApiCollectionManager={() => setApiCollectionManagerOpen(true)}
1907
1921
  panelFontSize={panelFontSize}
1908
1922
  setPanelFontSize={setPanelFontSize}
1909
1923
  locale={locale}
@@ -2768,6 +2782,43 @@ function ReactViewPanelInner({
2768
2782
  void handleBlueprintMetaConfirm(meta);
2769
2783
  }}
2770
2784
  />
2785
+ <ApiCollectionManagerDialog
2786
+ open={apiCollectionManagerOpen}
2787
+ onOpenChange={setApiCollectionManagerOpen}
2788
+ onUploadStart={(info) => {
2789
+ emitAbuilderEvent(AbuilderEvents.apiCollectionUploadStart, info);
2790
+ }}
2791
+ onUploadSuccess={(info) => {
2792
+ emitAbuilderEvent(AbuilderEvents.apiCollectionUploadSuccess, info);
2793
+ }}
2794
+ onSyncCollection={(record) => {
2795
+ const patches = buildApiCollectionSyncPatches({
2796
+ collectionId: record.id,
2797
+ document: record.document,
2798
+ nodes: blueprintGraph.document.nodes
2799
+ .filter((node) => node.role === "fetch" || Boolean(node.fetchConfig))
2800
+ .map((node) => ({
2801
+ id: node.id,
2802
+ label: node.label,
2803
+ fetchConfig: node.fetchConfig,
2804
+ })),
2805
+ });
2806
+ if (patches.length === 0) {
2807
+ return toApiCollectionSyncResult([]);
2808
+ }
2809
+ setBlueprintGraph((graph) => {
2810
+ let next = graph;
2811
+ for (const patch of patches) {
2812
+ next = next.updateNode(patch.nodeId, {
2813
+ fetchConfig:
2814
+ patch.fetchConfigPatch as import("@arronqzy/blueprint-dsl").FetchRequestConfig,
2815
+ });
2816
+ }
2817
+ return next;
2818
+ });
2819
+ return toApiCollectionSyncResult(patches);
2820
+ }}
2821
+ />
2771
2822
  {pendingBlueprintNodeSwitch ? (
2772
2823
  <BlueprintNodeSwitchTaskDialog
2773
2824
  open
@@ -34,6 +34,7 @@ export type PanelMenubarProps = {
34
34
  applyTheme: (dark: boolean) => void;
35
35
  isDark: boolean;
36
36
  openBlueprintExportDialog: () => void;
37
+ openApiCollectionManager: () => void;
37
38
  panelFontSize: "sm" | "md" | "lg";
38
39
  setPanelFontSize: (value: "sm" | "md" | "lg") => void;
39
40
  locale: Locale;
@@ -65,6 +66,7 @@ export function PanelMenubar({
65
66
  applyTheme,
66
67
  isDark,
67
68
  openBlueprintExportDialog,
69
+ openApiCollectionManager,
68
70
  panelFontSize,
69
71
  setPanelFontSize,
70
72
  locale,
@@ -121,6 +123,10 @@ export function PanelMenubar({
121
123
  <MenubarItem onClick={() => blueprintImportInputRef.current?.click()}>
122
124
  {t("panel.menubar.import")}
123
125
  </MenubarItem>
126
+ <MenubarSeparator />
127
+ <MenubarItem onClick={openApiCollectionManager}>
128
+ {t("panel.menubar.apiCollections")}
129
+ </MenubarItem>
124
130
  </MenubarContent>
125
131
  </MenubarMenu>
126
132
  <MenubarMenu>
@@ -73,17 +73,13 @@ export function ScopeTemplateAutocompleteHost({
73
73
 
74
74
  const refreshForInput = useCallback(
75
75
  (input: HTMLInputElement | HTMLTextAreaElement) => {
76
- if (scope === undefined) {
77
- closeDropdown();
78
- return;
79
- }
80
76
  const cursor = input.selectionStart ?? input.value.length;
81
- const parsed = parseScopeAutocomplete(input.value, cursor, scope);
77
+ const parsed = parseScopeAutocomplete(input.value, cursor, scope ?? {});
82
78
  if (!parsed) {
83
79
  closeDropdown();
84
80
  return;
85
81
  }
86
- const suggestions = getScopeAutocompleteSuggestions(scope, parsed);
82
+ const suggestions = getScopeAutocompleteSuggestions(scope ?? {}, parsed);
87
83
  if (suggestions.length === 0) {
88
84
  closeDropdown();
89
85
  return;
@@ -124,7 +120,7 @@ export function ScopeTemplateAutocompleteHost({
124
120
 
125
121
  useEffect(() => {
126
122
  const container = containerRef?.current;
127
- if (!container || scope === undefined) return;
123
+ if (!container) return;
128
124
 
129
125
  const onFocusIn = (event: FocusEvent) => {
130
126
  if (!isAutocompleteTarget(event.target)) return;
@@ -213,7 +209,7 @@ export function ScopeTemplateAutocompleteHost({
213
209
  scope,
214
210
  ]);
215
211
 
216
- if (!dropdown || scope === undefined) return null;
212
+ if (!dropdown) return null;
217
213
 
218
214
  return createPortal(
219
215
  <div
@@ -7,6 +7,7 @@ export function ScopeTemplateUsageHint() {
7
7
  <div className="space-y-2">
8
8
  <p>{t("panel.scope.usageHint1")}</p>
9
9
  <p>{t("panel.scope.usageHint2")}</p>
10
+ <p>{t("panel.scope.usageHintSystem")}</p>
10
11
  </div>
11
12
  );
12
13
  }
@@ -5,6 +5,10 @@ export const AbuilderEvents = {
5
5
  workspaceAdd: "workspace:add",
6
6
  /** 同步工作区 */
7
7
  workspaceSync: "workspace:sync",
8
+ /** 开始上传接口集合 */
9
+ apiCollectionUploadStart: "api-collection:upload-start",
10
+ /** 上传接口集合成功 */
11
+ apiCollectionUploadSuccess: "api-collection:upload-success",
8
12
  } as const;
9
13
 
10
14
  export type AbuilderEventName = (typeof AbuilderEvents)[keyof typeof AbuilderEvents];
@@ -15,9 +19,25 @@ export type WorkspaceData = WorkspaceProjectRecord;
15
19
  export type WorkspaceAddEventPayload = WorkspaceData;
16
20
  export type WorkspaceSyncEventPayload = WorkspaceData;
17
21
 
22
+ export type ApiCollectionUploadStartEventPayload = {
23
+ fileNames: string[];
24
+ count: number;
25
+ };
26
+
27
+ export type ApiCollectionUploadSuccessEventPayload = {
28
+ collections: Array<{
29
+ id: string;
30
+ name: string;
31
+ apiCount: number;
32
+ persistLocal: boolean;
33
+ }>;
34
+ };
35
+
18
36
  export type AbuilderEventPayloadMap = {
19
37
  [AbuilderEvents.workspaceAdd]: WorkspaceAddEventPayload;
20
38
  [AbuilderEvents.workspaceSync]: WorkspaceSyncEventPayload;
39
+ [AbuilderEvents.apiCollectionUploadStart]: ApiCollectionUploadStartEventPayload;
40
+ [AbuilderEvents.apiCollectionUploadSuccess]: ApiCollectionUploadSuccessEventPayload;
21
41
  };
22
42
 
23
43
  type EventCallback = (payload: unknown) => void;
@@ -1,6 +1,10 @@
1
1
  import type { PanelElement } from "../types";
2
2
  import type { PanelTableConfig } from "@arronqzy/view-table";
3
3
  import { isUsableTableRawData } from "@arronqzy/view-table";
4
+ import {
5
+ createSystemRoot,
6
+ isDayjsValue,
7
+ } from "@arronqzy/blueprint-dsl";
4
8
  import { materializeChartLabelsFromScope, materializeChartValuesFromScope } from "./scope-template-chart";
5
9
  import {
6
10
  SCOPE_SPREAD_TEMPLATE_RE,
@@ -11,12 +15,12 @@ import {
11
15
  resolveSpreadScopePath,
12
16
  } from "./scope-template-spread";
13
17
 
14
- const SCOPE_TOKEN_RE = /\{(scope[^}]*)\}/g;
18
+ const TEMPLATE_TOKEN_RE = /\{((?:scope|system)[^}]*)\}/g;
15
19
 
16
20
  export function hasScopeTemplate(value: string): boolean {
17
- SCOPE_TOKEN_RE.lastIndex = 0;
21
+ TEMPLATE_TOKEN_RE.lastIndex = 0;
18
22
  SCOPE_SPREAD_TEMPLATE_RE.lastIndex = 0;
19
- return SCOPE_TOKEN_RE.test(value) || SCOPE_SPREAD_TEMPLATE_RE.test(value);
23
+ return TEMPLATE_TOKEN_RE.test(value) || SCOPE_SPREAD_TEMPLATE_RE.test(value);
20
24
  }
21
25
 
22
26
  export function evaluateScopeExpression(
@@ -26,8 +30,13 @@ export function evaluateScopeExpression(
26
30
  const trimmed = expression.trim();
27
31
  if (!trimmed) return undefined;
28
32
  try {
29
- const fn = new Function("scope", `"use strict"; return (${trimmed});`);
30
- return fn(scope);
33
+ const system = createSystemRoot();
34
+ const fn = new Function(
35
+ "scope",
36
+ "system",
37
+ `"use strict"; return (${trimmed});`
38
+ );
39
+ return fn(scope, system);
31
40
  } catch {
32
41
  return undefined;
33
42
  }
@@ -39,6 +48,9 @@ function stringifyScopeValue(value: unknown): string {
39
48
  if (typeof value === "number" || typeof value === "boolean") {
40
49
  return String(value);
41
50
  }
51
+ if (isDayjsValue(value)) {
52
+ return value.toISOString();
53
+ }
42
54
  try {
43
55
  return JSON.stringify(value);
44
56
  } catch {
@@ -60,8 +72,8 @@ export function evaluateScopeTemplate(
60
72
  }
61
73
  );
62
74
 
63
- // Only `{scope...}` tokens — keep ECharts / CSS braces like `{b}` intact.
64
- return withSpread.replace(SCOPE_TOKEN_RE, (_: string, rawExpr: string) =>
75
+ // Only `{scope...}` / `{system...}` — keep ECharts / CSS braces like `{b}` intact.
76
+ return withSpread.replace(TEMPLATE_TOKEN_RE, (_: string, rawExpr: string) =>
65
77
  stringifyScopeValue(evaluateScopeExpression(rawExpr, scope))
66
78
  );
67
79
  }
@@ -91,7 +103,7 @@ function materializeTemplateValue(template: string, scope: unknown): unknown {
91
103
  );
92
104
  }
93
105
 
94
- const singleMatch = /^\{(scope[^}]*)\}$/.exec(trimmed);
106
+ const singleMatch = /^\{((?:scope|system)[^}]*)\}$/.exec(trimmed);
95
107
  if (singleMatch) {
96
108
  return evaluateScopeExpression(singleMatch[1]!, scope);
97
109
  }