cabloy 5.1.96 → 5.1.98
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 +34 -0
- package/cabloy-docs/.vitepress/config.mjs +7 -0
- package/cabloy-docs/backend/image-guide.md +544 -0
- package/cabloy-docs/frontend/image-guide.md +512 -0
- package/cabloy-docs/frontend/introduction.md +2 -0
- package/cabloy-docs/frontend/ioc-and-beans.md +34 -3
- package/cabloy-docs/frontend/model-architecture.md +2 -0
- package/cabloy-docs/frontend/model-resource-owner-pattern.md +2 -0
- package/cabloy-docs/frontend/model-state-guide.md +2 -0
- package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +10 -8
- package/cabloy-docs/frontend/server-data.md +2 -0
- package/cabloy-docs/frontend/state-architecture-for-vue-developers.md +218 -0
- package/cabloy-docs/frontend/system-startup-guide.md +2 -0
- package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +11 -10
- package/cabloy-docs/fullstack/image-workflow.md +403 -0
- package/cabloy-docs/fullstack/introduction.md +1 -0
- package/package.json +1 -1
- package/vona/package.original.json +0 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +500 -94
- package/vona/pnpm-workspace.yaml +1 -1
- package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/bean/ssrSite.admin.ts +4 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteweb/src/bean/ssrSite.web.ts +4 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/cli/ssrSite/boilerplate/{{sceneName}}.{{beanName}}.ts_ +3 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssrHandler.ts +11 -2
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrSite.ts +6 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
- package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileProvider/boilerplate/{{sceneName}}.{{beanName}}.ts_ +23 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileScene/boilerplate/{{sceneName}}.{{beanName}}.ts_ +5 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/package.json +66 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +425 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/locales.ts +18 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.file.ts +322 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileProvider.ts +62 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +196 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.redlock.ts +11 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.version.ts +38 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/config.ts +16 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +167 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadRequest.ts +33 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadResponse.ts +41 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDownloadRequest.ts +12 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadResponse.ts +59 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenRequest.ts +24 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenResponse.ts +16 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadUrlRequest.ts +33 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/file.ts +55 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/fileProvider.ts +24 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/index.ts +4 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileProvider.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileScene.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileUploadValidation.ts +15 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/index.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/model/file.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/model/fileProvider.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/file.ts +144 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileProvider.ts +111 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileScene.ts +53 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/index.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/package.json +53 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/index.ts +104 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/bean/fileProvider.cloudflare.ts +123 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/service/fileCloudflare.ts +245 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/package.json +50 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/index.ts +111 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/bean/fileProvider.native.ts +125 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/config/config.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/service/fileNative.ts +125 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/package.json +14 -0
- package/vona/src/suite-vendor/a-file/tsconfig.base.json +4 -0
- package/vona/src/suite-vendor/a-file/tsconfig.json +16 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/package.json +4 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +61 -3
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +336 -152
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageProvider.ts +21 -14
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +59 -11
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.index.ts +12 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.redlock.ts +1 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/schedule.imageDraftPrune.ts +29 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +24 -54
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeRequest.ts +13 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeResponse.ts +10 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +10 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageUploadValidation.ts +15 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +18 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +28 -2
- 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 +13 -1
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +26 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/package.json +6 -2
- package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +37 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +21 -1
- package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +57 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +142 -34
- package/vona/src/suite-vendor/a-image/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/entity.ts +2 -4
- package/vona/src/suite-vendor/a-vona/modules/a-ormutils/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormutils/src/lib/columns.ts +7 -3
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/pnpm-lock.yaml +438 -446
- package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +0 -9
- /package/vona/patches/{zova-core@5.1.75.patch → zova-core@5.1.76.patch} +0 -0
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
# Image Guide
|
|
2
|
+
|
|
3
|
+
This guide explains how to use **Zova image** in Cabloy Basic with a practical, contract-first path.
|
|
4
|
+
|
|
5
|
+
Zova image is not only a file input. It is a frontend image layer built around:
|
|
6
|
+
|
|
7
|
+
- the built-in form-field renderer `basic-image:formFieldImage`
|
|
8
|
+
- the built-in table-cell renderer `basic-image:image`
|
|
9
|
+
- resolved relation fields such as `image` and `sceneImages`
|
|
10
|
+
- upload-token based upload through the generated image API
|
|
11
|
+
- optional crop and resize behavior in the browser
|
|
12
|
+
- shared preview dialogs for form and table usage
|
|
13
|
+
|
|
14
|
+
Use this page together with:
|
|
15
|
+
|
|
16
|
+
- [Form Guide](/frontend/form-guide)
|
|
17
|
+
- [Table Guide](/frontend/table-guide)
|
|
18
|
+
- [TableCell Authoring Cookbook](/frontend/table-cell-cookbook)
|
|
19
|
+
- [API Schema Guide](/frontend/api-schema-guide)
|
|
20
|
+
- [SSR ClientOnly](/frontend/ssr-client-only)
|
|
21
|
+
- [Backend Image Guide](/backend/image-guide)
|
|
22
|
+
|
|
23
|
+
> [!TIP]
|
|
24
|
+
> **Image docs path**
|
|
25
|
+
>
|
|
26
|
+
> - **[Frontend Image Guide](/frontend/image-guide)** — learn `basic-image:formFieldImage`, `basic-image:image`, relation previews, and client-side image-field behavior
|
|
27
|
+
> - **[Backend Image Guide](/backend/image-guide)** — learn image scenes, providers, serializer transforms, delivery policy, and backend lifecycle behavior
|
|
28
|
+
>
|
|
29
|
+
> Use the frontend guide when the question is about forms, tables, previews, or client-side upload UX. Use the backend guide when the question is about image scenes, providers, serializer transforms, signed delivery, or upload policy enforcement.
|
|
30
|
+
|
|
31
|
+
Read this page first when your next question is:
|
|
32
|
+
|
|
33
|
+
- how to wire `basic-image:formFieldImage` into a form
|
|
34
|
+
- how to show image thumbnails in a table with `basic-image:image`
|
|
35
|
+
- why an image field has IDs on one side and resolved relation fields on the other
|
|
36
|
+
|
|
37
|
+
## What you should learn first
|
|
38
|
+
|
|
39
|
+
If you only remember one idea, remember this one:
|
|
40
|
+
|
|
41
|
+
> In Zova, an image field usually stores image ID data, while resolved relation fields provide the `DtoImageView` objects that the built-in image renderers use for previews, dialogs, and richer display.
|
|
42
|
+
|
|
43
|
+
That leads to two closely related data surfaces:
|
|
44
|
+
|
|
45
|
+
- persisted value such as `imageId` or `sceneImageIds`
|
|
46
|
+
- resolved relation field such as `image` or `sceneImages`
|
|
47
|
+
|
|
48
|
+
The built-in image resources work best when both surfaces are present and named consistently.
|
|
49
|
+
|
|
50
|
+
## What `basic-image` provides on the frontend
|
|
51
|
+
|
|
52
|
+
The `basic-image` module exposes two main public rendering resources.
|
|
53
|
+
|
|
54
|
+
### `basic-image:formFieldImage`
|
|
55
|
+
|
|
56
|
+
Use this as the standard image field renderer for forms.
|
|
57
|
+
|
|
58
|
+
It supports:
|
|
59
|
+
|
|
60
|
+
- single-image and multi-image fields
|
|
61
|
+
- upload-token based upload
|
|
62
|
+
- frontend file validation
|
|
63
|
+
- optional crop
|
|
64
|
+
- optional resize and re-encode before upload
|
|
65
|
+
- readonly image cards
|
|
66
|
+
- relation-backed preview refresh after upload
|
|
67
|
+
|
|
68
|
+
### `basic-image:image`
|
|
69
|
+
|
|
70
|
+
Use this as the standard image table-cell renderer.
|
|
71
|
+
|
|
72
|
+
It supports:
|
|
73
|
+
|
|
74
|
+
- thumbnail rendering
|
|
75
|
+
- relation-first preview lookup
|
|
76
|
+
- fallback URL preview behavior
|
|
77
|
+
- multi-image count badges
|
|
78
|
+
- shared preview dialog opening from the table cell
|
|
79
|
+
|
|
80
|
+
### Shared preview behavior
|
|
81
|
+
|
|
82
|
+
Both resources use the same preview conventions:
|
|
83
|
+
|
|
84
|
+
- preview URLs are normalized against `sys.config.api.baseURL` when needed
|
|
85
|
+
- preview titles use the field title when available
|
|
86
|
+
- multi-image preview supports keyboard left/right navigation
|
|
87
|
+
|
|
88
|
+
## Minimum backend/frontend contract
|
|
89
|
+
|
|
90
|
+
The frontend image resources depend on a small but important backend contract.
|
|
91
|
+
|
|
92
|
+
A practical minimum is:
|
|
93
|
+
|
|
94
|
+
1. the backend field stores image ID data such as `imageId` or `sceneImageIds`
|
|
95
|
+
2. the backend field metadata points to the built-in render resources
|
|
96
|
+
3. the backend DTO exposes resolved relation fields such as `image?: DtoImageView` or `sceneImages?: DtoImageView[]`
|
|
97
|
+
4. the backend image scene defines the authoritative upload policy
|
|
98
|
+
|
|
99
|
+
Representative contract pattern:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
ZovaRender.field('basic-image:formFieldImage', {
|
|
103
|
+
imageScene: 'training-student:studentImage',
|
|
104
|
+
relationName: 'image',
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
ZovaRender.cell('basic-image:image', {
|
|
108
|
+
relationName: 'image',
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
And on the DTO side:
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
v.serializerTransform('a-image:resolveView', {
|
|
116
|
+
fieldName: 'imageId',
|
|
117
|
+
imageScene: 'training-student:studentImage',
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
A practical rule is:
|
|
122
|
+
|
|
123
|
+
- frontend validation, crop, and resize improve UX
|
|
124
|
+
- backend image scenes and backend upload-token verification remain the true policy boundary
|
|
125
|
+
|
|
126
|
+
For the backend side of that contract, read [Backend Image Guide](/backend/image-guide), especially the sections on image scenes, upload-token flow, and DTO-side image view resolution.
|
|
127
|
+
|
|
128
|
+
## One running example through this guide: Student image
|
|
129
|
+
|
|
130
|
+
To keep the guide concrete, the first example uses the Student resource.
|
|
131
|
+
|
|
132
|
+
The Student entity stores one image field:
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
@Api.field(
|
|
136
|
+
v.title($locale('StudentImage')),
|
|
137
|
+
v.optional(),
|
|
138
|
+
ZovaRender.field('basic-image:formFieldImage', {
|
|
139
|
+
imageScene: 'training-student:studentImage',
|
|
140
|
+
accept: ['image/png', 'image/jpeg', 'image/webp'],
|
|
141
|
+
maxSize: 2 * 1024 * 1024,
|
|
142
|
+
enableCrop: true,
|
|
143
|
+
cropAspectRatio: 1,
|
|
144
|
+
relationName: 'image',
|
|
145
|
+
resize: {
|
|
146
|
+
width: 512,
|
|
147
|
+
height: 512,
|
|
148
|
+
fit: 'cover',
|
|
149
|
+
format: 'jpeg',
|
|
150
|
+
quality: 90,
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
ZovaRender.cell('basic-image:image', { relationName: 'image' }),
|
|
154
|
+
v.tableIdentity(),
|
|
155
|
+
)
|
|
156
|
+
imageId?: TableIdentity;
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
And the DTO exposes a resolved relation field:
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
@Api.field(
|
|
163
|
+
ZovaRender.visible(false),
|
|
164
|
+
v.optional(),
|
|
165
|
+
v.serializerTransform('a-image:resolveView', {
|
|
166
|
+
fieldName: 'imageId',
|
|
167
|
+
imageScene: 'training-student:studentImage',
|
|
168
|
+
}),
|
|
169
|
+
v.object(DtoImageView),
|
|
170
|
+
)
|
|
171
|
+
image?: DtoImageView;
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
This gives the frontend a clean split:
|
|
175
|
+
|
|
176
|
+
- `imageId` is the submitted field value
|
|
177
|
+
- `image` is the resolved relation field
|
|
178
|
+
|
|
179
|
+
The backend explanation for that pairing lives in [Backend Image Guide](/backend/image-guide), where the same example is described from the scene and serializer-transform perspective.
|
|
180
|
+
|
|
181
|
+
## Step 1: Choose the right image field style
|
|
182
|
+
|
|
183
|
+
Before writing code, choose which of these styles matches your field.
|
|
184
|
+
|
|
185
|
+
### Style A: built-in image resources
|
|
186
|
+
|
|
187
|
+
Use this when:
|
|
188
|
+
|
|
189
|
+
- standard upload, preview, and thumbnail behavior is enough
|
|
190
|
+
- the field should stay schema-driven
|
|
191
|
+
- you want the shortest path to a working CRUD field
|
|
192
|
+
|
|
193
|
+
This is the preferred default.
|
|
194
|
+
|
|
195
|
+
### Style B: built-in image field plus custom backend contract
|
|
196
|
+
|
|
197
|
+
Use this when:
|
|
198
|
+
|
|
199
|
+
- the frontend UX can stay standard
|
|
200
|
+
- but the backend scene, DTO, or relation structure is business-specific
|
|
201
|
+
- the field still fits the `basic-image` mental model
|
|
202
|
+
|
|
203
|
+
### Style C: custom image renderer
|
|
204
|
+
|
|
205
|
+
Use this when:
|
|
206
|
+
|
|
207
|
+
- the form interaction is business-specific
|
|
208
|
+
- the table thumbnail behavior must be custom
|
|
209
|
+
- built-in crop, preview, or value flow is not enough
|
|
210
|
+
|
|
211
|
+
A practical rule is:
|
|
212
|
+
|
|
213
|
+
- start with the built-in image resources first
|
|
214
|
+
- add custom renderers only when the business UI really needs module-owned image behavior
|
|
215
|
+
|
|
216
|
+
## Step 2: Understand relation naming and `relationName`
|
|
217
|
+
|
|
218
|
+
The built-in image resources infer relation names from the stored field name.
|
|
219
|
+
|
|
220
|
+
Current inference rules are:
|
|
221
|
+
|
|
222
|
+
- `imageId` -> `image`
|
|
223
|
+
- `sceneImageIds` -> `sceneImages`
|
|
224
|
+
|
|
225
|
+
That is why the Student example can naturally pair:
|
|
226
|
+
|
|
227
|
+
- `imageId`
|
|
228
|
+
- `image`
|
|
229
|
+
|
|
230
|
+
and the Record example can naturally pair:
|
|
231
|
+
|
|
232
|
+
- `sceneImageIds`
|
|
233
|
+
- `sceneImages`
|
|
234
|
+
|
|
235
|
+
Use explicit `relationName` when:
|
|
236
|
+
|
|
237
|
+
- the field name does not follow the `...Id` or `...Ids` pattern
|
|
238
|
+
- you want the contract to be more obvious in examples and maintenance
|
|
239
|
+
- nested or details flows should avoid any ambiguity
|
|
240
|
+
|
|
241
|
+
Even when inference would work, setting `relationName` explicitly is often a good public-doc choice because it makes the pairing obvious.
|
|
242
|
+
|
|
243
|
+
## Step 3: Understand `basic-image:formFieldImage`
|
|
244
|
+
|
|
245
|
+
`basic-image:formFieldImage` is the main form-side image renderer.
|
|
246
|
+
|
|
247
|
+
### Default behavior
|
|
248
|
+
|
|
249
|
+
The current defaults include:
|
|
250
|
+
|
|
251
|
+
- `accept: ['image/png', 'image/jpeg', 'image/webp']`
|
|
252
|
+
- `maxCount: 1`
|
|
253
|
+
- `enableCrop: true`
|
|
254
|
+
- `cropAspectRatio: 1`
|
|
255
|
+
- `cropShape: 'rect'`
|
|
256
|
+
- `resize: { width: 512, height: 512, fit: 'cover', format: 'jpeg', quality: 90 }`
|
|
257
|
+
|
|
258
|
+
These defaults are helpful, but they are not a replacement for explicit business intent.
|
|
259
|
+
|
|
260
|
+
### Important options
|
|
261
|
+
|
|
262
|
+
#### Required in practice
|
|
263
|
+
|
|
264
|
+
- `imageScene`
|
|
265
|
+
|
|
266
|
+
Although the option shape is optional at the type level, upload currently throws if no image scene is provided.
|
|
267
|
+
|
|
268
|
+
#### Value-shape options
|
|
269
|
+
|
|
270
|
+
- `multiple`
|
|
271
|
+
- `maxCount`
|
|
272
|
+
|
|
273
|
+
These control whether the field behaves like one image ID or an array of image IDs.
|
|
274
|
+
|
|
275
|
+
#### File-selection and validation options
|
|
276
|
+
|
|
277
|
+
- `accept`
|
|
278
|
+
- `mimeTypes`
|
|
279
|
+
- `extensions`
|
|
280
|
+
- `maxSize`
|
|
281
|
+
- `minSize`
|
|
282
|
+
- `placeholder`
|
|
283
|
+
|
|
284
|
+
These affect frontend input behavior and frontend validation messaging.
|
|
285
|
+
|
|
286
|
+
#### Crop options
|
|
287
|
+
|
|
288
|
+
- `enableCrop`
|
|
289
|
+
- `cropAspectRatio`
|
|
290
|
+
- `cropShape`
|
|
291
|
+
|
|
292
|
+
These control the optional crop dialog.
|
|
293
|
+
|
|
294
|
+
#### Resize options
|
|
295
|
+
|
|
296
|
+
- `resize.width`
|
|
297
|
+
- `resize.height`
|
|
298
|
+
- `resize.fit`
|
|
299
|
+
- `resize.background`
|
|
300
|
+
- `resize.quality`
|
|
301
|
+
- `resize.format`
|
|
302
|
+
|
|
303
|
+
These control browser-side preprocessing before upload.
|
|
304
|
+
|
|
305
|
+
#### Relation option
|
|
306
|
+
|
|
307
|
+
- `relationName`
|
|
308
|
+
|
|
309
|
+
Use this to pair the stored field with its resolved relation field explicitly.
|
|
310
|
+
|
|
311
|
+
## Step 4: Understand the upload lifecycle
|
|
312
|
+
|
|
313
|
+
The built-in form field follows a consistent frontend flow.
|
|
314
|
+
|
|
315
|
+
1. the user selects a file or files
|
|
316
|
+
2. the frontend validates the file type and size
|
|
317
|
+
3. if enabled, the crop dialog opens
|
|
318
|
+
4. if configured, the browser resizes and re-encodes the image
|
|
319
|
+
5. the frontend calls `scope.api.image.createUploadToken(...)`
|
|
320
|
+
6. the frontend uploads multipart data with `token` and `image`
|
|
321
|
+
7. the stored field value is updated to the uploaded image ID or image ID array
|
|
322
|
+
8. the resolved relation field is synchronized so the form can preview the uploaded image immediately
|
|
323
|
+
|
|
324
|
+
That last step is easy to miss, but it is one of the most important behaviors in the current implementation.
|
|
325
|
+
|
|
326
|
+
The field value stores IDs.
|
|
327
|
+
|
|
328
|
+
The resolved relation field stores preview-ready image objects.
|
|
329
|
+
|
|
330
|
+
## Step 5: Readonly and preview behavior
|
|
331
|
+
|
|
332
|
+
The built-in form field keeps image previews useful even outside edit mode.
|
|
333
|
+
|
|
334
|
+
### Editable mode
|
|
335
|
+
|
|
336
|
+
In editable mode, the field shows:
|
|
337
|
+
|
|
338
|
+
- select/add/replace button
|
|
339
|
+
- upload spinner
|
|
340
|
+
- validation or upload error message
|
|
341
|
+
- preview cards for current images
|
|
342
|
+
- preview button per image
|
|
343
|
+
- remove button per image
|
|
344
|
+
|
|
345
|
+
### Readonly mode
|
|
346
|
+
|
|
347
|
+
In readonly mode, the field still renders preview cards.
|
|
348
|
+
|
|
349
|
+
If no images are available, it shows a lightweight empty-state message instead of a file input.
|
|
350
|
+
|
|
351
|
+
### Preview dialog
|
|
352
|
+
|
|
353
|
+
When an image preview is available, the field opens a shared preview dialog that supports:
|
|
354
|
+
|
|
355
|
+
- large lead image preview
|
|
356
|
+
- filename display
|
|
357
|
+
- multi-image thumbnail selection
|
|
358
|
+
- `1 / N` style counters
|
|
359
|
+
- left/right keyboard navigation
|
|
360
|
+
|
|
361
|
+
## Step 6: Use the multi-image pattern when the business field is plural
|
|
362
|
+
|
|
363
|
+
The Record example shows the multi-image case.
|
|
364
|
+
|
|
365
|
+
Entity pattern:
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
@Api.field(
|
|
369
|
+
v.title($locale('ScenePhotos')),
|
|
370
|
+
v.optional(),
|
|
371
|
+
ZovaRender.field('basic-image:formFieldImage', {
|
|
372
|
+
imageScene: 'training-record:sceneImage',
|
|
373
|
+
multiple: true,
|
|
374
|
+
maxCount: 9,
|
|
375
|
+
relationName: 'sceneImages',
|
|
376
|
+
accept: ['image/png', 'image/jpeg', 'image/webp'],
|
|
377
|
+
maxSize: 2 * 1024 * 1024,
|
|
378
|
+
enableCrop: false,
|
|
379
|
+
resize: {
|
|
380
|
+
width: 1280,
|
|
381
|
+
height: 1280,
|
|
382
|
+
fit: 'contain',
|
|
383
|
+
format: 'jpeg',
|
|
384
|
+
quality: 90,
|
|
385
|
+
},
|
|
386
|
+
}),
|
|
387
|
+
ZovaRender.cell('basic-image:image', { relationName: 'sceneImages' }),
|
|
388
|
+
v.array(v.tableIdentity()),
|
|
389
|
+
)
|
|
390
|
+
sceneImageIds?: TableIdentity[];
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
DTO pattern:
|
|
394
|
+
|
|
395
|
+
```typescript
|
|
396
|
+
@Api.field(
|
|
397
|
+
ZovaRender.visible(false),
|
|
398
|
+
v.optional(),
|
|
399
|
+
v.serializerTransform('a-image:resolveViews', {
|
|
400
|
+
fieldName: 'sceneImageIds',
|
|
401
|
+
imageScene: 'training-record:sceneImage',
|
|
402
|
+
}),
|
|
403
|
+
v.array(DtoImageView),
|
|
404
|
+
)
|
|
405
|
+
sceneImages?: DtoImageView[];
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
This is the cleanest pattern when the business field should store many image IDs while the UI needs many preview-ready image objects.
|
|
409
|
+
|
|
410
|
+
## Step 7: Understand `basic-image:image` in tables
|
|
411
|
+
|
|
412
|
+
`basic-image:image` is the standard image table-cell renderer.
|
|
413
|
+
|
|
414
|
+
### Public options
|
|
415
|
+
|
|
416
|
+
- `size` — defaults to `40`
|
|
417
|
+
- `fit` — defaults to `cover`
|
|
418
|
+
- `relationName`
|
|
419
|
+
|
|
420
|
+
### Runtime behavior
|
|
421
|
+
|
|
422
|
+
The cell renderer:
|
|
423
|
+
|
|
424
|
+
- reads the normal table cell value through `next()`
|
|
425
|
+
- tries resolved relation field data first
|
|
426
|
+
- falls back to treating the cell value as URL or URL array data
|
|
427
|
+
- renders a thumbnail
|
|
428
|
+
- adds a `+N` badge when multiple images exist
|
|
429
|
+
- opens the shared preview dialog on click
|
|
430
|
+
- stops row-click propagation while opening the preview
|
|
431
|
+
|
|
432
|
+
A practical rule is:
|
|
433
|
+
|
|
434
|
+
- if the table should show reliable image thumbnails from ID-backed fields, make sure the row data includes the matching resolved relation field
|
|
435
|
+
|
|
436
|
+
## Step 8: Keep details and nested forms relation-aware
|
|
437
|
+
|
|
438
|
+
There is an important special case in the details flow.
|
|
439
|
+
|
|
440
|
+
The `basic-details` service currently special-cases `basic-image:formFieldImage` so submitted detail items keep the resolved relation field data.
|
|
441
|
+
|
|
442
|
+
That matters because nested or detail-based image editing should preserve:
|
|
443
|
+
|
|
444
|
+
- the stored image ID data
|
|
445
|
+
- the resolved relation field data
|
|
446
|
+
|
|
447
|
+
If your image field appears in details or nested form flows, make sure:
|
|
448
|
+
|
|
449
|
+
- the matching resolved relation field exists
|
|
450
|
+
- the field name follows the expected naming rule, or `relationName` is set explicitly
|
|
451
|
+
|
|
452
|
+
Without that relation pairing, image IDs may still save correctly while preview behavior becomes weaker or incomplete in the detail UI.
|
|
453
|
+
|
|
454
|
+
## Step 9: Remember the SSR and client-only boundary
|
|
455
|
+
|
|
456
|
+
Crop and resize are browser-side behaviors.
|
|
457
|
+
|
|
458
|
+
The current implementation relies on:
|
|
459
|
+
|
|
460
|
+
- `ClientOnly`
|
|
461
|
+
- browser `File`
|
|
462
|
+
- browser `Image`
|
|
463
|
+
- browser `canvas`
|
|
464
|
+
- object URLs
|
|
465
|
+
|
|
466
|
+
That means:
|
|
467
|
+
|
|
468
|
+
- existing preview data can still render in SSR-safe flows
|
|
469
|
+
- crop and resize behavior only happens on the client
|
|
470
|
+
- the guide should not describe crop or resize as SSR features
|
|
471
|
+
|
|
472
|
+
For the general boundary, read [SSR ClientOnly](/frontend/ssr-client-only).
|
|
473
|
+
|
|
474
|
+
## Common mistakes to avoid
|
|
475
|
+
|
|
476
|
+
When image fields misbehave, these are the first mistakes to check.
|
|
477
|
+
|
|
478
|
+
1. **Forgetting `imageScene`**
|
|
479
|
+
- the field may render, but upload cannot work correctly
|
|
480
|
+
2. **Treating frontend validation as the security boundary**
|
|
481
|
+
- backend image scene policy is still authoritative
|
|
482
|
+
3. **Storing only image IDs without exposing resolved relation fields**
|
|
483
|
+
- previews, cards, and table thumbnails become weaker
|
|
484
|
+
4. **Relying on inferred relation names when the field name is nonstandard**
|
|
485
|
+
- set `relationName` explicitly instead
|
|
486
|
+
5. **Forgetting `multiple: true` for array-backed fields**
|
|
487
|
+
- the UI and data shape will drift apart
|
|
488
|
+
6. **Describing crop or resize as backend processing**
|
|
489
|
+
- current crop and resize are frontend preprocessing steps before upload
|
|
490
|
+
7. **Ignoring details/nested-form relation behavior**
|
|
491
|
+
- saved data may be correct while preview state becomes fragile
|
|
492
|
+
|
|
493
|
+
## Verification checklist
|
|
494
|
+
|
|
495
|
+
When writing or changing frontend image docs, verify in this order.
|
|
496
|
+
|
|
497
|
+
1. confirm the form-field resource is still `basic-image:formFieldImage`
|
|
498
|
+
2. confirm the table-cell resource is still `basic-image:image`
|
|
499
|
+
3. confirm option names still match the current controller and table-cell source
|
|
500
|
+
4. confirm relation-name inference still matches:
|
|
501
|
+
- `imageId -> image`
|
|
502
|
+
- `...Ids -> ...s`
|
|
503
|
+
5. confirm the Student example still shows the single-image pattern
|
|
504
|
+
6. confirm the Record example still shows the multi-image pattern
|
|
505
|
+
7. confirm the details service still preserves image relation data specially
|
|
506
|
+
8. run the docs build and confirm the page appears in navigation
|
|
507
|
+
|
|
508
|
+
A good final question is:
|
|
509
|
+
|
|
510
|
+
- does this explanation stay frontend-first and contract-aware, or has it drifted into a backend image internals article?
|
|
511
|
+
|
|
512
|
+
If it stays centered on image fields, relation previews, table thumbnails, upload lifecycle, and client-side behavior, it matches how Zova image currently works.
|
|
@@ -41,6 +41,7 @@ Recommended starting set:
|
|
|
41
41
|
- [Quickstart](/frontend/quickstart)
|
|
42
42
|
- [Foundation](/frontend/foundation)
|
|
43
43
|
- [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers)
|
|
44
|
+
- [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers)
|
|
44
45
|
- [Frontend Source Reading Roadmap](/frontend/frontend-source-reading-roadmap)
|
|
45
46
|
- [Zova Source Reading Map](/frontend/zova-source-reading-map)
|
|
46
47
|
- [IoC and Beans](/frontend/ioc-and-beans)
|
|
@@ -79,6 +80,7 @@ Use this path when the task is about UI composition, component contracts, form o
|
|
|
79
80
|
|
|
80
81
|
- [Component Guide](/frontend/component-guide)
|
|
81
82
|
- [Form Guide](/frontend/form-guide)
|
|
83
|
+
- [Image Guide](/frontend/image-guide)
|
|
82
84
|
- [Zova Form Under the Hood](/frontend/zova-form-under-the-hood)
|
|
83
85
|
- [Zova Form Source Reading Map](/frontend/zova-form-source-reading-map)
|
|
84
86
|
- [Table Guide](/frontend/table-guide)
|
|
@@ -8,14 +8,45 @@ That matters because state sharing, resource access, lifecycle behavior, and cro
|
|
|
8
8
|
|
|
9
9
|
## The four state-sharing scopes
|
|
10
10
|
|
|
11
|
-
A key Zova idea is that several common sharing scopes can be handled through one IoC-centered model
|
|
11
|
+
A key Zova idea is that several common sharing scopes can be handled through one IoC-centered model.
|
|
12
|
+
|
|
13
|
+
In many Vue 3 codebases, developers combine local reactive state, composables, `provide/inject`, and store layers depending on where sharing must happen. Zova asks a different first question: which bean owns this state or behavior, and which container scope should own that bean?
|
|
12
14
|
|
|
13
15
|
- component-internal
|
|
14
16
|
- between-components
|
|
15
17
|
- app-global
|
|
16
18
|
- system-level
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
| Sharing scope | Common Vue 3 pattern | Zova implementation shape |
|
|
21
|
+
| ------------------ | ---------------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
22
|
+
| component-internal | local component state or component-local composables | `ctx`-scoped controller or bean ownership |
|
|
23
|
+
| between-components | props/emits, parent-owned composables, or `provide/inject` | hierarchical injection through the same IoC model, often with `host` or `skipSelf` |
|
|
24
|
+
| app-global | app-level stores or app-wide provided state | `app` container beans shared at app or request scope |
|
|
25
|
+
| system-level | module-level singletons or long-lived imported state | `sys` container beans and system-level startup/lifecycle |
|
|
26
|
+
|
|
27
|
+
The most important SSR distinction is between `app-global` and `system-level`: app-global behavior can be tied to an individual app instance or request, while system-level behavior is not request-scoped and can outlive individual SSR requests. For more on that lifecycle boundary, see [System Startup Guide](/frontend/system-startup-guide).
|
|
28
|
+
|
|
29
|
+
The point is not that Vue patterns are wrong. The point is that Zova can keep more of these sharing cases inside one bean-and-container architecture instead of switching between unrelated mechanisms. If you want the broader architectural comparison, read [Zova vs Vue 3 Comparison](/frontend/zova-vs-vue3-comparison). If your main question is how ownership, scope, persistence, cache semantics, and SSR behavior fit together as one large-project state system, continue with [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers).
|
|
30
|
+
|
|
31
|
+
A compact mental model looks like this:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
class ControllerPage {
|
|
35
|
+
@Use()
|
|
36
|
+
$$localCounterState: CounterState;
|
|
37
|
+
|
|
38
|
+
@Use({ injectionScope: 'host' })
|
|
39
|
+
$$hostCounterState: CounterState;
|
|
40
|
+
|
|
41
|
+
@Use({ injectionScope: 'app' })
|
|
42
|
+
$$appCounterState: CounterState;
|
|
43
|
+
|
|
44
|
+
@Use({ injectionScope: 'sys' })
|
|
45
|
+
$$sysCounterState: CounterState;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
This top-level example is intentionally compact. The injection, hierarchical lookup, and scope sections below explain these patterns in more detail.
|
|
19
50
|
|
|
20
51
|
## The three container types
|
|
21
52
|
|
|
@@ -104,7 +135,7 @@ In practice, Zova can still preserve the ergonomic class-based development exper
|
|
|
104
135
|
|
|
105
136
|
### Hierarchical injection patterns
|
|
106
137
|
|
|
107
|
-
Hierarchical injection replaces many cases where a generic Vue app would fall back to `provide/inject`.
|
|
138
|
+
Hierarchical injection replaces many common cases where a generic Vue app would fall back to `provide/inject`.
|
|
108
139
|
|
|
109
140
|
Representative child lookup pattern:
|
|
110
141
|
|
|
@@ -42,6 +42,8 @@ In practice, a model often sits above `$api` and below page rendering:
|
|
|
42
42
|
|
|
43
43
|
Read together with [Server Data](/frontend/server-data) when deciding which abstraction layer a feature should use.
|
|
44
44
|
|
|
45
|
+
If you want the larger docs-style overview of how ownership, scope, persistence, cache semantics, and SSR behavior connect in big Vue applications, read [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers) before diving deeper into model internals.
|
|
46
|
+
|
|
45
47
|
If your next question is how the generic lower-level model runtime works beneath these abstractions, continue with [A-Model Under the Hood](/frontend/a-model-under-the-hood).
|
|
46
48
|
|
|
47
49
|
## Relationship to TanStack Query
|
|
@@ -8,6 +8,8 @@ It uses the current `rest-resource` model as the main source specimen:
|
|
|
8
8
|
|
|
9
9
|
Read [Model Architecture](/frontend/model-architecture) and [Model State Guide](/frontend/model-state-guide) first if you want the broader Model runtime and helper surface.
|
|
10
10
|
|
|
11
|
+
If you want the larger docs-style overview of how ownership, scope, unified model state, and resource boundaries fit together in large Vue systems, read [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers) first.
|
|
12
|
+
|
|
11
13
|
If your next question is how `ModelResource` itself works internally at the source level, continue with [ModelResource Internals Deep Dive](/frontend/model-resource-internals-deep-dive).
|
|
12
14
|
|
|
13
15
|
If your next question is how the generic lower-level model runtime works beneath `ModelResource`, continue with [Model Runtime Under the Hood](/frontend/a-model-under-the-hood).
|
|
@@ -4,6 +4,8 @@ This guide explains how to author and consume model-managed state in Zova within
|
|
|
4
4
|
|
|
5
5
|
Read [Model Architecture](/frontend/model-architecture) first if you want the broader architectural role of Model.
|
|
6
6
|
|
|
7
|
+
If your main question is how Model fits into the larger ownership/scope/persistence picture for big Vue applications, read [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers) before going deeper into helper families.
|
|
8
|
+
|
|
7
9
|
If you specifically want the scalable resource-facade pattern, continue with [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern).
|
|
8
10
|
|
|
9
11
|
If you want the generic lower-level model runtime beneath these helper families, continue with [A-Model Under the Hood](/frontend/a-model-under-the-hood).
|
|
@@ -29,14 +29,14 @@ That is why Zova code often reads more like controller-oriented application code
|
|
|
29
29
|
|
|
30
30
|
## Quick translation table
|
|
31
31
|
|
|
32
|
-
| If you usually think in Vue terms
|
|
33
|
-
|
|
|
34
|
-
| `setup()` is the main wiring point
|
|
35
|
-
| `ref` / `reactive` are the visible state hosts
|
|
36
|
-
| `computed()` creates local derived refs
|
|
37
|
-
| `useRoute()` pulls route state into the component
|
|
38
|
-
| `provide/inject`, composables, props, and stores are separate sharing tools | bean scopes and IoC are used to unify more sharing patterns under one model
|
|
39
|
-
| template or component render is the obvious center
|
|
32
|
+
| If you usually think in Vue terms | Read Zova like this instead |
|
|
33
|
+
| --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
34
|
+
| `setup()` is the main wiring point | `__init__` and bean lifecycle are major wiring points |
|
|
35
|
+
| `ref` / `reactive` are the visible state hosts | controller and bean instances are the visible state hosts |
|
|
36
|
+
| `computed()` creates local derived refs | `$computed()` usually creates instance-level derived state |
|
|
37
|
+
| `useRoute()` pulls route state into the component | page controllers expose `$route`, `$params`, and `$query` as part of the controller surface |
|
|
38
|
+
| `provide/inject`, composables, props, and stores are separate sharing tools | bean scopes and IoC are used to unify more sharing patterns under one model |
|
|
39
|
+
| template or component render is the obvious center | controller-oriented architecture is the center; render can stay in the controller or move into render beans |
|
|
40
40
|
|
|
41
41
|
## Start from the right assumptions
|
|
42
42
|
|
|
@@ -226,6 +226,8 @@ Use this order when:
|
|
|
226
226
|
- you want to read Zova source code accurately
|
|
227
227
|
- you want to avoid rewriting framework-specific code back toward generic Vue habits
|
|
228
228
|
|
|
229
|
+
If your main question is specifically about large-project state organization rather than source-reading posture first, continue next with [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers).
|
|
230
|
+
|
|
229
231
|
## Common mistakes to avoid
|
|
230
232
|
|
|
231
233
|
### Mistake 1: "This should become a normal Vue SFC"
|
|
@@ -45,6 +45,8 @@ Use OpenAPI SDK generation when you want backend-generated API contracts to driv
|
|
|
45
45
|
|
|
46
46
|
Use schema-driven layers when metadata itself needs to participate in higher-level frontend behavior such as validation or automatic rendering.
|
|
47
47
|
|
|
48
|
+
If your main question is not only which data-access layer to choose, but how data ownership, sharing boundaries, persistence, and SSR semantics fit together in large Vue systems, read [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers) together with this page.
|
|
49
|
+
|
|
48
50
|
## Why this matters for Cabloy
|
|
49
51
|
|
|
50
52
|
This abstraction ladder is one of the key links between Zova and Vona.
|