@ramathibodi/nuxt-commons 0.1.73 → 0.1.75

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.
Files changed (111) hide show
  1. package/README.md +115 -96
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +1 -0
  4. package/dist/runtime/components/Alert.vue +58 -54
  5. package/dist/runtime/components/BarcodeReader.vue +130 -122
  6. package/dist/runtime/components/ExportCSV.vue +110 -102
  7. package/dist/runtime/components/FileBtn.vue +79 -67
  8. package/dist/runtime/components/ImportCSV.vue +151 -139
  9. package/dist/runtime/components/MrzReader.vue +168 -0
  10. package/dist/runtime/components/SplitterPanel.vue +67 -59
  11. package/dist/runtime/components/TabsGroup.vue +39 -31
  12. package/dist/runtime/components/TextBarcode.vue +66 -54
  13. package/dist/runtime/components/device/IdCardButton.vue +95 -83
  14. package/dist/runtime/components/device/IdCardWebSocket.vue +207 -195
  15. package/dist/runtime/components/device/Scanner.vue +350 -338
  16. package/dist/runtime/components/dialog/Confirm.vue +112 -100
  17. package/dist/runtime/components/dialog/Host.vue +88 -84
  18. package/dist/runtime/components/dialog/Index.vue +84 -72
  19. package/dist/runtime/components/dialog/Loading.vue +51 -39
  20. package/dist/runtime/components/dialog/default/Confirm.vue +112 -100
  21. package/dist/runtime/components/dialog/default/Loading.vue +60 -48
  22. package/dist/runtime/components/dialog/default/Notify.vue +82 -70
  23. package/dist/runtime/components/dialog/default/Printing.vue +46 -34
  24. package/dist/runtime/components/dialog/default/VerifyUser.vue +144 -132
  25. package/dist/runtime/components/document/Form.vue +50 -42
  26. package/dist/runtime/components/document/TemplateBuilder.vue +536 -524
  27. package/dist/runtime/components/form/ActionPad.vue +156 -144
  28. package/dist/runtime/components/form/Birthdate.vue +116 -104
  29. package/dist/runtime/components/form/CheckboxGroup.vue +99 -87
  30. package/dist/runtime/components/form/CodeEditor.vue +45 -37
  31. package/dist/runtime/components/form/Date.vue +270 -258
  32. package/dist/runtime/components/form/DateTime.vue +220 -208
  33. package/dist/runtime/components/form/Dialog.vue +178 -166
  34. package/dist/runtime/components/form/EditPad.vue +157 -145
  35. package/dist/runtime/components/form/File.vue +295 -283
  36. package/dist/runtime/components/form/Hidden.vue +44 -32
  37. package/dist/runtime/components/form/Iterator.vue +538 -526
  38. package/dist/runtime/components/form/Login.vue +143 -131
  39. package/dist/runtime/components/form/Pad.vue +399 -387
  40. package/dist/runtime/components/form/SignPad.vue +226 -218
  41. package/dist/runtime/components/form/System.vue +34 -26
  42. package/dist/runtime/components/form/Table.vue +391 -379
  43. package/dist/runtime/components/form/TableData.vue +236 -224
  44. package/dist/runtime/components/form/Time.vue +177 -165
  45. package/dist/runtime/components/form/images/Capture.vue +245 -237
  46. package/dist/runtime/components/form/images/Edit.vue +133 -121
  47. package/dist/runtime/components/form/images/Field.vue +331 -320
  48. package/dist/runtime/components/form/images/Pad.vue +54 -42
  49. package/dist/runtime/components/label/Date.vue +37 -29
  50. package/dist/runtime/components/label/DateAgo.vue +102 -94
  51. package/dist/runtime/components/label/DateCount.vue +152 -144
  52. package/dist/runtime/components/label/Field.vue +111 -103
  53. package/dist/runtime/components/label/FormatMoney.vue +37 -29
  54. package/dist/runtime/components/label/Mask.vue +46 -38
  55. package/dist/runtime/components/label/Object.vue +21 -13
  56. package/dist/runtime/components/master/Autocomplete.vue +89 -81
  57. package/dist/runtime/components/master/Combobox.vue +88 -80
  58. package/dist/runtime/components/master/RadioGroup.vue +90 -78
  59. package/dist/runtime/components/master/Select.vue +70 -62
  60. package/dist/runtime/components/master/label.vue +55 -47
  61. package/dist/runtime/components/model/Autocomplete.vue +91 -79
  62. package/dist/runtime/components/model/Combobox.vue +90 -78
  63. package/dist/runtime/components/model/Pad.vue +114 -102
  64. package/dist/runtime/components/model/Select.vue +78 -72
  65. package/dist/runtime/components/model/Table.vue +370 -358
  66. package/dist/runtime/components/model/iterator.vue +497 -489
  67. package/dist/runtime/components/model/label.vue +58 -50
  68. package/dist/runtime/components/pdf/Print.vue +75 -63
  69. package/dist/runtime/components/pdf/View.vue +146 -134
  70. package/dist/runtime/composables/alert.d.ts +4 -0
  71. package/dist/runtime/composables/api.d.ts +4 -0
  72. package/dist/runtime/composables/dialog.d.ts +1 -1
  73. package/dist/runtime/composables/document/templateFormHidden.d.ts +4 -0
  74. package/dist/runtime/composables/graphql.d.ts +1 -1
  75. package/dist/runtime/composables/graphqlModel.d.ts +9 -9
  76. package/dist/runtime/composables/graphqlModelItem.d.ts +7 -7
  77. package/dist/runtime/composables/graphqlModelOperation.d.ts +6 -6
  78. package/dist/runtime/composables/localStorageModel.d.ts +4 -0
  79. package/dist/runtime/composables/lookupList.d.ts +4 -0
  80. package/dist/runtime/composables/menu.d.ts +4 -0
  81. package/dist/runtime/composables/useMrzReader.d.ts +48 -0
  82. package/dist/runtime/composables/useMrzReader.js +423 -0
  83. package/dist/runtime/composables/useTesseract.d.ts +16 -0
  84. package/dist/runtime/composables/useTesseract.js +45 -0
  85. package/dist/runtime/composables/userPermission.d.ts +1 -1
  86. package/dist/runtime/labs/Calendar.vue +99 -99
  87. package/dist/runtime/labs/form/EditMobile.vue +152 -152
  88. package/dist/runtime/labs/form/TextFieldMask.vue +43 -43
  89. package/dist/runtime/plugins/clientConfig.d.ts +1 -1
  90. package/dist/runtime/plugins/default.d.ts +1 -1
  91. package/dist/runtime/plugins/dialogManager.d.ts +1 -1
  92. package/dist/runtime/plugins/permission.d.ts +1 -1
  93. package/dist/runtime/types/alert.d.ts +11 -11
  94. package/dist/runtime/types/clientConfig.d.ts +13 -13
  95. package/dist/runtime/types/dialogManager.d.ts +35 -35
  96. package/dist/runtime/types/formDialog.d.ts +5 -5
  97. package/dist/runtime/types/graphqlOperation.d.ts +23 -23
  98. package/dist/runtime/types/menu.d.ts +31 -31
  99. package/dist/runtime/types/modules.d.ts +7 -7
  100. package/dist/runtime/types/permission.d.ts +13 -13
  101. package/dist/runtime/utils/asset.d.ts +2 -0
  102. package/dist/runtime/utils/asset.js +49 -0
  103. package/package.json +131 -122
  104. package/scripts/enrich-vue-docs-from-ai.mjs +197 -0
  105. package/scripts/generate-ai-summary.mjs +321 -0
  106. package/scripts/generate-composables-md.mjs +129 -0
  107. package/scripts/postInstall.cjs +70 -70
  108. package/templates/.codegen/codegen.ts +32 -32
  109. package/templates/.codegen/plugin-schema-object.js +161 -161
  110. package/templates/public/tesseract/mrz.traineddata.gz +0 -0
  111. package/templates/public/tesseract/ocrb.traineddata.gz +0 -0
package/README.md CHANGED
@@ -1,96 +1,115 @@
1
- <!--
2
- Get your module up and running quickly.
3
-
4
- Find and replace all on all files (CMD+SHIFT+F):
5
- - Name: Ramahis Common Component
6
- - Package name: @ramahis/common-components
7
- -->
8
-
9
- # @ramathibodi/nuxt-commons
10
-
11
- [![npm version][npm-version-src]][npm-version-href]
12
- [![npm downloads][npm-downloads-src]][npm-downloads-href]
13
- [![License][license-src]][license-href]
14
- [![Nuxt][nuxt-src]][nuxt-href]
15
-
16
-
17
- ## Quick Setup
18
-
19
- 1. Add `'@ramathibodi/nuxt-commons'` dependency to your project
20
-
21
- ```bash
22
- # Using pnpm
23
- pnpm add -D @ramathibodi/nuxt-commons
24
-
25
- # Using yarn
26
- yarn add --dev @ramathibodi/nuxt-commons
27
-
28
- # Using npm
29
- npm install --save-dev @ramathibodi/nuxt-commons
30
- ```
31
-
32
- 2. Add `my-module` to the `modules` section of `nuxt.config.ts`
33
-
34
- ```js
35
- export default defineNuxtConfig({
36
- modules: [
37
- '@ramathibodi/nuxt-commons'
38
- ]
39
- })
40
- ```
41
-
42
- That's it! You can now use My Module in your Nuxt app ✨
43
-
44
- ## Development
45
-
46
- ```bash
47
- # Install dependencies
48
- npm install
49
-
50
- # Generate type stubs
51
- npm run dev:prepare
52
-
53
- # Develop with the playground
54
- npm run dev
55
-
56
- # Build the playground
57
- npm run dev:build
58
-
59
- # Run ESLint
60
- npm run lint
61
-
62
- # Run Vitest
63
- npm run test
64
- npm run test:watch
65
-
66
- # Release new version
67
- npm run release
68
- ```
69
-
70
- ## NPM Login
71
- ```bash
72
- npm login
73
- ```
74
- - จะส่ง link url มาให้เข้าไปที่ url login npm
75
-
76
- ## NPM PUBLISH
77
- ```bash
78
- 1. ตรวจสอบ source code, ตรวจสอบ Branch ว่าเป็น master แล้ว จากนั้นตรวจสอบ version ของ npm ว่ามีการเปลี่ยนและไม่ชนกับ version บน npm
79
- 2. npm run dev:build
80
- 3. npm publish --access public
81
- 4. set token ใน link ที่ npm ให้มา
82
- 5. เสร็จสิ้น
83
- ```
84
-
85
- <!-- Badges -->
86
- [npm-version-src]: https://img.shields.io/npm/v/my-module/latest.svg?style=flat&colorA=18181B&colorB=28CF8D
87
- [npm-version-href]: https://npmjs.com/package/my-module
88
-
89
- [npm-downloads-src]: https://img.shields.io/npm/dm/my-module.svg?style=flat&colorA=18181B&colorB=28CF8D
90
- [npm-downloads-href]: https://npmjs.com/package/my-module
91
-
92
- [license-src]: https://img.shields.io/npm/l/my-module.svg?style=flat&colorA=18181B&colorB=28CF8D
93
- [license-href]: https://npmjs.com/package/my-module
94
-
95
- [nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js
96
- [nuxt-href]: https://nuxt.com
1
+ # @ramathibodi/nuxt-commons
2
+
3
+ Nuxt 3 module that provides shared runtime building blocks for Rama projects:
4
+ - globally auto-imported UI components
5
+ - auto-imported composables
6
+ - runtime plugins for permission/dialog/default behavior
7
+ - utility and lab exports
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ # pnpm
13
+ pnpm add -D @ramathibodi/nuxt-commons
14
+
15
+ # yarn
16
+ yarn add --dev @ramathibodi/nuxt-commons
17
+
18
+ # npm
19
+ npm install --save-dev @ramathibodi/nuxt-commons
20
+ ```
21
+
22
+ ## Nuxt Setup
23
+
24
+ ```ts
25
+ // nuxt.config.ts
26
+ export default defineNuxtConfig({
27
+ modules: ['@ramathibodi/nuxt-commons'],
28
+ })
29
+ ```
30
+
31
+ ## What The Module Registers
32
+
33
+ Configured in `src/module.ts`:
34
+ - Components from `src/runtime/components` (global + prefixed)
35
+ - Composables from `src/runtime/composables/**` (auto-imported)
36
+ - Plugins:
37
+ - `src/runtime/plugins/permission.ts`
38
+ - `src/runtime/plugins/dialogManager.ts` (client)
39
+ - `src/runtime/plugins/clientConfig.ts` (client)
40
+ - `src/runtime/plugins/default.ts` (client)
41
+ - Type templates from `src/runtime/types/*.d.ts`
42
+
43
+ ## Runtime Config
44
+
45
+ This module uses public runtime config at:
46
+
47
+ ```ts
48
+ runtimeConfig.public['nuxt-commons']
49
+ ```
50
+
51
+ Module options are merged into this key by module setup.
52
+
53
+ ## Development
54
+
55
+ ```bash
56
+ # Install dependencies
57
+ npm install
58
+
59
+ # Prepare stubs/types
60
+ npm run dev:prepare
61
+
62
+ # Run playground
63
+ npm run dev
64
+
65
+ # Build playground
66
+ npm run dev:build
67
+
68
+ # Lint and test
69
+ npm run lint
70
+ npm run test
71
+ ```
72
+
73
+ ## Documentation
74
+
75
+ Generate documentation artifacts from source:
76
+
77
+ ```bash
78
+ # Vue component API docs (vue-docgen)
79
+ npm run docs:api:components
80
+
81
+ # Composable API docs (typedoc)
82
+ npm run docs:api:composables
83
+
84
+ # Both API docs
85
+ npm run docs:api
86
+
87
+ # AI-focused summary + type index
88
+ npm run docs:ai:summary
89
+ ```
90
+
91
+ `docs:api:components` now runs a post-process step that enriches missing prop/event descriptions and missing type/default cells in generated markdown using `docs/ai-summary.json`.
92
+
93
+ Documentation config files:
94
+ - [`docs/vue-docgen.config.cjs`](docs/vue-docgen.config.cjs) for component docs generation
95
+ - [`docs/typedoc.json`](docs/typedoc.json) for composable docs generation
96
+ - [`docs/typedoc.tsconfig.json`](docs/typedoc.tsconfig.json) for docs-only TypeScript scope
97
+
98
+ Generated outputs:
99
+ - [`docs/components/`](docs/components/) (vue-docgen output)
100
+ - [`docs/composables/`](docs/composables/) (typedoc output)
101
+ - [`docs/ai-summary.md`](docs/ai-summary.md) (compact AI-oriented markdown summary)
102
+ - [`docs/ai-summary.json`](docs/ai-summary.json) (machine-readable component/composable metadata)
103
+ - [`docs/type-index.json`](docs/type-index.json) (type index for tooling/AI pipelines)
104
+
105
+ `docs:ai:summary` generates:
106
+ - `docs/ai-summary.md`
107
+ - `docs/ai-summary.json`
108
+ - `docs/type-index.json`
109
+
110
+ ## Publish Notes
111
+
112
+ ```bash
113
+ npm run dev:build
114
+ npm publish --access public
115
+ ```
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "compatibility": {
5
5
  "nuxt": "^3.0.0"
6
6
  },
7
- "version": "0.1.73",
7
+ "version": "0.1.75",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
package/dist/module.mjs CHANGED
@@ -20,6 +20,7 @@ const module = defineNuxtModule({
20
20
  global: true
21
21
  });
22
22
  addImportsDir(resolver.resolve("runtime/composables/**"));
23
+ addImportsDir(resolver.resolve("runtime/utils/**"));
23
24
  addPlugin({
24
25
  src: resolver.resolve("runtime/plugins/permission")
25
26
  });
@@ -1,54 +1,58 @@
1
- <script lang="ts" setup>
2
- import { ref, watch } from 'vue'
3
- import { isEmpty } from 'lodash-es'
4
- import { useAlert } from '../composables/alert'
5
- import type { AlertItem } from '../types/alert'
6
-
7
- const isAlertOpen = ref(false)
8
- const timeout = ref(3000)
9
- const alert = useAlert()
10
- const currentItem = ref<AlertItem | undefined>()
11
-
12
- const renewAlert = () => {
13
- if (alert?.hasAlert()) {
14
- currentItem.value = alert?.takeAlert()
15
- isAlertOpen.value = true
16
- }
17
- else {
18
- currentItem.value = undefined
19
- isAlertOpen.value = false
20
- }
21
- }
22
-
23
- watch(() => alert?.hasAlert(), (hasAlert) => {
24
- if (hasAlert) {
25
- renewAlert()
26
- }
27
- })
28
- </script>
29
-
30
- <template>
31
- <VSnackbar
32
- v-if="currentItem"
33
- v-model="isAlertOpen"
34
- :timeout="timeout"
35
- location="center"
36
- multi-line
37
- variant="text"
38
- @update:model-value="renewAlert()"
39
- >
40
- <!-- @vue-expected-error Type conversion problem -->
41
- <VAlert
42
- v-model="isAlertOpen"
43
- closable
44
- :type="currentItem.alertType"
45
- elevation="2"
46
- theme="dark"
47
- variant="flat"
48
- v-bind="currentItem.alertIcon ? { icon: currentItem.alertIcon } : {}"
49
- @click:close="renewAlert()"
50
- >
51
- {{ currentItem.statusCode ? currentItem.statusCode + ' ' : '' }} {{ currentItem.message }} {{ !isEmpty(currentItem.data) ? currentItem.data : '' }}
52
- </VAlert>
53
- </VSnackbar>
54
- </template>
1
+ <script lang="ts" setup>
2
+ /**
3
+ * Alert displays runtime alert messages and bridges alert-store entries to a consistent UI variant system.
4
+ * This doc block is consumed by vue-docgen for generated API documentation.
5
+ */
6
+ import { ref, watch } from 'vue'
7
+ import { isEmpty } from 'lodash-es'
8
+ import { useAlert } from '../composables/alert'
9
+ import type { AlertItem } from '../types/alert'
10
+
11
+ const isAlertOpen = ref(false)
12
+ const timeout = ref(3000)
13
+ const alert = useAlert()
14
+ const currentItem = ref<AlertItem | undefined>()
15
+
16
+ const renewAlert = () => {
17
+ if (alert?.hasAlert()) {
18
+ currentItem.value = alert?.takeAlert()
19
+ isAlertOpen.value = true
20
+ }
21
+ else {
22
+ currentItem.value = undefined
23
+ isAlertOpen.value = false
24
+ }
25
+ }
26
+
27
+ watch(() => alert?.hasAlert(), (hasAlert) => {
28
+ if (hasAlert) {
29
+ renewAlert()
30
+ }
31
+ })
32
+ </script>
33
+
34
+ <template>
35
+ <VSnackbar
36
+ v-if="currentItem"
37
+ v-model="isAlertOpen"
38
+ :timeout="timeout"
39
+ location="center"
40
+ multi-line
41
+ variant="text"
42
+ @update:model-value="renewAlert()"
43
+ >
44
+ <!-- @vue-expected-error Type conversion problem -->
45
+ <VAlert
46
+ v-model="isAlertOpen"
47
+ closable
48
+ :type="currentItem.alertType"
49
+ elevation="2"
50
+ theme="dark"
51
+ variant="flat"
52
+ v-bind="currentItem.alertIcon ? { icon: currentItem.alertIcon } : {}"
53
+ @click:close="renewAlert()"
54
+ >
55
+ {{ currentItem.statusCode ? currentItem.statusCode + ' ' : '' }} {{ currentItem.message }} {{ !isEmpty(currentItem.data) ? currentItem.data : '' }}
56
+ </VAlert>
57
+ </VSnackbar>
58
+ </template>
@@ -1,122 +1,130 @@
1
- <script lang="ts" setup>
2
- import {BrowserMultiFormatReader} from '@zxing/browser'
3
- import {type IScannerControls} from '@zxing/browser/esm'
4
- import type {Exception, Result} from '@zxing/library'
5
- import {computed, onBeforeUnmount, onMounted, ref, watchEffect} from 'vue'
6
- import {useAlert} from '../composables/alert'
7
- import {useDevicesList, useUserMedia} from "@vueuse/core";
8
-
9
- const barcodeReader = new BrowserMultiFormatReader()
10
- const barcodeReaderControl = ref()
11
-
12
- const alert = useAlert()
13
- const isLoading = ref<boolean>(false)
14
-
15
- const emit = defineEmits<{
16
- (event: 'decode', barcodeValue: string): void
17
- (event: 'error', error: string | unknown): void
18
- }>()
19
-
20
- const videoScreen = ref<HTMLVideoElement>()
21
-
22
- const currentCameraId = ref<ConstrainDOMString | undefined>()
23
- const { videoInputs: cameras } = useDevicesList({
24
- requestPermissions: true,
25
- constraints: { audio: false, video: true },
26
- onUpdated() {
27
- if (!cameras.value.find(camera => camera.deviceId === currentCameraId.value))
28
- currentCameraId.value = cameras.value[0]?.deviceId
29
- },
30
- })
31
- const hasCamera = computed(()=>{ return !!currentCameraId.value })
32
-
33
- const { stream, start: cameraStart, stop: cameraStop, enabled: cameraEnabled } = useUserMedia({
34
- constraints: { video: { deviceId: currentCameraId.value}},
35
- })
36
-
37
- watchEffect(() => {
38
- if (videoScreen.value) videoScreen.value.srcObject = (stream.value) ? stream.value! : null
39
- })
40
-
41
- function startCamera() {
42
- if (!cameraEnabled.value) {
43
- isLoading.value = true
44
- cameraStart().then(()=>{
45
- barcodeReader.decodeFromVideoDevice(currentCameraId.value, videoScreen.value, (result: Result | undefined, error: Exception | undefined, controls: IScannerControls) => {
46
- if (result) {
47
- emit('decode', result.getText())
48
- cameraStop()
49
- }
50
- }).then((result: any)=>{
51
- barcodeReaderControl.value = result
52
- })
53
- }).finally(()=>{
54
- isLoading.value = false
55
- })
56
- }
57
- }
58
-
59
- function stopCamera() {
60
- if (barcodeReaderControl.value) barcodeReaderControl.value.stop()
61
- if (cameraEnabled.value) cameraStop()
62
- }
63
-
64
- function scanImageFile(selectedFile: File | File[] | undefined) {
65
- if (!selectedFile) {
66
- alert?.addAlert({ message: 'No file selected.', alertType: 'error' })
67
- return
68
- }
69
-
70
- const scanImageSingleFile: File = Array.isArray(selectedFile) ? selectedFile[0] : selectedFile
71
-
72
- const reader = new FileReader()
73
- reader.onload = async (event) => {
74
- try {
75
- const result = await barcodeReader.decodeFromImageUrl(event.target?.result as string)
76
- emit('decode', result.getText())
77
- }
78
- catch (e) {
79
- void e
80
- }
81
- }
82
- reader.readAsDataURL(scanImageSingleFile)
83
- }
84
-
85
- onMounted( () => {
86
- startCamera()
87
- })
88
-
89
- onBeforeUnmount( () => {
90
- stopCamera()
91
- })
92
- </script>
93
-
94
- <template>
95
- <v-card flat>
96
- <v-card-text class="d-flex justify-center" v-if="isLoading">
97
- <v-progress-circular indeterminate></v-progress-circular>
98
- </v-card-text>
99
- <v-card-text v-else>
100
- <v-col v-if="hasCamera">
101
- <div style="position: relative; display: inline-block; width: 100%;" :style="{maxWidth: '1024px'}">
102
- <video autoplay ref="videoScreen" width="100%" :style="{maxWidth:'1024px'}"></video>
103
- <div style="position: absolute; bottom: 10px; right: 10px; z-index: 2000;">
104
- <FileBtn
105
- accept="image/*"
106
- icon="mdi mdi-image-plus"
107
- icon-only
108
- @update:model-value="scanImageFile"
109
- />
110
- </div>
111
- </div>
112
- </v-col>
113
- <v-col v-else>
114
- <FileBtn
115
- accept="image/*"
116
- text="Upload Image"
117
- @update:model-value="scanImageFile"
118
- />
119
- </v-col>
120
- </v-card-text>
121
- </v-card>
122
- </template>
1
+ <script lang="ts" setup>
2
+ /**
3
+ * BarcodeReader starts and stops barcode scans, then emits parsed scan results back to parent workflows.
4
+ * This doc block is consumed by vue-docgen for generated API documentation.
5
+ */
6
+ import {BrowserMultiFormatReader} from '@zxing/browser'
7
+ import {type IScannerControls} from '@zxing/browser/esm'
8
+ import type {Exception, Result} from '@zxing/library'
9
+ import {computed, onBeforeUnmount, onMounted, ref, watchEffect} from 'vue'
10
+ import {useAlert} from '../composables/alert'
11
+ import {useDevicesList, useUserMedia} from "@vueuse/core";
12
+
13
+ const barcodeReader = new BrowserMultiFormatReader()
14
+ const barcodeReaderControl = ref()
15
+
16
+ const alert = useAlert()
17
+ const isLoading = ref<boolean>(false)
18
+
19
+ /**
20
+ * Custom events emitted by BarcodeReader.
21
+ * Parents can listen to these events to react to user actions and internal state changes.
22
+ */
23
+ const emit = defineEmits<{
24
+ (event: 'decode', barcodeValue: string): void
25
+ (event: 'error', error: string | unknown): void
26
+ }>()
27
+
28
+ const videoScreen = ref<HTMLVideoElement>()
29
+
30
+ const currentCameraId = ref<ConstrainDOMString | undefined>()
31
+ const { videoInputs: cameras } = useDevicesList({
32
+ requestPermissions: true,
33
+ constraints: { audio: false, video: true },
34
+ onUpdated() {
35
+ if (!cameras.value.find(camera => camera.deviceId === currentCameraId.value))
36
+ currentCameraId.value = cameras.value[0]?.deviceId
37
+ },
38
+ })
39
+ const hasCamera = computed(()=>{ return !!currentCameraId.value })
40
+
41
+ const { stream, start: cameraStart, stop: cameraStop, enabled: cameraEnabled } = useUserMedia({
42
+ constraints: { video: { deviceId: currentCameraId.value}},
43
+ })
44
+
45
+ watchEffect(() => {
46
+ if (videoScreen.value) videoScreen.value.srcObject = (stream.value) ? stream.value! : null
47
+ })
48
+
49
+ function startCamera() {
50
+ if (!cameraEnabled.value) {
51
+ isLoading.value = true
52
+ cameraStart().then(()=>{
53
+ barcodeReader.decodeFromVideoDevice(currentCameraId.value, videoScreen.value, (result: Result | undefined, error: Exception | undefined, controls: IScannerControls) => {
54
+ if (result) {
55
+ emit('decode', result.getText())
56
+ cameraStop()
57
+ }
58
+ }).then((result: any)=>{
59
+ barcodeReaderControl.value = result
60
+ })
61
+ }).finally(()=>{
62
+ isLoading.value = false
63
+ })
64
+ }
65
+ }
66
+
67
+ function stopCamera() {
68
+ if (barcodeReaderControl.value) barcodeReaderControl.value.stop()
69
+ if (cameraEnabled.value) cameraStop()
70
+ }
71
+
72
+ function scanImageFile(selectedFile: File | File[] | undefined) {
73
+ if (!selectedFile) {
74
+ alert?.addAlert({ message: 'No file selected.', alertType: 'error' })
75
+ return
76
+ }
77
+
78
+ const scanImageSingleFile: File = Array.isArray(selectedFile) ? selectedFile[0] : selectedFile
79
+
80
+ const reader = new FileReader()
81
+ reader.onload = async (event) => {
82
+ try {
83
+ const result = await barcodeReader.decodeFromImageUrl(event.target?.result as string)
84
+ emit('decode', result.getText())
85
+ }
86
+ catch (e) {
87
+ void e
88
+ }
89
+ }
90
+ reader.readAsDataURL(scanImageSingleFile)
91
+ }
92
+
93
+ onMounted( () => {
94
+ startCamera()
95
+ })
96
+
97
+ onBeforeUnmount( () => {
98
+ stopCamera()
99
+ })
100
+ </script>
101
+
102
+ <template>
103
+ <v-card flat>
104
+ <v-card-text class="d-flex justify-center" v-if="isLoading">
105
+ <v-progress-circular indeterminate></v-progress-circular>
106
+ </v-card-text>
107
+ <v-card-text v-else>
108
+ <v-col v-if="hasCamera">
109
+ <div style="position: relative; display: inline-block; width: 100%;" :style="{maxWidth: '1024px'}">
110
+ <video autoplay ref="videoScreen" width="100%" :style="{maxWidth:'1024px'}"></video>
111
+ <div style="position: absolute; bottom: 10px; right: 10px; z-index: 2000;">
112
+ <FileBtn
113
+ accept="image/*"
114
+ icon="mdi mdi-image-plus"
115
+ icon-only
116
+ @update:model-value="scanImageFile"
117
+ />
118
+ </div>
119
+ </div>
120
+ </v-col>
121
+ <v-col v-else>
122
+ <FileBtn
123
+ accept="image/*"
124
+ text="Upload Image"
125
+ @update:model-value="scanImageFile"
126
+ />
127
+ </v-col>
128
+ </v-card-text>
129
+ </v-card>
130
+ </template>