@userfrosting/sprinkle-core 6.0.0-beta.7 → 6.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +27 -46
  2. package/dist/Page401Unauthorized-D_EoXicK.js +11 -0
  3. package/dist/Page403Forbidden-C96BgRtx.js +11 -0
  4. package/dist/Page404NotFound-CIs-2Fy2.js +11 -0
  5. package/dist/PageError-Sk-QAv9A.js +11 -0
  6. package/dist/_plugin-vue_export-helper-CHgC5LLL.js +9 -0
  7. package/dist/composables/index.d.ts +4 -0
  8. package/dist/composables/useAxiosInterceptor.d.ts +10 -0
  9. package/dist/composables/useCsrf.d.ts +18 -0
  10. package/dist/composables/useRuleSchemaAdapter.d.ts +7 -0
  11. package/dist/composables/useSprunjer.d.ts +2 -0
  12. package/dist/composables.js +151 -0
  13. package/dist/index.d.ts +18 -0
  14. package/dist/index.js +15 -0
  15. package/{app/assets/interfaces/ApiResponse.ts → dist/interfaces/ApiResponse.d.ts} +5 -6
  16. package/{app/assets/interfaces/DictionaryApi.ts → dist/interfaces/DictionaryApi.d.ts} +9 -11
  17. package/dist/interfaces/alerts.d.ts +8 -0
  18. package/{app/assets/interfaces/common.ts → dist/interfaces/common.d.ts} +1 -1
  19. package/dist/interfaces/index.d.ts +13 -0
  20. package/{app/assets/interfaces/severity.ts → dist/interfaces/severity.d.ts} +9 -9
  21. package/dist/interfaces/sprunjer.d.ts +51 -0
  22. package/{app/assets/interfaces/sprunjerApi.ts → dist/interfaces/sprunjerApi.d.ts} +12 -15
  23. package/dist/interfaces.js +5 -0
  24. package/dist/routes/index.d.ts +16 -0
  25. package/dist/routes.js +41 -0
  26. package/dist/severity-DwLpzIij.js +4 -0
  27. package/{app/assets/stores/Helpers/PluralRules.ts → dist/stores/Helpers/PluralRules.d.ts} +17 -114
  28. package/dist/stores/index.d.ts +4 -0
  29. package/dist/stores/useAlertsStore.d.ts +29 -0
  30. package/dist/stores/useConfigStore.d.ts +11 -0
  31. package/dist/stores/usePageMeta.d.ts +51 -0
  32. package/dist/stores/useTranslator.d.ts +66 -0
  33. package/dist/stores.js +7 -0
  34. package/dist/useAlertsStore-Ca6nXz8C.js +179 -0
  35. package/dist/useAxiosInterceptor-DcOpTLHG.js +68 -0
  36. package/dist/views/Page401Unauthorized.vue.d.ts +2 -0
  37. package/dist/views/Page403Forbidden.vue.d.ts +2 -0
  38. package/dist/views/Page404NotFound.vue.d.ts +2 -0
  39. package/dist/views/PageError.vue.d.ts +2 -0
  40. package/package.json +37 -9
  41. package/app/assets/composables/index.ts +0 -4
  42. package/app/assets/composables/useAxiosInterceptor.ts +0 -30
  43. package/app/assets/composables/useCsrf.ts +0 -129
  44. package/app/assets/composables/useRuleSchemaAdapter.ts +0 -205
  45. package/app/assets/composables/useSprunjer.ts +0 -188
  46. package/app/assets/index.d.ts +0 -8
  47. package/app/assets/index.ts +0 -40
  48. package/app/assets/interfaces/alerts.ts +0 -16
  49. package/app/assets/interfaces/index.ts +0 -30
  50. package/app/assets/interfaces/sprunjer.ts +0 -60
  51. package/app/assets/routes/index.ts +0 -44
  52. package/app/assets/stores/index.ts +0 -4
  53. package/app/assets/stores/useAlertsStore.ts +0 -30
  54. package/app/assets/stores/useConfigStore.ts +0 -30
  55. package/app/assets/stores/usePageMeta.ts +0 -114
  56. package/app/assets/stores/useTranslator.ts +0 -293
  57. package/app/assets/tests/composables/useCsrf.test.ts +0 -212
  58. package/app/assets/tests/composables/useRuleSchemaAdapter.test.ts +0 -657
  59. package/app/assets/tests/interfaces/alerts.test.ts +0 -43
  60. package/app/assets/tests/plugin.test.ts +0 -29
  61. package/app/assets/tests/stores/Helpers/PluralRules.test.ts +0 -440
  62. package/app/assets/tests/stores/config.test.ts +0 -42
  63. package/app/assets/tests/stores/useTranslator.test.ts +0 -373
  64. package/app/assets/views/Page401Unauthorized.vue +0 -3
  65. package/app/assets/views/Page403Forbidden.vue +0 -3
  66. package/app/assets/views/Page404NotFound.vue +0 -3
  67. package/app/assets/views/PageError.vue +0 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@userfrosting/sprinkle-core",
3
- "version": "6.0.0-beta.7",
3
+ "version": "6.0.0-rc.1",
4
4
  "type": "module",
5
5
  "description": "Core Sprinkle for UserFrosting",
6
6
  "funding": "https://opencollective.com/userfrosting",
@@ -24,15 +24,39 @@
24
24
  "url": "https://github.com/userfrosting/UserFrosting/issues"
25
25
  },
26
26
  "exports": {
27
- ".": "./app/assets/index.ts",
28
- "./interfaces": "./app/assets/interfaces/index.ts",
29
- "./stores": "./app/assets/stores/index.ts",
30
- "./composables": "./app/assets/composables/index.ts",
31
- "./routes": "./app/assets/routes/index.ts"
27
+ ".": {
28
+ "userfrosting:monorepo": "./app/assets/index.ts",
29
+ "import": "./dist/index.js",
30
+ "types": "./dist/index.d.ts"
31
+ },
32
+ "./interfaces": {
33
+ "userfrosting:monorepo": "./app/assets/interfaces/index.ts",
34
+ "import": "./dist/interfaces.js",
35
+ "types": "./dist/interfaces/index.d.ts"
36
+ },
37
+ "./stores": {
38
+ "userfrosting:monorepo": "./app/assets/stores/index.ts",
39
+ "import": "./dist/stores.js",
40
+ "types": "./dist/stores/index.d.ts"
41
+ },
42
+ "./composables": {
43
+ "userfrosting:monorepo": "./app/assets/composables/index.ts",
44
+ "import": "./dist/composables.js",
45
+ "types": "./dist/composables/index.d.ts"
46
+ },
47
+ "./routes": {
48
+ "userfrosting:monorepo": "./app/assets/routes/index.ts",
49
+ "import": "./dist/routes.js",
50
+ "types": "./dist/routes/index.d.ts"
51
+ }
32
52
  },
53
+ "types": "./dist/index.d.ts",
33
54
  "files": [
34
- "app/assets/"
55
+ "dist/"
35
56
  ],
57
+ "engines": {
58
+ "node": ">= 18"
59
+ },
36
60
  "dependencies": {
37
61
  "@regle/core": "^1.6.0",
38
62
  "@regle/rules": "^1.6.0",
@@ -40,9 +64,13 @@
40
64
  "luxon": "^3.5.0"
41
65
  },
42
66
  "peerDependencies": {
43
- "axios": "^1.12.0",
67
+ "axios": "^1.16.0",
44
68
  "pinia": "^2.1.6",
45
69
  "pinia-plugin-persistedstate": "^3.2.0",
46
- "vue": "^3.4.21"
70
+ "vue": "^3.4.21",
71
+ "vue-router": "^4.2.4"
72
+ },
73
+ "scripts": {
74
+ "build": "vite build"
47
75
  }
48
76
  }
@@ -1,4 +0,0 @@
1
- export { useSprunjer } from './useSprunjer'
2
- export { useCsrf } from './useCsrf'
3
- export { useAxiosInterceptor } from './useAxiosInterceptor'
4
- export { useRuleSchemaAdapter } from './useRuleSchemaAdapter'
@@ -1,30 +0,0 @@
1
- import axios from 'axios'
2
- import { useAlertsStore } from '../stores/useAlertsStore'
3
- import { Severity } from '../interfaces'
4
-
5
- /**
6
- * Axios Error Handler
7
- *
8
- * This composable sets up an Axios interceptor to handle errors globally using
9
- * the Alerts store. It sends the error to the Alerts store, unless it's a 401
10
- * error.
11
- *
12
- * @see https://axios-http.com/docs/interceptors
13
- */
14
- export const useAxiosInterceptor = () => {
15
- axios.interceptors.response.use(
16
- (response) => response,
17
- (error) => {
18
- if (error.response.status !== 401) {
19
- const alertStore = useAlertsStore()
20
- alertStore.push({
21
- title: error.response.data.title ?? error.response?.statusText,
22
- description: error.response?.data?.description ?? error.message,
23
- style: Severity.Danger
24
- })
25
- }
26
-
27
- return Promise.reject(error)
28
- }
29
- )
30
- }
@@ -1,129 +0,0 @@
1
- import { ref, watchEffect } from 'vue'
2
- import { useConfigStore } from '../stores'
3
- import axios from 'axios'
4
-
5
- /**
6
- * CSRF Protection Composable
7
- *
8
- * Automatically sets the CSRF token in the axios headers for all requests.
9
- * The CSRF token is read from the meta tags in the HTML document.
10
- * The CSRF token can updated when the server responds with a new token in the headers.
11
- *
12
- * @see https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#axios
13
- */
14
- export const useCsrf = () => {
15
- /**
16
- * Public constant for the CSRF token name and value, plus respective keys.
17
- */
18
- const key_name = ref(getNameKey())
19
- const key_value = ref(getValueKey())
20
- const name = ref(readMetaTag(key_name.value))
21
- const token = ref(readMetaTag(key_value.value))
22
-
23
- /**
24
- * Set the axios headers for CSRF protection
25
- */
26
- function setAxiosHeader() {
27
- axios.defaults.headers.post[key_name.value] = name.value
28
- axios.defaults.headers.post[key_value.value] = token.value
29
- axios.defaults.headers.put[key_name.value] = name.value
30
- axios.defaults.headers.put[key_value.value] = token.value
31
- axios.defaults.headers.delete[key_name.value] = name.value
32
- axios.defaults.headers.delete[key_value.value] = token.value
33
- axios.defaults.headers.patch[key_name.value] = name.value
34
- axios.defaults.headers.patch[key_value.value] = token.value
35
- }
36
-
37
- /**
38
- * Fetch the CSRF token from the server
39
- */
40
- async function fetchCsrfToken() {
41
- const response = await axios.get('/api/csrf')
42
- updateFromHeaders(response.headers)
43
- }
44
-
45
- /**
46
- * Get the CSRF token name and value keys from config.
47
- */
48
- function getNameKey(): string {
49
- const config = useConfigStore()
50
- return config.get('csrf.name', 'csrf') + '_name'
51
- }
52
- function getValueKey(): string {
53
- const config = useConfigStore()
54
- return config.get('csrf.name', 'csrf') + '_value'
55
- }
56
-
57
- /**
58
- * Meta tag reader and writer
59
- */
60
- function readMetaTag(name: string): string {
61
- return document.querySelector("meta[name='" + name + "']")?.getAttribute('content') ?? ''
62
- }
63
- function writeMetaTag(name: string, value: string) {
64
- const metaTag = document.querySelector("meta[name='" + name + "']")
65
- if (metaTag) {
66
- metaTag.setAttribute('content', value)
67
- } else {
68
- const newMetaTag = document.createElement('meta')
69
- newMetaTag.setAttribute('name', name)
70
- newMetaTag.setAttribute('content', value)
71
- document.head.appendChild(newMetaTag)
72
- }
73
- }
74
-
75
- /**
76
- * Update the CSRF token with the values from the request headers.
77
- *
78
- * N.B.: CSRF keys are hardcoded with '{name}_name' and '{name}_value' in
79
- * PHP. However, the headers doesn't allows underscores that are replaced
80
- * with dashes automatically.
81
- */
82
- function updateFromHeaders(headers: any) {
83
- const config = useConfigStore()
84
- const nameKey = config.get('csrf.name', 'csrf') + '-name'
85
- const valueKey = config.get('csrf.name', 'csrf') + '-value'
86
-
87
- // Update both value only if the headers are present
88
- // This is to avoid overwriting the CSRF token with empty values
89
- if (nameKey in headers) {
90
- name.value = headers[nameKey]
91
- }
92
- if (valueKey in headers) {
93
- token.value = headers[valueKey]
94
- }
95
- }
96
-
97
- /**
98
- * Return if CSRF is enabled
99
- */
100
- function isEnabled(): boolean {
101
- const config = useConfigStore()
102
- return config.get('csrf.enabled', true)
103
- }
104
-
105
- /**
106
- * Watchers - Watch for changes in the CSRF token and update the axios
107
- * headers + meta tags
108
- */
109
- watchEffect(() => {
110
- if (isEnabled() && name.value !== '' && token.value !== '') {
111
- writeMetaTag(key_name.value, name.value)
112
- writeMetaTag(key_value.value, token.value)
113
- setAxiosHeader()
114
- }
115
- })
116
-
117
- /**
118
- * Export functions and managed states
119
- */
120
- return {
121
- key_name,
122
- key_value,
123
- name,
124
- token,
125
- isEnabled,
126
- updateFromHeaders,
127
- fetchCsrfToken
128
- }
129
- }
@@ -1,205 +0,0 @@
1
- import {
2
- withMessage,
3
- required,
4
- maxLength,
5
- minLength,
6
- email,
7
- integer,
8
- numeric,
9
- url,
10
- oneOf,
11
- between,
12
- regex,
13
- not
14
- } from '@regle/rules'
15
- import { useTranslator } from '../stores'
16
-
17
- export function useRuleSchemaAdapter() {
18
- /**
19
- * Parse the YAML schema string into a JavaScript object.
20
- *
21
- * @param rawSchema The YAML schema string to parse.
22
- * @returns RuleSchema The Regle schema object.
23
- */
24
- function adapt(sourceSchema: Record<string, any>): Record<string, any> {
25
- // The Regle schema object to be returned
26
- const regleSchema: any = {}
27
-
28
- // Iterate over each field in the schema
29
- for (const field in sourceSchema) {
30
- // Check if the field is a direct property of the sourceSchema object
31
- if (Object.prototype.hasOwnProperty.call(sourceSchema, field)) {
32
- // Get the field rules from the source schema
33
- const schemaFieldRules = sourceSchema[field]?.validators || {}
34
-
35
- // The returned regle rules for the field
36
- const regleRules: Record<string, any> = {}
37
-
38
- // Iterate over each rule in the source schema field rules
39
- for (const key of Object.keys(schemaFieldRules)) {
40
- adaptRule(key, schemaFieldRules, regleRules)
41
- }
42
-
43
- regleSchema[field] = regleRules
44
- }
45
- }
46
-
47
- return regleSchema
48
- }
49
-
50
- function translateMessage(fieldRulesMeta: { message?: string; [key: string]: any }): string {
51
- const { translate } = useTranslator()
52
-
53
- // If there's no message, return an empty string
54
- if (!fieldRulesMeta.message) {
55
- return ''
56
- }
57
-
58
- // Copy the field rules meta to avoid mutation and remove the message key
59
- const fieldRulesMetaCopy = { ...fieldRulesMeta }
60
- delete fieldRulesMetaCopy.message
61
-
62
- return translate(fieldRulesMeta.message, fieldRulesMetaCopy)
63
- }
64
-
65
- function adaptRule(key: string, schemaFieldRules: any, regleRules: Record<string, any>) {
66
- // Required
67
- if (key === 'required' && schemaFieldRules.required) {
68
- const message: string = translateMessage(schemaFieldRules.required)
69
- regleRules['required'] = message === '' ? required : withMessage(required, message)
70
- }
71
-
72
- // Email
73
- if (key === 'email' && schemaFieldRules.email) {
74
- const message: string = translateMessage(schemaFieldRules.email)
75
- regleRules['email'] = withMessage(email, message)
76
- }
77
-
78
- // Length
79
- if (key === 'length' && schemaFieldRules.length) {
80
- if (schemaFieldRules.length.min !== undefined) {
81
- const message: string = translateMessage(schemaFieldRules.length)
82
- regleRules['minLength'] =
83
- message === ''
84
- ? minLength(schemaFieldRules.length.min)
85
- : withMessage(minLength(schemaFieldRules.length.min), message)
86
- }
87
- if (schemaFieldRules.length.max !== undefined) {
88
- const message: string = translateMessage(schemaFieldRules.length)
89
- regleRules['maxLength'] =
90
- message === ''
91
- ? maxLength(schemaFieldRules.length.max)
92
- : withMessage(maxLength(schemaFieldRules.length.max), message)
93
- }
94
- }
95
-
96
- // TODO : matches
97
- if (key === 'matches' && schemaFieldRules.matches) {
98
- console.warn('Validation rule "matches" not implemented yet')
99
- // console.debug('Matched rule: matches', schemaFieldRules.matches)
100
- // sameAs: sameAs(() => form.value.password),
101
- }
102
-
103
- // TODO : equals
104
- if (key === 'equals' && schemaFieldRules.equals) {
105
- console.warn('Validation rule "equals" not implemented yet')
106
- }
107
-
108
- // Integer
109
- if (key === 'integer' && schemaFieldRules.integer) {
110
- const message: string = translateMessage(schemaFieldRules.integer)
111
- regleRules['integer'] = message === '' ? integer : withMessage(integer, message)
112
- }
113
-
114
- // member_of
115
- if (key === 'member_of' && schemaFieldRules.member_of) {
116
- const message: string = translateMessage(schemaFieldRules.member_of)
117
- regleRules['member_of'] =
118
- message === ''
119
- ? oneOf(schemaFieldRules.member_of.values)
120
- : withMessage(oneOf(schemaFieldRules.member_of.values), message)
121
- }
122
-
123
- // no_leading_whitespace
124
- if (key === 'no_leading_whitespace' && schemaFieldRules.no_leading_whitespace) {
125
- const message: string = translateMessage(schemaFieldRules.no_leading_whitespace)
126
- regleRules['no_leading_whitespace'] =
127
- message === '' ? regex(/^\S.*$/) : withMessage(regex(/^\S.*$/), message)
128
- }
129
-
130
- // no_trailing_whitespace
131
- if (key === 'no_trailing_whitespace' && schemaFieldRules.no_trailing_whitespace) {
132
- const message: string = translateMessage(schemaFieldRules.no_trailing_whitespace)
133
- regleRules['no_trailing_whitespace'] =
134
- message === '' ? regex(/^.*\S$/) : withMessage(regex(/^.*\S$/), message)
135
- }
136
-
137
- // TODO : not_equals
138
- if (key === 'not_equals' && schemaFieldRules.not_equals) {
139
- console.warn('Validation rule "not_equals" not implemented yet')
140
- }
141
-
142
- // TODO : not_matches
143
- if (key === 'not_matches' && schemaFieldRules.not_matches) {
144
- console.warn('Validation rule "not_matches" not implemented yet')
145
- }
146
-
147
- // not_member_of
148
- if (key === 'not_member_of' && schemaFieldRules.not_member_of) {
149
- const message: string = translateMessage(schemaFieldRules.not_member_of)
150
- regleRules['not_member_of'] =
151
- message === ''
152
- ? not(oneOf(schemaFieldRules.not_member_of.values))
153
- : withMessage(not(oneOf(schemaFieldRules.not_member_of.values)), message)
154
- }
155
-
156
- // Numeric
157
- if (key === 'numeric' && schemaFieldRules.numeric) {
158
- const message: string = translateMessage(schemaFieldRules.numeric)
159
- regleRules['numeric'] = message === '' ? numeric : withMessage(numeric, message)
160
- }
161
-
162
- // Range
163
- if (key === 'range' && schemaFieldRules.range) {
164
- const message: string = translateMessage(schemaFieldRules.range)
165
- regleRules['range'] =
166
- message === ''
167
- ? between(schemaFieldRules.range.min, schemaFieldRules.range.max)
168
- : withMessage(
169
- between(schemaFieldRules.range.min, schemaFieldRules.range.max),
170
- message
171
- )
172
- }
173
-
174
- // Regex
175
- if (key === 'regex' && schemaFieldRules.regex) {
176
- const message: string = translateMessage(schemaFieldRules.regex)
177
- regleRules['regex'] =
178
- message === ''
179
- ? regex(new RegExp(schemaFieldRules.regex.regex))
180
- : withMessage(regex(new RegExp(schemaFieldRules.regex.regex)), message)
181
- }
182
-
183
- // TODO : telephone
184
- if (key === 'telephone' && schemaFieldRules.telephone) {
185
- console.warn('Validation rule "telephone" not implemented yet')
186
- }
187
-
188
- // uri
189
- if (key === 'uri' && schemaFieldRules.uri) {
190
- const message: string = translateMessage(schemaFieldRules.uri)
191
- regleRules['uri'] = message === '' ? url : withMessage(url, message)
192
- }
193
-
194
- // Username
195
- if (key === 'username' && schemaFieldRules.username) {
196
- const message: string = translateMessage(schemaFieldRules.username)
197
- regleRules['username'] =
198
- message === ''
199
- ? regex(/^([a-z0-9.\-_])+$/i)
200
- : withMessage(regex(/^([a-z0-9.\-_])+$/i), message)
201
- }
202
- }
203
-
204
- return { adapt, translateMessage }
205
- }
@@ -1,188 +0,0 @@
1
- /**
2
- * Sprunjer Composable
3
- *
4
- * A composable function that fetches data from a Sprunjer API and provides
5
- * all necessary function like pagination, sorting and filtering.
6
- *
7
- * Pass the URL of the Sprunjer API to the function, and it will fetch the data.
8
- * A watcher will refetch the data whenever any parameters change.
9
- *
10
- * Params:
11
- * @param {String} dataUrl - The URL of the Sprunjer API
12
- * @param {Object} defaultSorts - An object of default sorts
13
- * @param {Object} defaultFilters - An object of default filters
14
- * @param {Number} defaultSize - The default number of items per page
15
- * @param {Number} defaultPage - The default page number
16
- *
17
- * Exports:
18
- * - size: The number of items per page
19
- * - page: The current page number
20
- * - sorts: An object of sorts
21
- * - filters: An object of filters
22
- * - data: The raw data from the API
23
- * - fetch: A function to fetch the data
24
- * - loading: A boolean indicating if the data is loading
25
- * - totalPages: The total number of pages
26
- * - downloadCsv: A function to download the data as a CSV file
27
- * - countFiltered: The total number of items after filtering
28
- * - count: The total number of items
29
- * - rows: The rows of data
30
- * - first: The index of the first item on the current page
31
- * - last: The index of the last item on the current page
32
- * - toggleSort: A function to toggle the sort order of a column
33
- */
34
- import { ref, toValue, watchEffect, computed } from 'vue'
35
- import axios from 'axios'
36
- import type {
37
- ApiErrorResponse,
38
- AssociativeArray,
39
- Sprunjer,
40
- SprunjerData,
41
- SprunjerResponse
42
- } from '../interfaces'
43
-
44
- export const useSprunjer = (
45
- dataUrl: string | (() => string),
46
- defaultSorts: AssociativeArray = {},
47
- defaultFilters: AssociativeArray = {},
48
- defaultSize: number = 10,
49
- defaultPage: number = 0
50
- ): Sprunjer => {
51
- // Sprunje parameters
52
- const size = ref<number>(defaultSize)
53
- const page = ref<number>(defaultPage)
54
- const sorts = ref<AssociativeArray>(defaultSorts)
55
- const filters = ref<AssociativeArray>(defaultFilters)
56
-
57
- // Raw data - Init with default data
58
- const data = ref<SprunjerData>({
59
- count: 0,
60
- count_filtered: 0,
61
- rows: [],
62
- listable: {},
63
- sortable: [],
64
- filterable: []
65
- })
66
-
67
- // State
68
- const loading = ref<boolean>(false)
69
- const error = ref<ApiErrorResponse | null>(null)
70
-
71
- /**
72
- * Api fetch function
73
- */
74
- async function fetch() {
75
- loading.value = true
76
- axios
77
- .get<SprunjerResponse>(toValue(dataUrl), {
78
- params: {
79
- size: size.value,
80
- page: page.value,
81
- sorts: sorts.value,
82
- filters: filters.value
83
- }
84
- })
85
- .then((response) => {
86
- // Assign the response data to the Sprunje Data.
87
- // Note both object can't be assigned directly, as the response
88
- // object is not a SprunjeData object.
89
- data.value.count = response.data.count
90
- data.value.count_filtered = response.data.count_filtered
91
- data.value.rows = response.data.rows
92
- data.value.listable = response.data.listable ?? {}
93
- data.value.sortable = response.data.sortable ?? []
94
- data.value.filterable = response.data.filterable ?? []
95
- })
96
- .catch((err) => {
97
- error.value = err.response.data as ApiErrorResponse
98
- })
99
- .finally(() => {
100
- loading.value = false
101
- })
102
- }
103
-
104
- /**
105
- * Computed properties
106
- */
107
- const totalPages = computed(() => {
108
- // N.B.: Sprunjer page starts at 0, not 1
109
- // Make sure page is never negative
110
- return Math.max(Math.ceil((data.value.count_filtered ?? 0) / size.value) - 1, 0)
111
- })
112
-
113
- const count = computed(() => {
114
- return data.value.count ?? 0
115
- })
116
-
117
- const first = computed(() => {
118
- return Math.min(page.value * size.value + 1, data.value.count ?? 0)
119
- })
120
-
121
- const last = computed(() => {
122
- return Math.min((page.value + 1) * size.value, data.value.count_filtered ?? 0)
123
- })
124
-
125
- const countFiltered = computed(() => {
126
- return data.value.count_filtered ?? 0
127
- })
128
-
129
- const rows = computed(() => {
130
- return data.value.rows ?? []
131
- })
132
-
133
- /**
134
- * Download the data as a CSV file
135
- */
136
- function downloadCsv() {
137
- console.log('Not yet implemented')
138
- }
139
-
140
- /**
141
- * Apply sorting to a column, cycling from the previous sort order.
142
- * Order goes : asc -> desc -> null -> asc
143
- * Used to toggle the sort order of a column when the column header is clicked
144
- * @param column The column to sort
145
- */
146
- function toggleSort(column: string) {
147
- let newOrder: string | null
148
- if (sorts.value[column] === 'asc') {
149
- newOrder = 'desc'
150
- } else if (sorts.value[column] === 'desc') {
151
- newOrder = null
152
- } else {
153
- newOrder = 'asc'
154
- }
155
-
156
- sorts.value[column] = newOrder
157
- }
158
-
159
- /**
160
- * Automatically fetch the data when any parameters change
161
- */
162
- watchEffect(() => {
163
- fetch()
164
- })
165
-
166
- /**
167
- * Export the functions and data
168
- */
169
- return {
170
- dataUrl,
171
- size,
172
- page,
173
- sorts,
174
- filters,
175
- data,
176
- fetch,
177
- loading,
178
- error,
179
- downloadCsv,
180
- totalPages,
181
- countFiltered,
182
- count,
183
- rows,
184
- first,
185
- last,
186
- toggleSort
187
- }
188
- }
@@ -1,8 +0,0 @@
1
- export {}
2
-
3
- declare module 'vue' {
4
- interface ComponentCustomProperties {
5
- $t: (key: string, placeholders?: string | number | object) => string
6
- $tdate: (date: string, format?: string | object) => string
7
- }
8
- }
@@ -1,40 +0,0 @@
1
- import type { App } from 'vue'
2
- import { useConfigStore, useTranslator } from './stores'
3
- import { useCsrf } from './composables/useCsrf'
4
- import { useAxiosInterceptor } from './composables'
5
-
6
- /**
7
- * Core Sprinkle initialization recipe.
8
- *
9
- * This recipe is responsible for loading the configuration from the api,
10
- * loading the translations, register the translator as $t and $tdate global
11
- * properties and setting up the axios CSRF headers.
12
- */
13
- export default {
14
- install: (app: App) => {
15
- /**
16
- * Add Axios error handler.
17
- * Load first to ensure that all axios requests are intercepted.
18
- * This is important for the config loading and translator loading.
19
- */
20
- useAxiosInterceptor()
21
-
22
- /**
23
- * Load configuration
24
- */
25
- useConfigStore().load()
26
-
27
- /**
28
- * Load translations & add $t+$tdate to global properties
29
- */
30
- const translator = useTranslator()
31
- translator.load()
32
- app.config.globalProperties.$t = translator.translate
33
- app.config.globalProperties.$tdate = translator.translateDate
34
-
35
- /**
36
- * Setup CSRF Protection.
37
- */
38
- useCsrf()
39
- }
40
- }