@blueprint-ts/core 1.0.0
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/.editorconfig +508 -0
- package/.eslintrc.cjs +15 -0
- package/.prettierrc.json +8 -0
- package/LICENSE +21 -0
- package/README.md +1 -0
- package/docker-compose.yaml +8 -0
- package/docs/.vitepress/config.ts +68 -0
- package/docs/.vitepress/theme/Layout.vue +14 -0
- package/docs/.vitepress/theme/components/VersionSelector.vue +64 -0
- package/docs/.vitepress/theme/index.js +13 -0
- package/docs/index.md +70 -0
- package/docs/services/laravel/pagination.md +54 -0
- package/docs/services/laravel/requests.md +62 -0
- package/docs/services/requests/index.md +74 -0
- package/docs/vue/forms.md +326 -0
- package/docs/vue/requests/route-model-binding.md +66 -0
- package/docs/vue/state.md +293 -0
- package/env.d.ts +1 -0
- package/eslint.config.js +15 -0
- package/examples/files/7z2404-x64.exe +0 -0
- package/examples/index.html +14 -0
- package/examples/js/app.js +8 -0
- package/examples/js/router.js +22 -0
- package/examples/js/view/App.vue +49 -0
- package/examples/js/view/layout/DemoPage.vue +28 -0
- package/examples/js/view/pagination/Pagination.vue +28 -0
- package/examples/js/view/pagination/components/errorPagination/ErrorPagination.vue +71 -0
- package/examples/js/view/pagination/components/errorPagination/GetProductsRequest.ts +54 -0
- package/examples/js/view/pagination/components/infiniteScrolling/GetProductsRequest.ts +50 -0
- package/examples/js/view/pagination/components/infiniteScrolling/InfiniteScrolling.vue +57 -0
- package/examples/js/view/pagination/components/tablePagination/GetProductsRequest.ts +50 -0
- package/examples/js/view/pagination/components/tablePagination/TablePagination.vue +63 -0
- package/examples/js/view/requests/Requests.vue +34 -0
- package/examples/js/view/requests/components/abortableRequest/AbortableRequest.vue +36 -0
- package/examples/js/view/requests/components/abortableRequest/GetProductsRequest.ts +25 -0
- package/examples/js/view/requests/components/fileDownloadRequest/DownloadFileRequest.ts +15 -0
- package/examples/js/view/requests/components/fileDownloadRequest/FileDownloadRequest.vue +44 -0
- package/examples/js/view/requests/components/getRequestWithDynamicParams/GetProductsRequest.ts +34 -0
- package/examples/js/view/requests/components/getRequestWithDynamicParams/GetRequestWithDynamicParams.vue +59 -0
- package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.ts +21 -0
- package/examples/js/view/requests/components/serverErrorRequest/ServerErrorRequest.vue +53 -0
- package/package.json +81 -0
- package/release-tool.json +7 -0
- package/src/helpers.ts +78 -0
- package/src/service/bulkRequests/BulkRequestEvent.enum.ts +4 -0
- package/src/service/bulkRequests/BulkRequestSender.ts +184 -0
- package/src/service/bulkRequests/BulkRequestWrapper.ts +49 -0
- package/src/service/bulkRequests/index.ts +6 -0
- package/src/service/laravel/pagination/contracts/PaginationParamsContract.ts +4 -0
- package/src/service/laravel/pagination/contracts/PaginationResponseBodyContract.ts +6 -0
- package/src/service/laravel/pagination/dataDrivers/RequestDriver.ts +32 -0
- package/src/service/laravel/pagination/index.ts +7 -0
- package/src/service/laravel/requests/JsonBaseRequest.ts +35 -0
- package/src/service/laravel/requests/PaginationJsonBaseRequest.ts +29 -0
- package/src/service/laravel/requests/index.ts +9 -0
- package/src/service/laravel/requests/responses/JsonResponse.ts +8 -0
- package/src/service/laravel/requests/responses/PaginationResponse.ts +16 -0
- package/src/service/pagination/InfiniteScroller.ts +21 -0
- package/src/service/pagination/Paginator.ts +149 -0
- package/src/service/pagination/contracts/PaginateableRequestContract.ts +13 -0
- package/src/service/pagination/contracts/PaginationDataDriverContract.ts +5 -0
- package/src/service/pagination/contracts/PaginationResponseContract.ts +7 -0
- package/src/service/pagination/contracts/PaginatorLoadDataOptions.ts +4 -0
- package/src/service/pagination/contracts/ViewDriverContract.ts +12 -0
- package/src/service/pagination/contracts/ViewDriverFactoryContract.ts +5 -0
- package/src/service/pagination/dataDrivers/ArrayDriver.ts +28 -0
- package/src/service/pagination/dtos/PaginationDataDto.ts +14 -0
- package/src/service/pagination/factories/VuePaginationDriverFactory.ts +9 -0
- package/src/service/pagination/frontendDrivers/VuePaginationDriver.ts +61 -0
- package/src/service/pagination/index.ts +16 -0
- package/src/service/persistenceDrivers/LocalStorageDriver.ts +22 -0
- package/src/service/persistenceDrivers/NonPersistentDriver.ts +12 -0
- package/src/service/persistenceDrivers/SessionStorageDriver.ts +22 -0
- package/src/service/persistenceDrivers/index.ts +8 -0
- package/src/service/persistenceDrivers/types/PersistenceDriver.ts +5 -0
- package/src/service/requests/BaseRequest.ts +197 -0
- package/src/service/requests/ErrorHandler.ts +64 -0
- package/src/service/requests/RequestEvents.enum.ts +3 -0
- package/src/service/requests/RequestMethod.enum.ts +8 -0
- package/src/service/requests/bodies/FormDataBody.ts +41 -0
- package/src/service/requests/bodies/JsonBody.ts +16 -0
- package/src/service/requests/contracts/AbortableRequestContract.ts +3 -0
- package/src/service/requests/contracts/BaseRequestContract.ts +36 -0
- package/src/service/requests/contracts/BodyContract.ts +7 -0
- package/src/service/requests/contracts/BodyFactoryContract.ts +5 -0
- package/src/service/requests/contracts/DriverConfigContract.ts +7 -0
- package/src/service/requests/contracts/HeadersContract.ts +5 -0
- package/src/service/requests/contracts/RequestDriverContract.ts +15 -0
- package/src/service/requests/contracts/RequestLoaderContract.ts +5 -0
- package/src/service/requests/contracts/RequestLoaderFactoryContract.ts +5 -0
- package/src/service/requests/contracts/ResponseContract.ts +7 -0
- package/src/service/requests/drivers/contracts/ResponseHandlerContract.ts +10 -0
- package/src/service/requests/drivers/fetch/FetchDriver.ts +115 -0
- package/src/service/requests/drivers/fetch/FetchResponse.ts +30 -0
- package/src/service/requests/exceptions/NoResponseReceivedException.ts +3 -0
- package/src/service/requests/exceptions/NotFoundException.ts +3 -0
- package/src/service/requests/exceptions/PageExpiredException.ts +3 -0
- package/src/service/requests/exceptions/ResponseBodyException.ts +15 -0
- package/src/service/requests/exceptions/ResponseException.ts +11 -0
- package/src/service/requests/exceptions/ServerErrorException.ts +3 -0
- package/src/service/requests/exceptions/UnauthorizedException.ts +3 -0
- package/src/service/requests/exceptions/ValidationException.ts +3 -0
- package/src/service/requests/exceptions/index.ts +19 -0
- package/src/service/requests/factories/FormDataFactory.ts +9 -0
- package/src/service/requests/factories/JsonBodyFactory.ts +9 -0
- package/src/service/requests/index.ts +50 -0
- package/src/service/requests/responses/BaseResponse.ts +41 -0
- package/src/service/requests/responses/BlobResponse.ts +19 -0
- package/src/service/requests/responses/JsonResponse.ts +15 -0
- package/src/service/requests/responses/PlainTextResponse.ts +15 -0
- package/src/service/support/DeferredPromise.ts +67 -0
- package/src/service/support/index.ts +3 -0
- package/src/vue/composables/useConfirmDialog.ts +59 -0
- package/src/vue/composables/useGlobalCheckbox.ts +145 -0
- package/src/vue/composables/useIsEmpty.ts +34 -0
- package/src/vue/composables/useIsOpen.ts +37 -0
- package/src/vue/composables/useIsOpenFromVar.ts +61 -0
- package/src/vue/composables/useModelWrapper.ts +24 -0
- package/src/vue/composables/useOnOpen.ts +34 -0
- package/src/vue/contracts/ModelValueOptions.ts +3 -0
- package/src/vue/contracts/ModelValueProps.ts +3 -0
- package/src/vue/forms/BaseForm.ts +1074 -0
- package/src/vue/forms/PropertyAwareArray.ts +78 -0
- package/src/vue/forms/index.ts +11 -0
- package/src/vue/forms/types/PersistedForm.ts +6 -0
- package/src/vue/forms/validation/ValidationMode.enum.ts +14 -0
- package/src/vue/forms/validation/index.ts +12 -0
- package/src/vue/forms/validation/rules/BaseRule.ts +7 -0
- package/src/vue/forms/validation/rules/ConfirmedRule.ts +39 -0
- package/src/vue/forms/validation/rules/MinRule.ts +61 -0
- package/src/vue/forms/validation/rules/RequiredRule.ts +19 -0
- package/src/vue/forms/validation/rules/UrlRule.ts +24 -0
- package/src/vue/forms/validation/types/BidirectionalRule.ts +11 -0
- package/src/vue/index.ts +14 -0
- package/src/vue/requests/factories/VueRequestLoaderFactory.ts +9 -0
- package/src/vue/requests/index.ts +5 -0
- package/src/vue/requests/loaders/VueRequestBatchLoader.ts +30 -0
- package/src/vue/requests/loaders/VueRequestLoader.ts +18 -0
- package/src/vue/router/routeModelBinding/RouteModelRequestResolver.ts +11 -0
- package/src/vue/router/routeModelBinding/defineRoute.ts +31 -0
- package/src/vue/router/routeModelBinding/index.ts +8 -0
- package/src/vue/router/routeModelBinding/installRouteInjection.ts +73 -0
- package/src/vue/router/routeModelBinding/types.ts +46 -0
- package/src/vue/state/State.ts +391 -0
- package/src/vue/state/index.ts +3 -0
- package/tests/service/helpers/mergeDeep.test.ts +53 -0
- package/tests/service/laravel/pagination/dataDrivers/RequestDriver.test.ts +84 -0
- package/tests/service/laravel/requests/JsonBaseRequest.test.ts +43 -0
- package/tests/service/laravel/requests/PaginationJsonBaseRequest.test.ts +58 -0
- package/tests/service/laravel/requests/responses/JsonResponse.test.ts +59 -0
- package/tests/service/laravel/requests/responses/PaginationResponse.test.ts +127 -0
- package/tests/service/pagination/dtos/PaginationDataDto.test.ts +35 -0
- package/tests/service/pagination/factories/VuePaginationDriverFactory.test.ts +32 -0
- package/tests/service/pagination/frontendDrivers/VuePaginationDriver.test.ts +66 -0
- package/tests/service/requests/ErrorHandler.test.ts +141 -0
- package/tsconfig.json +114 -0
- package/vite.config.ts +34 -0
- package/vitest.config.ts +14 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Route Model Binding
|
|
2
|
+
|
|
3
|
+
When using `vue-router`, you can automatically bind route parameters to resources, similar to how Laravel's route model binding works.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
To enable the router to load resources automatically, install the route injection plugin when initializing your router:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
installRouteInjection(router)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Defining Routes
|
|
14
|
+
|
|
15
|
+
Use the `defineRoute` helper to define your routes and specify which parameters should be resolved into resources:
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
defineRoute<{
|
|
19
|
+
product: ProductResource
|
|
20
|
+
}>()({
|
|
21
|
+
path: ':productId',
|
|
22
|
+
name: 'products.show',
|
|
23
|
+
component: ProductDetailPage,
|
|
24
|
+
meta: {
|
|
25
|
+
inject: {
|
|
26
|
+
product: {
|
|
27
|
+
from: 'productId',
|
|
28
|
+
resolve: (productId: string) => {
|
|
29
|
+
return new RouteModelRequestResolver(
|
|
30
|
+
new ProductShowRequest(productId)
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `beforeResolve` navigation guard will automatically fetch the `ProductResource` using the `ProductShowRequest` and the `productId` from the route.
|
|
40
|
+
|
|
41
|
+
## Usage in Components
|
|
42
|
+
|
|
43
|
+
Your component can then directly access the resolved resource via props:
|
|
44
|
+
|
|
45
|
+
```vue
|
|
46
|
+
<script setup lang="ts">
|
|
47
|
+
const props = defineProps<{
|
|
48
|
+
product: ProductResource
|
|
49
|
+
}>()
|
|
50
|
+
</script>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Handling Loading States
|
|
54
|
+
|
|
55
|
+
You can handle the loading state of the request by using the event system of the request class:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
resolve: (productId: string) => {
|
|
59
|
+
return new RouteModelRequestResolver(
|
|
60
|
+
new ProductShowRequest(productId).on<boolean>(RequestEvents.LOADING, (loading: boolean) => {
|
|
61
|
+
const loadingStore = useLoadingStore()
|
|
62
|
+
loadingStore.setLoading(loading)
|
|
63
|
+
})
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
```
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# State Management
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
The `State` class provides a reactive, type-safe state management system with rich features like change detection, nested property watching, persistence, and more. It's designed to make state management in Vue applications clean and flexible.
|
|
5
|
+
|
|
6
|
+
## Core Features
|
|
7
|
+
- **Type-safe state**: Fully typed with TypeScript generics
|
|
8
|
+
- **Reactive properties**: Seamless integration with Vue's reactivity system
|
|
9
|
+
- **Nested property watching**: Track changes to deeply nested properties
|
|
10
|
+
- **Persistence options**: Optional state persistence between sessions
|
|
11
|
+
- **Change subscriptions**: React to state changes with optional debouncing
|
|
12
|
+
- **Deep object watching**: Special handling for nested objects
|
|
13
|
+
- **Efficient equality checking**: Smart comparison of state values
|
|
14
|
+
- **State export/import**: Easily serialize and deserialize state
|
|
15
|
+
- **Reset capability**: Revert to initial state when needed
|
|
16
|
+
|
|
17
|
+
## Usage Guide
|
|
18
|
+
### Creating a State Class
|
|
19
|
+
Create a new state by extending the base class and defining your interface:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Define your state interface
|
|
23
|
+
interface UserStateInterface {
|
|
24
|
+
name: string;
|
|
25
|
+
email: string;
|
|
26
|
+
preferences: {
|
|
27
|
+
darkMode: boolean;
|
|
28
|
+
notifications: boolean;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Create your state class
|
|
33
|
+
class UserState extends State<UserStateInterface> {
|
|
34
|
+
constructor() {
|
|
35
|
+
super(
|
|
36
|
+
{
|
|
37
|
+
name: '',
|
|
38
|
+
email: '',
|
|
39
|
+
preferences: {
|
|
40
|
+
darkMode: false,
|
|
41
|
+
notifications: true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
persist: true // Enable persistence
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Optional: Override persistence method
|
|
51
|
+
protected override getPersistenceDriver(): PersistenceDriver {
|
|
52
|
+
return new LocalStorageDriver();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Create an instance
|
|
57
|
+
export const userState = new UserState();
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Accessing State
|
|
61
|
+
|
|
62
|
+
````typescript
|
|
63
|
+
// Read
|
|
64
|
+
const userName = userState.state.name;
|
|
65
|
+
|
|
66
|
+
// Write
|
|
67
|
+
userState.state.name = 'John Doe';
|
|
68
|
+
````
|
|
69
|
+
|
|
70
|
+
### Subscribing to Changes
|
|
71
|
+
Listen for changes to specific properties using the method: `subscribe`
|
|
72
|
+
|
|
73
|
+
#### Basic subscription to a single property
|
|
74
|
+
````typescript
|
|
75
|
+
// Subscribe to changes on a top-level property
|
|
76
|
+
userState.subscribe(
|
|
77
|
+
'name',
|
|
78
|
+
(newValue, oldValue) => {
|
|
79
|
+
console.log(`Name changed from ${oldValue} to ${newValue}`);
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
````
|
|
83
|
+
|
|
84
|
+
#### Subscribing to nested properties
|
|
85
|
+
|
|
86
|
+
````typescript
|
|
87
|
+
// Subscribe to a nested property using dot notation
|
|
88
|
+
userState.subscribe(
|
|
89
|
+
'preferences.darkMode',
|
|
90
|
+
(newValue, oldValue) => {
|
|
91
|
+
console.log(`Dark mode changed from ${oldValue} to ${newValue}`);
|
|
92
|
+
// Update UI theme
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
````
|
|
96
|
+
|
|
97
|
+
#### Debounced subscriptions
|
|
98
|
+
|
|
99
|
+
````typescript
|
|
100
|
+
// Debounce the handler to avoid frequent updates
|
|
101
|
+
userState.subscribe(
|
|
102
|
+
'email',
|
|
103
|
+
(newValue, oldValue) => {
|
|
104
|
+
// This will only execute after 300ms of stability
|
|
105
|
+
validateEmail(newValue);
|
|
106
|
+
},
|
|
107
|
+
{ debounce: 300 }
|
|
108
|
+
);
|
|
109
|
+
````
|
|
110
|
+
|
|
111
|
+
#### Watching multiple properties
|
|
112
|
+
````typescript
|
|
113
|
+
// Subscribe to multiple properties at once
|
|
114
|
+
userState.subscribe(
|
|
115
|
+
['name', 'email'],
|
|
116
|
+
(changedPath, state) => {
|
|
117
|
+
console.log(`Property ${changedPath} changed`);
|
|
118
|
+
console.log('Current state:', state);
|
|
119
|
+
}
|
|
120
|
+
);
|
|
121
|
+
````
|
|
122
|
+
|
|
123
|
+
### State Persistence
|
|
124
|
+
Enable state persistence by passing `persist: true` in the constructor options. Override the method `getPersistenceDriver` to use different storage mechanisms:
|
|
125
|
+
|
|
126
|
+
````typescript
|
|
127
|
+
// Examples of different persistence drivers:
|
|
128
|
+
protected override getPersistenceDriver(): PersistenceDriver {
|
|
129
|
+
// Session storage (lasts until browser tab is closed)
|
|
130
|
+
return new SessionStorageDriver();
|
|
131
|
+
|
|
132
|
+
// Local storage (persists between sessions)
|
|
133
|
+
// return new LocalStorageDriver();
|
|
134
|
+
|
|
135
|
+
// Non-persistent (for testing or when persistence not needed)
|
|
136
|
+
// return new NonPersistentDriver();
|
|
137
|
+
}
|
|
138
|
+
````
|
|
139
|
+
|
|
140
|
+
### Importing and Exporting State
|
|
141
|
+
|
|
142
|
+
Export the current state to a plain object or import values:
|
|
143
|
+
|
|
144
|
+
````typescript
|
|
145
|
+
// Export current state as a plain object
|
|
146
|
+
const stateSnapshot = userState.export();
|
|
147
|
+
console.log(stateSnapshot);
|
|
148
|
+
|
|
149
|
+
// Import partial state
|
|
150
|
+
userState.import({
|
|
151
|
+
name: 'Jane Doe',
|
|
152
|
+
preferences: {
|
|
153
|
+
darkMode: true
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Import with hook suppression (doesn't trigger change handlers)
|
|
158
|
+
userState.import(
|
|
159
|
+
{
|
|
160
|
+
name: 'Jane Doe'
|
|
161
|
+
},
|
|
162
|
+
true // suppress hooks
|
|
163
|
+
);
|
|
164
|
+
````
|
|
165
|
+
|
|
166
|
+
### Resetting State
|
|
167
|
+
Reset the state to its initial values:
|
|
168
|
+
|
|
169
|
+
````typescript
|
|
170
|
+
// Reset to initial values
|
|
171
|
+
userState.reset();
|
|
172
|
+
````
|
|
173
|
+
|
|
174
|
+
````
|
|
175
|
+
userState.subscribe(
|
|
176
|
+
'name',
|
|
177
|
+
(newValue, oldValue) => {
|
|
178
|
+
console.log(`Name reset from ${oldValue} to ${newValue}`);
|
|
179
|
+
},
|
|
180
|
+
{ executeOnReset: true }
|
|
181
|
+
);
|
|
182
|
+
````
|
|
183
|
+
|
|
184
|
+
## Advanced Features
|
|
185
|
+
### Deep Object Watching
|
|
186
|
+
When you modify nested properties, the system automatically detects these changes and triggers appropriate callbacks:
|
|
187
|
+
|
|
188
|
+
````typescript
|
|
189
|
+
// Both of these will trigger the 'preferences.darkMode' subscription
|
|
190
|
+
userState.state.preferences.darkMode = true;
|
|
191
|
+
userState.state.preferences = { darkMode: true, notifications: false };
|
|
192
|
+
````
|
|
193
|
+
|
|
194
|
+
### Type-Safe Path Access
|
|
195
|
+
The state system enforces type safety for property paths:
|
|
196
|
+
|
|
197
|
+
````typescript
|
|
198
|
+
// TypeScript will error on invalid paths
|
|
199
|
+
userState.subscribe(
|
|
200
|
+
'preferences.invalidProperty', // Type error!
|
|
201
|
+
(newValue, oldValue) => {
|
|
202
|
+
// ...
|
|
203
|
+
}
|
|
204
|
+
);
|
|
205
|
+
````
|
|
206
|
+
|
|
207
|
+
### Smart Comparison
|
|
208
|
+
The system performs deep equality checks before triggering change handlers, ensuring callbacks are only called when values actually change:
|
|
209
|
+
|
|
210
|
+
````typescript
|
|
211
|
+
// This won't trigger change handlers if the values are deeply equal
|
|
212
|
+
userState.state.preferences = { darkMode: false, notifications: true };
|
|
213
|
+
````
|
|
214
|
+
|
|
215
|
+
## Example: Complete Workflow
|
|
216
|
+
|
|
217
|
+
````typescript
|
|
218
|
+
// 1. Define state interface
|
|
219
|
+
interface FormState {
|
|
220
|
+
username: string;
|
|
221
|
+
password: string;
|
|
222
|
+
validation: {
|
|
223
|
+
usernameValid: boolean;
|
|
224
|
+
passwordValid: boolean;
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 2. Create state class
|
|
229
|
+
class FormStateManager extends State<FormState> {
|
|
230
|
+
constructor() {
|
|
231
|
+
super({
|
|
232
|
+
username: '',
|
|
233
|
+
password: '',
|
|
234
|
+
validation: {
|
|
235
|
+
usernameValid: false,
|
|
236
|
+
passwordValid: false
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Helper methods can be added to state classes
|
|
242
|
+
public validateForm(): boolean {
|
|
243
|
+
return this.state.validation.usernameValid &&
|
|
244
|
+
this.state.validation.passwordValid;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// 3. Create instance
|
|
249
|
+
const formState = new FormStateManager();
|
|
250
|
+
|
|
251
|
+
// 4. Set up subscriptions
|
|
252
|
+
formState.subscribe(
|
|
253
|
+
'username',
|
|
254
|
+
(username) => {
|
|
255
|
+
// Validate username
|
|
256
|
+
const valid = username.length >= 3;
|
|
257
|
+
formState.state.validation.usernameValid = valid;
|
|
258
|
+
},
|
|
259
|
+
{ debounce: 300 }
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
formState.subscribe(
|
|
263
|
+
'password',
|
|
264
|
+
(password) => {
|
|
265
|
+
// Validate password
|
|
266
|
+
const valid = password.length >= 8;
|
|
267
|
+
formState.state.validation.passwordValid = valid;
|
|
268
|
+
},
|
|
269
|
+
{ debounce: 300 }
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// 5. Watch form validity
|
|
273
|
+
formState.subscribe(
|
|
274
|
+
['validation.usernameValid', 'validation.passwordValid'],
|
|
275
|
+
(_, state) => {
|
|
276
|
+
const submitButton = document.getElementById('submit');
|
|
277
|
+
if (formState.validateForm()) {
|
|
278
|
+
submitButton.disabled = false;
|
|
279
|
+
} else {
|
|
280
|
+
submitButton.disabled = true;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// 6. Use the state
|
|
286
|
+
formState.state.username = 'john_doe';
|
|
287
|
+
formState.state.password = 'securePassword123';
|
|
288
|
+
|
|
289
|
+
// 7. Reset when needed
|
|
290
|
+
document.getElementById('reset').addEventListener('click', () => {
|
|
291
|
+
formState.reset();
|
|
292
|
+
});
|
|
293
|
+
````
|
package/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import tseslint from 'typescript-eslint';
|
|
3
|
+
import pluginVue from 'eslint-plugin-vue';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
defineConfigWithVueTs,
|
|
7
|
+
vueTsConfigs,
|
|
8
|
+
} from '@vue/eslint-config-typescript'
|
|
9
|
+
|
|
10
|
+
export default defineConfigWithVueTs(
|
|
11
|
+
eslint.configs.recommended,
|
|
12
|
+
...tseslint.configs.recommended,
|
|
13
|
+
...pluginVue.configs['flat/recommended'],
|
|
14
|
+
vueTsConfigs.recommended,
|
|
15
|
+
)
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" class="h-full">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Hank-IT UI</title>
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
</head>
|
|
8
|
+
<body class="h-full">
|
|
9
|
+
|
|
10
|
+
<div id="app" class="w-full h-full"></div>
|
|
11
|
+
|
|
12
|
+
<script type="module" src="/js/app.js"></script>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createWebHistory, createRouter } from 'vue-router'
|
|
2
|
+
|
|
3
|
+
const routes = [
|
|
4
|
+
{
|
|
5
|
+
path: '/pagination/:component?',
|
|
6
|
+
component: () => import('@view/pagination/Pagination.vue'),
|
|
7
|
+
name: 'pagination',
|
|
8
|
+
props: true,
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
path: '/requests/:component?',
|
|
12
|
+
component: () => import('@view/requests/Requests.vue'),
|
|
13
|
+
name: 'requests',
|
|
14
|
+
props: true,
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
export default createRouter({
|
|
20
|
+
history: createWebHistory(),
|
|
21
|
+
routes, // short for `routes: routes`,
|
|
22
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="container">
|
|
3
|
+
<div class="sidebar">
|
|
4
|
+
<ul>
|
|
5
|
+
<li><RouterLink :to="{ name: 'requests' }">Requests</RouterLink></li>
|
|
6
|
+
<li><RouterLink :to="{ name: 'pagination' }">Pagination</RouterLink></li>
|
|
7
|
+
</ul>
|
|
8
|
+
</div>
|
|
9
|
+
<div class="content">
|
|
10
|
+
<RouterView />
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup>
|
|
16
|
+
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
body {
|
|
21
|
+
margin: 0;
|
|
22
|
+
padding:0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.container {
|
|
26
|
+
display: flex;
|
|
27
|
+
flex-direction: row;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.sidebar,
|
|
31
|
+
.content {
|
|
32
|
+
display: flex;
|
|
33
|
+
flex-direction: column;
|
|
34
|
+
color: black;
|
|
35
|
+
min-height: 500px;
|
|
36
|
+
border-radius: 4px;
|
|
37
|
+
margin: 10px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.sidebar {
|
|
41
|
+
flex-grow: 1;
|
|
42
|
+
min-width: 300px;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.content {
|
|
46
|
+
flex-grow: 5;
|
|
47
|
+
min-width: 630px;
|
|
48
|
+
}
|
|
49
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<select v-model="selectedComponentKey">
|
|
3
|
+
<RouterLink
|
|
4
|
+
v-for="(component, key) in components"
|
|
5
|
+
:key="key"
|
|
6
|
+
:to="component.route"
|
|
7
|
+
custom
|
|
8
|
+
v-slot="{ isActive, href, navigate }"
|
|
9
|
+
>
|
|
10
|
+
<option @click="navigate" :value="key">
|
|
11
|
+
{{ component.name }}
|
|
12
|
+
</option>
|
|
13
|
+
</RouterLink>
|
|
14
|
+
</select>
|
|
15
|
+
|
|
16
|
+
<component v-if="selectedComponentKey" :is="components[selectedComponentKey].component" />
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<script setup lang="ts">
|
|
20
|
+
import {ref} from 'vue'
|
|
21
|
+
|
|
22
|
+
const props = defineProps({
|
|
23
|
+
components: {},
|
|
24
|
+
component: String,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const selectedComponentKey = ref(props.component)
|
|
28
|
+
</script>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<DemoPage :components="components" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import TablePagination from './components/tablePagination/TablePagination.vue'
|
|
7
|
+
import ErrorPagination from './components/errorPagination/ErrorPagination.vue'
|
|
8
|
+
import InfiniteScrolling from './components/infiniteScrolling/InfiniteScrolling.vue'
|
|
9
|
+
import DemoPage from '../layout/DemoPage.vue'
|
|
10
|
+
|
|
11
|
+
const components = {
|
|
12
|
+
'table-pagination': {
|
|
13
|
+
name: 'Paginated table',
|
|
14
|
+
component: TablePagination,
|
|
15
|
+
route: { name: 'pagination', params: { component: 'table-pagination' } },
|
|
16
|
+
},
|
|
17
|
+
'error-pagination': {
|
|
18
|
+
name: 'Pagination with error',
|
|
19
|
+
component: ErrorPagination,
|
|
20
|
+
route: { name: 'pagination', params: { component: 'error-pagination' } },
|
|
21
|
+
},
|
|
22
|
+
'infinite-scrolling': {
|
|
23
|
+
name: 'Infinite scrolling',
|
|
24
|
+
component: InfiniteScrolling,
|
|
25
|
+
route: { name: 'pagination', params: { component: 'infinite-scrolling' } },
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
</script>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<table>
|
|
3
|
+
<tr>
|
|
4
|
+
<th>ID</th>
|
|
5
|
+
<th>Title</th>
|
|
6
|
+
<th>Description</th>
|
|
7
|
+
</tr>
|
|
8
|
+
<tr v-for="row in paginator.getPageData()">
|
|
9
|
+
<td>{{ row.id }}</td>
|
|
10
|
+
<td>{{ row.title }}</td>
|
|
11
|
+
<td>{{ row.description }}</td>
|
|
12
|
+
</tr>
|
|
13
|
+
</table>
|
|
14
|
+
|
|
15
|
+
Current page: {{ paginator.getCurrentPage() }}
|
|
16
|
+
<br>
|
|
17
|
+
Pages: {{ paginator.getPages() }}
|
|
18
|
+
<br>
|
|
19
|
+
Page Size: <input v-model="pageSize" />
|
|
20
|
+
<br>
|
|
21
|
+
Back Page <button @click="paginator.toPreviousPage()">Back</button>
|
|
22
|
+
<br>
|
|
23
|
+
Next Page <button @click="paginator.toNextPage()">Next</button>
|
|
24
|
+
<br>
|
|
25
|
+
Showing {{ paginator.getFromItemNumber() }} to {{ paginator.getToItemNumber() }} of {{ paginator.getTotal() }} items.
|
|
26
|
+
|
|
27
|
+
{{ displayablePages }}
|
|
28
|
+
</template>
|
|
29
|
+
|
|
30
|
+
<script setup lang="ts">
|
|
31
|
+
import {BaseRequest, FetchDriver, VueLoaderDriverFactory} from "@hank-it/ui/service/requests"
|
|
32
|
+
import {Paginator, RequestDriver, VuePaginationDriverFactory} from "@hank-it/ui/service/pagination";
|
|
33
|
+
import {getDisplayablePages} from '@hank-it/ui/service/helpers'
|
|
34
|
+
import {GetProductsRequest} from "./GetProductsRequest";
|
|
35
|
+
import {computed} from 'vue'
|
|
36
|
+
|
|
37
|
+
/* Booting */
|
|
38
|
+
BaseRequest.setRequestDriver(new FetchDriver)
|
|
39
|
+
BaseRequest.setLoaderStateFactory(new VueLoaderDriverFactory)
|
|
40
|
+
Paginator.setViewDriverFactory(new VuePaginationDriverFactory())
|
|
41
|
+
|
|
42
|
+
/* component */
|
|
43
|
+
const getProductsRequest = new GetProductsRequest
|
|
44
|
+
|
|
45
|
+
const paginator = new Paginator(new RequestDriver(getProductsRequest))
|
|
46
|
+
|
|
47
|
+
paginator.init(1, 10).catch(paginationErrorHandler)
|
|
48
|
+
|
|
49
|
+
function paginationErrorHandler(response) {
|
|
50
|
+
console.log(response.getError())
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const pageSize = computed({
|
|
54
|
+
set(value) {
|
|
55
|
+
paginator.setPageSize(value)
|
|
56
|
+
},
|
|
57
|
+
get() {
|
|
58
|
+
return paginator.getPageSize()
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const displayablePages = computed(() => {
|
|
63
|
+
console.log(paginator.getPages())
|
|
64
|
+
|
|
65
|
+
return getDisplayablePages(paginator.getPages().length, paginator.getCurrentPage())
|
|
66
|
+
})
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<style scoped>
|
|
70
|
+
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {BaseRequest, type Paginatable, JsonResponse } from '@hank-it/ui/service/requests'
|
|
2
|
+
import type {PaginationResponseContract} from '@hank-it/ui/service/pagination'
|
|
3
|
+
|
|
4
|
+
export interface ProductResource {
|
|
5
|
+
id: number
|
|
6
|
+
title: string
|
|
7
|
+
description: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ProductPaginationResource {
|
|
11
|
+
products: ProductResource[]
|
|
12
|
+
total: number
|
|
13
|
+
limit: number
|
|
14
|
+
skip: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export class GetProductsRequestResponse extends JsonResponse implements PaginationResponseContract {
|
|
18
|
+
public getData() {
|
|
19
|
+
return this.data
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public getTotal(): number {
|
|
23
|
+
return this.body.total
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public dataHandler(data) {
|
|
27
|
+
return data.products
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class GetProductsRequest extends BaseRequest implements Paginatable {
|
|
32
|
+
method() {
|
|
33
|
+
return 'GET'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
url() {
|
|
37
|
+
return 'https://dummyjson.com/http/500'
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public setPaginationParams(page: number, size: number): BaseRequest {
|
|
41
|
+
return this.withParams({
|
|
42
|
+
skip: (page - 1) * size,
|
|
43
|
+
limit: size,
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public getCorsWithCredentials(): boolean {
|
|
48
|
+
return false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected getResponse() {
|
|
52
|
+
return new GetProductsRequestResponse
|
|
53
|
+
}
|
|
54
|
+
}
|