cabloy 5.1.102 → 5.1.103
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/.cabloy-version +1 -1
- package/CHANGELOG.md +29 -0
- package/CLAUDE.local.md +4 -0
- package/cabloy-docs/.vitepress/config.mjs +3 -0
- package/cabloy-docs/backend/file-guide.md +148 -0
- package/cabloy-docs/backend/image-guide.md +40 -40
- package/cabloy-docs/backend/jwt-guide.md +21 -0
- package/cabloy-docs/frontend/file-guide.md +147 -0
- package/cabloy-docs/frontend/image-guide.md +18 -9
- package/cabloy-docs/frontend/model-state-guide.md +12 -0
- package/cabloy-docs/frontend/use-state-data-best-practices.md +54 -0
- package/cabloy-docs/fullstack/file-workflow.md +135 -0
- package/cabloy-docs/fullstack/image-workflow.md +12 -3
- package/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/src/suite/a-training/modules/training-record/src/.metadata/index.ts +30 -4
- package/vona/src/suite/a-training/modules/training-record/src/bean/fileScene.dossierFile.ts +1 -1
- package/vona/src/suite/a-training/modules/training-record/src/bean/imageScene.sceneImage.ts +1 -0
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +8 -6
- package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +8 -6
- package/vona/src/suite/a-training/modules/training-record/src/index.ts +0 -1
- package/vona/src/suite/a-training/modules/training-record/src/lib/index.ts +0 -1
- package/vona/src/suite/a-training/modules/training-record/test/record.test.ts +10 -12
- package/vona/src/suite/a-training/modules/training-student/src/bean/imageScene.studentImage.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +9 -10
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +2 -2
- package/vona/src/suite-vendor/a-file/modules/a-file/package.json +3 -2
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +155 -35
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.file.ts +283 -54
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +46 -63
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.index.ts +12 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.redlock.ts +1 -1
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.version.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/schedule.fileDraftPrune.ts +29 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/serializerTransform.resolveView.ts +45 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/serializerTransform.resolveViews.ts +45 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/config.ts +6 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +64 -56
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadFinalizeRequest.ts +13 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadFinalizeResponse.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadRequest.ts +0 -3
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadResponse.ts +2 -14
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDownloadRequest.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadPolicyResponse.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadResponse.ts +0 -25
- package/vona/src/{suite/a-training/modules/training-record/src/dto/recordDossierFileView.ts → suite-vendor/a-file/modules/a-file/src/dto/fileView.ts} +3 -15
- package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/file.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileUploadUrl.ts +329 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/file.ts +51 -10
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileProvider.ts +8 -3
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileScene.ts +1 -1
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/package.json +1 -1
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/bean/fileProvider.cloudflare.ts +12 -4
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/service/fileCloudflare.ts +52 -18
- package/vona/src/suite-vendor/a-file/modules/file-native/package.json +1 -1
- package/vona/src/suite-vendor/a-file/modules/file-native/src/bean/fileProvider.native.ts +5 -5
- package/vona/src/suite-vendor/a-file/modules/file-native/src/service/fileNative.ts +7 -8
- package/vona/src/suite-vendor/a-file/package.json +4 -4
- package/vona/src/suite-vendor/a-image/modules/a-image/package.json +1 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +16 -19
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +99 -74
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +28 -68
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +1 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveView.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveViews.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +4 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +78 -34
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDeliveryRequest.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadRequest.ts +0 -3
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +1 -27
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadPolicyResponse.ts +6 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +2 -36
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadUrlRequest.ts +0 -3
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +4 -27
- package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +1 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +36 -29
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +4 -4
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageScene.ts +1 -0
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts +8 -11
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/lib/cloudflareImageUrl.ts +4 -16
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +17 -16
- package/vona/src/suite-vendor/a-image/modules/image-native/package.json +2 -6
- package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +0 -37
- package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +13 -36
- package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +100 -113
- package/vona/src/suite-vendor/a-image/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/config/config.ts +3 -1
- package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +9 -4
- package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-upload/src/bean/interceptor.upload.ts +113 -64
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +50 -12
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +610 -114
- package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +44 -1
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +50 -12
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +610 -114
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/.metadata/index.ts +16 -12
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/file.ts +25 -25
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/schemas.ts +18 -24
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/api/openapi/types.ts +228 -291
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/apiSchema/file.ts +5 -5
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/bean/tableCell.file.tsx +23 -5
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/component/formFieldFile/controller.tsx +72 -43
- package/zova/src/suite/cabloy-basic/modules/basic-file/src/lib/file.ts +22 -4
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +16 -42
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +5 -25
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +10 -24
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +216 -333
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +0 -5
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/bean/tableCell.image.tsx +38 -12
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +79 -32
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/lib/preview.ts +22 -4
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/types/image.ts +3 -4
- package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +29 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
- package/vona/src/suite/a-training/modules/training-record/src/lib/resolveDossierFiles.ts +0 -34
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenRequest.ts +0 -24
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenResponse.ts +0 -16
- package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +0 -57
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/imageNativeImage.ts +0 -31
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/imageNativeImage.ts +0 -13
|
@@ -5,7 +5,6 @@ import { ApiSchema } from 'zova-module-a-api';
|
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
7
|
ApiApiImagegetUploadPolicyPath,
|
|
8
|
-
ApiApiImagecreateUploadTokenPath,
|
|
9
8
|
ApiApiImageuploadPath,
|
|
10
9
|
ApiApiImagecreateDirectUploadPath,
|
|
11
10
|
ApiApiImagefinalizeDirectUploadPath,
|
|
@@ -19,10 +18,6 @@ export class ApiSchemaImage extends BeanBase {
|
|
|
19
18
|
return this.$sdk.createApiSchemas(ApiApiImagegetUploadPolicyPath, 'post', options);
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
createUploadToken(options?: IApiSchemaOptions) {
|
|
23
|
-
return this.$sdk.createApiSchemas(ApiApiImagecreateUploadTokenPath, 'post', options);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
21
|
upload(options?: IApiSchemaOptions) {
|
|
27
22
|
return this.$sdk.createApiSchemas(ApiApiImageuploadPath, 'post', options);
|
|
28
23
|
}
|
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
} from 'zova-module-a-table';
|
|
8
8
|
|
|
9
9
|
import { classes } from 'typestyle';
|
|
10
|
-
import { BeanBase } from 'zova';
|
|
10
|
+
import { BeanBase, ClientOnly } from 'zova';
|
|
11
11
|
import { TableCell } from 'zova-module-a-table';
|
|
12
12
|
|
|
13
13
|
import type { IImagePreviewItem } from '../lib/index.js';
|
|
@@ -105,6 +105,8 @@ export class TableCellImage extends BeanBase implements ITableCellRender {
|
|
|
105
105
|
item: IImagePreviewItem,
|
|
106
106
|
previewUrl: string,
|
|
107
107
|
): VNode {
|
|
108
|
+
const passportCode = this._getDeliveryPassportCode();
|
|
109
|
+
const src = passportCode ? this._resolvePreviewUrl(previewUrl, passportCode) : undefined;
|
|
108
110
|
const size = options.size ?? 40;
|
|
109
111
|
const style = {
|
|
110
112
|
...(options.style as any),
|
|
@@ -119,22 +121,30 @@ export class TableCellImage extends BeanBase implements ITableCellRender {
|
|
|
119
121
|
)}
|
|
120
122
|
style={style}
|
|
121
123
|
>
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
124
|
+
<ClientOnly>
|
|
125
|
+
{src && (
|
|
126
|
+
<img
|
|
127
|
+
class="h-full w-full object-cover"
|
|
128
|
+
style={{ objectFit: options.fit ?? 'cover' }}
|
|
129
|
+
alt={item.filename ?? 'image'}
|
|
130
|
+
src={src}
|
|
131
|
+
/>
|
|
132
|
+
)}
|
|
133
|
+
</ClientOnly>
|
|
128
134
|
</div>
|
|
129
135
|
);
|
|
130
136
|
}
|
|
131
137
|
|
|
132
|
-
private _openPreviewDialog(preview: IImagePreviewSummary, previewTitle: string) {
|
|
133
|
-
const
|
|
138
|
+
private async _openPreviewDialog(preview: IImagePreviewSummary, previewTitle: string) {
|
|
139
|
+
const passportCode = await this._ensureDeliveryPassportCode();
|
|
140
|
+
const items = this._resolveDialogItems(preview).map(item => ({
|
|
141
|
+
...item,
|
|
142
|
+
url: this._resolvePreviewUrl(item.url, passportCode),
|
|
143
|
+
}));
|
|
134
144
|
openImagePreviewDialog({
|
|
135
145
|
appModal: this.$appModal,
|
|
136
146
|
title: previewTitle,
|
|
137
|
-
items,
|
|
147
|
+
items: items.filter(item => !!item.url).map(item => ({ ...item, url: item.url! })),
|
|
138
148
|
baseURL: this.sys.config.api.baseURL,
|
|
139
149
|
});
|
|
140
150
|
}
|
|
@@ -194,7 +204,23 @@ export class TableCellImage extends BeanBase implements ITableCellRender {
|
|
|
194
204
|
return collectImageUrlPreviewItems(preview.source.value);
|
|
195
205
|
}
|
|
196
206
|
|
|
197
|
-
private
|
|
198
|
-
|
|
207
|
+
private _getDeliveryPassportCode() {
|
|
208
|
+
const apiPrefix = this.sys.config.api.prefix ?? '/api';
|
|
209
|
+
return this.$passport.getFreshTempAuthToken({
|
|
210
|
+
path: `${apiPrefix}/image/delivery`,
|
|
211
|
+
staleTime: 30 * 1000,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
private async _ensureDeliveryPassportCode() {
|
|
216
|
+
const apiPrefix = this.sys.config.api.prefix ?? '/api';
|
|
217
|
+
return await this.$passport.ensureFreshTempAuthToken({
|
|
218
|
+
path: `${apiPrefix}/image/delivery`,
|
|
219
|
+
staleTime: 30 * 1000,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private _resolvePreviewUrl(url: string, passportCode?: string) {
|
|
224
|
+
return resolveImagePreviewUrl(url, this.sys.config.api.baseURL, passportCode);
|
|
199
225
|
}
|
|
200
226
|
}
|
package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx
CHANGED
|
@@ -231,23 +231,24 @@ export class ControllerFormFieldImage extends BeanControllerBase {
|
|
|
231
231
|
disableNotifyChanged?: boolean,
|
|
232
232
|
) {
|
|
233
233
|
const previewUrl = item.url ? this._resolvePreviewUrl(item.url) : undefined;
|
|
234
|
+
const passportCode = this._getDeliveryPassportCode();
|
|
235
|
+
const src =
|
|
236
|
+
previewUrl && passportCode ? this._resolvePreviewUrl(previewUrl, passportCode) : undefined;
|
|
234
237
|
return (
|
|
235
238
|
<div
|
|
236
239
|
key={`${item.id}-${index}`}
|
|
237
240
|
class="rounded-box border border-base-300 bg-base-100 shadow-sm"
|
|
238
241
|
>
|
|
239
242
|
<div class="aspect-square overflow-hidden rounded-t-box bg-base-200">
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
</div>
|
|
250
|
-
)}
|
|
243
|
+
<ClientOnly>
|
|
244
|
+
{src && (
|
|
245
|
+
<img
|
|
246
|
+
class="h-full w-full object-cover"
|
|
247
|
+
alt={item.filename ?? `image-${index + 1}`}
|
|
248
|
+
src={src}
|
|
249
|
+
/>
|
|
250
|
+
)}
|
|
251
|
+
</ClientOnly>
|
|
251
252
|
</div>
|
|
252
253
|
<div class="space-y-2 p-3">
|
|
253
254
|
<div class="min-h-10 text-sm text-base-content/70">
|
|
@@ -365,15 +366,12 @@ export class ControllerFormFieldImage extends BeanControllerBase {
|
|
|
365
366
|
}
|
|
366
367
|
const preparedFile = await this._prepareFile(file, options);
|
|
367
368
|
if (!preparedFile) continue;
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
token: tokenRes.token,
|
|
375
|
-
image: preparedFile,
|
|
376
|
-
});
|
|
369
|
+
const uploaded = await this._uploadPreparedFile(
|
|
370
|
+
preparedFile,
|
|
371
|
+
file,
|
|
372
|
+
uploadTarget,
|
|
373
|
+
!!this._getCachedUploadPolicy(options)?.directUpload,
|
|
374
|
+
);
|
|
377
375
|
const item: IImagePreviewItem = {
|
|
378
376
|
id: uploaded.id,
|
|
379
377
|
url: uploaded.url,
|
|
@@ -397,6 +395,40 @@ export class ControllerFormFieldImage extends BeanControllerBase {
|
|
|
397
395
|
}
|
|
398
396
|
}
|
|
399
397
|
|
|
398
|
+
private async _uploadPreparedFile(
|
|
399
|
+
preparedFile: File,
|
|
400
|
+
sourceFile: File,
|
|
401
|
+
uploadTarget: { imageScene: string },
|
|
402
|
+
directUpload: boolean,
|
|
403
|
+
) {
|
|
404
|
+
if (!directUpload) {
|
|
405
|
+
return await this.scope.api.image.upload({
|
|
406
|
+
...uploadTarget,
|
|
407
|
+
image: preparedFile,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const mimeType = preparedFile.type || sourceFile.type;
|
|
412
|
+
const directUploadRes = await this.scope.api.image.createDirectUpload({
|
|
413
|
+
...uploadTarget,
|
|
414
|
+
filename: preparedFile.name,
|
|
415
|
+
size: preparedFile.size,
|
|
416
|
+
mimeType,
|
|
417
|
+
});
|
|
418
|
+
const formData = new FormData();
|
|
419
|
+
formData.append('file', preparedFile);
|
|
420
|
+
const providerRes = await fetch(directUploadRes.uploadUrl, {
|
|
421
|
+
method: 'POST',
|
|
422
|
+
body: formData,
|
|
423
|
+
});
|
|
424
|
+
if (!providerRes.ok) {
|
|
425
|
+
throw new Error(`image direct upload failed: ${providerRes.status}`);
|
|
426
|
+
}
|
|
427
|
+
return await this.scope.api.image.finalizeDirectUpload({
|
|
428
|
+
imageId: directUploadRes.id,
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
400
432
|
private _resolveUploadTarget() {
|
|
401
433
|
const imageScene = this.currentOptions.imageScene;
|
|
402
434
|
if (!imageScene) {
|
|
@@ -794,26 +826,25 @@ export class ControllerFormFieldImage extends BeanControllerBase {
|
|
|
794
826
|
});
|
|
795
827
|
}
|
|
796
828
|
|
|
797
|
-
private _openPreviewDialog(items: IImagePreviewItem[], currentIndex: number) {
|
|
829
|
+
private async _openPreviewDialog(items: IImagePreviewItem[], currentIndex: number) {
|
|
830
|
+
const passportCode = await this._ensureDeliveryPassportCode();
|
|
798
831
|
const previewItems = items
|
|
799
|
-
.map((item, index) => ({
|
|
800
|
-
index,
|
|
801
|
-
item,
|
|
802
|
-
}))
|
|
832
|
+
.map((item, index) => ({ index, item }))
|
|
803
833
|
.filter(({ item }) => !!item.url)
|
|
804
834
|
.map(({ index, item }) => ({
|
|
805
835
|
index,
|
|
806
836
|
item: {
|
|
807
|
-
url: item.url!,
|
|
837
|
+
url: this._resolvePreviewUrl(item.url!, passportCode),
|
|
808
838
|
filename: item.filename,
|
|
809
839
|
},
|
|
810
840
|
}));
|
|
811
|
-
|
|
812
|
-
|
|
841
|
+
const resolvedItems = previewItems.filter(item => !!item.item.url);
|
|
842
|
+
if (resolvedItems.length === 0) return;
|
|
843
|
+
const initialIndex = resolvedItems.findIndex(({ index }) => index === currentIndex);
|
|
813
844
|
openImagePreviewDialog({
|
|
814
845
|
appModal: this.$appModal,
|
|
815
|
-
title: this._getPreviewDialogTitle(
|
|
816
|
-
items:
|
|
846
|
+
title: this._getPreviewDialogTitle(resolvedItems.length),
|
|
847
|
+
items: resolvedItems.map(({ item }) => ({ ...item, url: item.url! })),
|
|
817
848
|
initialIndex: initialIndex === -1 ? 0 : initialIndex,
|
|
818
849
|
baseURL: this.sys.config.api.baseURL,
|
|
819
850
|
});
|
|
@@ -829,8 +860,24 @@ export class ControllerFormFieldImage extends BeanControllerBase {
|
|
|
829
860
|
);
|
|
830
861
|
}
|
|
831
862
|
|
|
832
|
-
private
|
|
833
|
-
|
|
863
|
+
private _getDeliveryPassportCode() {
|
|
864
|
+
const apiPrefix = this.sys.config.api.prefix ?? '/api';
|
|
865
|
+
return this.$passport.getFreshTempAuthToken({
|
|
866
|
+
path: `${apiPrefix}/image/delivery`,
|
|
867
|
+
staleTime: 30 * 1000,
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
private async _ensureDeliveryPassportCode() {
|
|
872
|
+
const apiPrefix = this.sys.config.api.prefix ?? '/api';
|
|
873
|
+
return await this.$passport.ensureFreshTempAuthToken({
|
|
874
|
+
path: `${apiPrefix}/image/delivery`,
|
|
875
|
+
staleTime: 30 * 1000,
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
private _resolvePreviewUrl(url: string, passportCode?: string) {
|
|
880
|
+
return resolveImagePreviewUrl(url, this.sys.config.api.baseURL, passportCode);
|
|
834
881
|
}
|
|
835
882
|
|
|
836
883
|
private _formatBytes(bytes: number) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { $protocolKey } from 'zova';
|
|
2
|
+
|
|
1
3
|
export interface IImagePreviewItemBase {
|
|
2
4
|
url?: string;
|
|
3
5
|
filename?: string;
|
|
@@ -19,10 +21,26 @@ export function inferImageRelationName(fieldName?: string, relationName?: string
|
|
|
19
21
|
return undefined;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
export function resolveImagePreviewUrl(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
export function resolveImagePreviewUrl(
|
|
25
|
+
url: string | undefined,
|
|
26
|
+
baseURL?: string,
|
|
27
|
+
passportCode?: string,
|
|
28
|
+
) {
|
|
29
|
+
if (!url) return url;
|
|
30
|
+
const resolvedUrl =
|
|
31
|
+
url.startsWith('/api/') && baseURL ? `${baseURL.replace(/\/$/, '')}${url}` : url;
|
|
32
|
+
if (!passportCode) return resolvedUrl;
|
|
33
|
+
const apiBaseUrl = new URL(baseURL ?? 'http://localhost', 'http://localhost');
|
|
34
|
+
const parsedUrl = new URL(resolvedUrl, apiBaseUrl);
|
|
35
|
+
if (parsedUrl.origin !== apiBaseUrl.origin || !parsedUrl.pathname.startsWith('/api/')) {
|
|
36
|
+
return resolvedUrl;
|
|
37
|
+
}
|
|
38
|
+
const passportCodeKey = $protocolKey('x-vona-passport-code');
|
|
39
|
+
if (!parsedUrl.searchParams.has(passportCodeKey)) {
|
|
40
|
+
parsedUrl.searchParams.set(passportCodeKey, passportCode);
|
|
41
|
+
}
|
|
42
|
+
if (!baseURL) return `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`;
|
|
43
|
+
return parsedUrl.toString();
|
|
26
44
|
}
|
|
27
45
|
|
|
28
46
|
export function buildImagePreviewTitle(
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ApiSchemaAImageDtoImageTransformOptions_2d063d28bc7243bed02ebd8bddf1212a93c6305b } from '../api/openapi/schemas.js';
|
|
2
2
|
|
|
3
|
-
export type IImageTransformOptions =
|
|
4
|
-
NonNullable<
|
|
5
|
-
>;
|
|
3
|
+
export type IImageTransformOptions =
|
|
4
|
+
NonNullable<ApiSchemaAImageDtoImageTransformOptions_2d063d28bc7243bed02ebd8bddf1212a93c6305b>;
|
|
@@ -1,6 +1,34 @@
|
|
|
1
1
|
import type { DataQuery } from '../types/query.js';
|
|
2
2
|
|
|
3
|
-
export type TypeQueryEnsureLoadedFn<
|
|
3
|
+
export type TypeQueryEnsureLoadedFn<TData, TError = Error | null> = () =>
|
|
4
|
+
| DataQuery<TData, TError>
|
|
5
|
+
| undefined;
|
|
6
|
+
|
|
7
|
+
export type TypeQueryIsStaleFn<TData, TError = Error | null> = (
|
|
8
|
+
query: DataQuery<TData, TError>,
|
|
9
|
+
) => boolean;
|
|
10
|
+
|
|
11
|
+
export function $QueryGetFresh<TData = any, TError = Error | null>(
|
|
12
|
+
fn: TypeQueryEnsureLoadedFn<TData, TError>,
|
|
13
|
+
isStale: TypeQueryIsStaleFn<TData, TError>,
|
|
14
|
+
): TData | undefined {
|
|
15
|
+
const query = fn();
|
|
16
|
+
if (!query) return;
|
|
17
|
+
if (!isStale(query)) return query.data;
|
|
18
|
+
void query.suspense();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function $QueryEnsureFresh<TData = any, TError = Error | null>(
|
|
22
|
+
fn: TypeQueryEnsureLoadedFn<TData, TError>,
|
|
23
|
+
isStale: TypeQueryIsStaleFn<TData, TError>,
|
|
24
|
+
): Promise<TData | undefined> {
|
|
25
|
+
const query = fn();
|
|
26
|
+
if (!query) return;
|
|
27
|
+
if (!isStale(query)) return query.data;
|
|
28
|
+
const result = await query.suspense();
|
|
29
|
+
if (result.error) throw result.error;
|
|
30
|
+
return result.data;
|
|
31
|
+
}
|
|
4
32
|
|
|
5
33
|
export async function $QueryEnsureLoaded<T = any>(fn: TypeQueryEnsureLoadedFn<T>) {
|
|
6
34
|
return _QueryEnsureLoadedInner<T>(fn);
|
|
@@ -108,8 +108,8 @@ export interface QueryMetaPersister {
|
|
|
108
108
|
refetchOnRestore?: boolean | 'always';
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
export type DataQuery<TData> = UnwrapNestedRefs<
|
|
112
|
-
ReturnType<typeof useQuery<TData | undefined,
|
|
111
|
+
export type DataQuery<TData, TError = Error | null> = UnwrapNestedRefs<
|
|
112
|
+
ReturnType<typeof useQuery<TData | undefined, TError>>
|
|
113
113
|
>;
|
|
114
114
|
|
|
115
115
|
export type DataMutation<TData = unknown, TVariables = void, TContext = unknown> = UnwrapNestedRefs<
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-suite-a-zova",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.132",
|
|
4
4
|
"gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
|
|
5
5
|
"description": "zova",
|
|
6
6
|
"license": "MIT",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"zova-module-a-interceptor": "^5.1.29",
|
|
22
22
|
"zova-module-a-logger": "^5.1.26",
|
|
23
23
|
"zova-module-a-meta": "^5.1.21",
|
|
24
|
-
"zova-module-a-model": "^5.1.
|
|
24
|
+
"zova-module-a-model": "^5.1.32",
|
|
25
25
|
"zova-module-a-openapi": "^5.1.43",
|
|
26
26
|
"zova-module-a-router": "^5.1.29",
|
|
27
27
|
"zova-module-a-routerstack": "^5.1.26",
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import type { TableIdentity } from 'table-identity';
|
|
2
|
-
import type { VonaContext } from 'vona';
|
|
3
|
-
|
|
4
|
-
import type { DtoRecordDossierFileView } from '../dto/recordDossierFileView.ts';
|
|
5
|
-
|
|
6
|
-
export async function resolveDossierFiles(
|
|
7
|
-
this: VonaContext,
|
|
8
|
-
_value: unknown,
|
|
9
|
-
data: { dossierFileIds?: TableIdentity[] },
|
|
10
|
-
): Promise<DtoRecordDossierFileView[]> {
|
|
11
|
-
const dossierFileIds = data.dossierFileIds;
|
|
12
|
-
if (!dossierFileIds?.length) return [];
|
|
13
|
-
const items: Array<DtoRecordDossierFileView | undefined> = await Promise.all(
|
|
14
|
-
dossierFileIds.map(async dossierFileId => {
|
|
15
|
-
const file = await this.bean.file.get(dossierFileId);
|
|
16
|
-
if (!file || file.fileScene !== 'training-record:dossierFile') return undefined;
|
|
17
|
-
return {
|
|
18
|
-
id: file.id,
|
|
19
|
-
provider: String(file.provider),
|
|
20
|
-
clientName: file.clientName,
|
|
21
|
-
fileScene: file.fileScene ? String(file.fileScene) : undefined,
|
|
22
|
-
filename: file.filename,
|
|
23
|
-
contentType: file.contentType,
|
|
24
|
-
size: file.size,
|
|
25
|
-
public: file.public,
|
|
26
|
-
uploadedAt: file.uploadedAt,
|
|
27
|
-
meta: file.meta as Record<string, unknown> | undefined,
|
|
28
|
-
downloadUrl: await this.bean.file.getDownloadUrl(file.id),
|
|
29
|
-
signed: !file.public,
|
|
30
|
-
} satisfies DtoRecordDossierFileView;
|
|
31
|
-
}),
|
|
32
|
-
);
|
|
33
|
-
return items.filter((item): item is DtoRecordDossierFileView => !!item);
|
|
34
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { IDecoratorDtoOptions } from 'vona-module-a-web';
|
|
2
|
-
|
|
3
|
-
import { Api, v } from 'vona-module-a-openapiutils';
|
|
4
|
-
import { Dto } from 'vona-module-a-web';
|
|
5
|
-
import z from 'zod';
|
|
6
|
-
|
|
7
|
-
import type { IImageSceneRecord } from '../types/imageScene.ts';
|
|
8
|
-
|
|
9
|
-
export interface IDtoOptionsImageUploadTokenRequest extends IDecoratorDtoOptions {}
|
|
10
|
-
|
|
11
|
-
@Dto<IDtoOptionsImageUploadTokenRequest>()
|
|
12
|
-
export class DtoImageUploadTokenRequest {
|
|
13
|
-
@Api.field(z.string())
|
|
14
|
-
imageScene: keyof IImageSceneRecord;
|
|
15
|
-
|
|
16
|
-
@Api.field(z.number().int().positive())
|
|
17
|
-
size: number;
|
|
18
|
-
|
|
19
|
-
@Api.field(z.string())
|
|
20
|
-
mimeType: string;
|
|
21
|
-
|
|
22
|
-
@Api.field(v.optional(), z.number().int().positive())
|
|
23
|
-
expiresIn?: number;
|
|
24
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { IDecoratorDtoOptions } from 'vona-module-a-web';
|
|
2
|
-
|
|
3
|
-
import { Api, v } from 'vona-module-a-openapiutils';
|
|
4
|
-
import { Dto } from 'vona-module-a-web';
|
|
5
|
-
import z from 'zod';
|
|
6
|
-
|
|
7
|
-
export interface IDtoOptionsImageUploadTokenResponse extends IDecoratorDtoOptions {}
|
|
8
|
-
|
|
9
|
-
@Dto<IDtoOptionsImageUploadTokenResponse>()
|
|
10
|
-
export class DtoImageUploadTokenResponse {
|
|
11
|
-
@Api.field(z.string())
|
|
12
|
-
token: string;
|
|
13
|
-
|
|
14
|
-
@Api.field(v.optional(), z.number().int().positive())
|
|
15
|
-
expiresIn?: number;
|
|
16
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { IUploadFile } from 'vona-module-a-upload';
|
|
2
|
-
import type { IDecoratorControllerOptions } from 'vona-module-a-web';
|
|
3
|
-
|
|
4
|
-
import fse from 'fs-extra';
|
|
5
|
-
import { BeanBase } from 'vona';
|
|
6
|
-
import { Core } from 'vona-module-a-core';
|
|
7
|
-
import { Passport } from 'vona-module-a-user';
|
|
8
|
-
import { Arg, Controller, Web } from 'vona-module-a-web';
|
|
9
|
-
|
|
10
|
-
export interface IControllerOptionsImage extends IDecoratorControllerOptions {}
|
|
11
|
-
|
|
12
|
-
@Controller<IControllerOptionsImage>({ path: 'image-native' })
|
|
13
|
-
export class ControllerImage extends BeanBase {
|
|
14
|
-
@Web.post('direct-upload/:resourceId')
|
|
15
|
-
@Passport.public()
|
|
16
|
-
@Core.fileUpload()
|
|
17
|
-
async directUpload(
|
|
18
|
-
@Arg.param('resourceId') resourceId: string,
|
|
19
|
-
@Arg.query('token') token: string,
|
|
20
|
-
@Arg.file('image') file: IUploadFile,
|
|
21
|
-
) {
|
|
22
|
-
const payload = await this.bean.imageUploadPolicy.verifyDirectUploadToken(
|
|
23
|
-
token,
|
|
24
|
-
this.ctx.route.routePathRaw,
|
|
25
|
-
);
|
|
26
|
-
if (payload.resourceId !== resourceId) {
|
|
27
|
-
return this.app.throw(401);
|
|
28
|
-
}
|
|
29
|
-
const modelImage = this.app.bean._getBean<any>('a-image.model.image' as never);
|
|
30
|
-
const image = await modelImage.get({
|
|
31
|
-
providerName: 'image-native:native',
|
|
32
|
-
resourceId,
|
|
33
|
-
});
|
|
34
|
-
if (!image || image.status !== 'draft') {
|
|
35
|
-
return this.app.throw(403, `image is not draft: ${resourceId}`);
|
|
36
|
-
}
|
|
37
|
-
if (!image.imageScene) {
|
|
38
|
-
throw new Error(`image scene missing: ${resourceId}`);
|
|
39
|
-
}
|
|
40
|
-
const stat = await fse.stat(file.file);
|
|
41
|
-
const policy = await this.bean.imageUploadPolicy.resolveUploadPolicy({
|
|
42
|
-
imageScene: image.imageScene as never,
|
|
43
|
-
size: Number(stat.size),
|
|
44
|
-
mimeType: file.info.mimeType,
|
|
45
|
-
});
|
|
46
|
-
await this.bean.imageUploadPolicy.validateUploadFile(
|
|
47
|
-
{
|
|
48
|
-
file: file.file,
|
|
49
|
-
filename: file.info.filename,
|
|
50
|
-
mimeType: file.info.mimeType,
|
|
51
|
-
},
|
|
52
|
-
policy,
|
|
53
|
-
);
|
|
54
|
-
await this.scope.service.imageNative.uploadDirectFile(image, file);
|
|
55
|
-
return { ok: true };
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { IApiActionOptions } from 'zova-module-a-api';
|
|
2
|
-
|
|
3
|
-
import { Api, BeanApiBase } from 'zova-module-a-api';
|
|
4
|
-
|
|
5
|
-
import type { paths } from './openapi/index.js';
|
|
6
|
-
|
|
7
|
-
import { OpenApiBaseURL } from './openapi/index.js';
|
|
8
|
-
|
|
9
|
-
/** ImageNativeImage_directUpload */
|
|
10
|
-
export const ApiApiImageNativeImagedirectUploadPath =
|
|
11
|
-
'/api/image/native/image-native/direct-upload/{resourceId}';
|
|
12
|
-
export type ApiApiImageNativeImagedirectUploadPath =
|
|
13
|
-
'/api/image/native/image-native/direct-upload/{resourceId}';
|
|
14
|
-
export type ApiApiImageNativeImagedirectUploadMethod = 'post';
|
|
15
|
-
export type ApiApiImageNativeImagedirectUploadRequestBody = {
|
|
16
|
-
/** Format: binary */
|
|
17
|
-
image: Blob;
|
|
18
|
-
};
|
|
19
|
-
export type ApiApiImageNativeImagedirectUploadResponseBody =
|
|
20
|
-
paths[ApiApiImageNativeImagedirectUploadPath][ApiApiImageNativeImagedirectUploadMethod]['responses']['200']['content']['application/json']['data'];
|
|
21
|
-
|
|
22
|
-
@Api()
|
|
23
|
-
export class ApiImageNativeImage extends BeanApiBase {
|
|
24
|
-
directUpload(body: ApiApiImageNativeImagedirectUploadRequestBody, options?: IApiActionOptions) {
|
|
25
|
-
return this.$fetch.post<any, ApiApiImageNativeImagedirectUploadResponseBody>(
|
|
26
|
-
ApiApiImageNativeImagedirectUploadPath,
|
|
27
|
-
this.$formData(body),
|
|
28
|
-
this.$configPrepare(OpenApiBaseURL(this.sys), options),
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
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
|
-
}
|