@userfrosting/sprinkle-core 6.0.0-alpha.1 → 6.0.0-alpha.3

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.
@@ -1 +1,2 @@
1
1
  export { useSprunjer } from './sprunjer'
2
+ export { usePageMeta } from './usePageMeta'
@@ -33,10 +33,10 @@
33
33
  */
34
34
  import { ref, toValue, watchEffect, computed } from 'vue'
35
35
  import axios from 'axios'
36
- import type { AssociativeArray, Sprunjer } from '../interfaces'
36
+ import type { AssociativeArray, Sprunjer, SprunjerData, SprunjerResponse } from '../interfaces'
37
37
 
38
38
  export const useSprunjer = (
39
- dataUrl: any,
39
+ dataUrl: string | (() => string),
40
40
  defaultSorts: AssociativeArray = {},
41
41
  defaultFilters: AssociativeArray = {},
42
42
  defaultSize: number = 10,
@@ -48,8 +48,15 @@ export const useSprunjer = (
48
48
  const sorts = ref<AssociativeArray>(defaultSorts)
49
49
  const filters = ref<AssociativeArray>(defaultFilters)
50
50
 
51
- // Raw data
52
- const data = ref<any>({})
51
+ // Raw data - Init with default data
52
+ const data = ref<SprunjerData>({
53
+ count: 0,
54
+ count_filtered: 0,
55
+ rows: [],
56
+ listable: {},
57
+ sortable: [],
58
+ filterable: []
59
+ })
53
60
 
54
61
  // State
55
62
  const loading = ref<boolean>(false)
@@ -60,7 +67,7 @@ export const useSprunjer = (
60
67
  async function fetch() {
61
68
  loading.value = true
62
69
  axios
63
- .get(toValue(dataUrl), {
70
+ .get<SprunjerResponse>(toValue(dataUrl), {
64
71
  params: {
65
72
  size: size.value,
66
73
  page: page.value,
@@ -69,13 +76,23 @@ export const useSprunjer = (
69
76
  }
70
77
  })
71
78
  .then((response) => {
72
- data.value = response.data
73
- loading.value = false
79
+ // Assign the response data to the Sprunje Data.
80
+ // Note both object can't be assigned directly, as the response
81
+ // object is not a SprunjeData object.
82
+ data.value.count = response.data.count
83
+ data.value.count_filtered = response.data.count_filtered
84
+ data.value.rows = response.data.rows
85
+ data.value.listable = response.data.listable ?? {}
86
+ data.value.sortable = response.data.sortable ?? []
87
+ data.value.filterable = response.data.filterable ?? []
74
88
  })
75
89
  .catch((err) => {
76
90
  // TODO : User toast alert, or export alert
77
91
  console.error(err)
78
92
  })
93
+ .finally(() => {
94
+ loading.value = false
95
+ })
79
96
  }
80
97
 
81
98
  /**
@@ -0,0 +1,114 @@
1
+ import { computed, ref, watch } from 'vue'
2
+ import { useRoute } from 'vue-router'
3
+ import { useTranslator } from '@userfrosting/sprinkle-core/stores'
4
+ import { useConfigStore } from '../stores'
5
+ import { defineStore } from 'pinia'
6
+
7
+ /**
8
+ * Page Meta Composable
9
+ *
10
+ * Handles the page meta data such as title, description, plus generate
11
+ * breadcrumbs from the frontend router. The title, description and breadcrumbs
12
+ * are updated automatically when the route changes.
13
+ *
14
+ * Available States : breadcrumbs, title, description
15
+ */
16
+ export const usePageMeta = defineStore('pageMeta', () => {
17
+ /**
18
+ * Globally provided properties
19
+ */
20
+ const route = useRoute()
21
+ const { translate } = useTranslator()
22
+
23
+ /**
24
+ * States
25
+ *
26
+ * - title: The current page title
27
+ * - description: The current page description
28
+ * - breadcrumbs: The current page breadcrumbs
29
+ * - hideBreadcrumbs: Ask the component to hide the breadcrumbs on the page
30
+ * - hideTitle: Ask the component to hide the title on the page
31
+ */
32
+ const title = ref<string>('')
33
+ const description = ref<string>('')
34
+ const breadcrumbs = ref<Breadcrumb[]>([])
35
+ const hideBreadcrumbs = ref<boolean>(false)
36
+ const hideTitle = ref<boolean>(false)
37
+
38
+ /**
39
+ * Actions - Refresh the breadcrumbs, title and description
40
+ */
41
+ function refresh() {
42
+ // Reset default visibility attributes
43
+ hideBreadcrumbs.value = false
44
+ hideTitle.value = false
45
+
46
+ // Get route trail
47
+ const matchedRoutes = route.matched
48
+
49
+ // Filter to remove routes without title and assign values as defined
50
+ // in the breadcrumbs interface.
51
+ const crumbs = matchedRoutes
52
+ .filter(
53
+ (routeItem) => routeItem.meta.title !== undefined && routeItem.meta.title !== ''
54
+ )
55
+ .map((routeItem) => {
56
+ return {
57
+ label: routeItem.meta?.title || '',
58
+ to: routeItem.path
59
+ }
60
+ })
61
+
62
+ // Add site title as first breadcrumb
63
+ crumbs.unshift({
64
+ label: siteTitle.value,
65
+ to: '/'
66
+ })
67
+
68
+ // Replace ref with new values
69
+ breadcrumbs.value = crumbs
70
+
71
+ // Update Page Title & Description with current route
72
+ title.value = route.meta.title || ''
73
+ description.value = translate(route.meta.description || '')
74
+ }
75
+
76
+ // Update the document title
77
+ function updatePageTitle() {
78
+ document.title = pageFullTitle.value
79
+ }
80
+
81
+ // Update the document description in the HTML meta tag
82
+ function updatePageDescription() {
83
+ const descriptionElement = document.querySelector('head meta[name="description"]')
84
+ descriptionElement?.setAttribute('content', description.value)
85
+ }
86
+
87
+ /**
88
+ * Computed Properties - Getters
89
+ *
90
+ * - siteTitle: Return the site title from the config Store
91
+ * - pageFullTitle: Return the full page title
92
+ */
93
+ const siteTitle = computed<string>(() => useConfigStore().get('site.title') || '')
94
+ const pageFullTitle = computed<string>(() => {
95
+ return title.value ? translate(title.value) + ' | ' + siteTitle.value : siteTitle.value
96
+ })
97
+
98
+ /**
99
+ * Watchers - route, page title and description changes
100
+ */
101
+ watch(route, refresh, { immediate: true })
102
+ watch(pageFullTitle, updatePageTitle, { immediate: true })
103
+ watch(description, updatePageDescription, { immediate: true })
104
+
105
+ /**
106
+ * Returns the states and actions
107
+ */
108
+ return { breadcrumbs, title, description, hideBreadcrumbs, hideTitle }
109
+ })
110
+
111
+ interface Breadcrumb {
112
+ label: string
113
+ to: string
114
+ }
@@ -0,0 +1,9 @@
1
+ export {}
2
+
3
+ declare module 'vue' {
4
+ interface ComponentCustomProperties {
5
+ // TODO : Use interface from sprinkle-core
6
+ $t: (key: string, placeholders?: string | number | object) => string
7
+ $tdate: (date: string, format?: string | object) => string
8
+ }
9
+ }
@@ -1,8 +1,19 @@
1
- import { useConfigStore } from './stores'
1
+ import type { App } from 'vue'
2
+ import { useConfigStore, useTranslator } from './stores'
2
3
 
4
+ /**
5
+ * Core Sprinkle initialization recipe.
6
+ *
7
+ * This recipe is responsible for loading the configuration from the api.
8
+ */
3
9
  export default {
4
- install: () => {
5
- const config = useConfigStore()
6
- config.load()
10
+ install: (app: App) => {
11
+ useConfigStore().load()
12
+
13
+ // Load translations & add $t to global properties
14
+ const { translate, translateDate, load } = useTranslator()
15
+ load()
16
+ app.config.globalProperties.$t = translate
17
+ app.config.globalProperties.$tdate = translateDate
7
18
  }
8
19
  }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Interfaces - What the API expects and what it returns
3
+ *
4
+ * Generic API Response interface.
5
+ */
6
+ export interface ApiResponse {
7
+ message: string
8
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Api Interface - What the API expects and what it returns
3
+ *
4
+ * This interface is tied to the `DictionaryController` API, accessed at the
5
+ * GET `/api/dictionary` endpoint.
6
+ *
7
+ * This api doesn't have a corresponding Request data interface.
8
+ */
9
+ export interface DictionaryResponse {
10
+ identifier: string
11
+ config: DictionaryConfig
12
+ dictionary: DictionaryEntries
13
+ }
14
+
15
+ export interface DictionaryEntries {
16
+ [key: string]: string
17
+ }
18
+
19
+ export interface DictionaryConfig {
20
+ name: string
21
+ regional: string
22
+ authors: string[]
23
+ plural_rule: number
24
+ dates: string
25
+ }
@@ -1,4 +1,30 @@
1
+ /**
2
+ * Interface for custom routes meta fields.
3
+ *
4
+ * Meta Fields Added :
5
+ * - title: string - Page title
6
+ * - description: string - Page description
7
+ *
8
+ * Theses fields are used to set the document title and description, as well as
9
+ * for breadcrumbs generation.
10
+ *
11
+ * @see https://router.vuejs.org/guide/advanced/meta.html#TypeScript
12
+ */
13
+ import 'vue-router'
14
+
15
+ declare module 'vue-router' {
16
+ interface RouteMeta {
17
+ title?: string
18
+ description?: string
19
+ }
20
+ }
21
+
1
22
  export type { AlertInterface } from './alerts'
2
23
  export type { AssociativeArray } from './common'
3
24
  export { Severity } from './severity'
4
- export type { Sprunjer } from './sprunjer'
25
+ export type { Sprunjer, SprunjerData, SprunjerListable, SprunjerListableOption } from './sprunjer'
26
+ export type { SprunjerRequest, SprunjerResponse } from './sprunjerApi'
27
+ export type { DictionaryResponse, DictionaryEntries, DictionaryConfig } from './DictionaryApi'
28
+
29
+ // Misc
30
+ export type { ApiResponse } from './ApiResponse'
@@ -7,12 +7,12 @@ import type { Ref, ComputedRef } from 'vue'
7
7
  import type { AssociativeArray } from '.'
8
8
 
9
9
  export interface Sprunjer {
10
- dataUrl: any
10
+ dataUrl: string | (() => string)
11
11
  size: Ref<number>
12
12
  page: Ref<number>
13
13
  sorts: Ref<AssociativeArray>
14
14
  filters: Ref<AssociativeArray>
15
- data: Ref<any>
15
+ data: Ref<SprunjerData>
16
16
  fetch: () => void
17
17
  loading: Ref<boolean>
18
18
  totalPages: ComputedRef<number>
@@ -24,3 +24,36 @@ export interface Sprunjer {
24
24
  last: ComputedRef<number>
25
25
  toggleSort: (column: string) => void
26
26
  }
27
+
28
+ /**
29
+ * Sprunjer Data. Represents the data that is returned from any Sprunjer
30
+ * Composable. It is different than SprunjerResponse, as the response if what
31
+ * the API return, Data is what Vue provides. Both are similar, but Data doesn't
32
+ * have optional values.
33
+ *
34
+ * N.B.: "rows" uses a generic array. It can contain any object, and should
35
+ * actually be can be extended for each Sprunjer
36
+ */
37
+ export interface SprunjerData {
38
+ count: number
39
+ count_filtered: number
40
+ rows: any[]
41
+ listable: SprunjerListable
42
+ sortable: string[]
43
+ filterable: string[]
44
+ }
45
+
46
+ /**
47
+ * Sprunjer Listable. Represents a listable for a Sprunjer.
48
+ */
49
+ export interface SprunjerListable {
50
+ [key: string]: SprunjerListableOption[]
51
+ }
52
+
53
+ /**
54
+ * Sprunjer Listable Option. Represents a listable option for a Sprunjer.
55
+ */
56
+ export interface SprunjerListableOption {
57
+ value: string
58
+ text: string
59
+ }
@@ -0,0 +1,33 @@
1
+ import type { AssociativeArray, SprunjerListable } from '.'
2
+
3
+ /**
4
+ * Sprunje API Interfaces - What the API expects and what it returns.
5
+ */
6
+
7
+ /**
8
+ * Sprunjer Response. All the data that is returned from any Sprunjer API.
9
+ * Note listable, sortable and filterable are optional when dealing with the API.
10
+ *
11
+ * N.B.: "rows" uses a generic array. It can contain any object, and should
12
+ * actually be can be extended for each Sprunjer
13
+ */
14
+ export interface SprunjerResponse {
15
+ count: number
16
+ count_filtered: number
17
+ rows: any[]
18
+ listable?: SprunjerListable
19
+ sortable?: string[]
20
+ filterable?: string[]
21
+ }
22
+
23
+ /**
24
+ * Sprunjer Request. All the parameters that can be passed to a Sprunjer.
25
+ * All parameters are optional.
26
+ */
27
+ export interface SprunjerRequest {
28
+ size?: number
29
+ page?: number
30
+ sorts?: AssociativeArray
31
+ filters?: AssociativeArray
32
+ format?: string
33
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * Families: Asian (Chinese, Japanese, Korean, Vietnamese), Persian, Turkic/Altaic (Turkish), Thai, Lao
3
+ * 1 - everything: 0, 1, 2, ...
4
+ */
5
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
6
+ export const rule0 = (number: number): number => {
7
+ return 1
8
+ }
9
+
10
+ /**
11
+ * Families: Germanic (Danish, Dutch, English, Faroese, Frisian, German, Norwegian, Swedish),
12
+ * Finno-Ugric (Estonian, Finnish, Hungarian), Language isolate (Basque),
13
+ * Latin/Greek (Greek), Semitic (Hebrew), Romanic (Italian, Portuguese, Spanish, Catalan)
14
+ * 1 - 1
15
+ * 2 - everything else: 0, 2, 3, ...
16
+ */
17
+ export const rule1 = (number: number): number => {
18
+ return number === 1 ? 1 : 2
19
+ }
20
+
21
+ /**
22
+ * Families: Romanic (French, Brazilian Portuguese)
23
+ * 1 - 0, 1
24
+ * 2 - everything else: 2, 3, ...
25
+ */
26
+ export const rule2 = (number: number): number => {
27
+ return number === 0 || number === 1 ? 1 : 2
28
+ }
29
+
30
+ /**
31
+ * Families: Baltic (Latvian)
32
+ * 1 - 0
33
+ * 2 - ends in 1, not 11: 1, 21, ... 101, 121, ...
34
+ * 3 - everything else: 2, 3, ... 10, 11, 12, ... 20, 22, ...
35
+ */
36
+ export const rule3 = (number: number): number => {
37
+ return number === 0 ? 1 : number % 10 == 1 && number % 100 != 11 ? 2 : 3
38
+ }
39
+
40
+ /**
41
+ * Families: Celtic (Scottish Gaelic)
42
+ * 1 - is 1 or 11: 1, 11
43
+ * 2 - is 2 or 12: 2, 12
44
+ * 3 - others between 3 and 19: 3, 4, ... 10, 13, ... 18, 19
45
+ * 4 - everything else: 0, 20, 21, ...
46
+ */
47
+ export const rule4 = (number: number): number => {
48
+ return number === 1 || number == 11
49
+ ? 1
50
+ : number === 2 || number === 12
51
+ ? 2
52
+ : number >= 3 && number <= 19
53
+ ? 3
54
+ : 4
55
+ }
56
+
57
+ /**
58
+ * Families: Romanic (Romanian)
59
+ * 1 - 1
60
+ * 2 - is 0 or ends in 01-19: 0, 2, 3, ... 19, 101, 102, ... 119, 201, ...
61
+ * 3 - everything else: 20, 21, ...
62
+ */
63
+ export const rule5 = (number: number): number => {
64
+ return number === 1 ? 1 : number === 0 || (number % 100 > 0 && number % 100 < 20) ? 2 : 3
65
+ }
66
+
67
+ /**
68
+ * Families: Baltic (Lithuanian)
69
+ * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ...
70
+ * 2 - ends in 0 or ends in 10-20: 0, 10, 11, 12, ... 19, 20, 30, 40, ...
71
+ * 3 - everything else: 2, 3, ... 8, 9, 22, 23, ... 29, 32, 33, ...
72
+ */
73
+ export const rule6 = (number: number): number => {
74
+ return number % 10 === 1 && number % 100 !== 11
75
+ ? 1
76
+ : number % 10 < 2 || (number % 100 >= 10 && number % 100 < 20)
77
+ ? 2
78
+ : 3
79
+ }
80
+
81
+ /**
82
+ * Families: Slavic (Croatian, Serbian, Russian, Ukrainian)
83
+ * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, ...
84
+ * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ...
85
+ * 3 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, ...
86
+ */
87
+ export const rule7 = (number: number): number => {
88
+ return number % 10 === 1 && number % 100 !== 11
89
+ ? 1
90
+ : number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 10 || number % 100 >= 20)
91
+ ? 2
92
+ : 3
93
+ }
94
+
95
+ /**
96
+ * Families: Slavic (Slovak, Czech)
97
+ * 1 - 1
98
+ * 2 - 2, 3, 4
99
+ * 3 - everything else: 0, 5, 6, 7, ...
100
+ */
101
+ export const rule8 = (number: number): number => {
102
+ return number === 1 ? 1 : number >= 2 && number <= 4 ? 2 : 3
103
+ }
104
+
105
+ /**
106
+ * Families: Slavic (Polish)
107
+ * 1 - 1
108
+ * 2 - ends in 2-4, not 12-14: 2, 3, 4, 22, 23, 24, 32, ... 104, 122, ...
109
+ * 3 - everything else: 0, 5, 6, ... 11, 12, 13, 14, 15, ... 20, 21, 25, ...
110
+ */
111
+ export const rule9 = (number: number): number => {
112
+ return number === 1
113
+ ? 1
114
+ : number % 10 >= 2 && number % 10 <= 4 && (number % 100 < 12 || number % 100 > 14)
115
+ ? 2
116
+ : 3
117
+ }
118
+
119
+ /**
120
+ * Families: Slavic (Slovenian, Sorbian)
121
+ * 1 - ends in 01: 1, 101, 201, ...
122
+ * 2 - ends in 02: 2, 102, 202, ...
123
+ * 3 - ends in 03-04: 3, 4, 103, 104, 203, 204, ...
124
+ * 4 - everything else: 0, 5, 6, 7, 8, 9, 10, 11, ...
125
+ */
126
+ export const rule10 = (number: number): number => {
127
+ return number % 100 === 1
128
+ ? 1
129
+ : number % 100 === 2
130
+ ? 2
131
+ : number % 100 === 3 || number % 100 === 4
132
+ ? 3
133
+ : 4
134
+ }
135
+
136
+ /**
137
+ * Families: Celtic (Irish Gaeilge)
138
+ * 1 - 1
139
+ * 2 - 2
140
+ * 3 - is 3-6: 3, 4, 5, 6
141
+ * 4 - is 7-10: 7, 8, 9, 10
142
+ * 5 - everything else: 0, 11, 12, ...
143
+ */
144
+ export const rule11 = (number: number): number => {
145
+ return number === 1
146
+ ? 1
147
+ : number === 2
148
+ ? 2
149
+ : number >= 3 && number <= 6
150
+ ? 3
151
+ : number >= 7 && number <= 10
152
+ ? 4
153
+ : 5
154
+ }
155
+
156
+ /**
157
+ * Families: Semitic (Arabic).
158
+ *
159
+ * 1 - 1
160
+ * 2 - 2
161
+ * 3 - ends in 03-10: 3, 4, ... 10, 103, 104, ... 110, 203, 204, ...
162
+ * 4 - ends in 11-99: 11, ... 99, 111, 112, ...
163
+ * 5 - everything else: 100, 101, 102, 200, 201, 202, ...
164
+ * 6 - 0
165
+ */
166
+ export const rule12 = (number: number): number => {
167
+ return number === 1
168
+ ? 1
169
+ : number === 2
170
+ ? 2
171
+ : number % 100 >= 3 && number % 100 <= 10
172
+ ? 3
173
+ : number % 100 >= 11
174
+ ? 4
175
+ : number != 0
176
+ ? 5
177
+ : 6
178
+ }
179
+
180
+ /**
181
+ * Families: Semitic (Maltese)
182
+ * 1 - 1
183
+ * 2 - is 0 or ends in 01-10: 0, 2, 3, ... 9, 10, 101, 102, ...
184
+ * 3 - ends in 11-19: 11, 12, ... 18, 19, 111, 112, ...
185
+ * 4 - everything else: 20, 21, ...
186
+ */
187
+ export const rule13 = (number: number): number => {
188
+ return number === 1
189
+ ? 1
190
+ : number === 0 || (number % 100 >= 1 && number % 100 < 11)
191
+ ? 2
192
+ : number % 100 > 10 && number % 100 < 20
193
+ ? 3
194
+ : 4
195
+ }
196
+
197
+ /**
198
+ * Families: Slavic (Macedonian)
199
+ * 1 - ends in 1: 1, 11, 21, ...
200
+ * 2 - ends in 2: 2, 12, 22, ...
201
+ * 3 - everything else: 0, 3, 4, ... 10, 13, 14, ... 20, 23, ...
202
+ */
203
+ export const rule14 = (number: number): number => {
204
+ return number % 10 === 1 ? 1 : number % 10 === 2 ? 2 : 3
205
+ }
206
+
207
+ /**
208
+ * Families: Icelandic
209
+ * 1 - ends in 1, not 11: 1, 21, 31, ... 101, 121, 131, ...
210
+ * 2 - everything else: 0, 2, 3, ... 10, 11, 12, ... 20, 22, ...
211
+ */
212
+ export const rule15 = (number: number): number => {
213
+ return number % 10 === 1 && number % 100 != 11 ? 1 : 2
214
+ }
215
+
216
+ export interface PluralRules {
217
+ [key: number]: (pluralValue: number) => number
218
+ }
@@ -1 +1,2 @@
1
1
  export { useConfigStore } from './config'
2
+ export { useTranslator } from './useTranslator'