@ramathibodi/nuxt-commons 0.1.9 → 0.1.10

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
@@ -4,5 +4,5 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0"
6
6
  },
7
- "version": "0.1.9"
7
+ "version": "0.1.10"
8
8
  }
package/dist/module.mjs CHANGED
@@ -33,6 +33,14 @@ const module = defineNuxtModule({
33
33
  src: resolver.resolve("runtime/types/menu.d.ts"),
34
34
  filename: "types/menu.d.ts"
35
35
  });
36
+ addTypeTemplate({
37
+ src: resolver.resolve("runtime/types/graphqlOperation.d.ts"),
38
+ filename: "types/graphqlOperation.d.ts"
39
+ });
40
+ addTypeTemplate({
41
+ src: resolver.resolve("runtime/types/formDialog.d.ts"),
42
+ filename: "types/formDialog.d.ts"
43
+ });
36
44
  }
37
45
  });
38
46
 
@@ -1,7 +1,7 @@
1
1
  <script lang="ts" setup>
2
2
  import * as XLSX from 'xlsx'
3
- import { ref } from 'vue'
4
- import { useAlert } from '../composables/alert'
3
+ import {ref} from 'vue'
4
+ import {useAlert} from '../composables/alert'
5
5
 
6
6
  const alert = useAlert()
7
7
  const emit = defineEmits<{
@@ -12,10 +12,7 @@ const loading = ref(false)
12
12
  const fileBtnRef = ref()
13
13
 
14
14
  function uploadedFile(files: File[] | File | undefined) {
15
- if (!files) {
16
- alert?.addAlert({ message: 'Please upload a file again', alertType: 'error' })
17
- return
18
- }
15
+ if (!files) return
19
16
 
20
17
  if (Array.isArray(files) && files.length != 1) {
21
18
  alert?.addAlert({ message: 'Please select a single file for import', alertType: 'error' })
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
- import { Codemirror } from 'vue-codemirror'
3
- import { oneDark } from '@codemirror/theme-one-dark'
4
- import { javascript } from '@codemirror/lang-javascript'
5
- import { html } from '@codemirror/lang-html'
6
- import { vue } from '@codemirror/lang-vue'
2
+ import {Codemirror} from 'vue-codemirror'
3
+ import {oneDark} from '@codemirror/theme-one-dark'
4
+ import {javascript} from '@codemirror/lang-javascript'
5
+ import {html} from '@codemirror/lang-html'
6
+ import {vue as vueLang} from '@codemirror/lang-vue'
7
7
 
8
8
  interface Props {
9
9
  height?: string | number
@@ -19,7 +19,7 @@ const props = withDefaults(defineProps<Props>(), {
19
19
 
20
20
  const extensions = [oneDark]
21
21
  if (props.lang == 'vue') {
22
- extensions.push(vue({ base: html({ autoCloseTags: true, matchClosingTags: true, selfClosingTags: true }) }))
22
+ extensions.push(vueLang({ base: html({ autoCloseTags: true, matchClosingTags: true, selfClosingTags: true }) }))
23
23
  }
24
24
  else {
25
25
  extensions.push(javascript({ typescript: true }))
@@ -1,11 +1,7 @@
1
1
  <script lang="ts" setup>
2
- import { ref, defineModel, computed, watch, watchEffect } from 'vue'
3
- import { cloneDeep, isEqual } from 'lodash-es'
4
-
5
- export interface FormDialogCallback {
6
- done: Function
7
- error: Function
8
- }
2
+ import {computed, defineModel, ref, watch, watchEffect} from 'vue'
3
+ import {cloneDeep, isEqual} from 'lodash-es'
4
+ import type {FormDialogCallback} from '../../types/formDialog'
9
5
 
10
6
  interface Props {
11
7
  maxWidth?: number | string
@@ -1,8 +1,9 @@
1
1
  <script lang="ts" setup>
2
- import { VDataTable } from 'vuetify/components/VDataTable'
3
- import { ref, watch, nextTick, defineOptions,computed,useAttrs } from 'vue'
4
- import type { FormDialogCallback } from './Dialog.vue'
5
- import { omit } from 'lodash-es'
2
+ import {VDataTable} from 'vuetify/components/VDataTable'
3
+ import {computed, defineOptions, nextTick, ref, useAttrs, watch} from 'vue'
4
+ import type {FormDialogCallback} from '../../types/formDialog'
5
+ import {omit} from 'lodash-es'
6
+
6
7
  defineOptions({
7
8
  inheritAttrs: false,
8
9
  })
@@ -0,0 +1,217 @@
1
+ <script lang="ts" setup>
2
+ import {computed, nextTick, ref, useAttrs} from 'vue'
3
+ import type {GraphqlModelProps, HeaderProps} from '../../composables/graphqlModel'
4
+ import {useGraphqlModel} from '../../composables/graphqlModel'
5
+ import {VDataTable} from "vuetify/components/VDataTable";
6
+ import {omit} from "lodash-es";
7
+
8
+ defineOptions({
9
+ inheritAttrs: false,
10
+ })
11
+
12
+ interface Props extends /* @vue-ignore */ InstanceType<typeof VDataTable['$props']> {
13
+ title: string
14
+ noDataText?: string
15
+ dialogFullscreen?: boolean
16
+ initialData?: Record<string, any>
17
+ toolbarColor?: string
18
+ importable?: boolean
19
+ exportable?: boolean
20
+ }
21
+
22
+ const props = withDefaults(defineProps<Props & GraphqlModelProps & HeaderProps>(), {
23
+ noDataText: 'ไม่พบข้อมูล',
24
+ dialogFullscreen: false,
25
+ toolbarColor: 'primary',
26
+ importable: true,
27
+ exportable: true,
28
+ modelKey: 'id',
29
+ modelBy: undefined,
30
+ fields: ()=>[]
31
+ })
32
+
33
+ const attrs = useAttrs()
34
+ const plainAttrs = computed(() => {
35
+ let returnAttrs = omit(attrs, ['modelValue', 'onUpdate:modelValue'])
36
+ if (props.headers) returnAttrs['headers'] = props.headers
37
+ return returnAttrs
38
+ })
39
+ const currentItem = ref<Record<string, any> | undefined>(undefined)
40
+ const isDialogOpen = ref<boolean>(false)
41
+
42
+ const {items,itemsLength,
43
+ search,
44
+ canServerPageable,canServerSearch,canCreate,canUpdate,canDelete,
45
+ createItem,importItems,updateItem,deleteItem,
46
+ loadItems,reload,
47
+ isLoading} = useGraphqlModel(props)
48
+
49
+ function openDialog(item?: object) {
50
+ currentItem.value = item
51
+ nextTick(() => {
52
+ isDialogOpen.value = true
53
+ })
54
+ }
55
+
56
+ defineExpose({ reload })
57
+ </script>
58
+ <template>
59
+ <v-card>
60
+ <VToolbar :color="toolbarColor">
61
+ <v-row
62
+ justify="end"
63
+ class="ma-1"
64
+ dense
65
+ no-gutters
66
+ align="center"
67
+ >
68
+ <v-col cols="7">
69
+ <VToolbarTitle class="pl-3">
70
+ <slot name="title">
71
+ {{ title }}
72
+ </slot>
73
+ </VToolbarTitle>
74
+ </v-col>
75
+ <v-col cols="5">
76
+ <slot name="search">
77
+ <VTextField
78
+ v-model="search"
79
+ class="justify-end w-100"
80
+ density="compact"
81
+ hide-details
82
+ placeholder="ค้นหา"
83
+ clearable
84
+ variant="solo"
85
+ />
86
+ </slot>
87
+ </v-col>
88
+ </v-row>
89
+
90
+ <VToolbarItems>
91
+ <slot name="toolbarItems" />
92
+ <ImportCSV
93
+ v-if="props.importable"
94
+ icon="mdi mdi-file-upload"
95
+ variant="flat"
96
+ @import="importItems"
97
+ />
98
+ <ExportCSV
99
+ v-if="props.exportable && items.length"
100
+ icon="mdi mdi-file-download"
101
+ variant="flat"
102
+ :file-name="title"
103
+ :model-value="items"
104
+ />
105
+ <VBtn
106
+ :color="toolbarColor"
107
+ prepend-icon="mdi mdi-plus"
108
+ variant="flat"
109
+ @click="openDialog()"
110
+ v-if="canCreate"
111
+ >
112
+ add
113
+ </VBtn>
114
+ </VToolbarItems>
115
+ </VToolbar>
116
+ <v-data-table-server
117
+ v-if="canServerPageable"
118
+ v-bind="plainAttrs"
119
+ color="primary"
120
+ :items="items"
121
+ :items-length="itemsLength"
122
+ :item-value="props.modelKey"
123
+ :search="search"
124
+ :loading="isLoading"
125
+ @update:options="loadItems"
126
+ >
127
+ <!-- @ts-ignore -->
128
+ <template
129
+ v-for="(_, name, index) in ($slots as {})"
130
+ :key="index"
131
+ #[name]="slotData"
132
+ >
133
+ <slot
134
+ :name="name"
135
+ v-bind="(slotData as object)"
136
+ :operation="operation"
137
+ />
138
+ </template>
139
+ <template
140
+ v-if="!$slots['item.action']"
141
+ #item.action="{ item }"
142
+ >
143
+ <v-btn
144
+ variant="flat"
145
+ density="compact"
146
+ icon="mdi mdi-note-edit"
147
+ @click="openDialog(item)"
148
+ v-if="canUpdate"
149
+ />
150
+ <v-btn
151
+ variant="flat"
152
+ density="compact"
153
+ icon="mdi mdi-delete"
154
+ @click="deleteItem(item)"
155
+ v-if="canDelete"
156
+ />
157
+ </template>
158
+ </v-data-table-server>
159
+ <v-data-table
160
+ v-else
161
+ v-bind="plainAttrs"
162
+ color="primary"
163
+ :items="items"
164
+ :item-value="props.modelKey"
165
+ :search="search"
166
+ :loading="isLoading"
167
+ >
168
+ <!-- @ts-ignore -->
169
+ <template
170
+ v-for="(_, name, index) in ($slots as {})"
171
+ :key="index"
172
+ #[name]="slotData"
173
+ >
174
+ <slot
175
+ :name="name"
176
+ v-bind="(slotData as object)"
177
+ :operation="operation"
178
+ />
179
+ </template>
180
+ <template
181
+ v-if="!$slots['item.action']"
182
+ #item.action="{ item }"
183
+ >
184
+ <v-btn
185
+ variant="flat"
186
+ density="compact"
187
+ icon="mdi mdi-note-edit"
188
+ @click="openDialog(item)"
189
+ v-if="canUpdate"
190
+ />
191
+ <v-btn
192
+ variant="flat"
193
+ density="compact"
194
+ icon="mdi mdi-delete"
195
+ @click="deleteItem(item)"
196
+ v-if="canDelete"
197
+ />
198
+ </template>
199
+ </v-data-table>
200
+ <FormDialog
201
+ v-model="isDialogOpen"
202
+ :title="title"
203
+ :fullscreen="dialogFullscreen"
204
+ :initial-data="initialData"
205
+ :form-data="currentItem"
206
+ @create="createItem"
207
+ @update="updateItem"
208
+ >
209
+ <template #default="slotData">
210
+ <slot
211
+ name="form"
212
+ v-bind="slotData"
213
+ />
214
+ </template>
215
+ </FormDialog>
216
+ </v-card>
217
+ </template>
@@ -1,12 +1,12 @@
1
1
  import { trimEnd, trimStart } from "lodash-es";
2
- import { useRuntimeConfig, useFetch } from "#imports";
2
+ import { useFetch, useRuntimeConfig } from "#imports";
3
3
  export function useApi() {
4
4
  const config = useRuntimeConfig();
5
5
  function urlBuilder(url) {
6
6
  let returnUrl = "";
7
7
  if (Array.isArray(url)) {
8
8
  if (url[0].toLowerCase() == "agent")
9
- url[0] = config?.public.WS_AGENT;
9
+ url[0] = config?.public.WS_AGENT || config?.public.WS_DEVICE;
10
10
  returnUrl = url.join("/");
11
11
  } else {
12
12
  returnUrl = url;
@@ -0,0 +1,44 @@
1
+ import type { FormDialogCallback } from "../types/formDialog";
2
+ import type { graphqlOperationObject } from "../types/graphqlOperation";
3
+ export interface HeaderProps {
4
+ headers?: any[];
5
+ }
6
+ export interface GraphqlModelProps {
7
+ modelName: string;
8
+ modelKey?: string;
9
+ modelBy?: object;
10
+ operationCreate?: graphqlOperationObject<any, any> | string;
11
+ operationUpdate?: graphqlOperationObject<any, any> | string;
12
+ operationDelete?: graphqlOperationObject<any, any> | string;
13
+ operationRead?: graphqlOperationObject<any, any> | string;
14
+ operationReadPageable?: graphqlOperationObject<any, any> | string;
15
+ operationSearch?: graphqlOperationObject<any, any> | string;
16
+ fields?: Array<string | object>;
17
+ }
18
+ type GraphqlModelPropsWithOptionalHeaders = GraphqlModelProps & Partial<HeaderProps>;
19
+ export declare function useGraphqlModel<T extends GraphqlModelPropsWithOptionalHeaders>(props: T): {
20
+ items: import("vue").Ref<Record<string, any>[]>;
21
+ itemsLength: import("vue").Ref<number>;
22
+ search: import("vue").Ref<string | undefined>;
23
+ currentOptions: import("vue").Ref<any>;
24
+ operationCreate: import("vue").ComputedRef<any>;
25
+ operationUpdate: import("vue").ComputedRef<any>;
26
+ operationDelete: import("vue").ComputedRef<any>;
27
+ operationRead: import("vue").ComputedRef<any>;
28
+ operationReadPageable: import("vue").ComputedRef<any>;
29
+ operationSearch: import("vue").ComputedRef<any>;
30
+ fields: import("vue").ComputedRef<any[]>;
31
+ canServerPageable: import("vue").ComputedRef<boolean | "">;
32
+ canServerSearch: import("vue").ComputedRef<boolean>;
33
+ canCreate: import("vue").ComputedRef<boolean>;
34
+ canUpdate: import("vue").ComputedRef<boolean>;
35
+ canDelete: import("vue").ComputedRef<boolean>;
36
+ createItem: (item: Record<string, any>, callback?: FormDialogCallback, importing?: boolean) => any;
37
+ importItems: (importItems: Record<string, any>[], callback?: FormDialogCallback) => void;
38
+ updateItem: (item: Record<string, any>, callback?: FormDialogCallback) => any;
39
+ deleteItem: (item: Record<string, any>, callback?: FormDialogCallback) => any;
40
+ loadItems: (options: any) => void;
41
+ reload: () => void;
42
+ isLoading: import("vue").Ref<boolean>;
43
+ };
44
+ export {};
@@ -0,0 +1,261 @@
1
+ import { computed, ref, watch } from "vue";
2
+ import { useAlert } from "./alert.mjs";
3
+ import { graphqlOperation } from "#imports";
4
+ import { buildRequiredInputFields } from "./graphqlOperation.mjs";
5
+ export function useGraphqlModel(props) {
6
+ const alert = useAlert();
7
+ const items = ref([]);
8
+ const itemsLength = ref(0);
9
+ const search = ref();
10
+ const currentOptions = ref();
11
+ const isLoading = ref(false);
12
+ function capitalizeFirstLetter(string) {
13
+ return string?.charAt(0).toUpperCase() + string?.slice(1);
14
+ }
15
+ function lowercaseFirstLetter(string) {
16
+ return string?.charAt(0).toLowerCase() + string?.slice(1);
17
+ }
18
+ const operationCreate = computed(() => {
19
+ if (props.operationCreate) {
20
+ if (typeof props.operationCreate === "string") {
21
+ if (graphqlOperation[props.operationCreate])
22
+ return graphqlOperation[props.operationCreate];
23
+ } else {
24
+ return props.operationCreate;
25
+ }
26
+ }
27
+ return graphqlOperation["create" + capitalizeFirstLetter(props.modelName)];
28
+ });
29
+ const operationUpdate = computed(() => {
30
+ if (props.operationUpdate) {
31
+ if (typeof props.operationUpdate === "string") {
32
+ if (graphqlOperation[props.operationUpdate])
33
+ return graphqlOperation[props.operationUpdate];
34
+ } else {
35
+ return props.operationUpdate;
36
+ }
37
+ }
38
+ return graphqlOperation["update" + capitalizeFirstLetter(props.modelName)];
39
+ });
40
+ const operationDelete = computed(() => {
41
+ if (props.operationDelete) {
42
+ if (typeof props.operationDelete === "string") {
43
+ if (graphqlOperation[props.operationDelete])
44
+ return graphqlOperation[props.operationDelete];
45
+ } else {
46
+ return props.operationDelete;
47
+ }
48
+ }
49
+ return graphqlOperation["delete" + capitalizeFirstLetter(props.modelName)];
50
+ });
51
+ const operationRead = computed(() => {
52
+ if (props.operationRead) {
53
+ if (typeof props.operationRead === "string") {
54
+ if (graphqlOperation[props.operationRead])
55
+ return graphqlOperation[props.operationRead];
56
+ } else {
57
+ return props.operationRead;
58
+ }
59
+ }
60
+ return graphqlOperation[lowercaseFirstLetter(props.modelName)];
61
+ });
62
+ const operationReadPageable = computed(() => {
63
+ if (props.operationReadPageable) {
64
+ if (typeof props.operationReadPageable === "string") {
65
+ if (graphqlOperation[props.operationReadPageable])
66
+ return graphqlOperation[props.operationReadPageable];
67
+ } else {
68
+ return props.operationReadPageable;
69
+ }
70
+ }
71
+ return graphqlOperation[lowercaseFirstLetter(props.modelName) + "Pageable"];
72
+ });
73
+ const operationSearch = computed(() => {
74
+ if (props.operationSearch) {
75
+ if (typeof props.operationSearch === "string") {
76
+ if (graphqlOperation[props.operationSearch])
77
+ return graphqlOperation[props.operationSearch];
78
+ } else {
79
+ return props.operationSearch;
80
+ }
81
+ }
82
+ return graphqlOperation["search" + capitalizeFirstLetter(props.modelName)];
83
+ });
84
+ function keyToField(key) {
85
+ const parts = key.split(".");
86
+ if (parts.length > 1) {
87
+ const result = {};
88
+ let current = result;
89
+ for (let i = 0; i < parts.length; i++) {
90
+ const part = parts[i];
91
+ const isLast = i === parts.length - 1;
92
+ if (isLast) {
93
+ current[part] = [part];
94
+ } else {
95
+ current[part] = [{}];
96
+ current = current[part][0];
97
+ }
98
+ }
99
+ return result;
100
+ } else {
101
+ return key;
102
+ }
103
+ }
104
+ const fields = computed(() => {
105
+ let tmpFields = [];
106
+ let fieldsProps = props.fields || [];
107
+ let requiredInputFields = [];
108
+ if (props.headers && props.headers.length > 0) {
109
+ props.headers.forEach((header) => {
110
+ if (!fieldsProps.includes(header.key))
111
+ tmpFields.push(keyToField(header.key));
112
+ });
113
+ }
114
+ if (canUpdate.value) {
115
+ requiredInputFields = buildRequiredInputFields(operationUpdate.value?.variables);
116
+ }
117
+ return [.../* @__PURE__ */ new Set([...fieldsProps, ...tmpFields, ...requiredInputFields])];
118
+ });
119
+ const canServerPageable = computed(() => {
120
+ return !!operationReadPageable.value && (!search.value || search.value && canServerSearch.value);
121
+ });
122
+ const canServerSearch = computed(() => {
123
+ return !!operationSearch.value;
124
+ });
125
+ const canCreate = computed(() => {
126
+ return !!operationCreate.value;
127
+ });
128
+ const canUpdate = computed(() => {
129
+ return !!operationUpdate.value;
130
+ });
131
+ const canDelete = computed(() => {
132
+ return !!operationDelete.value;
133
+ });
134
+ function createItem(item, callback, importing = false) {
135
+ isLoading.value = true;
136
+ return operationCreate.value?.call(fields.value, { input: item }).then((result) => {
137
+ if (canServerPageable) {
138
+ if (!importing)
139
+ loadItems(currentOptions.value);
140
+ } else
141
+ items.value.push(result);
142
+ }).catch((error) => {
143
+ alert?.addAlert({ alertType: "error", message: error });
144
+ }).finally(() => {
145
+ if (!importing)
146
+ isLoading.value = false;
147
+ if (callback)
148
+ callback.done();
149
+ });
150
+ }
151
+ function importItems(importItems2, callback) {
152
+ isLoading.value = true;
153
+ let importPromise = [];
154
+ importItems2.forEach((item) => {
155
+ let eachPromise = createItem(item, void 0, true);
156
+ if (eachPromise)
157
+ importPromise.push(eachPromise);
158
+ });
159
+ Promise.all(importPromise).finally(() => {
160
+ isLoading.value = false;
161
+ reload();
162
+ if (callback)
163
+ callback.done();
164
+ });
165
+ }
166
+ function updateItem(item, callback) {
167
+ isLoading.value = true;
168
+ return operationUpdate.value?.call(fields.value, { input: item }).then((result) => {
169
+ if (canServerPageable)
170
+ loadItems(currentOptions.value);
171
+ else {
172
+ let index = items.value.findIndex((item2) => item2[props.modelKey || "id"] === result[props.modelKey || "id"]);
173
+ if (index !== -1) {
174
+ items.value[index] = result;
175
+ }
176
+ }
177
+ }).catch((error) => {
178
+ alert?.addAlert({ alertType: "error", message: error });
179
+ }).finally(() => {
180
+ isLoading.value = false;
181
+ if (callback)
182
+ callback.done();
183
+ });
184
+ }
185
+ function deleteItem(item, callback) {
186
+ isLoading.value = true;
187
+ return operationDelete.value?.call(fields.value, { input: item }).catch((error) => {
188
+ alert?.addAlert({ alertType: "error", message: error });
189
+ }).finally(() => {
190
+ isLoading.value = false;
191
+ reload();
192
+ if (callback)
193
+ callback.done();
194
+ });
195
+ }
196
+ function loadItems(options) {
197
+ currentOptions.value = options;
198
+ if (canServerPageable) {
199
+ let pageableVariable = {
200
+ page: options.page,
201
+ perPage: options.itemsPerPage,
202
+ sortBy: options.sortBy
203
+ };
204
+ isLoading.value = true;
205
+ operationReadPageable.value?.call([{ data: fields.value }, "meta"], Object.assign(props.modelBy || {}, { pageable: pageableVariable })).then((result) => {
206
+ items.value = result.data;
207
+ itemsLength.value = result.meta.totalItems;
208
+ }).catch((error) => {
209
+ alert?.addAlert({ alertType: "error", message: error });
210
+ }).finally(() => {
211
+ isLoading.value = false;
212
+ });
213
+ }
214
+ }
215
+ function reload() {
216
+ if (canServerPageable.value) {
217
+ loadItems(currentOptions.value);
218
+ } else {
219
+ isLoading.value = true;
220
+ operationRead.value?.call(fields.value, props.modelBy).then((result) => {
221
+ items.value = result;
222
+ }).catch((error) => {
223
+ alert?.addAlert({ alertType: "error", message: error });
224
+ }).finally(() => {
225
+ isLoading.value = false;
226
+ });
227
+ }
228
+ }
229
+ watch(() => props.modelName, () => {
230
+ if (!canServerPageable.value)
231
+ reload();
232
+ }, { immediate: true });
233
+ watch(canServerPageable, () => {
234
+ reload();
235
+ });
236
+ return {
237
+ items,
238
+ itemsLength,
239
+ search,
240
+ currentOptions,
241
+ operationCreate,
242
+ operationUpdate,
243
+ operationDelete,
244
+ operationRead,
245
+ operationReadPageable,
246
+ operationSearch,
247
+ fields,
248
+ canServerPageable,
249
+ canServerSearch,
250
+ canCreate,
251
+ canUpdate,
252
+ canDelete,
253
+ createItem,
254
+ importItems,
255
+ updateItem,
256
+ deleteItem,
257
+ loadItems,
258
+ reload,
259
+ isLoading
260
+ };
261
+ }
@@ -0,0 +1,10 @@
1
+ import { type ClassConstructor } from "../utils/object";
2
+ import type { graphqlTypeObject, graphqlVariable } from "../types/graphqlOperation";
3
+ export declare function buildFields(operationFields: graphqlTypeObject, fields?: Array<string | Object> | ClassConstructor<any>, depth?: number): Array<string | Object>;
4
+ export declare function buildVariables(outputVariables?: graphqlVariable[], inputVariables?: {
5
+ [p: string]: any;
6
+ }, reject?: Function, isRoot?: boolean): Record<string, any> | undefined;
7
+ export declare function buildRequiredInputFields(variables: graphqlVariable[] | undefined, inputField?: string): string[];
8
+ export declare function useGraphQlOperation<T>(operationType: string, operation: string, fields?: Array<string | Object> | ClassConstructor<any>, variables?: {
9
+ [p: string]: any;
10
+ }, cache?: boolean): Promise<T>;
@@ -0,0 +1,123 @@
1
+ import { graphqlInputType, graphqlOperation, graphqlType, scalarType } from "#imports";
2
+ import { classAttributes, isClassConstructor } from "../utils/object.mjs";
3
+ import { useGraphQl } from "./graphql.mjs";
4
+ export function buildFields(operationFields, fields, depth = 0) {
5
+ if (!operationFields)
6
+ return [];
7
+ if (isClassConstructor(fields))
8
+ fields = classAttributes(fields);
9
+ if (!fields || fields.length == 0)
10
+ fields = [...operationFields.fields];
11
+ if (fields.includes("*")) {
12
+ operationFields.fields.forEach((field) => {
13
+ if (!fields.includes(field))
14
+ fields.push(field);
15
+ });
16
+ fields.splice(fields.indexOf("*"), 1);
17
+ }
18
+ if (depth > 0)
19
+ operationFields.relations.forEach((relation) => fields.push(relation.name));
20
+ if (!fields.includes("timestampField") && operationFields.relations.find((relation) => relation.name == "timestampField"))
21
+ fields.push("timestampField");
22
+ if (!fields.includes("userstampField") && operationFields.relations.find((relation) => relation.name == "userstampField"))
23
+ fields.push("userstampField");
24
+ return fields.map((field) => {
25
+ if (typeof field == "string" && !operationFields?.fields?.includes(field)) {
26
+ let relationField = operationFields?.relations?.find((relation) => relation.name == field);
27
+ if (relationField && relationField.type) {
28
+ return { [relationField.name]: buildFields(graphqlType[relationField.type], void 0, depth - 1) };
29
+ } else {
30
+ return void 0;
31
+ }
32
+ } else if (typeof field == "object") {
33
+ Object.keys(field).forEach((key) => {
34
+ let relationField = operationFields?.relations?.find((relation) => relation.name == key);
35
+ if (relationField && relationField.type) {
36
+ let castField = field;
37
+ castField[key] = buildFields(graphqlType[relationField.type], castField[key], depth - 1);
38
+ }
39
+ });
40
+ }
41
+ return field;
42
+ }).filter((field) => !!field);
43
+ }
44
+ export function buildVariables(outputVariables, inputVariables, reject, isRoot = true) {
45
+ if (!outputVariables)
46
+ return void 0;
47
+ if (inputVariables) {
48
+ outputVariables.map((variable) => {
49
+ if (variable.type && !scalarType.includes(variable.type)) {
50
+ if (variable.list) {
51
+ if (inputVariables[variable.name] && Array.isArray(inputVariables[variable.name])) {
52
+ let tmpVariables = [];
53
+ inputVariables[variable.name].forEach((inputVariable) => {
54
+ tmpVariables.push(buildVariables(graphqlInputType[variable.type].variables, inputVariable, reject, false));
55
+ });
56
+ variable.value = tmpVariables;
57
+ } else {
58
+ variable.value = void 0;
59
+ }
60
+ } else {
61
+ variable.value = buildVariables(graphqlInputType[variable.type].variables, inputVariables[variable.name], reject, false);
62
+ }
63
+ } else {
64
+ variable.value = inputVariables[variable.name];
65
+ }
66
+ return variable;
67
+ });
68
+ }
69
+ outputVariables.forEach((variable) => {
70
+ if (variable.required && variable.value === void 0) {
71
+ console.warn("Required variable " + variable.name + " is not found, operation abort");
72
+ if (reject) {
73
+ reject("Required variable " + variable.name + " is not found");
74
+ return;
75
+ }
76
+ }
77
+ });
78
+ let usedVariables = outputVariables.map((variable) => variable.name);
79
+ let droppedVariable = Object.keys(inputVariables || {}).filter((key) => !usedVariables.includes(key));
80
+ if (droppedVariable.length > 0)
81
+ console.debug("There is data not appeared in schema and dropped before operation.", droppedVariable);
82
+ return outputVariables.reduce((acc, item) => {
83
+ acc[item.name] = isRoot ? item : item.value;
84
+ return acc;
85
+ }, {});
86
+ }
87
+ export function buildRequiredInputFields(variables, inputField = "input") {
88
+ let inputVariable = variables?.find((variable) => variable.name == inputField);
89
+ if (inputVariable) {
90
+ let returnFields = [];
91
+ graphqlInputType[inputVariable.type]?.variables?.forEach((variable) => {
92
+ if (variable.required)
93
+ returnFields.push(variable.name);
94
+ });
95
+ return returnFields;
96
+ } else {
97
+ return [];
98
+ }
99
+ }
100
+ export function useGraphQlOperation(operationType, operation, fields, variables, cache = false) {
101
+ return new Promise((resolve, reject) => {
102
+ let rejectReason = void 0;
103
+ function rejectInside(reason) {
104
+ rejectReason = reason;
105
+ }
106
+ variables = buildVariables(graphqlOperation[operation]?.variables, variables, rejectInside);
107
+ if (rejectReason) {
108
+ reject(rejectReason);
109
+ return;
110
+ }
111
+ if (graphqlOperation[operation]?.fields) {
112
+ fields = buildFields(graphqlOperation[operation]?.fields, fields, 0);
113
+ if (operationType == "Query")
114
+ useGraphQl().queryPromise(operation, fields, variables, cache).then((result) => resolve(result)).catch((error) => reject(error));
115
+ else if (operationType == "Mutation")
116
+ useGraphQl().mutationPromise(operation, fields, variables).then((result) => resolve(result)).catch((error) => reject(error));
117
+ else
118
+ reject("Invalid Operation Type");
119
+ } else {
120
+ reject("No schema of return data available");
121
+ }
122
+ });
123
+ }
@@ -0,0 +1,4 @@
1
+ export interface FormDialogCallback {
2
+ done: Function
3
+ error: Function
4
+ }
@@ -0,0 +1,23 @@
1
+ export interface graphqlVariable {
2
+ name: string;
3
+ list?: boolean;
4
+ required?: boolean;
5
+ type?: string;
6
+ value?: any;
7
+ }
8
+ export interface graphqlOperationObject<T, U> {
9
+ variables?: graphqlVariable[];
10
+ fields?: graphqlTypeObject;
11
+ operationType: "Query" | "Mutation";
12
+ name: string;
13
+ call: (fields?: Array<string | Object>, variables?: U) => Promise<T>;
14
+ }
15
+
16
+ export interface graphqlTypeObject {
17
+ fields: string[];
18
+ relations: graphqlVariable[];
19
+ }
20
+
21
+ export interface graphqlInputObject {
22
+ variables: graphqlVariable[];
23
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramathibodi/nuxt-commons",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Ramathibodi Nuxt modules for common components",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,13 +19,27 @@
19
19
  "import": "./dist/runtime/utils/*.mjs",
20
20
  "require": "./dist/runtime/utils/*.cjs"
21
21
  },
22
+ "./composables/*": {
23
+ "types": "./dist/runtime/composables/*.d.ts",
24
+ "import": "./dist/runtime/composables/*.mjs",
25
+ "require": "./dist/runtime/composables/*.cjs"
26
+ },
27
+ "./composables/*/*": {
28
+ "types": "./dist/runtime/composables/*/*.d.ts",
29
+ "import": "./dist/runtime/composables/*/*.mjs",
30
+ "require": "./dist/runtime/composables/*/*.cjs"
31
+ },
32
+ "./components/*": "./dist/runtime/components/*.vue",
33
+ "./components/*/*": "./dist/runtime/components/*/*.vue",
22
34
  "./lab/*": "./dist/runtime/labs/*.vue",
23
35
  "./lab/*/*": "./dist/runtime/labs/*/*.vue"
24
36
  },
25
37
  "main": "./dist/module.cjs",
26
38
  "types": "./dist/types.d.ts",
27
39
  "files": [
28
- "dist"
40
+ "dist",
41
+ "scripts",
42
+ "templates"
29
43
  ],
30
44
  "scripts": {
31
45
  "prepack": "nuxt-module-build build",
@@ -36,7 +50,8 @@
36
50
  "lint": "eslint .",
37
51
  "lint:fix": "eslint . --fix",
38
52
  "test": "vitest run",
39
- "test:watch": "vitest watch"
53
+ "test:watch": "vitest watch",
54
+ "postinstall": "node scripts/postInstall.cjs"
40
55
  },
41
56
  "dependencies": {
42
57
  "@codemirror/lang-html": "^6.4.9",
@@ -51,6 +66,10 @@
51
66
  "@fullcalendar/multimonth": "^6.1.11",
52
67
  "@fullcalendar/timegrid": "^6.1.11",
53
68
  "@fullcalendar/vue3": "^6.1.11",
69
+ "@graphql-codegen/cli": "^5.0.2",
70
+ "@graphql-codegen/add": "^5.0.2",
71
+ "@graphql-codegen/plugin-helpers": "^5.0.4",
72
+ "@graphql-codegen/typescript": "^4.0.6",
54
73
  "@mdi/font": "^7.4.47",
55
74
  "@nuxt/kit": "^3.11.2",
56
75
  "@nuxtjs/apollo": "5.0.0-alpha.14",
@@ -61,6 +80,7 @@
61
80
  "cropperjs": "^1.6.2",
62
81
  "currency.js": "^2.0.4",
63
82
  "fuse.js": "^7.0.0",
83
+ "graphql": "^16.9.0",
64
84
  "gql-query-builder": "^3.8.0",
65
85
  "lodash": "^4.17.21",
66
86
  "luxon": "^3.4.4",
@@ -95,4 +115,4 @@
95
115
  "vitest": "^1.5.1",
96
116
  "vue-tsc": "^1.8.27"
97
117
  }
98
- }
118
+ }
@@ -0,0 +1,76 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { execSync } = require('child_process');
4
+
5
+ // Function to copy a file from source to destination
6
+ function copyFile(src, dest) {
7
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
8
+ fs.copyFileSync(src, dest);
9
+ console.log(`Copied ${src} to ${dest}`);
10
+ }
11
+
12
+ // Function to copy all files in a directory recursively
13
+ function copyDirectory(srcDir, destDir) {
14
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
15
+
16
+ for (let entry of entries) {
17
+ const srcPath = path.join(srcDir, entry.name);
18
+ const destPath = path.join(destDir, entry.name);
19
+
20
+ if (entry.isDirectory()) {
21
+ copyDirectory(srcPath, destPath);
22
+ } else {
23
+ copyFile(srcPath, destPath);
24
+ }
25
+ }
26
+ }
27
+
28
+ // Function to add copied files to git
29
+ function addFilesToGit(files, projectRoot) {
30
+ files.forEach(file => {
31
+ const filePath = path.join(projectRoot, file);
32
+ execSync(`git add ${filePath}`, { stdio: 'inherit' });
33
+ console.log(`Added ${filePath} to git`);
34
+ });
35
+ }
36
+
37
+ // Function to modify package.json to add a new script
38
+ function addScriptToPackageJson(scriptName, scriptCommand) {
39
+ const packageJsonPath = path.join(process.env.INIT_CWD, 'package.json');
40
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
41
+
42
+ packageJson.scripts = packageJson.scripts || {};
43
+ packageJson.scripts[scriptName] = scriptCommand;
44
+
45
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
46
+ console.log(`Added script "${scriptName}": "${scriptCommand}" to package.json`);
47
+ }
48
+
49
+ // Function to run npm run codegen safely
50
+ function runCodegen(projectRoot) {
51
+ try {
52
+ execSync('npm run codegen', { cwd: projectRoot, stdio: 'inherit' });
53
+ console.log('Successfully ran npm run codegen');
54
+ } catch (error) {
55
+ console.log('npm run codegen failed or script not found, continuing...');
56
+ }
57
+ }
58
+
59
+ if (process.env.INIT_CWD === process.cwd()) process.exit()
60
+
61
+ // Define source and destination directories
62
+ const srcDir = path.join(__dirname, '..', 'templates');
63
+ const destDir = process.env.INIT_CWD; // Project root
64
+
65
+ // Copy all files from templates directory to project root
66
+ copyDirectory(srcDir, destDir);
67
+
68
+ // Add new script to package.json
69
+ addScriptToPackageJson('codegen', 'graphql-codegen --require dotenv/config --config ./.codegen/codegen.ts dotenv_config_path=.env');
70
+
71
+ // Add copied files to git
72
+ const copiedFiles = fs.readdirSync(srcDir).map(file => path.join('.', file));
73
+ addFilesToGit(copiedFiles, destDir);
74
+
75
+ // Run npm run codegen safely
76
+ runCodegen(destDir);
@@ -0,0 +1,32 @@
1
+ import type {CodegenConfig} from '@graphql-codegen/cli';
2
+
3
+ const config: CodegenConfig = {
4
+ overwrite: true,
5
+ schema: process.env.NUXT_PUBLIC_WS_GRAPHQL,
6
+ generates: {
7
+ './types/graphql.ts': {
8
+ plugins: [{
9
+ add: {
10
+ content: '//Auto-generated file, do not make any change. Content could be overwritten.',
11
+ }
12
+ },'typescript'],
13
+ config: {
14
+ maybeValue: 'T | null | undefined',
15
+ inputMaybeValue: 'T | null | undefined',
16
+ declarationKind: 'class',
17
+ enumsAsTypes: true,
18
+ skipTypename: true,
19
+ },
20
+ },
21
+ './composables/graphqlObject.ts': {
22
+ plugins: [{
23
+ add: {
24
+ content: '//Auto-generated file, do not make any change. Content could be overwritten.',
25
+ }
26
+ },'./.codegen/plugin-schema-object.js'],
27
+ },
28
+ },
29
+ hooks: { afterAllFileWrite: ['prettier --parser typescript --tab-width 4 --write','git add'] }
30
+ };
31
+
32
+ export default config;
@@ -0,0 +1,148 @@
1
+ const { getCachedDocumentNodeFromSchema } = require('@graphql-codegen/plugin-helpers')
2
+ const { visit } = require('graphql')
3
+ const { camelCase } = require('lodash')
4
+
5
+ function capitalizeFirstLetter(string) {
6
+ return string.charAt(0).toUpperCase() + string.slice(1);
7
+ }
8
+
9
+ function inputTypeToObject(inputType,result) {
10
+ if (inputType.kind==="NonNullType") {
11
+ result["required"] = true
12
+ inputTypeToObject(inputType.type,result)
13
+ }
14
+ if (inputType.kind==="ListType") {
15
+ result["list"] = true
16
+ inputTypeToObject(inputType.type,result)
17
+ }
18
+ if (inputType.kind==="NamedType") {
19
+ result["type"] = inputType.name.value
20
+ }
21
+ }
22
+
23
+ module.exports = {
24
+ plugin(schema, _documents, _config) {
25
+ const astNode = getCachedDocumentNodeFromSchema(schema) // Transforms the GraphQLSchema into ASTNode
26
+
27
+ const operationNode = []
28
+ const typeNode = []
29
+ const inputNode = []
30
+
31
+ const scalarType =['ID','String','Boolean','Int','Float']
32
+
33
+ const result = visit(astNode, {
34
+ ObjectTypeDefinition(node) {
35
+ if (node.name.value==="Query" || node.name.value==="Mutation")
36
+ {
37
+ node.fields.forEach((field)=>{
38
+ let fieldType = {}
39
+ let fieldVariables = []
40
+ inputTypeToObject(field.type,fieldType)
41
+ field.arguments.forEach((argument)=>{
42
+ let fieldVariablesItem = {name: argument.name.value}
43
+ if (argument.defaultValue) fieldVariablesItem.value = argument.defaultValue.value
44
+ if (argument.type) inputTypeToObject(argument.type,fieldVariablesItem)
45
+ fieldVariables.push(fieldVariablesItem)
46
+ })
47
+ operationNode.push({
48
+ name: field.name.value,
49
+ variables: fieldVariables,
50
+ type: fieldType.type,
51
+ operationType: node.name.value
52
+ })
53
+ })
54
+ } else {
55
+ let fields = []
56
+ node.fields.forEach((field)=>{
57
+ let fieldVariablesItem = {name: field.name.value}
58
+ if (field.type) inputTypeToObject(field.type,fieldVariablesItem)
59
+ fields.push(fieldVariablesItem)
60
+ })
61
+ typeNode.push({
62
+ name: node.name.value,
63
+ fields: fields
64
+ })
65
+ }
66
+ },
67
+ InputObjectTypeDefinition(node) {
68
+ let fields = []
69
+ node.fields.forEach((field)=>{
70
+ let fieldVariablesItem = {name: field.name.value}
71
+ if (field.defaultValue) fieldVariablesItem.value = field.defaultValue.value
72
+ if (field.type) inputTypeToObject(field.type,fieldVariablesItem)
73
+ fields.push(fieldVariablesItem)
74
+ })
75
+ inputNode.push({
76
+ name: node.name.value,
77
+ variables: fields,
78
+ })
79
+ },
80
+ ScalarTypeDefinition(node) {
81
+ scalarType.push(node.name.value)
82
+ }
83
+ })
84
+
85
+ typeNode.map((node)=>{
86
+ let scalars = []
87
+ let relations = []
88
+ node.fields.forEach((field)=>{
89
+ if (scalarType.includes(field.type)) scalars.push(field.name)
90
+ else relations.push(field)
91
+ })
92
+ node.scalars = scalars
93
+ node.relations = relations
94
+ })
95
+
96
+ return `
97
+ import * as graphQlClass from "~/types/graphql"
98
+ import { useGraphQlOperation } from "#imports";
99
+
100
+ export const scalarType = ${JSON.stringify(scalarType)}
101
+
102
+ function operationCall${"<"+"T,U>"}(operationType:string, operation: string,fields?: ${"Array"+"<string | Object>"},variables?: U) {
103
+ return useGraphQlOperation${"<"+"T>"}(operationType,operation,fields,variables as {[p: string] : any} | undefined)
104
+ }
105
+
106
+ function createGraphQLOperation${"<"+"T,U>"}(operationType: "Query" | "Mutation",name: string,variables?: graphqlVariable[],fields?: graphqlTypeObject): graphqlOperationObject${"<"+"T,U>"} {
107
+ return {
108
+ operationType,
109
+ name,
110
+ variables,
111
+ fields,
112
+ call: async function(fields?: ${"Array"+"<string | Object>"}, variables?: U): Promise${"<"+"T>"} {
113
+ return operationCall(operationType,name, fields, variables);
114
+ }
115
+ }
116
+ }
117
+
118
+ interface graphqlTypeObjects extends ${"Record<string,"+"graphqlTypeObject>"} {
119
+ ${typeNode.map((node)=>`${node.name}: graphqlTypeObject`).join("\n")}
120
+ }
121
+
122
+ export const graphqlType: graphqlTypeObjects= {
123
+ ${typeNode.map((node)=>`${node.name} : {
124
+ fields : ${JSON.stringify(node.scalars)},
125
+ relations: ${JSON.stringify(node.relations)}
126
+ } as graphqlTypeObject `).join(",")}
127
+ }
128
+
129
+ interface graphqlInputObjects extends ${"Record<string,"+"graphqlInputObject>"} {
130
+ ${inputNode.map((node)=>`${node.name}: graphqlInputObject`).join("\n")}
131
+ }
132
+
133
+ export const graphqlInputType: graphqlInputObjects = {
134
+ ${inputNode.map((node)=>`${node.name} : {
135
+ variables: ${JSON.stringify(node.variables)},
136
+ }`).join(",")}
137
+ }
138
+
139
+ interface graphqlOperationObjects extends ${"Record<string,"+"graphqlOperationObject"+"<"+"any"+",any>"+">"} {
140
+ ${operationNode.map((node)=>`${node.name}: graphqlOperationObject${"<graphQlClass"+"."+node.operationType+"['"+node.name+"'],"+ ((node.variables.length) ? "graphQlClass."+node.operationType+capitalizeFirstLetter(camelCase(node.name))+"Args" : "undefined")+">"}`).join("\n")}
141
+ }
142
+
143
+ export const graphqlOperation: graphqlOperationObjects = {
144
+ ${operationNode.map((node)=>`${node.name}: createGraphQLOperation${"<graphQlClass"+"."+node.operationType+"['"+node.name+"'],"+ ((node.variables.length) ? "graphQlClass."+node.operationType+capitalizeFirstLetter(camelCase(node.name))+"Args" : "undefined")+">"}('${node.operationType}','${node.name}',${JSON.stringify(node.variables)},${typeNode.find((type)=>type.name===node.type) ? "graphqlType."+node.type : "undefined"})`).join(",")}
145
+ }
146
+ `
147
+ }
148
+ }