@ramathibodi/nuxt-commons 0.1.74 → 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 (96) hide show
  1. package/README.md +115 -115
  2. package/dist/module.json +1 -1
  3. package/dist/runtime/components/Alert.vue +58 -58
  4. package/dist/runtime/components/BarcodeReader.vue +130 -130
  5. package/dist/runtime/components/ExportCSV.vue +110 -110
  6. package/dist/runtime/components/FileBtn.vue +79 -79
  7. package/dist/runtime/components/ImportCSV.vue +151 -151
  8. package/dist/runtime/components/MrzReader.vue +168 -168
  9. package/dist/runtime/components/SplitterPanel.vue +67 -67
  10. package/dist/runtime/components/TabsGroup.vue +39 -39
  11. package/dist/runtime/components/TextBarcode.vue +66 -66
  12. package/dist/runtime/components/device/IdCardButton.vue +95 -95
  13. package/dist/runtime/components/device/IdCardWebSocket.vue +207 -207
  14. package/dist/runtime/components/device/Scanner.vue +350 -350
  15. package/dist/runtime/components/dialog/Confirm.vue +112 -112
  16. package/dist/runtime/components/dialog/Host.vue +88 -88
  17. package/dist/runtime/components/dialog/Index.vue +84 -84
  18. package/dist/runtime/components/dialog/Loading.vue +51 -51
  19. package/dist/runtime/components/dialog/default/Confirm.vue +112 -112
  20. package/dist/runtime/components/dialog/default/Loading.vue +60 -60
  21. package/dist/runtime/components/dialog/default/Notify.vue +82 -82
  22. package/dist/runtime/components/dialog/default/Printing.vue +46 -46
  23. package/dist/runtime/components/dialog/default/VerifyUser.vue +144 -144
  24. package/dist/runtime/components/document/Form.vue +50 -50
  25. package/dist/runtime/components/document/TemplateBuilder.vue +536 -536
  26. package/dist/runtime/components/form/ActionPad.vue +156 -156
  27. package/dist/runtime/components/form/Birthdate.vue +116 -116
  28. package/dist/runtime/components/form/CheckboxGroup.vue +99 -99
  29. package/dist/runtime/components/form/CodeEditor.vue +45 -45
  30. package/dist/runtime/components/form/Date.vue +270 -270
  31. package/dist/runtime/components/form/DateTime.vue +220 -220
  32. package/dist/runtime/components/form/Dialog.vue +178 -178
  33. package/dist/runtime/components/form/EditPad.vue +157 -157
  34. package/dist/runtime/components/form/File.vue +295 -295
  35. package/dist/runtime/components/form/Hidden.vue +44 -44
  36. package/dist/runtime/components/form/Iterator.vue +538 -538
  37. package/dist/runtime/components/form/Login.vue +143 -143
  38. package/dist/runtime/components/form/Pad.vue +399 -399
  39. package/dist/runtime/components/form/SignPad.vue +226 -226
  40. package/dist/runtime/components/form/System.vue +34 -34
  41. package/dist/runtime/components/form/Table.vue +391 -391
  42. package/dist/runtime/components/form/TableData.vue +236 -236
  43. package/dist/runtime/components/form/Time.vue +177 -177
  44. package/dist/runtime/components/form/images/Capture.vue +245 -245
  45. package/dist/runtime/components/form/images/Edit.vue +133 -133
  46. package/dist/runtime/components/form/images/Field.vue +331 -331
  47. package/dist/runtime/components/form/images/Pad.vue +54 -54
  48. package/dist/runtime/components/label/Date.vue +37 -37
  49. package/dist/runtime/components/label/DateAgo.vue +102 -102
  50. package/dist/runtime/components/label/DateCount.vue +152 -152
  51. package/dist/runtime/components/label/Field.vue +111 -111
  52. package/dist/runtime/components/label/FormatMoney.vue +37 -37
  53. package/dist/runtime/components/label/Mask.vue +46 -46
  54. package/dist/runtime/components/label/Object.vue +21 -21
  55. package/dist/runtime/components/master/Autocomplete.vue +89 -89
  56. package/dist/runtime/components/master/Combobox.vue +88 -88
  57. package/dist/runtime/components/master/RadioGroup.vue +90 -90
  58. package/dist/runtime/components/master/Select.vue +70 -70
  59. package/dist/runtime/components/master/label.vue +55 -55
  60. package/dist/runtime/components/model/Autocomplete.vue +91 -91
  61. package/dist/runtime/components/model/Combobox.vue +90 -90
  62. package/dist/runtime/components/model/Pad.vue +114 -114
  63. package/dist/runtime/components/model/Select.vue +78 -84
  64. package/dist/runtime/components/model/Table.vue +370 -370
  65. package/dist/runtime/components/model/iterator.vue +497 -497
  66. package/dist/runtime/components/model/label.vue +58 -58
  67. package/dist/runtime/components/pdf/Print.vue +75 -75
  68. package/dist/runtime/components/pdf/View.vue +146 -146
  69. package/dist/runtime/composables/dialog.d.ts +1 -1
  70. package/dist/runtime/composables/graphql.d.ts +1 -1
  71. package/dist/runtime/composables/graphqlModel.d.ts +9 -9
  72. package/dist/runtime/composables/graphqlModelItem.d.ts +7 -7
  73. package/dist/runtime/composables/graphqlModelOperation.d.ts +6 -6
  74. package/dist/runtime/composables/userPermission.d.ts +1 -1
  75. package/dist/runtime/labs/Calendar.vue +99 -99
  76. package/dist/runtime/labs/form/EditMobile.vue +152 -152
  77. package/dist/runtime/labs/form/TextFieldMask.vue +43 -43
  78. package/dist/runtime/plugins/clientConfig.d.ts +1 -1
  79. package/dist/runtime/plugins/default.d.ts +1 -1
  80. package/dist/runtime/plugins/dialogManager.d.ts +1 -1
  81. package/dist/runtime/plugins/permission.d.ts +1 -1
  82. package/dist/runtime/types/alert.d.ts +11 -11
  83. package/dist/runtime/types/clientConfig.d.ts +13 -13
  84. package/dist/runtime/types/dialogManager.d.ts +35 -35
  85. package/dist/runtime/types/formDialog.d.ts +5 -5
  86. package/dist/runtime/types/graphqlOperation.d.ts +23 -23
  87. package/dist/runtime/types/menu.d.ts +31 -31
  88. package/dist/runtime/types/modules.d.ts +7 -7
  89. package/dist/runtime/types/permission.d.ts +13 -13
  90. package/package.json +131 -131
  91. package/scripts/enrich-vue-docs-from-ai.mjs +197 -197
  92. package/scripts/generate-ai-summary.mjs +321 -321
  93. package/scripts/generate-composables-md.mjs +129 -129
  94. package/scripts/postInstall.cjs +70 -70
  95. package/templates/.codegen/codegen.ts +32 -32
  96. package/templates/.codegen/plugin-schema-object.js +161 -161
package/README.md CHANGED
@@ -1,115 +1,115 @@
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
- ```
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.74",
7
+ "version": "0.1.75",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "0.8.4",
10
10
  "unbuild": "2.0.0"
@@ -1,58 +1,58 @@
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
+ <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,130 +1,130 @@
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>
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>