@hostlink/nuxt-light 1.1.8 → 1.3.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/dist/module.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "light",
3
3
  "configKey": "light",
4
- "version": "1.1.8"
4
+ "version": "1.3.0"
5
5
  }
@@ -1,11 +1,12 @@
1
1
  <script setup>
2
- import { useLight } from "#imports";
3
- import { useQuasar } from 'quasar';
2
+ import { useRoute } from 'vue-router';
3
+ import { useLight, q, m } from "#imports";
4
+ import { useQuasar, Loading, Dialog } from 'quasar';
4
5
  import { useI18n } from 'vue-i18n';
5
- import { q, m, f } from '../';
6
- import { ref, computed, reactive, provide, watch } from 'vue';
6
+ import { ref, computed, reactive, provide, watch, toRaw } from 'vue';
7
7
  import { useRuntimeConfig } from 'nuxt/app';
8
8
 
9
+ Loading.show()
9
10
  //download system value
10
11
  /* import { download } from './../lib/SystemValue'
11
12
  await download();
@@ -20,15 +21,28 @@ const tt = await q({
20
21
  app: ["menus", "viewAsMode", "languages",
21
22
  "copyrightYear",
22
23
  "copyrightName",
24
+ "hasFavorite",
23
25
  { i18nMessages: ["name", "value"] }],
24
- my: ['username', 'first_name', 'last_name', 'roles', "styles", "language", f('granted_storage:granted', { right: "system.storage" }, [])],
26
+ my: ['username', 'first_name', 'last_name', 'roles', "styles", "language", "permissions", { myFavorites: ["my_favorite_id", "label", "path", "icon"] }],
25
27
  })
26
28
 
27
29
  let app = tt.app
28
- let my = tt.my
30
+ let my = reactive(tt.my)
29
31
 
30
32
  const light = useLight();
31
33
  light.init(my.styles);
34
+ //set permission
35
+ light.setPermissions(my.permissions);
36
+
37
+ light.setMyFavorites(toRaw(my.myFavorites))
38
+
39
+ const myFavorites = computed(() => {
40
+ return light.getMyFavorites();
41
+ });
42
+
43
+ const myFavoritesCount = computed(() => {
44
+ return light.getMyFavorites().length;
45
+ });
32
46
 
33
47
  quasar.dark.set(light.isDarkMode());
34
48
 
@@ -36,7 +50,8 @@ const i18n = useI18n();
36
50
  i18n.locale = my.language || 'en';
37
51
 
38
52
  let system = null
39
- if (my.granted_storage) {
53
+
54
+ if (light.isGranted("system.storage")) {
40
55
  system = await q("system", ["diskFreeSpace", "diskUsageSpace", "diskTotalSpace", "diskFreeSpacePercent"]);
41
56
  }
42
57
 
@@ -65,6 +80,7 @@ const toggleRightDrawer = () => {
65
80
  let showViewAs = false;
66
81
  if (my && my.roles.indexOf('Administrators') != -1) {
67
82
  showViewAs = true;
83
+ light.isAdmin = true;
68
84
  }
69
85
 
70
86
  const menuOverlayHeader = ref(false)
@@ -188,19 +204,60 @@ const containerStyle = computed(() => {
188
204
  }
189
205
  })
190
206
 
191
- const fullscreen = ref(document.fullscreenElement)
207
+ const route = useRoute()
208
+
192
209
 
193
- const onFullscreen = () => {
194
- if (document.fullscreenElement) {
195
- document.exitFullscreen();
196
- fullscreen.value = false;
197
- } else {
198
- fullscreen.value = true;
210
+ const onToggleFav = async () => {
199
211
 
200
- document.documentElement.requestFullscreen();
212
+ if (isFav.value) {
213
+ await m("removeMyFavorite", {
214
+ path: route.fullPath,
215
+ })
216
+
217
+ //reload my.myFavorites
218
+ light.reloadMyFavorites();
219
+
220
+
221
+ return;
201
222
  }
223
+
224
+ Dialog.create({
225
+ title: 'Add to favorite',
226
+ message: 'Enter favorite label',
227
+ prompt: {
228
+ //get window title, remove - and replace with space
229
+ model: route.name.replace(/-/g, ' '),
230
+ },
231
+ cancel: true,
232
+ persistent: true,
233
+ }).onOk(async (data) => {
234
+ if (data === '') return;
235
+ if (await m("addMyFavorite", {
236
+ path: route.fullPath,
237
+ label: data,
238
+ })) {
239
+ light.reloadMyFavorites();
240
+
241
+ }
242
+ })
202
243
  }
203
244
 
245
+ const isFav = computed(() => {
246
+ //check my.myFavorites
247
+ let fav = my.myFavorites.find((item) => {
248
+ return item.path == route.fullPath;
249
+ })
250
+ return fav;
251
+ })
252
+
253
+ Loading.hide()
254
+
255
+ const menuWidth = ref(my.styles.menuWidth || 280);
256
+
257
+ watch(menuWidth, () => {
258
+ light.setStyle("menuWidth", menuWidth.value)
259
+ })
260
+
204
261
  </script>
205
262
 
206
263
  <template>
@@ -218,26 +275,33 @@ const onFullscreen = () => {
218
275
 
219
276
  <q-space />
220
277
 
221
- <q-btn :icon="fullscreen ? 'fullscreen_exit' : 'fullscreen'" round flat dense class="q-mr-sm"
222
- @click="onFullscreen">
278
+ <q-btn :icon="isFav ? 'favorite' : 'sym_o_favorite'" round flat dense class="q-mr-xs" @click="onToggleFav"
279
+ v-if="app.hasFavorite">
280
+ </q-btn>
223
281
 
282
+ <q-btn :icon="$q.fullscreen.isActive ? 'fullscreen_exit' : 'fullscreen'" round flat dense class="q-mr-xs"
283
+ @click="$q.fullscreen.toggle()">
224
284
  </q-btn>
225
285
 
226
- <q-btn v-if="languages.length > 1" rounded flat icon="language" :label="my.language" class="q-mr-sm">
286
+ <q-btn v-if="languages.length > 1" round flat icon="language" class="q-mr-xs">
287
+ <q-tooltip>
288
+ {{ my.language }}
289
+ </q-tooltip>
227
290
  <q-menu>
228
291
  <q-list>
229
- <q-item v-for="language in languages" :key="language.value" v-close-popup clickable
230
- @click="onChangeLocale(language.value)">
292
+ <q-item v-for=" language in languages " :key="language.value" v-close-popup clickable
293
+ @click="onChangeLocale(language.value)" :active="language.value == my.language">
231
294
  <q-item-section>
232
295
  <q-item-label>{{ language.name }}</q-item-label>
233
296
  </q-item-section>
234
297
  </q-item>
235
298
  </q-list>
236
299
  </q-menu>
300
+
301
+
237
302
  </q-btn>
238
303
 
239
- <q-btn icon="sym_o_storage" flat round dense class="q-mr-sm" v-if="my.granted_storage"
240
- :color="storageColor">
304
+ <q-btn icon="sym_o_storage" flat round dense class="q-mr-sm" v-if="system" :color="storageColor">
241
305
  <q-menu>
242
306
  <q-card style="width:250px">
243
307
  <q-card-section>
@@ -310,12 +374,18 @@ const onFullscreen = () => {
310
374
  </q-toolbar>
311
375
  </q-header>
312
376
 
313
- <q-drawer v-model="leftDrawerOpen" bordered show-if-above side="left" :dark="menuDark" :width="280"
377
+ <q-drawer v-model="leftDrawerOpen" bordered show-if-above side="left" :dark="menuDark" :width="menuWidth"
314
378
  :mini-to-overlay="style.miniState" :mini="isMini" @mouseout="isMouseOnDrawer = false"
315
379
  @mouseover="isMouseOnDrawer = true">
316
380
  <!-- drawer content -->
317
381
  <q-scroll-area class="fit">
318
- <l-menu v-for="menu in menus" :value="menu" :dense="style.dense" />
382
+
383
+ <div class="q-mx-sm">
384
+ <l-fav-menu :value="myFavorites" :dense="style.dense" v-if="myFavoritesCount > 0" />
385
+ <l-menu v-for=" menu in menus " :value="menu" :dense="style.dense" />
386
+
387
+ </div>
388
+
319
389
  </q-scroll-area>
320
390
  </q-drawer>
321
391
 
@@ -324,14 +394,14 @@ const onFullscreen = () => {
324
394
  <q-scroll-area class="fit">
325
395
  <l-customizer v-model:color="light.color" v-model:theme="light.theme" v-model:miniState="style.miniState"
326
396
  v-model:dense="style.dense" v-model:menuOverlayHeader="style.menuOverlayHeader"
327
- v-model:footer="style.footer" />
397
+ v-model:footer="style.footer" v-model:menuWidth="menuWidth" />
328
398
  </q-scroll-area>
329
399
  </q-drawer>
330
400
 
331
401
  <q-page-container :class="containerClass" :style="containerStyle">
332
402
 
333
403
  <!-- Error message -->
334
- <q-banner dense inline-actions class="bg-grey-4 q-ma-md" v-for="error in errors" rounded>
404
+ <q-banner dense inline-actions class="bg-grey-4 q-ma-md" v-for=" error in errors " rounded>
335
405
  {{ error }}
336
406
  <template v-slot:action>
337
407
  <q-btn flat icon="sym_o_close" round dense @click="light.removeError(error)" />
@@ -1,29 +1,20 @@
1
1
  <script setup lang="ts">
2
2
  import { type QBtnProps } from "quasar";
3
- import { ref, computed } from "vue";
4
- import { q, f, useLight } from '#imports';
3
+ import { computed } from "vue";
4
+ import { useLight } from '#imports';
5
+ import { useI18n } from "vue-i18n";
5
6
 
6
7
  export interface LBtnProps extends QBtnProps {
7
8
  permission?: string;
8
9
  }
9
10
 
11
+ const { t } = useI18n();
10
12
  const props = defineProps<LBtnProps>();
11
13
 
12
14
  const light = useLight();
13
15
 
14
- const granted = ref(false);
15
- if (props.permission) {
16
- const my = await q("my", [f("granted", { right: props.permission }, [])]);
17
-
18
- if (my.granted) {
19
- granted.value = true;
20
- }
21
- } else {
22
- granted.value = true;
23
- }
24
-
25
16
  const attrs = computed(() => {
26
- return {
17
+ const a = {
27
18
  ...{
28
19
  rounded: light.getStyle("buttonRounded"),
29
20
  outline: light.getStyle("buttonOutline"),
@@ -32,12 +23,19 @@ const attrs = computed(() => {
32
23
  },
33
24
  ...props,
34
25
  }
26
+
27
+ if (a.label !== undefined) {
28
+ a.label = t(a.label)
29
+ }
30
+
31
+ return a;
32
+
35
33
  });
36
34
 
37
35
 
38
36
  </script>
39
37
  <template>
40
- <q-btn v-bind="attrs" color="primary" v-if="granted">
38
+ <q-btn v-bind="attrs" color="primary" v-if="$light.isGranted(permission)">
41
39
  <slot></slot>
42
40
  </q-btn>
43
41
  </template>
@@ -1,11 +1,15 @@
1
1
  <script setup lang="ts">
2
- import { useLight } from '#imports'
2
+ import { useLight, q, m } from '#imports'
3
3
  import { ref, computed } from 'vue'
4
4
  import { type QCardProps } from 'quasar';
5
5
 
6
6
  export interface LCardProps extends QCardProps {
7
7
  loading?: boolean;
8
8
  title?: string;
9
+ /**
10
+ * Permission to access this card, if not granted, the card will not be shown, if the user is admin, a lock icon will be shown to allow the user to grant the permission
11
+ */
12
+ permission?: string
9
13
  }
10
14
 
11
15
  const light = useLight();
@@ -40,18 +44,89 @@ const fullScreenIcon = computed(() => fullScreen.value ? "sym_o_fullscreen_exit"
40
44
 
41
45
  const showBody = computed(() => !minimize.value || fullScreen.value);
42
46
 
47
+ const showBar = computed(() => {
48
+ if (props.title !== undefined) return true;
49
+ if (showSecurity.value) return true;
50
+ return false;
51
+ });
52
+
53
+
54
+ const showSecurity = computed(() => {
55
+ if (props.permission === undefined) return false;
56
+ if (!light.isAdmin) return false;
57
+ return true;
58
+ });
59
+
60
+
61
+ const roles = ref<{
62
+ name: string;
63
+ granted: boolean;
64
+ }[]>([]);
65
+
66
+ if (props.permission && light.isAdmin) {
67
+ //get role
68
+ const data = await q({
69
+ listRole: {
70
+ name: true
71
+ },
72
+ listPermission: {
73
+ __args: {
74
+ filters: {
75
+ value: props.permission
76
+ }
77
+ }, data: {
78
+ role: true
79
+ }
80
+
81
+ }
82
+ });
83
+
84
+ roles.value = (data.listRole.map((r: any) => {
85
+ return {
86
+ name: r.name,
87
+ granted: data.listPermission.data.find((p: any) => p.role == r.name) != undefined
88
+ }
89
+ }));
90
+ }
91
+
92
+ const onUpdatePermission = async (role: any) => {
93
+ if (role.granted) {
94
+ await m("addPermission", {
95
+ role: role.name,
96
+ value: props.permission,
97
+ });
98
+ } else {
99
+ //remove permission
100
+ await m("removePermission", {
101
+ role: role.name,
102
+ value: props.permission,
103
+ })
104
+ }
105
+ }
106
+
107
+
108
+
43
109
  </script>
44
110
  <template>
45
- <q-card v-bind="attrs" :class="{ 'fullscreen': fullScreen, 'no-margin': fullScreen }">
46
- <q-bar :class="cl" v-if="title">
47
- <div>{{ $t(title) }}</div>
111
+ <q-card v-bind="attrs" :class="{ 'fullscreen': fullScreen, 'no-margin': fullScreen }"
112
+ v-if="$light.isGranted(permission)" :dark="$q.dark.isActive">
113
+ <q-bar :class="cl" v-if="showBar">
114
+ <div>{{ $t(title ?? "") }}</div>
48
115
  <q-space />
49
- <!-- q-btn-dropdown dense flat icon="sym_o_search" persistent>
50
- <div class="q-ma-md">
51
- <q-input></q-input>
52
- </div>
53
-
54
- </q-btn-dropdown -->
116
+
117
+ <q-btn dense flat icon="sym_o_lock" persistent v-if="showSecurity">
118
+ <q-menu>
119
+ <q-list>
120
+ <q-item clickable v-for="role in roles">
121
+ <q-item-section>
122
+ <q-checkbox :label="role.name" v-model="role.granted"
123
+ @update:model-value="onUpdatePermission(role)"></q-checkbox>
124
+ </q-item-section>
125
+ </q-item>
126
+ </q-list>
127
+ </q-menu>
128
+ </q-btn>
129
+
55
130
  <q-btn dense flat @click="fullScreen = !fullScreen" :icon="fullScreenIcon" />
56
131
  <q-btn dense flat :icon="icon" @click="minimize = !minimize" v-if="!fullScreen" />
57
132
  </q-bar>
@@ -1,4 +1,9 @@
1
1
  <script setup>
2
+ const menuWidth = defineModel('menuWidth', {
3
+ type: Number,
4
+ default: 280
5
+ });
6
+
2
7
  const COLORS = [
3
8
  'primary',
4
9
  'secondary',
@@ -28,6 +33,8 @@ const COLORS = [
28
33
  'blue-grey'
29
34
  ]
30
35
 
36
+
37
+
31
38
  defineEmits(['update:theme', 'update:menuOverlayHeader', 'update:dense', 'update:color', 'update:miniState', 'update:footer'])
32
39
 
33
40
  const props = defineProps({
@@ -54,7 +61,8 @@ const props = defineProps({
54
61
  footer: {
55
62
  type: Boolean,
56
63
  default: false
57
- }
64
+ },
65
+
58
66
  })
59
67
 
60
68
  </script>
@@ -132,6 +140,15 @@ const props = defineProps({
132
140
  <q-toggle :model-value="footer" @update:model-value="$emit('update:footer', $event)" />
133
141
  </q-item-section>
134
142
  </q-item>
143
+ <q-separator />
144
+ <q-item-label header>{{ $t('Menu width') }}</q-item-label>
145
+ <q-item>
146
+ <q-item-section>
147
+ <q-slider v-model="menuWidth" :min="200" :max="360" label :color="$light.color" />
148
+ </q-item-section>
149
+
150
+ </q-item>
151
+ <q-separator />
135
152
 
136
153
  </q-list>
137
154
  </template>
@@ -0,0 +1,21 @@
1
+ <script setup>
2
+ const props = defineProps(["value", "dense"])
3
+ </script>
4
+
5
+ <style scoped>
6
+ .menu-list .q-item{border-radius:12px 12px 12px 12px}.menu-list .q-router-link--exact-active{background:linear-gradient(118deg,var(--q-primary),rgba(115,103,240,.7));color:#fff}
7
+ </style>
8
+ <template>
9
+ <q-expansion-item :label="$t('My favorite')" :dense="dense" ref="expansion" icon="sym_o_favorite">
10
+ <q-list class="q-pl-md menu-list">
11
+ <q-item v-ripple :to="item.path" v-for="item in value">
12
+ <q-item-section avatar>
13
+ <q-icon :name="item.icon ?? 'sym_o_link'" />
14
+ </q-item-section>
15
+ <q-item-section>
16
+ <q-item-label v-text="$t(item.label)"></q-item-label>
17
+ </q-item-section>
18
+ </q-item>
19
+ </q-list>
20
+ </q-expansion-item>
21
+ </template>
@@ -167,7 +167,7 @@ const onUploadFile = async () => {
167
167
  </q-card-section>
168
168
 
169
169
  <q-card-actions align="right">
170
- <q-btn flat :label="$t('Cancel')" color="primary" v-close-popup></q-btn>
170
+ <q-btn flat :label="$t('Cancel')" color="primary" @click="uploadFile = null" v-close-popup></q-btn>
171
171
  <q-btn flat :label="$t('Upload')" color="primary" @click="onUploadFile"></q-btn>
172
172
  </q-card-actions>
173
173
  </l-card>
@@ -175,5 +175,9 @@ const onUploadFile = async () => {
175
175
  <template v-slot:prepend>
176
176
  <q-btn dense flat round icon="sym_o_file_upload" @click="show = true"></q-btn>
177
177
  </template>
178
+
179
+ <template v-for="(s, name) in $slots" v-slot:[name]="props" :key="name">
180
+ <slot :name="name" v-bind="props ?? {}"></slot>
181
+ </template>
178
182
  </q-input>
179
183
  </template>