codevdesign 1.0.21 → 1.0.23

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,203 +1,203 @@
1
- <template>
2
- <v-toolbar
3
- color="primary"
4
- height="72px"
5
- elevation="1"
6
- >
7
- <v-row
8
- class="pl-6 pr-6"
9
- align="center"
10
- no-gutters
11
- >
12
- <!-- Première colonne : Logo -->
13
- <v-col cols="auto">
14
- <a
15
- :href="href"
16
- :target="cssUrlValide ? '_blank' : undefined"
17
- :rel="cssUrlValide ? 'noopener noreferrer' : undefined"
18
- >
19
- <!-- Placeholder (même taille) pendant la décision -->
20
- <div
21
- v-if="!ready"
22
- class="logo-placeholder"
23
- ></div>
24
- <!-- On ne rend l'image qu'une fois la source choisie -->
25
- <img
26
- v-else
27
- class="logo-img"
28
- id="pivImage"
29
- :src="currentSrc!"
30
- :alt="$t('csqc.pivFooter.logoAlt')"
31
- decoding="async"
32
- loading="eager"
33
- @error="ErreurLogo"
34
- />
35
- </a>
36
- </v-col>
37
-
38
- <!-- Colonne pour le nom de l'application (Pour le mode desktop) -->
39
- <v-col
40
- v-if="!estMobile"
41
- class="d-flex justify-center"
42
- >
43
- <v-app-bar-title
44
- class="pl-12 ml-12"
45
- style="font-size: 16px !important"
46
- >
47
- {{ formulaireNom }}
48
- </v-app-bar-title>
49
- </v-col>
50
-
51
- <!-- Colonne pour le bouton de langue et icône d'aide -->
52
- <v-col class="d-none d-flex justify-end">
53
- <!-- langue -->
54
- <v-btn
55
- variant="text"
56
- @click="enregistrerLangue()"
57
- >{{ $t('csqc.pivEntete.langue') }}
58
- </v-btn>
59
-
60
- <!-- icône d'aide si dispo -->
61
- <v-btn
62
- v-if="aideUrl"
63
- icon="mdi-help-circle-outline"
64
- :href="aideUrl"
65
- target="_blank"
66
- rel="noopener noreferrer"
67
- style="margin-top: -6px"
68
- >
69
- </v-btn>
70
- </v-col>
71
-
72
- <!-- Colonne pour le nom de l'application (Pour le mode mobile) -->
73
- <v-col
74
- v-if="props.estMobile"
75
- cols="12"
76
- >
77
- <v-app-bar-title style="font-size: 16px !important">
78
- {{ formulaireNom }}
79
- </v-app-bar-title>
80
- </v-col>
81
- </v-row>
82
- </v-toolbar>
83
- </template>
84
-
85
- <script setup lang="ts">
86
- import { ref, watch, computed } from 'vue'
87
- import { useLocale } from 'vuetify'
88
- import { useI18n } from 'vue-i18n'
89
-
90
- const { current } = useLocale()
91
- const props = defineProps({
92
- estMobile: { type: Boolean, default: false },
93
- urlBase: { type: String, required: true },
94
- aideUrl: { type: String, default: '' },
95
- cssUrl: { type: String, default: '' },
96
- logoUrl: { type: String, default: '' },
97
- lienLogo: { type: String, default: '' },
98
- formulaireId: { type: Number, default: 0 },
99
- formulaireNom: { type: String, default: '' },
100
- })
101
- const emit = defineEmits(['changementLangue'])
102
- const { t } = useI18n()
103
-
104
- const FALLBACK = '/images/QUEBEC_blanc.svg'
105
- const currentSrc = ref<string | null>(null) // pas d'image tant que null
106
- const ready = ref(false)
107
-
108
- const formulaireNom = computed(() => {
109
- return props.formulaireNom || t('nom_application')
110
- })
111
-
112
- const href = computed(() => (props.cssUrl?.trim() ? props.cssUrl.trim() : '/'))
113
- const cssUrlValide = computed(() => /^https?:\/\//i.test(href.value))
114
-
115
- let loadToken = 0 // ← identifiant de “requête” pour annuler logiquement
116
-
117
- // Sur changement de l’URL du logo, on tente de le charger, sinon fallback apres 3.5 secs
118
- watch(
119
- () => props.logoUrl,
120
- async (nouvelle, ancienne) => {
121
- const url = (nouvelle ?? '').trim()
122
-
123
- // même URL → ne rien faire
124
- if (url === (ancienne ?? '').trim() && currentSrc.value !== null) return
125
-
126
- // pas d’URL → fallback immédiat
127
- if (!url) {
128
- currentSrc.value = FALLBACK
129
- ready.value = true
130
- return
131
- }
132
-
133
- // nouvelle tentative (avec “annulation logique”)
134
- const token = ++loadToken
135
- ready.value = false
136
- const ok = await loadWithTimeout(url, 3500)
137
- if (token !== loadToken) return // une nouvelle tentative a démarré entre-temps
138
-
139
- currentSrc.value = ok ? url : FALLBACK
140
- ready.value = true
141
- },
142
- { immediate: true },
143
- )
144
-
145
- // Charge une image avec un timeout
146
- function loadWithTimeout(url: string, timeoutMs: number): Promise<boolean> {
147
- return new Promise<boolean>(resolve => {
148
- const img = new Image()
149
- const timer = setTimeout(() => {
150
- // trop long → on abandonne
151
- img.src = '' // stoppe le chargement
152
- resolve(false)
153
- }, timeoutMs)
154
-
155
- img.onload = () => {
156
- clearTimeout(timer)
157
- resolve(true)
158
- }
159
- img.onerror = () => {
160
- clearTimeout(timer)
161
- resolve(false)
162
- }
163
- img.src = url
164
- })
165
- }
166
-
167
- // Si l'image choisie a un problème, switch sur le fallback
168
- function ErreurLogo() {
169
- if (currentSrc.value !== FALLBACK) currentSrc.value = FALLBACK
170
- }
171
-
172
- const enregistrerLangue = (): void => {
173
- const langueDispo: string = current.value === 'fr' ? 'en' : 'fr'
174
- let returnUrl = window.location.pathname + window.location.search
175
- if (import.meta.env.MODE === 'development') {
176
- returnUrl = '/'
177
- }
178
- window.location.href =
179
- props.urlBase + `/Traducteur/SetLanguage?culture=${langueDispo}&returnUrl=${encodeURIComponent(returnUrl)}`
180
- emit('changementLangue')
181
- }
182
- </script>
183
-
184
- <style scoped>
185
- .container {
186
- max-width: none !important;
187
- }
188
- .theme--light.v-app-bar.v-toolbar.v-sheet {
189
- background: #095797;
190
-
191
- color: #fff;
192
- }
193
- .logo-placeholder {
194
- height: 72px;
195
- width: 180px;
196
- }
197
- .logo-img {
198
- height: 72px;
199
- transition: opacity 0.15s;
200
- opacity: 1;
201
- display: block;
202
- }
203
- </style>
1
+ <template>
2
+ <v-toolbar
3
+ color="primary"
4
+ height="72px"
5
+ elevation="1"
6
+ >
7
+ <v-row
8
+ class="pl-6 pr-6"
9
+ align="center"
10
+ no-gutters
11
+ >
12
+ <!-- Première colonne : Logo -->
13
+ <v-col cols="auto">
14
+ <a
15
+ :href="href"
16
+ :target="cssUrlValide ? '_blank' : undefined"
17
+ :rel="cssUrlValide ? 'noopener noreferrer' : undefined"
18
+ >
19
+ <!-- Placeholder (même taille) pendant la décision -->
20
+ <div
21
+ v-if="!ready"
22
+ class="logo-placeholder"
23
+ ></div>
24
+ <!-- On ne rend l'image qu'une fois la source choisie -->
25
+ <img
26
+ v-else
27
+ class="logo-img"
28
+ id="pivImage"
29
+ :src="currentSrc!"
30
+ :alt="$t('csqc.pivFooter.logoAlt')"
31
+ decoding="async"
32
+ loading="eager"
33
+ @error="ErreurLogo"
34
+ />
35
+ </a>
36
+ </v-col>
37
+
38
+ <!-- Colonne pour le nom de l'application (Pour le mode desktop) -->
39
+ <v-col
40
+ v-if="!estMobile"
41
+ class="d-flex justify-center"
42
+ >
43
+ <v-app-bar-title
44
+ class="pl-12 ml-12"
45
+ style="font-size: 16px !important"
46
+ >
47
+ {{ formulaireNom }}
48
+ </v-app-bar-title>
49
+ </v-col>
50
+
51
+ <!-- Colonne pour le bouton de langue et icône d'aide -->
52
+ <v-col class="d-none d-flex justify-end">
53
+ <!-- langue -->
54
+ <v-btn
55
+ variant="text"
56
+ @click="enregistrerLangue()"
57
+ >{{ $t('csqc.pivEntete.langue') }}
58
+ </v-btn>
59
+
60
+ <!-- icône d'aide si dispo -->
61
+ <v-btn
62
+ v-if="aideUrl"
63
+ icon="mdi-help-circle-outline"
64
+ :href="aideUrl"
65
+ target="_blank"
66
+ rel="noopener noreferrer"
67
+ style="margin-top: -6px"
68
+ >
69
+ </v-btn>
70
+ </v-col>
71
+
72
+ <!-- Colonne pour le nom de l'application (Pour le mode mobile) -->
73
+ <v-col
74
+ v-if="props.estMobile"
75
+ cols="12"
76
+ >
77
+ <v-app-bar-title style="font-size: 16px !important">
78
+ {{ formulaireNom }}
79
+ </v-app-bar-title>
80
+ </v-col>
81
+ </v-row>
82
+ </v-toolbar>
83
+ </template>
84
+
85
+ <script setup lang="ts">
86
+ import { ref, watch, computed } from 'vue'
87
+ import { useLocale } from 'vuetify'
88
+ import { useI18n } from 'vue-i18n'
89
+
90
+ const { current } = useLocale()
91
+ const props = defineProps({
92
+ estMobile: { type: Boolean, default: false },
93
+ urlBase: { type: String, required: true },
94
+ aideUrl: { type: String, default: '' },
95
+ cssUrl: { type: String, default: '' },
96
+ logoUrl: { type: String, default: '' },
97
+ lienLogo: { type: String, default: '' },
98
+ formulaireId: { type: Number, default: 0 },
99
+ formulaireNom: { type: String, default: '' },
100
+ })
101
+ const emit = defineEmits(['changementLangue'])
102
+ const { t } = useI18n()
103
+
104
+ const FALLBACK = '/images/QUEBEC_blanc.svg'
105
+ const currentSrc = ref<string | null>(null) // pas d'image tant que null
106
+ const ready = ref(false)
107
+
108
+ const formulaireNom = computed(() => {
109
+ return props.formulaireNom || t('nom_application')
110
+ })
111
+
112
+ const href = computed(() => (props.cssUrl?.trim() ? props.cssUrl.trim() : '/'))
113
+ const cssUrlValide = computed(() => /^https?:\/\//i.test(href.value))
114
+
115
+ let loadToken = 0 // ← identifiant de “requête” pour annuler logiquement
116
+
117
+ // Sur changement de l’URL du logo, on tente de le charger, sinon fallback apres 3.5 secs
118
+ watch(
119
+ () => props.logoUrl,
120
+ async (nouvelle, ancienne) => {
121
+ const url = (nouvelle ?? '').trim()
122
+
123
+ // même URL → ne rien faire
124
+ if (url === (ancienne ?? '').trim() && currentSrc.value !== null) return
125
+
126
+ // pas d’URL → fallback immédiat
127
+ if (!url) {
128
+ currentSrc.value = FALLBACK
129
+ ready.value = true
130
+ return
131
+ }
132
+
133
+ // nouvelle tentative (avec “annulation logique”)
134
+ const token = ++loadToken
135
+ ready.value = false
136
+ const ok = await loadWithTimeout(url, 3500)
137
+ if (token !== loadToken) return // une nouvelle tentative a démarré entre-temps
138
+
139
+ currentSrc.value = ok ? url : FALLBACK
140
+ ready.value = true
141
+ },
142
+ { immediate: true },
143
+ )
144
+
145
+ // Charge une image avec un timeout
146
+ function loadWithTimeout(url: string, timeoutMs: number): Promise<boolean> {
147
+ return new Promise<boolean>(resolve => {
148
+ const img = new Image()
149
+ const timer = setTimeout(() => {
150
+ // trop long → on abandonne
151
+ img.src = '' // stoppe le chargement
152
+ resolve(false)
153
+ }, timeoutMs)
154
+
155
+ img.onload = () => {
156
+ clearTimeout(timer)
157
+ resolve(true)
158
+ }
159
+ img.onerror = () => {
160
+ clearTimeout(timer)
161
+ resolve(false)
162
+ }
163
+ img.src = url
164
+ })
165
+ }
166
+
167
+ // Si l'image choisie a un problème, switch sur le fallback
168
+ function ErreurLogo() {
169
+ if (currentSrc.value !== FALLBACK) currentSrc.value = FALLBACK
170
+ }
171
+
172
+ const enregistrerLangue = (): void => {
173
+ const langueDispo: string = current.value === 'fr' ? 'en' : 'fr'
174
+ let returnUrl = window.location.pathname + window.location.search
175
+ if (import.meta.env.MODE === 'development') {
176
+ returnUrl = '/'
177
+ }
178
+ window.location.href =
179
+ props.urlBase + `/Traducteur/SetLanguage?culture=${langueDispo}&returnUrl=${encodeURIComponent(returnUrl)}`
180
+ emit('changementLangue')
181
+ }
182
+ </script>
183
+
184
+ <style scoped>
185
+ .container {
186
+ max-width: none !important;
187
+ }
188
+ .theme--light.v-app-bar.v-toolbar.v-sheet {
189
+ background: #095797;
190
+
191
+ color: #fff;
192
+ }
193
+ .logo-placeholder {
194
+ height: 72px;
195
+ width: 180px;
196
+ }
197
+ .logo-img {
198
+ height: 72px;
199
+ transition: opacity 0.15s;
200
+ opacity: 1;
201
+ display: block;
202
+ }
203
+ </style>
@@ -1,18 +1,18 @@
1
- export interface MenuItem {
2
- nom: string
3
- path: string
4
- droit: string
5
- }
6
-
7
- export interface SousListe {
8
- droit: string
9
- id: number
10
- nom: string
11
- }
12
-
13
- export interface SousListeItems {
14
- parentId: number
15
- nom: string
16
- path: string
17
- droit: string
18
- }
1
+ export interface MenuItem {
2
+ nom: string
3
+ path: string
4
+ droit: string
5
+ }
6
+
7
+ export interface SousListe {
8
+ droit: string
9
+ id: number
10
+ nom: string
11
+ }
12
+
13
+ export interface SousListeItems {
14
+ parentId: number
15
+ nom: string
16
+ path: string
17
+ droit: string
18
+ }
@@ -1,31 +1,31 @@
1
- import type { DataTableCompareFunction, DataTableHeader, FilterFunction } from 'vuetify'
2
- import { HeaderCellProps } from 'vuetify/lib/components/VDataTable/types.mjs';
3
- import { SelectItemKey } from 'vuetify/lib/util/helpers.mjs';
4
-
5
- class datatableColonne<T = any> implements DataTableHeader<T> {
6
- key?: 'data-table-group' | 'data-table-select' | 'data-table-expand' | (string & {});
7
- value?: SelectItemKey<T>;
8
- title?: string;
9
- fixed?: boolean | 'start' | 'end';
10
- align?: 'start' | 'end' | 'center';
11
- width?: number | string;
12
- minWidth?: number | string;
13
- maxWidth?: number | string;
14
- nowrap?: boolean;
15
- intent?: number;
16
- headerProps?: Record<string, any>;
17
- cellProps?: HeaderCellProps;
18
- sortable?: boolean;
19
- sort?: DataTableCompareFunction;
20
- sortRaw?: DataTableCompareFunction;
21
- filter?: FilterFunction;
22
- children?: DataTableHeader<T>[];
23
-
24
- constructor(title: string, key: string) {
25
- this.title = title
26
- this.key = key
27
- this.align = key === 'action' ? 'end' : key === 'estActif' ? 'center' : 'start'
28
- this.sortable = key !== 'action'
29
- }
30
- }
31
- export default datatableColonne
1
+ import type { DataTableCompareFunction, DataTableHeader, FilterFunction } from 'vuetify'
2
+ import { HeaderCellProps } from 'vuetify/lib/components/VDataTable/types.mjs';
3
+ import { SelectItemKey } from 'vuetify/lib/util/helpers.mjs';
4
+
5
+ class datatableColonne<T = any> implements DataTableHeader<T> {
6
+ key?: 'data-table-group' | 'data-table-select' | 'data-table-expand' | (string & {});
7
+ value?: SelectItemKey<T>;
8
+ title?: string;
9
+ fixed?: boolean | 'start' | 'end';
10
+ align?: 'start' | 'end' | 'center';
11
+ width?: number | string;
12
+ minWidth?: number | string;
13
+ maxWidth?: number | string;
14
+ nowrap?: boolean;
15
+ intent?: number;
16
+ headerProps?: Record<string, any>;
17
+ cellProps?: HeaderCellProps;
18
+ sortable?: boolean;
19
+ sort?: DataTableCompareFunction;
20
+ sortRaw?: DataTableCompareFunction;
21
+ filter?: FilterFunction;
22
+ children?: DataTableHeader<T>[];
23
+
24
+ constructor(title: string, key: string) {
25
+ this.title = title
26
+ this.key = key
27
+ this.align = key === 'action' ? 'end' : key === 'estActif' ? 'center' : 'start'
28
+ this.sortable = key !== 'action'
29
+ }
30
+ }
31
+ export default datatableColonne
@@ -1,4 +1,3 @@
1
-
2
1
  class RafraichisseurToken {
3
2
  private intervalleEnSecondes = 15
4
3
  private secondesAvantExpirationTokenPourRafraichir = 20
@@ -7,12 +6,12 @@ class RafraichisseurToken {
7
6
  private timerId: number | null = null
8
7
 
9
8
  // Lance une seule fois
10
- public async demarrer(nomCookie: string | null, urlPortail: string): Promise<void> {
11
- if (nomCookie == null || nomCookie === '') nomCookie = 'csqc_jeton_secure_expiration'
12
- await this.verifierToken(nomCookie, urlPortail)
9
+ public async demarrer(nomTemoin: string | null, urlPortail: string): Promise<void> {
10
+ if (nomTemoin == null || nomTemoin === '') nomTemoin = 'csqc_jeton_secure_expiration'
11
+ await this.verifierJeton(nomTemoin, urlPortail)
13
12
  if (this.timerId != null) return
14
13
  this.timerId = window.setInterval(() => {
15
- this.verifierToken(nomCookie, urlPortail)
14
+ this.verifierJeton(nomTemoin, urlPortail)
16
15
  }, this.intervalleEnSecondes * 1000)
17
16
  }
18
17
 
@@ -24,35 +23,48 @@ class RafraichisseurToken {
24
23
  }
25
24
  }
26
25
 
27
- private async verifierToken(nomCookie: string, urlPortail: string): Promise<void> {
26
+ public existeJeton = (nomTemoin: string) => {
27
+ return this.estJetonValide(nomTemoin)
28
+ }
29
+
30
+ private async verifierJeton(nomTemoin: string, urlPortail: string): Promise<void> {
31
+ if (this.popupAffiche) return
28
32
 
29
- if (this.popupAffiche || !nomCookie) return
30
- const tokenEncode = this.lireCookie(nomCookie)
31
- if (!tokenEncode) {
32
- await this.rafraichir(nomCookie, urlPortail)
33
+ if (!this.estJetonValide(nomTemoin)) {
34
+ this.rafraichir(nomTemoin, urlPortail)
33
35
  return
34
36
  }
37
+ }
38
+
39
+ private estJetonValide(nomTemoin: string): boolean {
40
+ if (this.popupAffiche || !nomTemoin) return true //On fait semblant que c'est valide pour ne pas provoquer un autre affichage du popup.
41
+
42
+ const tokenEncode = this.lireCookie(nomTemoin)
43
+ if (!tokenEncode) {
44
+ return false
45
+ }
35
46
 
36
47
  let token: any
37
48
  try {
38
49
  token = this.parseJwt(tokenEncode)
39
50
  } catch {
40
- await this.rafraichir(nomCookie, urlPortail)
41
- return
51
+ return false
42
52
  }
43
53
 
44
54
  const exp = Number(token?.exp)
45
55
  if (!Number.isFinite(exp)) {
46
56
  // exp manquant/invalide → tente refresh
47
- await this.rafraichir(nomCookie, urlPortail)
48
- return
57
+
58
+ return false
49
59
  }
50
60
 
51
61
  const now = Math.floor(Date.now() / 1000)
52
62
  const refreshAt = exp - this.secondesAvantExpirationTokenPourRafraichir - this.skewSeconds
53
63
  if (now >= refreshAt) {
54
- await this.rafraichir(nomCookie, urlPortail)
64
+ return false
55
65
  }
66
+
67
+ return true
56
68
  }
57
69
 
58
70
  private async rafraichir(nomCookie: string, urlPortail: string): Promise<void> {
@@ -132,7 +144,6 @@ class RafraichisseurToken {
132
144
  window.location.replace(`${urlPortail}deconnecte?urlRetour=${encodeURIComponent(window.location.href)}`)
133
145
  }
134
146
 
135
-
136
147
  private lireCookie(nom: string): string | null {
137
148
  if (!document.cookie) return null
138
149
  const cookies = document.cookie.split(';').map(c => c.trim())
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codevdesign",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "Composants Vuetify 3 pour les projets Codev",
5
5
  "files": [
6
6
  "./**/*.vue",