@hostlink/nuxt-light 1.13.7 → 1.14.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.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "light",
3
3
  "configKey": "light",
4
- "version": "1.13.7",
4
+ "version": "1.14.1",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.3",
7
7
  "unbuild": "2.0.0"
@@ -1,18 +1,18 @@
1
1
  <script setup>
2
2
  import { useRoute, useRouter } from 'vue-router';
3
3
  import { useLight, q, m } from "#imports";
4
- import { useQuasar, Loading } from 'quasar';
4
+ import { useQuasar } from 'quasar';
5
5
  import { useI18n } from 'vue-i18n';
6
6
  import { ref, computed, reactive, provide, watch, toRaw } from 'vue';
7
7
  import { useRuntimeConfig } from 'nuxt/app';
8
8
 
9
- Loading.show()
9
+ const $q = useQuasar();
10
+ $q.loading.show()
10
11
 
11
12
  const config = useRuntimeConfig();
12
13
 
13
14
  const appVersion = config.public.appVersion ?? '0.0.1';
14
15
 
15
- const quasar = useQuasar();
16
16
  const tt = await q({
17
17
  system: {
18
18
  devMode: true,
@@ -74,7 +74,7 @@ const myFavoritesCount = computed(() => {
74
74
  return light.getMyFavorites().length;
75
75
  });
76
76
 
77
- quasar.dark.set(light.isDarkMode());
77
+ $q.dark.set(light.isDarkMode());
78
78
 
79
79
  const i18n = useI18n();
80
80
  i18n.locale = my.language || 'en';
@@ -172,7 +172,7 @@ watch(() => style.dense, async (value) => await light.setStyle("dense", value))
172
172
 
173
173
 
174
174
  watch(() => light.theme, async () => {
175
- quasar.dark.set(light.isDarkMode());
175
+ $q.dark.set(light.isDarkMode());
176
176
  })
177
177
 
178
178
  watch(() => style.miniState, async (value) => {
@@ -228,7 +228,7 @@ const onToggleFav = async () => {
228
228
  return;
229
229
  }
230
230
 
231
- quasar.dialog({
231
+ $q.dialog({
232
232
  title: 'Add to favorite',
233
233
  message: 'Enter favorite label',
234
234
  prompt: {
@@ -257,7 +257,7 @@ const isFav = computed(() => {
257
257
  return fav;
258
258
  })
259
259
 
260
- Loading.hide()
260
+ $q.loading.hide()
261
261
 
262
262
  const menuWidth = ref(my.styles.menuWidth || 280);
263
263
 
@@ -306,7 +306,7 @@ if (route.fullPath == "/" && my.default_page) {
306
306
  </q-tooltip>
307
307
  </q-btn>
308
308
 
309
- <q-btn v-if="languages.length > 1" round flat icon="language" class="q-mr-xs">
309
+ <q-btn v-if="languages.length > 1" round flat dense icon="language" class="q-mr-xs">
310
310
  <q-tooltip>
311
311
  {{ my.language }}
312
312
  </q-tooltip>
@@ -334,8 +334,11 @@ if (route.fullPath == "/" && my.default_page) {
334
334
  {{ my.first_name }} {{ my.last_name }}
335
335
  </div>
336
336
 
337
- <div class="text-right">
338
- {{ my.roles[0] }}
337
+ <div class="text-right ellipsis" style="max-width: 120px;">
338
+ {{ my.roles.join(", ") }}
339
+ <q-tooltip>
340
+ {{ my.roles.join(", ") }}
341
+ </q-tooltip>
339
342
  </div>
340
343
  </div>
341
344
 
@@ -430,7 +433,7 @@ if (route.fullPath == "/" && my.default_page) {
430
433
  </template>
431
434
  </router-view -->
432
435
  <NuxtLoadingIndicator />
433
-
436
+
434
437
  <NuxtPage />
435
438
 
436
439
 
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ import { useLight } from '#imports';
3
+ import type { QBarProps } from 'quasar';
4
+ import { computed } from 'vue';
5
+
6
+ export type LBarProps = QBarProps;
7
+
8
+ const light = useLight();
9
+ const barClass = computed(() => {
10
+ const c = ["text-white"];
11
+ const color = light.color;
12
+ c.push(`bg-${color}`);
13
+ return c;
14
+ });
15
+ </script>
16
+
17
+ <template>
18
+ <q-bar :class="barClass">
19
+ <slot></slot>
20
+ </q-bar>
21
+ </template>
@@ -4,7 +4,7 @@ import { computed } from "vue";
4
4
  import { useLight } from '#imports';
5
5
  import { useI18n } from "vue-i18n";
6
6
 
7
- export interface LBtnProps extends QBtnProps {
7
+ export interface LBtnProps extends QBtnProps {
8
8
  permission?: string;
9
9
  }
10
10
 
@@ -8,7 +8,7 @@ const emit = defineEmits(["close"]);
8
8
  const minimized = defineModel<boolean>("minimized", { default: false })
9
9
  const maximized = defineModel<boolean>("maximized", { default: false })
10
10
 
11
- export interface LCardProps extends QCardProps {
11
+ export interface LCardProps extends QCardProps {
12
12
  loading?: boolean;
13
13
  title?: string;
14
14
  /**
@@ -2,7 +2,7 @@
2
2
  import { computed } from "vue"
3
3
  import { type QCheckboxProps } from "quasar"
4
4
 
5
- export interface LCheckboxProps extends QCheckboxProps {
5
+ export interface LCheckboxProps extends QCheckboxProps {
6
6
  }
7
7
 
8
8
  const emit = defineEmits(["update:modelValue"]);
@@ -75,11 +75,24 @@ const props = defineProps({
75
75
  const time_diff = ref(new Date().getTime() - new Date(props.time).getTime());
76
76
  const server_time = ref(new Date(props.time).toLocaleTimeString());
77
77
  const server_date = ref(new Date(props.time).toLocaleDateString());
78
+ let start;
78
79
 
79
- setInterval(() => {
80
- server_time.value = new Date(new Date().getTime() - time_diff.value).toLocaleTimeString();
81
- server_date.value = new Date(new Date().getTime() - time_diff.value).toLocaleDateString();
82
- }, 500);
80
+ const refreshServerTime = (timestamp) => {
81
+ if (!start) start = timestamp;
82
+ let elapsed = timestamp - start;
83
+
84
+
85
+ if (elapsed > 1000) {
86
+ server_time.value = new Date(new Date().getTime() - time_diff.value).toLocaleTimeString();
87
+ server_date.value = new Date(new Date().getTime() - time_diff.value).toLocaleDateString();
88
+ start = timestamp;
89
+ }
90
+
91
+ //refresh every second
92
+ requestAnimationFrame(refreshServerTime);
93
+ }
94
+
95
+ requestAnimationFrame(refreshServerTime);
83
96
 
84
97
 
85
98
  </script>
@@ -132,7 +145,8 @@ setInterval(() => {
132
145
  <q-item-label>Menu dense mode</q-item-label>
133
146
  </q-item-section>
134
147
  <q-item-section side>
135
- <q-toggle :model-value="dense" @update:model-value="$emit('update:dense', $event)" :color="$light.color" />
148
+ <q-toggle :model-value="dense" @update:model-value="$emit('update:dense', $event)"
149
+ :color="$light.color" />
136
150
  </q-item-section>
137
151
  </q-item>
138
152
 
@@ -143,8 +157,8 @@ setInterval(() => {
143
157
  <q-item-label>Menu overlay header</q-item-label>
144
158
  </q-item-section>
145
159
  <q-item-section side>
146
- <q-toggle :model-value="menuOverlayHeader" @update:model-value="$emit('update:menuOverlayHeader', $event)"
147
- :color="$light.color" />
160
+ <q-toggle :model-value="menuOverlayHeader"
161
+ @update:model-value="$emit('update:menuOverlayHeader', $event)" :color="$light.color" />
148
162
  </q-item-section>
149
163
  </q-item>
150
164
 
@@ -0,0 +1,61 @@
1
+ <script setup>
2
+ import { useDialogPluginComponent } from 'quasar'
3
+
4
+ const props = defineProps({
5
+ // ...your custom props
6
+ })
7
+
8
+ defineEmits([
9
+ // REQUIRED; need to specify some events that your
10
+ // component will emit through useDialogPluginComponent()
11
+ ...useDialogPluginComponent.emits
12
+ ])
13
+
14
+ const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
15
+ // dialogRef - Vue ref to be applied to QDialog
16
+ // onDialogHide - Function to be used as handler for @hide on QDialog
17
+ // onDialogOK - Function to call to settle dialog with "ok" outcome
18
+ // example: onDialogOK() - no payload
19
+ // example: onDialogOK({ /*...*/ }) - with payload
20
+ // onDialogCancel - Function to call to settle dialog with "cancel" outcome
21
+
22
+ // this is part of our example (so not required)
23
+ function onOKClick(value) {
24
+ // on OK, it is REQUIRED to
25
+ // call onDialogOK (with optional payload)
26
+ onDialogOK(value)
27
+ // or with payload: onDialogOK({ ... })
28
+ // ...and it will also hide the dialog automatically
29
+ }
30
+
31
+ const types = ["varchar", "int", "date", "time", "datetime", "timestamp", "text", "blob", "json", "jsonb", "uuid", "boolean", "decimal", "float", "double", "real", "numeric", "smallint", "bigint", "tinyint", "mediumint", "char", "binary", "varbinary", "enum"]
32
+ </script>
33
+
34
+
35
+ <template>
36
+ <q-dialog ref="dialogRef" @hide="onDialogHide">
37
+ <q-card class="q-dialog-plugin">
38
+ <l-bar>
39
+ <q-space />
40
+ <q-btn dense flat icon="sym_o_close" @click="onDialogCancel" />
41
+ </l-bar>
42
+ <form-kit type="l-form" @submit="onOKClick" :value="{
43
+ name: '',
44
+ type: '',
45
+ length: '',
46
+ default: '',
47
+ nullable: false,
48
+ autoincrement: false
49
+ }">
50
+ <form-kit type="l-input" name="name" label="Name" validation="required" />
51
+ <form-kit type="l-select" name="type" label="Type" :options="types" validation="required" />
52
+ <form-kit type="l-input" name="length" label="Length" />
53
+ <form-kit type="l-input" name="default" label="Default" />
54
+ <form-kit type="l-checkbox" name="nullable" label="Nullable" />
55
+ <form-kit type="l-checkbox" name="autoincrement" label="Autoincrement" />
56
+ </form-kit>
57
+
58
+
59
+ </q-card>
60
+ </q-dialog>
61
+ </template>
@@ -27,7 +27,7 @@ const TextHightlightCMD = ((cmd, name) => {
27
27
  edit.focus()
28
28
  })
29
29
 
30
- export interface LEditorProps extends QEditorProps {
30
+ export interface LEditorProps extends QEditorProps {
31
31
  }
32
32
 
33
33
  const emit = defineEmits(["update:modelValue"]);
@@ -1,4 +1,4 @@
1
- <script setup>
1
+ <script setup lang="ts">
2
2
  import { ref } from "vue";
3
3
  import { useRouter, useRoute } from "vue-router";
4
4
  import { useQuasar } from "quasar";
@@ -28,12 +28,17 @@ const props = defineProps({
28
28
  submitIcon: {
29
29
  type: String,
30
30
  default: "sym_o_save"
31
+ },
32
+ noRedirect: {
33
+ type: Boolean,
34
+ default: false
31
35
  }
36
+
32
37
  });
33
38
 
34
39
  const loading = ref(false)
35
40
 
36
- const emit = defineEmits(["save", "submit"]);
41
+ const emit = defineEmits(["save", "submit", "submitted"]);
37
42
  const save = async () => {
38
43
 
39
44
  let valid = await form.value.validate();
@@ -50,12 +55,23 @@ const save = async () => {
50
55
  try {
51
56
  if (route.params[id_name]) {//edit
52
57
  if (await model(module).update(parseInt(route.params[id_name]), props.modelValue)) {
53
- router.push(`/${module}`);
58
+
59
+ if (!props.noRedirect) {
60
+ router.push(`/${module}`);
61
+ }
62
+
63
+ emit('submitted');
64
+
54
65
  return;
55
66
  }
56
67
  } else {
57
68
  if (await model(module).add(props.modelValue)) {
58
- router.push(`/${module}`);
69
+
70
+ if (!props.noRedirect) {
71
+ router.push(`/${module}`);
72
+ }
73
+
74
+ emit('submitted');
59
75
  return;
60
76
  }
61
77
  }
@@ -78,7 +94,7 @@ const onSubmit = (e) => {
78
94
  </script>
79
95
  <template>
80
96
  <q-form ref="form" @submit="onSubmit">
81
- <l-card :bordered="bordered" >
97
+ <l-card :bordered="bordered">
82
98
  <q-card-section>
83
99
  <div :class="`q-gutter-${gutter}`">
84
100
  <slot></slot>
@@ -5,7 +5,7 @@ import tc2sc from "../lib/tc2sc";
5
5
  import { useLight } from '#imports';
6
6
 
7
7
  import type { QInputProps } from "quasar";
8
- export interface LInputProps extends QInputProps {
8
+ export interface LInputProps extends QInputProps {
9
9
  showPassword?: boolean;
10
10
  translate?: boolean;
11
11
  required?: boolean;
@@ -1,7 +1,7 @@
1
1
  <script setup lang="ts">
2
2
  import type { QItemProps } from 'quasar';
3
3
 
4
- export interface LItemProps extends QItemProps {
4
+ export interface LItemProps extends QItemProps {
5
5
  label?: string;
6
6
  type?: 'text' | 'caption';
7
7
  name?: string;
@@ -2,7 +2,7 @@
2
2
 
3
3
  import type { QListProps } from 'quasar';
4
4
 
5
- export interface LListProps extends QListProps {
5
+ export interface LListProps extends QListProps {
6
6
  fields?: any[];
7
7
  modelValue?: any;
8
8
  }
@@ -1,12 +1,11 @@
1
1
  <script setup>
2
2
  import { useLight } from "#imports";
3
3
  import { ref, reactive, onMounted } from 'vue'
4
- import { useQuasar, Dialog } from 'quasar';
4
+ import { useQuasar } from 'quasar';
5
5
  import { useI18n } from 'vue-i18n';
6
6
  import { m, notify, api } from '#imports';
7
7
 
8
8
 
9
-
10
9
  const light = useLight();
11
10
 
12
11
  const props = defineProps({
@@ -20,7 +19,7 @@ const data = reactive({
20
19
  username: "", password: "", code: ""
21
20
  });
22
21
 
23
- const qua = useQuasar()
22
+ const $q = useQuasar()
24
23
 
25
24
  const submit = async () => {
26
25
  if (await form1.value.validate()) {
@@ -36,83 +35,70 @@ const submit = async () => {
36
35
  }
37
36
 
38
37
  const forgetPassword = async () => {
39
- Dialog.create({
38
+ $q.dialog({
40
39
  title: i18n.t("Forget password"),
41
40
  message: "Please enter your email address, we will send you a code to reset your password",
42
41
  prompt: {
43
- model: "",
44
42
  type: "email",
43
+ required: true,
44
+ isValid: (val) => {
45
+ //test val is email
46
+ const re = /\S+@\S+\.\S+/;
47
+ return re.test(val);
48
+ }
45
49
  },
46
50
  cancel: true
47
51
  }).onOk(async email => {
48
- if (email.length == 0) {
49
- return;
50
- }
51
-
52
+ $q.loading.show();
52
53
  try {
53
- await m("forgetPassword", { email: email });
54
- qua.dialog({
55
- title: "Enter your code",
56
- message: "Please enter the code sent to your email, your code will expire in 10 minutes",
57
- prompt: {
58
- model: "",
59
- type: "text"
60
- },
61
- cancel: true,
62
- persistent: true
63
- }).onOk(async code => {
64
- if (code.length == 0) {
65
- return;
66
- }
67
-
68
- if (await m("verifyCode", { code: code, email: email })) {
69
- qua.dialog({
70
- title: "Reset password",
71
- message: "Please enter your new password",
72
- prompt: {
73
- model: "",
74
- type: "password"
75
- },
76
- }).onOk(async password => {
77
- if (password.length == 0) {
78
- return;
79
- }
80
-
81
- if (await m("resetPassword", { password: password, email: email, code: code })) {
82
- qua.notify({
83
- message: "Your password has been reset successfully",
84
- color: "positive",
85
- icon: "sym_o_check",
86
- position: "top",
87
- timeout: 2000
88
- });
89
- }
90
- });
91
- } else {
92
- qua.notify({
93
- message: "Your code is invalid",
94
- color: "negative",
95
- icon: "sym_o_error",
96
- position: "top",
97
- timeout: 2000
98
- });
99
-
100
- }
101
-
102
-
103
- });
54
+ await api.auth.forgetPassword(email);
104
55
  } catch (e) {
105
- qua.notify({
56
+ $q.notify({
106
57
  message: e.message,
107
58
  color: "negative",
108
59
  icon: "sym_o_error",
109
- position: "top",
110
- timeout: 2000
111
60
  });
61
+ return;
62
+ } finally {
63
+ $q.loading.hide();
112
64
  }
113
- });
114
-
115
65
 
66
+ $q.dialog({
67
+ title: "Enter your code",
68
+ message: "Please enter the code sent to your email, your code will expire in 10 minutes",
69
+ prompt: {
70
+ type: "text",
71
+ required: true
72
+ },
73
+ cancel: true,
74
+ persistent: true
75
+ }).onOk(async code => {
76
+ if (await api.auth.verifyCode(email, code)) {
77
+ $q.dialog({
78
+ title: "Reset password",
79
+ message: "Please enter your new password",
80
+ required: true,
81
+ prompt: {
82
+ type: "password"
83
+ },
84
+ }).onOk(async password => {
85
+ if (await api.auth.resetPassword(email, password, code)) {
86
+ $q.notify({
87
+ message: "Your password has been reset successfully",
88
+ color: "positive",
89
+ icon: "sym_o_check",
90
+ });
91
+ }
92
+ });
93
+ } else {
94
+ $q.notify({
95
+ message: "Your code is invalid",
96
+ color: "negative",
97
+ icon: "sym_o_error",
98
+ });
99
+ }
100
+ });
101
+ });
116
102
  };
117
103
 
118
104
  const hasBioLogin = ref(false);
@@ -122,7 +108,7 @@ if (localStorage.getItem("username")) {
122
108
  }
123
109
  const bioLogin = async () => {
124
110
  try {
125
- await webauthnLogin(localStorage.getItem("username"));
111
+ await api.auth.WebAuthn.login(localStorage.getItem("username"));
126
112
  window.self.location.reload();
127
113
  } catch (e) {
128
114
  notify(e.message, "negative");
@@ -131,7 +117,7 @@ const bioLogin = async () => {
131
117
  }
132
118
  const handleGoogleCredentialResponse = async (response) => {
133
119
  try {
134
- await m("googleLogin", { credential: response.credential });
120
+ await api.auth.googleLogin(response.credential);
135
121
  window.self.location.reload();
136
122
  } catch (e) {
137
123
  notify(e.message, "negative");
@@ -140,15 +126,11 @@ const handleGoogleCredentialResponse = async (response) => {
140
126
 
141
127
  onMounted(() => {
142
128
  if (props.googleClientId) {
143
-
144
-
145
129
  if (!window.google) {
146
- qua.notify({
130
+ $q.notify({
147
131
  message: "Google login is not available", //set script https://accounts.google.com/gsi/client in nuuxt.config.js
148
132
  color: "negative",
149
133
  icon: "sym_o_error",
150
- position: "top",
151
- timeout: 2000
152
134
  });
153
135
  return;
154
136
 
@@ -9,17 +9,20 @@ const router = useRouter();
9
9
  const route = useRoute();
10
10
  const light = useLight();
11
11
 
12
- export interface LPageProps extends QPageProps {
12
+ export type LPageProps = QPageProps & {
13
13
  title?: string;
14
14
  backBtn?: boolean;
15
15
  editBtn?: boolean;
16
16
  deleteBtn?: boolean;
17
17
  addBtn?: boolean;
18
+ gutter?: string;
19
+ padding?: boolean
18
20
  }
19
21
 
20
22
  const props = withDefaults(defineProps<LPageProps>(), {
21
23
  title: "",
22
24
  backBtn: true,
25
+ padding: true,
23
26
  });
24
27
 
25
28
  const showToolbar = computed(() => {
@@ -49,10 +52,21 @@ const onDelete = async () => {
49
52
  useHead({
50
53
  title: light.getCompany() + " - " + title,
51
54
  })
55
+
56
+ const localClass = computed(() => {
57
+ return {
58
+ "q-gutter-none": props.gutter === "none",
59
+ "q-gutter-xs": props.gutter === "xs",
60
+ "q-gutter-sm": props.gutter === "sm",
61
+ "q-gutter-md": props.gutter === "md",
62
+ "q-gutter-lg": props.gutter === "lg",
63
+ "q-gutter-xl": props.gutter === "xl",
64
+ }
65
+ })
52
66
  </script>
53
67
 
54
68
  <template>
55
- <q-page padding>
69
+ <q-page :padding="props.padding" :class="localClass">
56
70
  <!-- q-breadcrumbs>
57
71
  <q-breadcrumbs-el icon="home" to="/" />
58
72
  <q-breadcrumbs-el label="Permission" to="/Permission" />
@@ -8,7 +8,7 @@ const emits = defineEmits(["update:modelValue"]);
8
8
 
9
9
  const { t } = useI18n();
10
10
  const light = useLight();
11
- interface LSelectProps extends QSelectProps {
11
+ interface LSelectProps extends QSelectProps {
12
12
  required?: boolean,
13
13
  }
14
14
 
@@ -261,6 +261,7 @@ const onRequest = async (p: any) => {
261
261
  props.columns?.forEach((col) => {
262
262
  if (col.gqlField) {
263
263
  builder.add(col.gqlField);
264
+ return;
264
265
  }
265
266
 
266
267
  if (!col.name) return;
@@ -428,9 +429,10 @@ const onDelete = async (id: any) => {
428
429
  table.value.requestServerInteraction();
429
430
  }
430
431
  }
431
-
432
432
  const attrs = computed(() => {
433
433
  return {
434
+ ...props,
435
+
434
436
  ...{
435
437
  dense: light.getStyle("tableDense"),
436
438
  flat: light.getStyle("tableFlat"),
@@ -2,7 +2,7 @@
2
2
  import type { QTabsProps } from 'quasar';
3
3
  import { onMounted, useSlots } from '#imports';
4
4
  const model = defineModel<string | number | null | undefined>()
5
- export interface LTabsProps extends QTabsProps {
5
+ export interface LTabsProps extends QTabsProps {
6
6
  }
7
7
  defineProps<LTabsProps>();
8
8
  const slots = useSlots()
@@ -23,10 +23,16 @@ let bordered = true;
23
23
  if (props.context.bordered !== undefined) {
24
24
  bordered = props.context.bordered;
25
25
  }
26
- const loading = ref(false);
26
+
27
+ let redirect = true;
28
+ if (props.context.attrs.onSubmitted) {
29
+ redirect = false;
30
+ }
31
+
32
+
27
33
  if (!props.context.onSubmit) {
28
34
  props.context.node.props.onSubmit = async function () {
29
- loading.value = true;
35
+
30
36
  const [module, id_name] = route.name.split("-");
31
37
 
32
38
 
@@ -46,8 +52,9 @@ if (!props.context.onSubmit) {
46
52
  color: "positive",
47
53
  icon: "sym_o_check"
48
54
  });
49
- router.go(-1);
50
- return;
55
+ if (redirect) {
56
+ router.go(-1);
57
+ }
51
58
  }
52
59
  } else {
53
60
  if (await model(module).add(v)) {
@@ -56,10 +63,16 @@ if (!props.context.onSubmit) {
56
63
  color: "positive",
57
64
  icon: "sym_o_check"
58
65
  });
59
- router.go(-1);
60
- return;
66
+
67
+ if (redirect) {
68
+ router.go(-1);
69
+ }
61
70
  }
62
71
  }
72
+ //emit submitted event
73
+ if (props.context.attrs.onSubmitted) {
74
+ props.context.attrs.onSubmitted(v);
75
+ }
63
76
  } catch (e) {
64
77
  quasar.dialog({
65
78
  title: "Error",
@@ -67,7 +80,7 @@ if (!props.context.onSubmit) {
67
80
  ok: "OK"
68
81
  });
69
82
  }
70
- loading.value = false;
83
+
71
84
  }
72
85
 
73
86
  }
@@ -84,8 +97,8 @@ if (!props.context.onSubmit) {
84
97
  </q-card-section>
85
98
 
86
99
  <q-card-actions align="right">
87
- <l-btn icon="sym_o_check" label="Submit" @click="onSubmit" :disabled="!context.state.dirty"
88
- :loading="loading"></l-btn>
100
+ <l-btn icon="sym_o_check" :label="context.submitLabel ?? 'Submit'" @click="onSubmit"
101
+ :disabled="!context.state.dirty" :loading="context.state.loading"></l-btn>
89
102
  </q-card-actions>
90
103
  </l-card>
91
104
  </form>
@@ -1,24 +1,143 @@
1
1
  <script setup>
2
- import { api } from '#imports';
2
+ import { api, m } from '#imports';
3
+ import { useQuasar } from 'quasar';
4
+ import { resolveComponent, ref, reactive } from 'vue';
5
+
6
+ const $q = useQuasar();
3
7
 
4
8
  const { system: { database } } = await api.query({
5
9
  system: {
6
10
  database: {
7
- table: true
11
+ table: true,
12
+ version: true
8
13
  }
9
14
  }
10
15
  })
11
16
 
17
+ const tables = ref(database.table);
18
+
19
+ const refresh = async () => {
20
+ const { system: { database } } = await api.query({
21
+ system: {
22
+ database: {
23
+ table: true,
24
+ version: true
25
+ }
26
+ }
27
+ })
28
+ tables.value = database.table;
29
+ }
30
+
31
+
32
+ const field_add = resolveComponent('l-dialog-database-field-add');
33
+ const add = async (table) => {
34
+ $q.dialog({
35
+ component: field_add
36
+ }).onOk(async (data) => {
37
+ //data.name
38
+ //data.type
39
+ try {
40
+ const addDatabaseField = await m("addDatabaseField", {
41
+ table,
42
+ field: data.name,
43
+ type: data.type,
44
+ length: data.length,
45
+ default: data.default,
46
+ nullable: data.nullable,
47
+ autoincrement: data.autoincrement
48
+ })
49
+ $q.notify({
50
+ type: 'positive',
51
+ message: addDatabaseField
52
+ })
53
+
54
+ refresh();
55
+
56
+ } catch (e) {
57
+ $q.notify({
58
+ type: 'negative',
59
+ message: e.message
60
+ })
61
+ }
62
+ })
63
+
64
+ }
65
+
66
+ const selected = reactive({});
67
+
68
+ for (let table of database.table) {
69
+ selected[table.name] = [];
70
+ }
71
+
72
+
73
+ const removeField = async (table) => {
74
+ //confirmation
75
+
76
+ $q.dialog({
77
+ title: 'Remove field',
78
+ message: 'Are you sure you want to remove the selected fields?',
79
+ ok: 'Yes',
80
+ cancel: 'No'
81
+ }).onOk(async () => {
12
82
 
83
+
84
+ //map selected fields
85
+
86
+ const fields = selected[table].map(field => field.Field)
87
+
88
+ try {
89
+ const removeDatabaseFields = await m("removeDatabaseFields", {
90
+ table,
91
+ fields: fields
92
+ })
93
+ $q.notify({
94
+ type: 'positive',
95
+ message: removeDatabaseFields
96
+ })
97
+
98
+ selected[table] = [];
99
+
100
+ refresh();
101
+ } catch (e) {
102
+ $q.notify({
103
+ type: 'negative',
104
+ message: e.message
105
+ })
106
+ }
107
+ })
108
+
109
+
110
+
111
+
112
+
113
+
114
+ }
13
115
  </script>
14
116
  <template>
15
- <l-page>
117
+ <l-page gutter="sm">
118
+ <l-card>
119
+ <l-list>
120
+ <l-item label="Version">{{ database.version }}</l-item>
121
+ </l-list>
122
+ </l-card>
123
+
16
124
  <q-card flat bordered>
17
125
  <q-list class="rounded-borders" separator bordered>
18
- <q-expansion-item :label="table.name" v-for="table in database.table" dense>
126
+ <q-expansion-item :label="table.name" v-for="table in tables" dense>
127
+ <q-toolbar>
128
+ <q-btn icon="sym_o_add" @click="add(table.name)" round flat size="sm">
129
+ <q-tooltip>Add field</q-tooltip>
130
+ </q-btn>
131
+ <q-btn icon="sym_o_delete" @click="removeField(table.name)" round flat size="sm"
132
+ :disable="selected[table.name].length == 0">
133
+ <q-tooltip>Remove field</q-tooltip>
134
+ </q-btn>
135
+ </q-toolbar>
19
136
  <div class="q-ma-sm">
20
- <q-table separator="cell" dense :rows="table.columns" :rows-per-page-options="[0]"
21
- hide-pagination flat bordered></q-table>
137
+ <q-table row-key="Field" separator="cell" dense :rows="table.columns"
138
+ :rows-per-page-options="[0]" hide-pagination flat bordered selection="multiple"
139
+ v-model:selected="selected[table.name]">
140
+ </q-table>
22
141
  </div>
23
142
 
24
143
  </q-expansion-item>
@@ -2,9 +2,10 @@
2
2
  import { list, m } from '#imports'
3
3
  import { useRouter } from "vue-router"
4
4
  import { ref, computed } from "vue"
5
- import { Dialog } from 'quasar'
5
+ import { useQuasar } from 'quasar'
6
+ const $q = useQuasar()
6
7
 
7
- let { data: users } = await list("User", null, ["user_id", "username", "name", "roles"]);
8
+ let { data: users } = await list("User", null, ["user_id", "username", "name", "roles", "email"]);
8
9
 
9
10
  let columns = [
10
11
  {
@@ -19,6 +20,12 @@ let columns = [
19
20
  field: "name",
20
21
  align: "left",
21
22
  }, {
23
+ name: "email",
24
+ label: "Email",
25
+ align: "left",
26
+ field: "email"
27
+ },
28
+ {
22
29
  name: "roles",
23
30
  label: "Roles",
24
31
  align: "left",
@@ -30,17 +37,14 @@ let columns = [
30
37
  }
31
38
  ]
32
39
 
33
- const router = useRouter();
34
40
  const onCickView = async (id) => {
35
41
  try {
36
42
  if (await m("viewAs", { user_id: id })) {
37
-
38
43
  //redirect to last path
39
44
  window.location.reload();
40
-
41
45
  }
42
46
  } catch (e) {
43
- Dialog.create({
47
+ $q.dialog({
44
48
  title: "Error",
45
49
  message: e.message,
46
50
  color: "negative",
@@ -62,11 +66,11 @@ const filtered = computed(() => {
62
66
  </script>
63
67
  <template>
64
68
  <l-page class="q-gutter-md">
65
- <q-banner >
69
+ <q-banner>
66
70
  Use this page to view the system as another user. This is useful for testing permissions.
67
71
  </q-banner>
68
72
 
69
- <q-table flat :columns="columns" :rows="filtered" :rows-per-page-options="[0]" dense>
73
+ <q-table flat :columns="columns" :rows="filtered" :rows-per-page-options="[0]" dense bordered="">
70
74
  <template v-slot:top-right>
71
75
  <q-input outlined dense debounce="300" v-model="filter" placeholder="Search" clearable>
72
76
  <template v-slot:append>
@@ -1,5 +1,5 @@
1
1
  <script setup>
2
- import { reactive } from 'vue'
2
+ import { reactive, ref } from 'vue'
3
3
  import { getObject, q } from '#imports'
4
4
  const obj = reactive(await getObject(["username", "first_name", "last_name", "email", "phone",
5
5
  "addr1", "addr2", "addr3", "join_date", "expiry_date", "status", "language", "default_page"
@@ -20,7 +20,6 @@ const languages = tt.app.languages.map((lang) => {
20
20
  value: lang.value,
21
21
  };
22
22
  })
23
-
24
23
  </script>
25
24
 
26
25
  <template>
@@ -45,7 +44,8 @@ const languages = tt.app.languages.map((lang) => {
45
44
  <FormKit type="l-date-picker" label="Join date" name="join_date" validation="required" />
46
45
  <FormKit type="l-date-picker" label="Expiry date" name="expiry_date" />
47
46
  <FormKit type="l-select" label="Status" name="status" :options="options" validation="required" />
48
- <FormKit type="l-select" label="Language" name="language" :options="languages" validation="required" />
47
+ <FormKit type="l-select" label="Language" name="language" :options="languages"
48
+ validation="required" />
49
49
  <FormKit type="l-input" label="Default page" name="default_page" />
50
50
  </l-col>
51
51
  </l-row>
@@ -16,6 +16,8 @@ const columns = model("User").columns({
16
16
  has2FA: true,
17
17
  })
18
18
 
19
+ console.log(columns);
20
+
19
21
  const status = ref("0");
20
22
 
21
23
  </script>
@@ -82,22 +82,14 @@ eventLogCols.forEach(col => {
82
82
  </l-col>
83
83
 
84
84
  <l-col md="8">
85
-
86
85
  <l-tabs>
87
- <l-tab label="User Log">
88
- <l-table :rows="my.userLog.data" :columns="userlogColumns" searchable
89
- :rows-per-page-options="[0]">
90
- </l-table>
86
+ <l-tab label="User Log" name="user_log">
87
+ <l-table :rows="my.userLog.data" searchable :columns="userlogColumns" hide-pagination />
91
88
  </l-tab>
92
- <l-tab label="Event Log">
93
- <l-table :rows="my.eventLog.data" :columns="eventLogCols" searchable
94
- :rows-per-page-options="[0]">
95
- </l-table>
89
+ <l-tab label="Event Log" name="event_log">
90
+ <l-table :rows="my.eventLog.data" :columns="eventLogCols" searchable hide-pagination />
96
91
  </l-tab>
97
-
98
92
  </l-tabs>
99
-
100
-
101
93
  </l-col>
102
94
  </l-row>
103
95
 
@@ -1,7 +1,7 @@
1
1
  <script setup>
2
2
  import { reactive, onMounted, nextTick } from "vue"
3
3
  import { useQuasar } from "quasar";
4
- import { q, m } from '#imports'
4
+ import { q, m, api } from '#imports'
5
5
  const quasar = useQuasar();
6
6
  let { app, my } = await q({ app: ['googleClientId'], my: ["gmail"] })
7
7
 
@@ -79,16 +79,12 @@ const unlink = async () => {
79
79
 
80
80
  <template v-if="app.googleClientId">
81
81
  <q-card-section v-if="my.gmail">
82
- You have already linked your Google account.<br />
83
-
84
- Your gmail is {{ my.gmail }}
85
-
86
-
87
-
88
- <div>
89
- <l-btn label="Unlink" @click="unlink" icon="sym_o_delete"></l-btn>
90
- </div>
82
+ <p>
83
+ You have already linked your Google account.<br />
91
84
 
85
+ Your gmail is {{ my.gmail }}
86
+ </p>
87
+ <l-btn label="Unlink" @click="unlink" icon="sym_o_delete"></l-btn>
92
88
  </q-card-section>
93
89
 
94
90
  <q-card-section v-else>
@@ -24,7 +24,7 @@ const route = useRoute()
24
24
  exact />
25
25
  <q-route-tab name="openid" icon="sym_o_key" :label="$t('Open ID')" to="/User/setting/open_id"
26
26
  exact />
27
- <q-route-tab name="myfav" icon="sym_o_star" :label="$t('My favorite')"
27
+ <q-route-tab name="myfav" icon="sym_o_favorite" :label="$t('My favorite')"
28
28
  to="/User/setting/my_favorite" exact />
29
29
 
30
30
  </q-tabs>
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "@hostlink/nuxt-light",
3
- "version": "1.13.7",
4
- "description": "HostLink Nuxt Light Framework",
5
- "repository": {
6
- "type": "git",
7
- "url": "@hostlink/nuxt-light"
8
- },
9
- "license": "MIT",
10
- "type": "module",
11
- "exports": {
12
- ".": {
13
- "types": "./dist/types.d.ts",
14
- "import": "./dist/module.mjs",
15
- "require": "./dist/module.cjs"
16
- }
17
- },
18
- "main": "./dist/module.cjs",
19
- "types": "./dist/types.d.ts",
20
- "files": [
21
- "dist"
22
- ],
23
- "scripts": {
24
- "prepack": "nuxt-module-build build",
25
- "dev": "nuxi dev playground --host",
26
- "dev:build": "nuxi build playground",
27
- "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
28
- "release:org": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
29
- "release": "npm run prepack && changelogen --release && npm publish --access=public && git push --follow-tags",
30
- "lint": "eslint .",
31
- "test": "vitest run",
32
- "test:watch": "vitest watch"
33
- },
34
- "dependencies": {
35
- "@formkit/drag-and-drop": "^0.1.6",
36
- "@hostlink/light": "^2.0.1",
37
- "@nuxt/kit": "^3.7.4",
38
- "@nuxt/module-builder": "^0.8.3",
39
- "@quasar/extras": "^1.16.11",
40
- "@quasar/vite-plugin": "^1.7.0",
41
- "axios": "^1.5.0",
42
- "diff2html": "^3.4.47",
43
- "formkit-quasar": "^0.0.15",
44
- "json-to-graphql-query": "^2.2.5",
45
- "quasar": "^2.15.4",
46
- "vue-i18n": "^9.2.2",
47
- "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
48
- },
49
- "devDependencies": {
50
- "@nuxt/devtools": "latest",
51
- "@nuxt/eslint-config": "^0.2.0",
52
- "@nuxt/schema": "^3.7.4",
53
- "@nuxt/test-utils": "^3.7.0",
54
- "@types/node": "^22.5.0",
55
- "changelogen": "^0.5.4",
56
- "eslint": "^8.46.0",
57
- "nuxt": "^3.7.0"
58
- }
59
- }
1
+ {
2
+ "name": "@hostlink/nuxt-light",
3
+ "version": "1.14.1",
4
+ "description": "HostLink Nuxt Light Framework",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "@hostlink/nuxt-light"
8
+ },
9
+ "license": "MIT",
10
+ "type": "module",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/types.d.ts",
14
+ "import": "./dist/module.mjs",
15
+ "require": "./dist/module.cjs"
16
+ }
17
+ },
18
+ "main": "./dist/module.cjs",
19
+ "types": "./dist/types.d.ts",
20
+ "files": [
21
+ "dist"
22
+ ],
23
+ "scripts": {
24
+ "prepack": "nuxt-module-build build",
25
+ "dev": "nuxi dev playground --host",
26
+ "dev:build": "nuxi build playground",
27
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
28
+ "release:org": "npm run lint && npm run test && npm run prepack && npm publish && git push --follow-tags",
29
+ "release": "npm run prepack && npm publish --access=public && git push --follow-tags",
30
+ "lint": "eslint .",
31
+ "test": "vitest run",
32
+ "test:watch": "vitest watch"
33
+ },
34
+ "dependencies": {
35
+ "@formkit/drag-and-drop": "^0.1.6",
36
+ "@hostlink/light": "^2.0.1",
37
+ "@nuxt/kit": "^3.7.4",
38
+ "@nuxt/module-builder": "^0.8.3",
39
+ "@quasar/extras": "^1.16.11",
40
+ "@quasar/vite-plugin": "^1.7.0",
41
+ "axios": "^1.5.0",
42
+ "diff2html": "^3.4.47",
43
+ "formkit-quasar": "^0.0.15",
44
+ "json-to-graphql-query": "^2.2.5",
45
+ "quasar": "^2.15.4",
46
+ "vue-i18n": "^9.2.2",
47
+ "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.2/xlsx-0.20.2.tgz"
48
+ },
49
+ "devDependencies": {
50
+ "@nuxt/devtools": "latest",
51
+ "@nuxt/eslint-config": "^0.2.0",
52
+ "@nuxt/schema": "^3.7.4",
53
+ "@nuxt/test-utils": "^3.7.0",
54
+ "@types/node": "^22.5.0",
55
+ "changelogen": "^0.5.4",
56
+ "eslint": "^8.46.0",
57
+ "nuxt": "^3.7.0"
58
+ }
59
+ }