cabloy 5.1.98 → 5.1.100

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 (141) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/hooks/contract-loop-gate.ts +151 -44
  3. package/CHANGELOG.md +28 -0
  4. package/CLAUDE.md +2 -0
  5. package/cabloy-docs/.vitepress/config.mjs +4 -0
  6. package/cabloy-docs/backend/controller-aop-guide.md +0 -1
  7. package/cabloy-docs/frontend/a-openapi-under-the-hood.md +1 -1
  8. package/cabloy-docs/frontend/generated-contract-consumption-entry-branch.md +1 -1
  9. package/cabloy-docs/frontend/generated-contract-consumption-list-branch.md +1 -1
  10. package/cabloy-docs/frontend/generated-contract-consumption-verify-playbook.md +1 -1
  11. package/cabloy-docs/frontend/model-resource-owner-pattern.md +1 -1
  12. package/cabloy-docs/frontend/model-state-guide.md +3 -1
  13. package/cabloy-docs/frontend/resource-entry-page-deep-dive.md +2 -2
  14. package/cabloy-docs/frontend/resource-list-page-deep-dive.md +1 -1
  15. package/cabloy-docs/frontend/rest-resource-source-reading-map.md +2 -2
  16. package/cabloy-docs/frontend/rest-resource-under-the-hood.md +4 -4
  17. package/cabloy-docs/frontend/use-state-data-best-practices.md +384 -0
  18. package/package.json +1 -1
  19. package/scripts/upgrade.ts +2 -0
  20. package/vona/packages-cli/cli/package.json +1 -1
  21. package/vona/packages-cli/cli-set-api/package.json +1 -1
  22. package/vona/packages-vona/vona/package.json +1 -1
  23. package/vona/packages-vona/vona-core/package.json +1 -1
  24. package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +9 -9
  25. package/vona/packages-vona/vona-mock/package.json +1 -1
  26. package/vona/pnpm-lock.yaml +56 -56
  27. package/vona/pnpm-workspace.yaml +1 -1
  28. package/vona/src/suite/a-training/modules/training-record/src/bean/fileScene.dossierFile.ts +22 -0
  29. package/vona/src/suite/a-training/modules/training-record/src/bean/meta.version.ts +1 -0
  30. package/vona/src/suite/a-training/modules/training-record/src/config/locale/en-us.ts +1 -0
  31. package/vona/src/suite/a-training/modules/training-record/src/config/locale/zh-cn.ts +1 -0
  32. package/vona/src/suite/a-training/modules/training-record/src/dto/recordDossierFileView.ts +47 -0
  33. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +13 -0
  34. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +13 -0
  35. package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +16 -5
  36. package/vona/src/suite/a-training/modules/training-record/src/index.ts +2 -0
  37. package/vona/src/suite/a-training/modules/training-record/src/lib/index.ts +1 -0
  38. package/vona/src/suite/a-training/modules/training-record/src/lib/resolveDossierFiles.ts +34 -0
  39. package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +209 -123
  40. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
  41. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
  42. package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +18 -1
  43. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -2
  44. package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +1 -0
  45. package/vona/src/suite-vendor/a-file/modules/a-file/package.json +5 -3
  46. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +28 -0
  47. package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +8 -0
  48. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyRequest.ts +15 -0
  49. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyResponse.ts +30 -0
  50. package/vona/src/suite-vendor/a-file/package.json +2 -2
  51. package/vona/src/suite-vendor/a-image/modules/a-image/package.json +1 -1
  52. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +27 -0
  53. package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +8 -0
  54. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyRequest.ts +15 -0
  55. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyResponse.ts +27 -0
  56. package/vona/src/suite-vendor/a-image/modules/image-native/package.json +1 -1
  57. package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +1 -1
  58. package/vona/src/suite-vendor/a-image/package.json +1 -1
  59. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  60. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  61. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/.metadata/index.ts +24 -9
  62. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +87 -73
  63. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permissionActionByRoles.ts +27 -0
  64. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permissionUser.ts +20 -0
  65. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  66. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +4 -3
  67. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +1 -1
  68. package/vona/src/suite-vendor/a-vona/modules/a-upload/src/bean/interceptor.upload.ts +5 -1
  69. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  70. package/vona/src/suite-vendor/a-vona/modules/a-user/src/.metadata/index.ts +0 -13
  71. package/vona/src/suite-vendor/a-vona/modules/a-user/src/lib/passport.ts +0 -7
  72. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  73. package/vona/src/suite-vendor/a-vona/modules/a-version/src/bean/meta.version.ts +2 -24
  74. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  75. package/zova/packages-utils/zova-jsx/package.json +2 -2
  76. package/zova/packages-zova/zova/package.json +3 -3
  77. package/zova/packages-zova/zova-core/package.json +1 -1
  78. package/zova/packages-zova/zova-core/src/bean/beanBase.ts +9 -13
  79. package/zova/pnpm-lock.yaml +24 -9
  80. package/zova/src/suite/a-demo/modules/demo-basic/src/page/toolOne/controller.tsx +2 -2
  81. package/zova/src/suite/a-demo/modules/demo-todo/src/page/todo/controller.tsx +2 -2
  82. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/controller.tsx +2 -2
  83. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/controller.tsx +2 -2
  84. package/zova/src/suite/a-home/modules/home-login/src/page/login/controller.tsx +2 -2
  85. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockDetails/controller.tsx +5 -1
  86. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/blockTable/controller.tsx +20 -15
  87. package/zova/src/suite/cabloy-basic/modules/basic-details/src/service/detail.tsx +6 -1
  88. package/zova/src/suite/cabloy-basic/modules/basic-file/cli/openapi.config.ts +9 -0
  89. package/zova/src/suite/cabloy-basic/modules/basic-file/package.json +58 -0
  90. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/component/formFieldFile.ts +31 -0
  91. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/index.ts +222 -0
  92. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/locales.ts +7 -0
  93. package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/this.ts +2 -0
  94. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/file.ts +121 -0
  95. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/baseURL.ts +5 -0
  96. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/index.ts +3 -0
  97. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/schemas.ts +306 -0
  98. package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +5718 -0
  99. package/zova/src/suite/cabloy-basic/modules/basic-file/src/apiSchema/file.ts +40 -0
  100. package/zova/src/suite/cabloy-basic/modules/basic-file/src/bean/tableCell.file.tsx +187 -0
  101. package/zova/src/suite/cabloy-basic/modules/basic-file/src/component/formFieldFile/controller.tsx +534 -0
  102. package/zova/src/suite/cabloy-basic/modules/basic-file/src/config/locale/en-us.ts +15 -0
  103. package/zova/src/suite/cabloy-basic/modules/basic-file/src/config/locale/zh-cn.ts +15 -0
  104. package/zova/src/suite/cabloy-basic/modules/basic-file/src/index.ts +4 -0
  105. package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/file.ts +74 -0
  106. package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/index.ts +1 -0
  107. package/zova/src/suite/cabloy-basic/modules/basic-file/src/model/file.ts +23 -0
  108. package/zova/src/suite/cabloy-basic/modules/basic-file/src/types/file.ts +12 -0
  109. package/zova/src/suite/cabloy-basic/modules/basic-file/src/types/index.ts +1 -0
  110. package/zova/src/suite/cabloy-basic/modules/basic-file/tsconfig.build.json +13 -0
  111. package/zova/src/suite/cabloy-basic/modules/basic-file/tsconfig.json +5 -0
  112. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +61 -0
  113. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +38 -1
  114. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/imageNativeImage.ts +31 -0
  115. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +52 -0
  116. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +614 -1
  117. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +10 -0
  118. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/imageNativeImage.ts +13 -0
  119. package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +126 -53
  120. package/zova/src/suite/cabloy-basic/modules/basic-image/src/model/image.ts +23 -0
  121. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +44 -4
  122. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockTable/controller.tsx +17 -12
  123. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +2 -2
  124. package/zova/src/suite/cabloy-basic/modules/basic-resource/src/component/formFieldResourcePicker/controller.tsx +2 -2
  125. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/controller.tsx +7 -2
  126. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/table/render.tsx +34 -33
  127. package/zova/src/suite/cabloy-basic/package.json +1 -0
  128. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  129. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +5 -3
  130. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx +2 -2
  131. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx +2 -2
  132. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  133. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  134. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +17 -17
  135. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  136. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +8 -3
  137. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  138. package/zova/src/suite-vendor/a-zova/package.json +4 -4
  139. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/summerCache.permission.ts +0 -17
  140. package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/guard.userName.ts +0 -35
  141. /package/vona/patches/{zova-core@5.1.76.patch → zova-core@5.1.77.patch} +0 -0
@@ -4,15 +4,21 @@ import { BeanBase } from 'zova';
4
4
  import { ApiSchema } from 'zova-module-a-api';
5
5
 
6
6
  import {
7
+ ApiApiImagegetUploadPolicyPath,
7
8
  ApiApiImagecreateUploadTokenPath,
8
9
  ApiApiImageuploadPath,
9
10
  ApiApiImagecreateDirectUploadPath,
11
+ ApiApiImagefinalizeDirectUploadPath,
10
12
  ApiApiImageuploadUrlPath,
11
13
  ApiApiImagedeliveryPath,
12
14
  } from '../api/image.js';
13
15
 
14
16
  @ApiSchema()
15
17
  export class ApiSchemaImage extends BeanBase {
18
+ getUploadPolicy(options?: IApiSchemaOptions) {
19
+ return this.$sdk.createApiSchemas(ApiApiImagegetUploadPolicyPath, 'post', options);
20
+ }
21
+
16
22
  createUploadToken(options?: IApiSchemaOptions) {
17
23
  return this.$sdk.createApiSchemas(ApiApiImagecreateUploadTokenPath, 'post', options);
18
24
  }
@@ -25,6 +31,10 @@ export class ApiSchemaImage extends BeanBase {
25
31
  return this.$sdk.createApiSchemas(ApiApiImagecreateDirectUploadPath, 'post', options);
26
32
  }
27
33
 
34
+ finalizeDirectUpload(options?: IApiSchemaOptions) {
35
+ return this.$sdk.createApiSchemas(ApiApiImagefinalizeDirectUploadPath, 'post', options);
36
+ }
37
+
28
38
  uploadUrl(options?: IApiSchemaOptions) {
29
39
  return this.$sdk.createApiSchemas(ApiApiImageuploadUrlPath, 'post', options);
30
40
  }
@@ -0,0 +1,13 @@
1
+ import type { IApiSchemaOptions } from 'zova-module-a-api';
2
+
3
+ import { BeanBase } from 'zova';
4
+ import { ApiSchema } from 'zova-module-a-api';
5
+
6
+ import { ApiApiImageNativeImagedirectUploadPath } from '../api/imageNativeImage.js';
7
+
8
+ @ApiSchema()
9
+ export class ApiSchemaImageNativeImage extends BeanBase {
10
+ directUpload(options?: IApiSchemaOptions) {
11
+ return this.$sdk.createApiSchemas(ApiApiImageNativeImagedirectUploadPath, 'post', options);
12
+ }
13
+ }
@@ -9,6 +9,7 @@ import { CircleStencil, Cropper, RectangleStencil } from 'vue-advanced-cropper';
9
9
  import { BeanControllerBase, ClientOnly, Use } from 'zova';
10
10
  import { Controller } from 'zova-module-a-bean';
11
11
  import { ZFormField } from 'zova-module-a-form';
12
+ import { $QueryEnsureLoaded } from 'zova-module-a-model';
12
13
  import {
13
14
  buildImagePreviewTitle,
14
15
  inferImageRelationName,
@@ -17,6 +18,8 @@ import {
17
18
  } from 'zova-module-basic-image';
18
19
 
19
20
  import type { IImageTransformOptions } from '../../types/image.js';
21
+
22
+ import { ModelImage } from '../../model/image.js';
20
23
  import 'vue-advanced-cropper/dist/style.css';
21
24
 
22
25
  declare module 'zova-module-a-openapi' {
@@ -55,6 +58,12 @@ export interface ControllerFormFieldImageProps extends IFormFieldComponentOption
55
58
  options?: IResourceFormFieldImageOptions;
56
59
  }
57
60
 
61
+ interface IUploadPolicyState {
62
+ acceptAttr: string;
63
+ multiple: boolean;
64
+ pending: boolean;
65
+ }
66
+
58
67
  interface IImagePreviewItem {
59
68
  id: TableIdentity;
60
69
  url?: string;
@@ -67,7 +76,6 @@ interface IImagePreviewItem {
67
76
  export class ControllerFormFieldImage extends BeanControllerBase {
68
77
  static $propsDefault = {
69
78
  options: {
70
- accept: ['image/png', 'image/jpeg', 'image/webp'],
71
79
  maxCount: 1,
72
80
  enableCrop: true,
73
81
  cropAspectRatio: 1,
@@ -90,6 +98,10 @@ export class ControllerFormFieldImage extends BeanControllerBase {
90
98
  currentValue?: TableIdentity | TableIdentity[] | string;
91
99
  currentOptions: IResourceFormFieldImageOptions = {};
92
100
  $$formField?: ControllerFormField;
101
+
102
+ @Use()
103
+ $$modelImage: ModelImage;
104
+
93
105
  uploadedPreviewMap: Record<string, IImagePreviewItem> = {};
94
106
 
95
107
  @Use({ injectionScope: 'host' })
@@ -108,7 +120,9 @@ export class ControllerFormFieldImage extends BeanControllerBase {
108
120
  this.$$formField = $$formField;
109
121
  this.currentValue = propsBucket.value as any;
110
122
  this.currentOptions = propsBucket.options ?? {};
111
- const items = this._getPreviewItems(propsBucket.value);
123
+ const uploadPolicyState = this._getUploadPolicyState(this.currentOptions);
124
+ const items = this._getPreviewItems(propsBucket.value, uploadPolicyState.multiple);
125
+ const maxCount = this._getMaxCount(this.currentOptions);
112
126
  const hasValidationError = !$$formField.field.state.meta.isValid;
113
127
  const cardClass = classes(
114
128
  'rounded-box border border-base-300 bg-base-100 p-4',
@@ -117,29 +131,38 @@ export class ControllerFormFieldImage extends BeanControllerBase {
117
131
  );
118
132
  return (
119
133
  <div class={cardClass}>
120
- <input
121
- ref={ref => {
122
- this.fileInputRef = ref as HTMLInputElement;
123
- }}
124
- class="hidden"
125
- type="file"
126
- accept={this._getAcceptAttr(propsBucket.options)}
127
- multiple={!!propsBucket.options?.multiple}
128
- onChange={event => {
129
- void this._handleFileChange(event, propsBucket.disableNotifyChanged);
130
- }}
131
- />
132
- <div class="flex flex-wrap items-center gap-3">
133
- <button
134
- type="button"
135
- class={classes('btn btn-primary', this.isUploading && 'btn-disabled')}
136
- onClick={() => {
137
- if (this.isUploading) return;
138
- this.fileInputRef?.click();
134
+ <ClientOnly>
135
+ <input
136
+ ref={ref => {
137
+ this.fileInputRef = ref as HTMLInputElement;
138
+ }}
139
+ class="hidden"
140
+ type="file"
141
+ accept={uploadPolicyState.acceptAttr}
142
+ multiple={uploadPolicyState.multiple}
143
+ onChange={event => {
144
+ void this._handleFileChange(event, propsBucket.disableNotifyChanged);
139
145
  }}
140
- >
141
- {this._getUploadButtonText(items.length)}
142
- </button>
146
+ />
147
+ </ClientOnly>
148
+ <div class="flex flex-wrap items-center gap-3">
149
+ {items.length < maxCount && (
150
+ <button
151
+ type="button"
152
+ disabled={this.isUploading || uploadPolicyState.pending}
153
+ class={classes(
154
+ 'btn btn-primary',
155
+ (this.isUploading || uploadPolicyState.pending) && 'btn-disabled',
156
+ )}
157
+ onClick={() => {
158
+ if (this.isUploading || uploadPolicyState.pending) return;
159
+ this._applyInputPolicy(uploadPolicyState);
160
+ this.fileInputRef?.click();
161
+ }}
162
+ >
163
+ {this._getUploadButtonText(items.length, uploadPolicyState.multiple)}
164
+ </button>
165
+ )}
143
166
  {this.isUploading && (
144
167
  <span class="inline-flex items-center gap-2 text-sm text-base-content/70">
145
168
  <span class="loading loading-spinner loading-sm text-primary"></span>
@@ -266,14 +289,51 @@ export class ControllerFormFieldImage extends BeanControllerBase {
266
289
  );
267
290
  }
268
291
 
269
- private _getUploadButtonText(itemCount: number) {
270
- const options = this.currentOptions ?? {};
271
- if (options.multiple) {
292
+ private _getUploadButtonText(
293
+ itemCount: number,
294
+ multiple = this._getEffectiveMultiple(this.currentOptions),
295
+ ) {
296
+ if (multiple) {
272
297
  return itemCount > 0 ? this.scope.locale.AddImage() : this.scope.locale.SelectImage();
273
298
  }
274
299
  return itemCount > 0 ? this.scope.locale.ReplaceImage() : this.scope.locale.SelectImage();
275
300
  }
276
301
 
302
+ private _getUploadPolicyQuery(options?: IResourceFormFieldImageOptions) {
303
+ return this.$$modelImage.getUploadPolicy(options?.imageScene as string | undefined);
304
+ }
305
+
306
+ private _getUploadPolicyState(options?: IResourceFormFieldImageOptions): IUploadPolicyState {
307
+ const query = this._getUploadPolicyQuery(options);
308
+ const policy = query?.data;
309
+ return {
310
+ acceptAttr: this._getAcceptAttr(options, policy),
311
+ multiple: this._getEffectiveMultiple(options, policy),
312
+ pending: query ? query.data === undefined && !!(query.isPending || query.isFetching) : false,
313
+ };
314
+ }
315
+
316
+ private _getCachedUploadPolicy(options?: IResourceFormFieldImageOptions) {
317
+ return this._getUploadPolicyQuery(options)?.data;
318
+ }
319
+
320
+ private async _waitForUploadPolicy(options?: IResourceFormFieldImageOptions) {
321
+ await $QueryEnsureLoaded(() => this._getUploadPolicyQuery(options));
322
+ }
323
+
324
+ private _getEffectiveMultiple(
325
+ options?: IResourceFormFieldImageOptions,
326
+ policy = this._getCachedUploadPolicy(options),
327
+ ) {
328
+ return !!(options?.multiple ?? policy?.multiple);
329
+ }
330
+
331
+ private _applyInputPolicy(uploadPolicyState: IUploadPolicyState) {
332
+ if (!this.fileInputRef) return;
333
+ this.fileInputRef.accept = uploadPolicyState.acceptAttr;
334
+ this.fileInputRef.multiple = uploadPolicyState.multiple;
335
+ }
336
+
277
337
  private async _handleFileChange(event: Event, disableNotifyChanged?: boolean) {
278
338
  const input = event.target as HTMLInputElement;
279
339
  const files = Array.from(input.files ?? []);
@@ -281,20 +341,21 @@ export class ControllerFormFieldImage extends BeanControllerBase {
281
341
  if (files.length === 0) return;
282
342
  this.errorMessage = undefined;
283
343
  const options = this.currentOptions ?? {};
284
- const multiple = !!options.multiple;
285
- const currentIds = this._normalizeValueToImageIds(this.currentValue, multiple);
286
- const filesToHandle = options.multiple ? files : files.slice(0, 1);
287
- const maxCount = this._getMaxCount(options);
288
- const nextCountCandidate = options.multiple
289
- ? currentIds.length + filesToHandle.length
290
- : filesToHandle.length;
291
- if (nextCountCandidate > maxCount) {
292
- this.errorMessage = this.scope.locale.TooManyImages(maxCount);
293
- return;
294
- }
295
344
  const uploadTarget = this._resolveUploadTarget();
296
345
  this.isUploading = true;
297
346
  try {
347
+ await this._waitForUploadPolicy(options);
348
+ const multiple = this._getEffectiveMultiple(options);
349
+ const currentIds = this._normalizeValueToImageIds(this.currentValue, multiple);
350
+ const filesToHandle = multiple ? files : files.slice(0, 1);
351
+ const maxCount = this._getMaxCount(options);
352
+ const nextCountCandidate = multiple
353
+ ? currentIds.length + filesToHandle.length
354
+ : filesToHandle.length;
355
+ if (nextCountCandidate > maxCount) {
356
+ this.errorMessage = this.scope.locale.TooManyImages(maxCount);
357
+ return;
358
+ }
298
359
  const uploadedItems: IImagePreviewItem[] = [];
299
360
  for (const file of filesToHandle) {
300
361
  const validationMessage = this._validateFile(file, options);
@@ -324,7 +385,7 @@ export class ControllerFormFieldImage extends BeanControllerBase {
324
385
  uploadedItems.push(item);
325
386
  }
326
387
  if (uploadedItems.length === 0) return;
327
- const nextIds = options.multiple
388
+ const nextIds = multiple
328
389
  ? [...currentIds, ...uploadedItems.map(item => item.id)]
329
390
  : [uploadedItems[uploadedItems.length - 1].id];
330
391
  this._setFieldValue(nextIds, disableNotifyChanged, multiple);
@@ -347,7 +408,7 @@ export class ControllerFormFieldImage extends BeanControllerBase {
347
408
  }
348
409
 
349
410
  private _removeItem(imageId: TableIdentity, disableNotifyChanged?: boolean) {
350
- const multiple = !!this.currentOptions?.multiple;
411
+ const multiple = this._getEffectiveMultiple(this.currentOptions);
351
412
  const currentIds = this._normalizeValueToImageIds(this.currentValue, multiple);
352
413
  const nextIds = currentIds.filter(item => String(item) !== String(imageId));
353
414
  delete this.uploadedPreviewMap[String(imageId)];
@@ -362,12 +423,12 @@ export class ControllerFormFieldImage extends BeanControllerBase {
362
423
  ) {
363
424
  const nextValue = multiple ? imageIds : (imageIds[0] ?? '');
364
425
  this.currentValue = nextValue as any;
365
- this._syncRelationField(imageIds);
426
+ this._syncRelationField(imageIds, multiple);
366
427
  this.$$formField?.setValue(nextValue, disableNotifyChanged);
367
428
  this.$$formField?.handleBlur();
368
429
  }
369
430
 
370
- private _syncRelationField(imageIds: TableIdentity[]) {
431
+ private _syncRelationField(imageIds: TableIdentity[], multiple: boolean) {
371
432
  const relationName = this._getRelationName();
372
433
  if (!relationName) return;
373
434
  const relationMap = this._getRelationPreviewMap();
@@ -375,14 +436,14 @@ export class ControllerFormFieldImage extends BeanControllerBase {
375
436
  const key = String(imageId);
376
437
  return this.uploadedPreviewMap[key] ?? relationMap[key] ?? { id: imageId };
377
438
  });
378
- const relationValue = this.currentOptions?.multiple
379
- ? relationItems
380
- : (relationItems[0] ?? undefined);
439
+ const relationValue = multiple ? relationItems : (relationItems[0] ?? undefined);
381
440
  this.$$renderContext.$$form.setFieldValue(relationName as never, relationValue, true);
382
441
  }
383
442
 
384
- private _getPreviewItems(value: unknown) {
385
- const multiple = !!this.currentOptions?.multiple;
443
+ private _getPreviewItems(
444
+ value: unknown,
445
+ multiple = this._getEffectiveMultiple(this.currentOptions),
446
+ ) {
386
447
  const imageIds = this._normalizeValueToImageIds(value, multiple);
387
448
  const relationMap = this._getRelationPreviewMap();
388
449
  if (imageIds.length === 0) {
@@ -434,15 +495,21 @@ export class ControllerFormFieldImage extends BeanControllerBase {
434
495
  }
435
496
 
436
497
  private _getMaxCount(options: IResourceFormFieldImageOptions) {
437
- if (!options.multiple) return 1;
498
+ if (!this._getEffectiveMultiple(options)) return 1;
438
499
  return Math.max(options.maxCount ?? 1, 1);
439
500
  }
440
501
 
441
- private _getAcceptAttr(options?: IResourceFormFieldImageOptions) {
502
+ private _getAcceptAttr(
503
+ options?: IResourceFormFieldImageOptions,
504
+ policy = this._getCachedUploadPolicy(options),
505
+ ) {
442
506
  if (!options) return 'image/*';
443
507
  if (typeof options.accept === 'string') return options.accept;
444
508
  if (options.accept?.length) return options.accept.join(',');
445
- const parts = [...(options.mimeTypes ?? []), ...(options.extensions ?? [])];
509
+ const parts = [
510
+ ...(options.mimeTypes ?? policy?.mimeTypes ?? []),
511
+ ...(options.extensions ?? policy?.extensions ?? []),
512
+ ];
446
513
  return parts.length > 0 ? parts.join(',') : 'image/*';
447
514
  }
448
515
 
@@ -451,8 +518,9 @@ export class ControllerFormFieldImage extends BeanControllerBase {
451
518
  if (acceptTokens.length > 0 && !this._matchesAccept(file, acceptTokens)) {
452
519
  return this.scope.locale.InvalidImageType();
453
520
  }
454
- if (options.maxSize && file.size > options.maxSize) {
455
- return this.scope.locale.ImageTooLarge(this._formatBytes(options.maxSize));
521
+ const maxSize = options.maxSize ?? this._getCachedUploadPolicy(options)?.maxSize;
522
+ if (typeof maxSize === 'number' && file.size > maxSize) {
523
+ return this.scope.locale.ImageTooLarge(this._formatBytes(maxSize));
456
524
  }
457
525
  if (options.minSize && file.size < options.minSize) {
458
526
  return this.scope.locale.ImageTooSmall(this._formatBytes(options.minSize));
@@ -466,7 +534,12 @@ export class ControllerFormFieldImage extends BeanControllerBase {
466
534
  ? options.accept
467
535
  : options.accept.split(',')
468
536
  : [];
469
- return [...accept, ...(options.mimeTypes ?? []), ...(options.extensions ?? [])]
537
+ const policy = this._getCachedUploadPolicy(options);
538
+ return [
539
+ ...accept,
540
+ ...(options.mimeTypes ?? policy?.mimeTypes ?? []),
541
+ ...(options.extensions ?? policy?.extensions ?? []),
542
+ ]
470
543
  .map(item => item.trim().toLowerCase())
471
544
  .filter(item => !!item);
472
545
  }
@@ -0,0 +1,23 @@
1
+ import type { IDecoratorModelOptions } from 'zova-module-a-model';
2
+
3
+ import { BeanModelBase, Model } from 'zova-module-a-model';
4
+
5
+ import type { ApiApiImagegetUploadPolicyResponseBody } from '../api/image.js';
6
+
7
+ export interface IModelOptionsImage extends IDecoratorModelOptions {}
8
+
9
+ @Model<IModelOptionsImage>()
10
+ export class ModelImage extends BeanModelBase {
11
+ getUploadPolicy(imageScene?: string) {
12
+ if (!imageScene) return undefined;
13
+ return this.$useStateData<ApiApiImagegetUploadPolicyResponseBody>({
14
+ queryKey: ['uploadPolicy', 'image', imageScene],
15
+ queryFn: async () => {
16
+ return this.scope.api.image.getUploadPolicy({ imageScene });
17
+ },
18
+ meta: {
19
+ disableSuspenseOnInit: true,
20
+ },
21
+ });
22
+ }
23
+ }
@@ -14,7 +14,7 @@ import { VNode } from 'vue';
14
14
  import { BeanControllerBase, deepEqual } from 'zova';
15
15
  import { ZovaJsx } from 'zova-jsx';
16
16
  import { Controller } from 'zova-module-a-bean';
17
- import { $QueriesAutoLoad } from 'zova-module-a-model';
17
+ import { $QueriesEnsureLoaded } from 'zova-module-a-model';
18
18
  import { BeanControllerTableBase } from 'zova-module-a-table';
19
19
  import { ModelResource } from 'zova-module-rest-resource';
20
20
 
@@ -38,7 +38,9 @@ export class ControllerBlockPage<TData extends {} = {}> extends BeanControllerBa
38
38
 
39
39
  static $componentOptions: IComponentOptions = { inheritAttrs: false, deepExtendDefault: true };
40
40
 
41
- tableRef: BeanControllerTableBase<TData>;
41
+ tableRef: BeanControllerTableBase<TData> | undefined;
42
+ private _tableMetaRefreshPending = false;
43
+ private _tableMetaRefreshTask: Promise<void> | undefined;
42
44
 
43
45
  jsxZova: ZovaJsx;
44
46
  jsxCelScope: IPageScope;
@@ -65,7 +67,7 @@ export class ControllerBlockPage<TData extends {} = {}> extends BeanControllerBa
65
67
  return Object.assign({}, this.queryFilterData, this.queryPaged);
66
68
  });
67
69
  // load schema/data
68
- await $QueriesAutoLoad(
70
+ await $QueriesEnsureLoaded(
69
71
  () => this.$$modelResource.apiSchemasSelect.sdk,
70
72
  () => this.queryData,
71
73
  );
@@ -74,7 +76,7 @@ export class ControllerBlockPage<TData extends {} = {}> extends BeanControllerBa
74
76
  () => this.permissions,
75
77
  async (newValue, oldValue) => {
76
78
  if (deepEqual(newValue, oldValue)) return;
77
- await this.tableRef?.refreshMeta();
79
+ await this._requestTableMetaRefresh();
78
80
  },
79
81
  );
80
82
  }
@@ -123,6 +125,44 @@ export class ControllerBlockPage<TData extends {} = {}> extends BeanControllerBa
123
125
  this.queryFilterData = data;
124
126
  }
125
127
 
128
+ public setTableRef(tableRef: BeanControllerTableBase<TData> | undefined) {
129
+ this.tableRef = tableRef;
130
+ if (!tableRef || !this._tableMetaRefreshPending) return;
131
+ this._scheduleTableMetaRefresh();
132
+ }
133
+
134
+ private _scheduleTableMetaRefresh() {
135
+ void this._flushTableMetaRefresh().catch(err => {
136
+ this.$errorHandler(err, 'ControllerBlockPage.setTableRef');
137
+ });
138
+ }
139
+
140
+ private async _requestTableMetaRefresh() {
141
+ this._tableMetaRefreshPending = true;
142
+ await this._flushTableMetaRefresh();
143
+ }
144
+
145
+ private async _flushTableMetaRefresh() {
146
+ if (this._tableMetaRefreshTask) return await this._tableMetaRefreshTask;
147
+ const task = (async () => {
148
+ while (this._tableMetaRefreshPending) {
149
+ const tableRef = this.tableRef;
150
+ if (!tableRef) return;
151
+ this._tableMetaRefreshPending = false;
152
+ await tableRef.refreshMeta();
153
+ }
154
+ })();
155
+ this._tableMetaRefreshTask = task;
156
+ try {
157
+ await task;
158
+ } finally {
159
+ this._tableMetaRefreshTask = undefined;
160
+ if (this._tableMetaRefreshPending && this.tableRef) {
161
+ this._scheduleTableMetaRefresh();
162
+ }
163
+ }
164
+ }
165
+
126
166
  private _prepareJsx() {
127
167
  const jsxCelEnv = celEnvBase.clone();
128
168
  this.jsxZova = this.bean._newBeanSimple(ZovaJsx, false, undefined, jsxCelEnv);
@@ -3,7 +3,8 @@ import type { IJsxRenderContextPage, IResourceBlockOptionsBase } from 'zova-modu
3
3
 
4
4
  import { BeanControllerBase, Use } from 'zova';
5
5
  import { Controller } from 'zova-module-a-bean';
6
- import { BeanControllerTableBase, ZTable } from 'zova-module-a-table';
6
+ import { BeanControllerTableBase } from 'zova-module-a-table';
7
+ import { ZTable } from 'zova-module-basic-table';
7
8
 
8
9
  declare module 'zova-module-a-openapi' {
9
10
  export interface IResourceBlockRecord {
@@ -15,7 +16,10 @@ export interface ControllerBlockTableProps extends IResourceBlockOptionsBase {}
15
16
 
16
17
  @Controller()
17
18
  export class ControllerBlockTable<TData extends {} = {}> extends BeanControllerBase {
18
- static $propsDefault = {};
19
+ static $propsDefault = {
20
+ class: 'overflow-x-auto rounded-box border border-base-content/15 bg-base-100',
21
+ };
22
+
19
23
  static $componentOptions: IComponentOptions = { inheritAttrs: false, deepExtendDefault: true };
20
24
 
21
25
  tableRef: BeanControllerTableBase<TData>;
@@ -32,16 +36,17 @@ export class ControllerBlockTable<TData extends {} = {}> extends BeanControllerB
32
36
  protected render() {
33
37
  const { $$page } = this.$$renderContext;
34
38
  return (
35
- <ZTable<TData>
36
- class={this.$props.class}
37
- controllerRef={ref => {
38
- this.tableRef = ref;
39
- $$page.tableRef = ref as unknown as BeanControllerTableBase<{}>;
40
- }}
41
- data={$$page.data as unknown as TData[]}
42
- schema={$$page.schemaRow}
43
- tableScope={$$page.jsxCelScope}
44
- ></ZTable>
39
+ <div class={this.$props.class}>
40
+ <ZTable<TData>
41
+ tableRef={ref => {
42
+ this.tableRef = ref;
43
+ $$page.setTableRef(ref as unknown as BeanControllerTableBase<{}> | undefined);
44
+ }}
45
+ data={$$page.data as unknown as TData[]}
46
+ schema={$$page.schemaRow}
47
+ tableScope={$$page.jsxCelScope}
48
+ ></ZTable>
49
+ </div>
45
50
  );
46
51
  }
47
52
  }
@@ -22,7 +22,7 @@ import {
22
22
  formMetaFromFormScene,
23
23
  TypeFormOnSubmitData,
24
24
  } from 'zova-module-a-form';
25
- import { $QueriesAutoLoad } from 'zova-module-a-model';
25
+ import { $QueriesEnsureLoaded } from 'zova-module-a-model';
26
26
  import { ModelResource } from 'zova-module-rest-resource';
27
27
 
28
28
  declare module 'zova-module-a-openapi' {
@@ -81,7 +81,7 @@ export class ControllerBlockPageEntry<TData extends {} = {}> extends BeanControl
81
81
  // jsx
82
82
  this._prepareJsx();
83
83
  // load schema/data
84
- await $QueriesAutoLoad(
84
+ await $QueriesEnsureLoaded(
85
85
  () => this.$$modelResource.getFormApiSchemas(this.formMeta)?.sdk,
86
86
  () => this.queryData,
87
87
  );
@@ -6,7 +6,7 @@ import { classes } from 'typestyle';
6
6
  import { BeanControllerBase, Use } from 'zova';
7
7
  import { Controller } from 'zova-module-a-bean';
8
8
  import { ZFormField, ZFormFieldPreset } from 'zova-module-a-form';
9
- import { $QueryAutoLoad } from 'zova-module-a-model';
9
+ import { $QueryEnsureLoaded } from 'zova-module-a-model';
10
10
  import { ZSelect, ZSelectProps } from 'zova-module-basic-select';
11
11
  import { ModelResource } from 'zova-module-rest-resource';
12
12
 
@@ -52,7 +52,7 @@ export class ControllerFormFieldResourcePicker extends BeanControllerBase {
52
52
  this.resource,
53
53
  );
54
54
  // load data
55
- await $QueryAutoLoad(() => this.queryData);
55
+ await $QueryEnsureLoaded(() => this.queryData);
56
56
  }
57
57
 
58
58
  get resource() {
@@ -1,11 +1,16 @@
1
- import type { ControllerTableProps as ControllerTablePropsSuper } from 'zova-module-a-table';
1
+ import type {
2
+ BeanControllerTableBase,
3
+ ControllerTableProps as ControllerTablePropsSuper,
4
+ } from 'zova-module-a-table';
2
5
 
3
6
  import { BeanControllerBase } from 'zova';
4
7
  import { Controller } from 'zova-module-a-bean';
5
8
 
6
9
  export interface ControllerTableProps<
7
10
  TData extends {} = {},
8
- > extends ControllerTablePropsSuper<TData> {}
11
+ > extends ControllerTablePropsSuper<TData> {
12
+ tableRef?: (ref: BeanControllerTableBase<TData>) => void;
13
+ }
9
14
 
10
15
  @Controller()
11
16
  export class ControllerTable extends BeanControllerBase {
@@ -9,6 +9,9 @@ export class RenderTable<TData extends {} = {}> extends BeanRenderBase {
9
9
  return (
10
10
  <ZTable
11
11
  {...this.$props}
12
+ controllerRef={ref => {
13
+ this.$props?.tableRef?.(ref);
14
+ }}
12
15
  slotDefault={$$table => {
13
16
  return this._renderTable($$table);
14
17
  }}
@@ -19,42 +22,40 @@ export class RenderTable<TData extends {} = {}> extends BeanRenderBase {
19
22
  public _renderTable($$table: ControllerTable<TData>) {
20
23
  const table = $$table.table;
21
24
  return (
22
- <div class="overflow-x-auto">
23
- <table class="table">
24
- <thead>
25
- <tr>
26
- {table.getFlatHeaders().map(header => {
27
- return (
28
- <th key={header.id}>
29
- <FlexRender
30
- render={header.column.columnDef.header}
31
- props={header.getContext()}
32
- ></FlexRender>
33
- </th>
34
- );
35
- })}
36
- </tr>
37
- </thead>
38
- <tbody>
39
- {table.getRowModel().rows.map(row => {
25
+ <table class="table">
26
+ <thead>
27
+ <tr>
28
+ {table.getFlatHeaders().map(header => {
40
29
  return (
41
- <tr key={row.id}>
42
- {row.getVisibleCells().map(cell => {
43
- return (
44
- <td key={cell.id}>
45
- <FlexRender
46
- render={cell.column.columnDef.cell}
47
- props={cell.getContext()}
48
- ></FlexRender>
49
- </td>
50
- );
51
- })}
52
- </tr>
30
+ <th key={header.id}>
31
+ <FlexRender
32
+ render={header.column.columnDef.header}
33
+ props={header.getContext()}
34
+ ></FlexRender>
35
+ </th>
53
36
  );
54
37
  })}
55
- </tbody>
56
- </table>
57
- </div>
38
+ </tr>
39
+ </thead>
40
+ <tbody>
41
+ {table.getRowModel().rows.map(row => {
42
+ return (
43
+ <tr key={row.id}>
44
+ {row.getVisibleCells().map(cell => {
45
+ return (
46
+ <td key={cell.id}>
47
+ <FlexRender
48
+ render={cell.column.columnDef.cell}
49
+ props={cell.getContext()}
50
+ ></FlexRender>
51
+ </td>
52
+ );
53
+ })}
54
+ </tr>
55
+ );
56
+ })}
57
+ </tbody>
58
+ </table>
58
59
  );
59
60
  }
60
61
  }
@@ -15,6 +15,7 @@
15
15
  "zova-module-basic-currency": "^5.1.15",
16
16
  "zova-module-basic-date": "^5.0.6",
17
17
  "zova-module-basic-details": "^5.0.0",
18
+ "zova-module-basic-file": "^5.0.0",
18
19
  "zova-module-basic-form": "^5.0.7",
19
20
  "zova-module-basic-image": "^5.0.0",
20
21
  "zova-module-basic-input": "^5.0.7",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-rest-resource",
3
- "version": "5.1.40",
3
+ "version": "5.1.41",
4
4
  "gitHead": "4dece229b6d3f4735930e833afcb4b95ec51bf86",
5
5
  "description": "",
6
6
  "keywords": [