@bildvitta/quasar-ui-asteroid 2.12.4 → 3.0.0-alpha.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.
Files changed (188) hide show
  1. package/dist/api/QasBox.json +16 -0
  2. package/dist/api/QasBreakline.json +32 -0
  3. package/dist/api/QasBtn.json +15 -0
  4. package/dist/api/QasDebugger.json +13 -0
  5. package/dist/asteroid.cjs.css +1 -0
  6. package/dist/asteroid.cjs.js +9154 -0
  7. package/dist/asteroid.cjs.min.js +6 -0
  8. package/dist/asteroid.esm.css +1 -0
  9. package/dist/asteroid.esm.js +9145 -0
  10. package/dist/asteroid.esm.min.js +6 -0
  11. package/dist/asteroid.umd.css +1 -0
  12. package/dist/asteroid.umd.js +9148 -0
  13. package/dist/asteroid.umd.min.js +6 -0
  14. package/dist/vetur/asteroid-attributes.json +30 -0
  15. package/dist/vetur/asteroid-tags.json +29 -0
  16. package/package.json +42 -56
  17. package/src/assets/logo-modular.svg +1 -0
  18. package/src/asteroid.js +1 -0
  19. package/src/components/actions/QasActions.vue +45 -0
  20. package/src/components/actions-menu/QasActionsMenu.vue +8 -19
  21. package/src/components/alert/QasAlert.vue +90 -0
  22. package/src/components/app-bar/QasAppBar.vue +59 -61
  23. package/src/components/app-menu/QasAppMenu.vue +128 -41
  24. package/src/components/avatar/QasAvatar.vue +7 -3
  25. package/src/components/box/QasBox.vue +12 -4
  26. package/src/components/box/QasBox.yml +13 -0
  27. package/src/components/breakline/QasBreakline.vue +37 -0
  28. package/src/components/breakline/QasBreakline.yml +25 -0
  29. package/src/components/btn/QasBtn.vue +27 -24
  30. package/src/components/btn/QasBtn.yml +12 -0
  31. package/src/components/card/QasCard.vue +29 -21
  32. package/src/components/checkbox-group/QasCheckboxGroup.vue +31 -17
  33. package/src/components/copy/QasCopy.vue +22 -11
  34. package/src/components/date-time-input/QasDateTimeInput.vue +16 -26
  35. package/src/components/debugger/QasDebugger.vue +2 -0
  36. package/src/components/debugger/QasDebugger.yml +10 -0
  37. package/src/components/delete/QasDelete.vue +28 -15
  38. package/src/components/dialog/QasDialog.vue +71 -67
  39. package/src/components/dialog-router/QasDialogRouter.vue +12 -4
  40. package/src/components/field/QasField.vue +22 -25
  41. package/src/components/filters/QasFilters.vue +31 -24
  42. package/src/components/form-generator/QasFormGenerator.vue +13 -15
  43. package/src/components/form-view/QasFormView.vue +117 -66
  44. package/src/components/gallery/QasGallery.vue +39 -26
  45. package/src/components/grid-generator/QasGridGenerator.vue +12 -6
  46. package/src/components/index.js +0 -0
  47. package/src/components/input/QasInput.vue +38 -36
  48. package/src/components/label/QasLabel.vue +14 -15
  49. package/src/components/layout/QasLayout.vue +81 -0
  50. package/src/components/list-items/QasListItems.vue +16 -8
  51. package/src/components/list-view/QasListView.vue +31 -28
  52. package/src/components/map/QasMap.vue +15 -25
  53. package/src/components/nested-fields/QasNestedFields.vue +39 -36
  54. package/src/components/numeric-input/QasNumericInput.vue +125 -0
  55. package/src/components/page-header/QasPageHeader.vue +19 -10
  56. package/src/components/password-input/QasPasswordInput.vue +20 -18
  57. package/src/components/password-strength-checker/QasPasswordStrengthChecker.vue +52 -31
  58. package/src/components/profile/QasProfile.vue +14 -12
  59. package/src/components/resizer/QasResizer.vue +1 -1
  60. package/src/components/search-box/QasSearchBox.vue +36 -20
  61. package/src/components/select/QasSelect.vue +43 -44
  62. package/src/components/select-list/QasSelectList.vue +64 -51
  63. package/src/components/signature-pad/QasSignaturePad.vue +57 -41
  64. package/src/components/signature-uploader/QasSignatureUploader.vue +15 -13
  65. package/src/components/single-view/QasSingleView.vue +31 -17
  66. package/src/components/sortable/QasSortable.vue +45 -27
  67. package/src/components/table-generator/QasTableGenerator.vue +95 -22
  68. package/src/components/tabs-generator/QasTabsGenerator.vue +36 -24
  69. package/src/components/text-truncate/QasTextTruncate.vue +25 -17
  70. package/src/components/transfer/QasTransfer.vue +57 -53
  71. package/src/components/uploader/QasUploader.vue +169 -48
  72. package/src/css/background.scss +1 -1
  73. package/src/css/border.scss +7 -6
  74. package/src/css/design-system.scss +0 -43
  75. package/src/css/fonts.scss +2 -28
  76. package/src/css/opacity.scss +0 -4
  77. package/src/css/set-brand.scss +15 -0
  78. package/src/css/transitions.scss +1 -1
  79. package/src/helpers/add-counter-suffix.js +3 -0
  80. package/src/helpers/{base64ToBlob.js → base-64-to-blob.js} +0 -0
  81. package/src/helpers/{constructObject.js → construct-object.js} +0 -0
  82. package/src/helpers/filter-object.js +8 -6
  83. package/src/helpers/filters.js +3 -4
  84. package/src/helpers/get-slot-children-text.js +15 -0
  85. package/src/helpers/{greatestCommonDivisor.js → greatest-common-divisor.js} +0 -0
  86. package/src/helpers/images.js +28 -0
  87. package/src/helpers/index.js +11 -57
  88. package/src/helpers/is-local-development.js +3 -0
  89. package/src/helpers/scroll-on-grap.js +61 -0
  90. package/src/index.cjs.js +1 -0
  91. package/src/index.esm.js +4 -0
  92. package/src/index.scss +18 -20
  93. package/src/index.umd.js +2 -0
  94. package/src/mixins/context.js +1 -1
  95. package/src/mixins/dialog-router.js +17 -0
  96. package/src/mixins/form.js +4 -12
  97. package/src/mixins/generator.js +14 -14
  98. package/src/mixins/index.js +2 -8
  99. package/src/mixins/password.js +73 -11
  100. package/src/mixins/screen.js +8 -6
  101. package/src/mixins/view.js +57 -20
  102. package/src/plugins/Dialog.js +14 -0
  103. package/src/plugins/NotifySuccess.js +3 -3
  104. package/src/plugins/index.js +4 -2
  105. package/src/store/history.js +43 -0
  106. package/src/store/index.js +1 -0
  107. package/src/vue-plugin.js +185 -0
  108. package/.babelrc +0 -12
  109. package/.storybook/main.js +0 -35
  110. package/.storybook/preview.js +0 -26
  111. package/debug.log +0 -1
  112. package/index.js +0 -4
  113. package/jest-setup.js +0 -1
  114. package/jest.config.json +0 -22
  115. package/postcss.config.js +0 -5
  116. package/src/components/Introduction.stories.mdx +0 -12
  117. package/src/components/actions-menu/QasActionsMenu.stories.js +0 -73
  118. package/src/components/app-bar/QasAppBar.stories.js +0 -88
  119. package/src/components/app-menu/QasAppMenu.stories.js +0 -62
  120. package/src/components/apps-menu/QasAppsMenu.spec.js +0 -58
  121. package/src/components/apps-menu/QasAppsMenu.stories.js +0 -54
  122. package/src/components/apps-menu/QasAppsMenu.vue +0 -48
  123. package/src/components/avatar/QasAvatar.spec.js +0 -14
  124. package/src/components/avatar/QasAvatar.stories.js +0 -52
  125. package/src/components/box/QasBox.spec.js +0 -18
  126. package/src/components/box/QasBox.stories.js +0 -35
  127. package/src/components/break-line/QasBreakLine.stories.js +0 -57
  128. package/src/components/break-line/QasBreakLine.vue +0 -52
  129. package/src/components/btn/QasBtn.stories.js +0 -45
  130. package/src/components/btn-actions/QasBtnActions.stories.js +0 -77
  131. package/src/components/btn-actions/QasBtnActions.vue +0 -54
  132. package/src/components/card/QasCard.stories.js +0 -126
  133. package/src/components/checkbox-group/QasCheckboxGroup.stories.js +0 -59
  134. package/src/components/copy/QasCopy.stories.js +0 -41
  135. package/src/components/date-time-input/QasDateTimeInput.stories.js +0 -67
  136. package/src/components/debugger/QasDebugger.stories.js +0 -33
  137. package/src/components/decimal-input/QasDecimalInput.stories.js +0 -82
  138. package/src/components/decimal-input/QasDecimalInput.vue +0 -92
  139. package/src/components/delete/QasDelete.stories.js +0 -80
  140. package/src/components/dialog/QasDialog.stories.js +0 -139
  141. package/src/components/dialog-router/QasDialogRouter.stories.js +0 -38
  142. package/src/components/field/QasField.stories.js +0 -181
  143. package/src/components/filters/QasFilters.stories.js +0 -121
  144. package/src/components/form-generator/QasFormGenerator.stories.js +0 -115
  145. package/src/components/form-view/QasFormView.stories.js +0 -236
  146. package/src/components/gallery/QasGallery.stories.js +0 -91
  147. package/src/components/grid-generator/QasGridGenerator.stories.js +0 -138
  148. package/src/components/input/QasInput.stories.js +0 -78
  149. package/src/components/label/QasLabel.stories.js +0 -60
  150. package/src/components/list-items/QasListItems.stories.js +0 -130
  151. package/src/components/list-view/QasListView.stories.js +0 -168
  152. package/src/components/map/QasMap.stories.js +0 -75
  153. package/src/components/nested-fields/QasNestedFields.stories.js +0 -255
  154. package/src/components/page-header/QasPageHeader.stories.js +0 -61
  155. package/src/components/password-input/QasPasswordInput.stories.js +0 -76
  156. package/src/components/password-strength-checker/QasPasswordStrengthChecker.stories.js +0 -54
  157. package/src/components/profile/QasProfile.stories.js +0 -131
  158. package/src/components/resizer/QasResizer.stories.js +0 -43
  159. package/src/components/search-box/QasSearchBox.stories.js +0 -111
  160. package/src/components/select/QasSelect.stories.js +0 -113
  161. package/src/components/select-list/QasSelectList.stories.js +0 -153
  162. package/src/components/signature-pad/QasSignaturePad.stories.js +0 -51
  163. package/src/components/signature-uploader/QasSignatureUploader.stories.js +0 -69
  164. package/src/components/single-view/QasSingleView.stories.js +0 -130
  165. package/src/components/sortable/QasSortable.stories.js +0 -80
  166. package/src/components/table-generator/QasTableGenerator.stories.js +0 -116
  167. package/src/components/tabs-generator/QasTabsGenerator.stories.js +0 -145
  168. package/src/components/text-truncate/QasTextTruncate.stories.js +0 -55
  169. package/src/components/tip/QasTip.stories.js +0 -57
  170. package/src/components/tip/QasTip.vue +0 -68
  171. package/src/components/tooltip/QasTooltip.stories.js +0 -63
  172. package/src/components/tooltip/QasTooltip.vue +0 -81
  173. package/src/components/transfer/QasTransfer.stories.js +0 -118
  174. package/src/components/uploader/QasCustomUploader.vue +0 -121
  175. package/src/components/uploader/QasUploader.stories.js +0 -139
  176. package/src/directives/Test.js +0 -13
  177. package/src/helpers/historyHandler.js +0 -52
  178. package/src/helpers/label.js +0 -3
  179. package/src/index.js +0 -245
  180. package/src/mixins/map-markers.js +0 -26
  181. package/src/mixins/unsaved-changes.js +0 -24
  182. package/src/mixins/uploader.js +0 -30
  183. package/src/mocks/json/user.json +0 -27
  184. package/src/mocks/json/users-new.json +0 -23
  185. package/src/mocks/json/users.json +0 -97
  186. package/src/mocks/storeModule.js +0 -71
  187. package/src/pages/Forbidden.vue +0 -6
  188. package/src/pages/NotFound.vue +0 -6
@@ -1,14 +1,15 @@
1
1
  <template>
2
2
  <div class="qas-transfer row" :class="gutterClass">
3
+ <div class="col-12" />
3
4
  <div class="col-12 col-sm">
4
5
  <qas-label :label="label" :quantity="optionsList.length" />
5
6
 
6
7
  <qas-search-box form-mode :list="optionsList" v-bind="searchBoxProps">
7
8
  <template #default="{ results }">
8
9
  <q-list separator>
9
- <q-item v-for="(item, index) in results" :key="index" :class="itemClass(item, true)" clickable @click="onSelectQueue(item, true)">
10
+ <q-item v-for="(item, index) in results" :key="index" :class="getItemClass(item, true)" clickable @click="onSelectQueue(item, true)">
10
11
  <slot name="item-first-column">
11
- <q-item-section>{{ item[labelKey] }}</q-item-section>
12
+ <q-item-section>{{ getItemLabel(item) }}</q-item-section>
12
13
  </slot>
13
14
  </q-item>
14
15
  </q-list>
@@ -18,10 +19,8 @@
18
19
 
19
20
  <div class="col-12 col-sm-auto items-center justify-center q-col-gutter-md row" :class="actionsClass">
20
21
  <div>
21
- <div>
22
- <qas-btn :class="iconClass" dense :disabled="!firstQueue.length" flat icon="o_arrow_circle_down" rounded @click="setSelectedFromClick(true)" />
23
- <q-tooltip anchor="top middle" self="center middle">Selecionar</q-tooltip>
24
- </div>
22
+ <qas-btn :class="iconClass" dense :disabled="!firstQueue.length" flat icon="o_arrow_circle_down" rounded @click="setSelectedFromClick(true)" />
23
+ <q-tooltip anchor="top middle" self="center middle">Selecionar</q-tooltip>
25
24
  </div>
26
25
  <div>
27
26
  <div>
@@ -34,12 +33,12 @@
34
33
  <div class="col-12 col-sm">
35
34
  <qas-label label="Selecionadas" :quantity="selectedList.length" />
36
35
 
37
- <qas-search-box v-bind="searchBoxProps" empty-list-height="300px" form-mode label="Selecionadas" :list="selectedList" :quantity="selectedList.length">
36
+ <qas-search-box v-bind="searchBoxProps" empty-list-height="300px" form-mode label="Selecionadas" :list="selectedList">
38
37
  <template #default="{ results }">
39
38
  <q-list separator>
40
- <q-item v-for="(item, index) in results" :key="index" :class="itemClass(item)" clickable @click="onSelectQueue(item)">
39
+ <q-item v-for="(item, index) in results" :key="index" :class="getItemClass(item)" clickable @click="onSelectQueue(item)">
41
40
  <slot name="item-second-column">
42
- <q-item-section>{{ item[labelKey] }}</q-item-section>
41
+ <q-item-section>{{ getItemLabel(item) }}</q-item-section>
43
42
  </slot>
44
43
  </q-item>
45
44
  </q-list>
@@ -50,19 +49,24 @@
50
49
  </template>
51
50
 
52
51
  <script>
52
+ import { screenMixin } from '../../mixins'
53
53
  import { extend } from 'quasar'
54
54
 
55
- import QasBtn from '../btn/QasBtn'
56
- import QasLabel from '../label/QasLabel'
57
- import QasSearchBox from '../search-box/QasSearchBox'
55
+ import QasBtn from '../btn/QasBtn.vue'
56
+ import QasLabel from '../label/QasLabel.vue'
57
+ import QasSearchBox from '../search-box/QasSearchBox.vue'
58
58
 
59
59
  export default {
60
+ name: 'QasTransfer',
61
+
60
62
  components: {
61
63
  QasBtn,
62
64
  QasLabel,
63
65
  QasSearchBox
64
66
  },
65
67
 
68
+ mixins: [screenMixin],
69
+
66
70
  props: {
67
71
  emitValue: {
68
72
  type: Boolean
@@ -73,12 +77,6 @@ export default {
73
77
  type: Object
74
78
  },
75
79
 
76
- hideEmptySlot: {
77
- default: true,
78
- type: Boolean
79
- },
80
-
81
- // TODO: Criar o "toLabel" para o slugar de selecionados.
82
80
  label: {
83
81
  default: '',
84
82
  required: true,
@@ -90,22 +88,29 @@ export default {
90
88
  type: String
91
89
  },
92
90
 
93
- options: {
91
+ modelValue: {
94
92
  default: () => [],
95
93
  type: Array
96
94
  },
97
95
 
98
- value: {
96
+ options: {
99
97
  default: () => [],
100
98
  type: Array
101
99
  },
102
100
 
101
+ useEmptySlot: {
102
+ default: true,
103
+ type: Boolean
104
+ },
105
+
103
106
  valueKey: {
104
107
  default: 'value',
105
108
  type: String
106
109
  }
107
110
  },
108
111
 
112
+ emits: ['update:modelValue'],
113
+
109
114
  data () {
110
115
  return {
111
116
  firstQueue: [],
@@ -117,50 +122,46 @@ export default {
117
122
 
118
123
  computed: {
119
124
  actionsClass () {
120
- return !this.isSmall && 'column'
125
+ return !this.mx_isSmall && 'column'
121
126
  },
122
127
 
123
128
  gutterClass () {
124
- return `q-col-gutter-${this.isMedium ? 'md' : 'xl'}`
129
+ return `q-col-gutter-${this.mx_untilLarge ? 'md' : 'xl'}`
125
130
  },
126
131
 
127
132
  iconClass () {
128
- return !this.isSmall && 'qas-transfer__icon'
129
- },
130
-
131
- isMedium () {
132
- return this.$q.screen.lt.md
133
- },
134
-
135
- // TODO: Small seria se fosse sm.
136
- isSmall () {
137
- return this.$q.screen.xs
133
+ return !this.mx_isSmall && 'qas-transfer__icon'
138
134
  },
139
135
 
140
136
  searchBoxProps () {
141
137
  return {
142
138
  fuseOptions: this.fuseOptions,
143
- hideEmptySlot: this.hideEmptySlot,
144
- list: this.options
139
+ useEmptySlot: this.useEmptySlot
145
140
  }
146
141
  }
147
142
  },
148
143
 
149
144
  watch: {
150
- options: {
151
- handler (value) {
152
- this.optionsList = extend(true, [], value)
145
+ modelValue: {
146
+ handler () {
147
+ this.setSelectedFromValue(true)
153
148
  },
154
149
 
155
150
  immediate: true
156
151
  },
157
152
 
158
- value: {
159
- handler (value, oldValue) {
160
- this.setSelectedFromValue(true)
153
+ options: {
154
+ handler (value) {
155
+ this.optionsList = extend(true, [], value)
161
156
  },
162
157
 
163
158
  immediate: true
159
+ },
160
+
161
+ selectedList: {
162
+ handler () {
163
+ this.updateModelValue()
164
+ }
164
165
  }
165
166
  },
166
167
 
@@ -178,7 +179,18 @@ export default {
178
179
  })
179
180
  },
180
181
 
181
- handleEmit () {
182
+ getItemClass (object, isFirst) {
183
+ return this[isFirst
184
+ ? 'firstQueue'
185
+ : 'secondQueue'
186
+ ].some(item => item[this.valueKey] === object[this.valueKey]) && 'bg-secondary'
187
+ },
188
+
189
+ getItemLabel (item) {
190
+ return item?.[this.labelKey]
191
+ },
192
+
193
+ getModelValue () {
182
194
  const selectedList = extend(true, [], this.selectedList)
183
195
  return this.emitValue ? selectedList.map(item => item[this.valueKey]) : selectedList
184
196
  },
@@ -191,27 +203,19 @@ export default {
191
203
  this[model] = []
192
204
  },
193
205
 
194
- itemClass (object, isFirst) {
195
- return this[isFirst
196
- ? 'firstQueue'
197
- : 'secondQueue'
198
- ].some(item => item[this.valueKey] === object[this.valueKey]) && 'bg-secondary'
199
- },
200
-
201
206
  onSelectQueue (item, isFirst) {
202
207
  const model = isFirst ? 'firstQueue' : 'secondQueue'
203
208
  const index = this[model].findIndex(selected => selected[this.valueKey] === item[this.valueKey])
204
209
 
205
- return ~index ? this[model].splice(index, 1) : this[model].push(item)
210
+ ~index ? this[model].splice(index, 1) : this[model].push(item)
206
211
  },
207
212
 
208
213
  setSelectedFromClick (isFirst) {
209
214
  this.handleSelectedList(isFirst)
210
- this.updateValue()
211
215
  },
212
216
 
213
217
  setSelectedFromValue (isFirst) {
214
- this.value.forEach(item => {
218
+ this.modelValue.forEach(item => {
215
219
  const selected = this.optionsList.find(option => {
216
220
  return option[this.valueKey] === (this.emitValue ? item : item[this.valueKey])
217
221
  })
@@ -224,8 +228,8 @@ export default {
224
228
  this.handleSelectedList(isFirst)
225
229
  },
226
230
 
227
- updateValue () {
228
- return this.$emit('input', this.handleEmit())
231
+ updateModelValue () {
232
+ return this.$emit('update:modelValue', this.getModelValue())
229
233
  }
230
234
  }
231
235
  }
@@ -1,24 +1,25 @@
1
1
  <template>
2
- <q-field borderless :error="hasErrorMessage" :error-message="errorMessage" :hint="hintValue" no-error-icon>
3
- <qas-custom-uploader v-bind="attributes" ref="uploader" auto-upload bordered :class="uploaderClasses" :factory="factory" flat :max-files="maxFiles" method="PUT" :readonly="readonly" v-on="$listeners" @factory-failed="factoryFailed" @uploaded="uploaded">
2
+ <q-field borderless class="qas-uploader" :error="hasErrorMessage" :error-message="errorMessage" :hint="hintValue" no-error-icon>
3
+ <q-uploader ref="uploader" auto-upload bordered :class="uploaderClasses" :factory="factory" flat :max-files="maxFiles" method="PUT" :readonly="readonly" v-bind="attributes" @factory-failed="factoryFailed" @uploaded="uploaded" @uploading="updateUploading(true)">
4
4
  <template #header="scope">
5
5
  <slot name="header" :scope="scope">
6
6
  <div class="flex flex-center full-width justify-between no-border no-wrap q-gutter-xs q-pa-sm text-white transparent">
7
7
  <q-spinner v-if="scope.isUploading" size="24px" />
8
8
 
9
9
  <div class="col column items-start justify-center">
10
- <div v-if="scope.label" class="q-uploader__title">{{ scope.label }}</div>
11
- <div v-if="scope.files.length" class="q-uploader__subtitle">{{ scope.uploadProgressLabel }} ({{ scope.uploadSizeLabel }})</div>
10
+ <div v-if="$attrs.label" class="q-uploader__title">{{ $attrs.label }}</div>
11
+ <div v-if="scope.files.length" class="q-uploader__subtitle">
12
+ {{ scope.uploadProgressLabel }} ({{ scope.uploadSizeLabel }})
13
+ </div>
12
14
  </div>
13
15
 
14
- <q-btn v-if="showAddFile" ref="buttonUpload" dense flat icon="o_add" round />
15
-
16
- <q-uploader-add-trigger v-if="showAddFile" ref="uploaderTrigger" />
16
+ <qas-btn v-if="showAddFile" ref="buttonUpload" color="white" dense flat icon="o_add" round @click="$refs.hidenInput.click()" />
17
17
 
18
- <q-btn ref="buttonCleanFiles" class="hidden" @click="scope.removeUploadedFiles" />
18
+ <input ref="hidenInput" class="qas-uploader__input" :multiple="isMultiple" type="file">
19
19
 
20
- <q-btn v-if="scope.canUpload" dense flat icon="o_cloud_upload" round @click="scope.upload" />
21
- <q-btn v-if="scope.isUploading" dense flat icon="o_clear" round @click="scope.abort" />
20
+ <qas-btn ref="buttonCleanFiles" class="hidden" color="white" @click="scope.removeUploadedFiles" />
21
+ <qas-btn v-if="scope.canUpload" color="white" dense flat icon="o_cloud_upload" round @click="scope.upload" />
22
+ <qas-btn v-if="scope.isUploading" color="white" dense flat icon="o_clear" round @click="scope.abort" />
22
23
  </div>
23
24
  </slot>
24
25
  </template>
@@ -26,24 +27,24 @@
26
27
  <template #list="scope">
27
28
  <slot name="list" :scope="scope">
28
29
  <div class="col-12 q-col-gutter-md row">
29
- <div v-for="(file, index) in filesList(scope.files, scope)" :key="index" class="row" :class="itemClass">
30
- <qas-avatar class="q-mr-sm" color="grey-3" icon="o_attach_file" :image="file.image" rounded :text-color="colorFileIcon(file)" />
30
+ <div v-for="(file, index) in getFilesList(scope.files, scope)" :key="index" class="row" :class="itemClass">
31
+ <qas-avatar class="q-mr-sm" color="grey-3" icon="o_attach_file" :image="file.image" rounded :text-color="getColorFileIcon(file)" />
31
32
 
32
33
  <div class="col items-center no-wrap row">
33
34
  <div class="column no-wrap" :class="{ col: isMultiple }">
34
- <div class="ellipsis" :class="fileNameClass(file.isFailed)">{{ file.name }}</div>
35
+ <div class="ellipsis" :class="getFileNameClass(file.isFailed)">{{ file.name }}</div>
35
36
  <div v-if="file.isUploaded" class="text-caption">{{ file.progressLabel }} ({{ file.sizeLabel }})</div>
36
37
  </div>
37
38
  <div class="items-center q-ml-sm row">
38
39
  <q-icon v-if="file.isFailed" color="negative" name="o_warning" size="20px" />
39
- <q-btn v-if="!scope.readonly" dense flat icon="o_delete" round @click="removeItem(index, scope, file)" />
40
+ <qas-btn v-if="!scope.readonly" dense flat icon="o_delete" round @click="removeItem(index, scope, file)" />
40
41
  </div>
41
42
  </div>
42
43
  </div>
43
44
  </div>
44
45
  </slot>
45
46
  </template>
46
- </qas-custom-uploader>
47
+ </q-uploader>
47
48
 
48
49
  <slot :context="self" name="custom-upload" />
49
50
 
@@ -54,24 +55,37 @@
54
55
  </template>
55
56
 
56
57
  <script>
57
- import QasAvatar from '../avatar/QasAvatar'
58
- import QasCustomUploader from '../uploader/QasCustomUploader'
59
- import api from 'axios'
58
+ import QasAvatar from '../avatar/QasAvatar.vue'
59
+
60
60
  import { uid, extend } from 'quasar'
61
61
  import { NotifyError } from '../../plugins'
62
- import uploaderMixin from '../../mixins/uploader'
62
+ import { getImageSize, getResizeDimensions } from '../../helpers/images'
63
+
64
+ import Pica from 'pica'
65
+ import api from 'axios'
63
66
 
64
67
  export default {
65
68
  name: 'QasUploader',
66
69
 
67
70
  components: {
68
- QasAvatar,
69
- QasCustomUploader
71
+ QasAvatar
70
72
  },
71
73
 
72
- mixins: [uploaderMixin],
74
+ inheritAttrs: false,
73
75
 
74
76
  props: {
77
+ acceptResizeTypes: {
78
+ default: () => [
79
+ 'image/jpeg',
80
+ 'image/png',
81
+ 'image/gif',
82
+ 'image/bmp',
83
+ 'image/webp',
84
+ 'image/jpg'
85
+ ],
86
+ type: Array
87
+ },
88
+
75
89
  entity: {
76
90
  required: true,
77
91
  type: String
@@ -92,20 +106,43 @@ export default {
92
106
  type: Number
93
107
  },
94
108
 
109
+ picaResizeOptions: {
110
+ default: () => ({}),
111
+ type: Object
112
+ },
113
+
114
+ sizeLimit: {
115
+ default: 1280,
116
+ type: Number
117
+ },
118
+
119
+ useResize: {
120
+ default: true,
121
+ type: Boolean
122
+ },
123
+
95
124
  readonly: {
96
125
  type: Boolean
97
126
  },
98
127
 
99
- value: {
128
+ modelValue: {
100
129
  default: '',
101
130
  type: [Array, String]
131
+ },
132
+
133
+ uploading: {
134
+ type: Boolean
102
135
  }
103
136
  },
104
137
 
138
+ emits: ['update:modelValue', 'update:uploading'],
139
+
105
140
  data () {
106
141
  return {
107
142
  isFetching: false,
108
- isUploading: false
143
+ isUploading: false,
144
+ hidenInputElement: null,
145
+ uploader: null
109
146
  }
110
147
  },
111
148
 
@@ -121,7 +158,7 @@ export default {
121
158
  showAddFile () {
122
159
  if (this.readonly) return
123
160
 
124
- return this.maxFiles ? this.value.length < this.maxFiles : true
161
+ return this.maxFiles ? this.modelValue.length < this.maxFiles : true
125
162
  },
126
163
 
127
164
  isMultiple () {
@@ -129,7 +166,7 @@ export default {
129
166
  },
130
167
 
131
168
  hasCustomUpload () {
132
- return this.$slots['custom-upload'] || this.$scopedSlots['custom-upload']
169
+ return this.$slots['custom-upload']
133
170
  },
134
171
 
135
172
  itemClass () {
@@ -141,7 +178,7 @@ export default {
141
178
  },
142
179
 
143
180
  hasHeaderSlot () {
144
- return this.$slots.header || this.$scopedSlots.header
181
+ return this.$slots.header
145
182
  },
146
183
 
147
184
  hasErrorMessage () {
@@ -153,19 +190,37 @@ export default {
153
190
  ...this.$attrs,
154
191
  ...this.$props
155
192
  }
193
+ },
194
+
195
+ defaultPicaResizeOptions () {
196
+ return {
197
+ unsharpAmount: 160,
198
+ unsharpRadius: 0.6,
199
+ unsharpThreshold: 1,
200
+ ...this.picaResizeOptions
201
+ }
156
202
  }
157
203
  },
158
204
 
159
- methods: {
160
- NotifyError,
205
+ mounted () {
206
+ this.hidenInputElement = this.$refs.hidenInput
207
+ this.uploader = this.$refs.uploader
208
+
209
+ this.hidenInputElement?.addEventListener?.('change', this.addFiles)
210
+ },
161
211
 
212
+ unmounted () {
213
+ this.hidenInputElement?.removeEventListener?.('change', this.addFiles)
214
+ },
215
+
216
+ methods: {
162
217
  async factory ([file]) {
163
218
  if (!this.isMultiple && !this.hasHeaderSlot) {
164
219
  this.$refs.buttonCleanFiles.$el.click()
165
220
  }
166
221
 
167
222
  const name = `${uid()}.${file.name.split('.').pop()}`
168
- const { endpoint } = await this.fetch(name)
223
+ const { endpoint } = await this.fetchCredentials(name)
169
224
 
170
225
  return {
171
226
  headers: [
@@ -178,16 +233,27 @@ export default {
178
233
  },
179
234
 
180
235
  factoryFailed () {
236
+ this.updateUploading(false)
181
237
  NotifyError('Ops! Erro ao enviar o arquivo.')
182
238
  },
183
239
 
240
+ dispatchUpload () {
241
+ this.$refs.buttonCleanFiles.$el.click()
242
+ this.hidenInputElement.click()
243
+ },
244
+
184
245
  uploaded (response) {
185
246
  const fullPath = response.xhr.responseURL.split('?').shift()
186
247
 
187
- this.$emit('input', this.isMultiple ? [...this.value, fullPath] : fullPath || '')
248
+ this.$emit(
249
+ 'update:modelValue',
250
+ this.isMultiple ? [...this.modelValue, fullPath] : fullPath || ''
251
+ )
252
+
253
+ this.updateUploading(false)
188
254
  },
189
255
 
190
- async fetch (filename) {
256
+ async fetchCredentials (filename) {
191
257
  this.isFetching = true
192
258
 
193
259
  try {
@@ -209,26 +275,21 @@ export default {
209
275
  if (file.isFailed) return
210
276
 
211
277
  if (!this.isMultiple) {
212
- return this.$emit('input')
278
+ return this.$emit('update:modelValue')
213
279
  }
214
280
 
215
- const clonedValue = extend(true, [], this.value)
216
- const numberIndex = this.value.findIndex(file => this.fileName(file) === index)
281
+ const clonedValue = extend(true, [], this.modelValue)
282
+ const numberIndex = this.modelValue.findIndex(file => this.getFileName(file) === index)
217
283
  clonedValue.splice(numberIndex, 1)
218
- this.$emit('input', clonedValue)
219
- },
220
-
221
- dispatchUpload () {
222
- this.$refs.buttonCleanFiles.$el.click()
223
- this.$refs.uploaderTrigger.$el.click()
284
+ this.$emit('update:modelValue', clonedValue)
224
285
  },
225
286
 
226
- fileName (value) {
287
+ getFileName (value) {
227
288
  return value.split('/').pop()
228
289
  },
229
290
 
230
- filesList (uploadedFiles) {
231
- const pathsList = Array.isArray(this.value) ? this.value : (this.value ? [this.value] : [])
291
+ getFilesList (uploadedFiles) {
292
+ const pathsList = Array.isArray(this.modelValue) ? this.modelValue : (this.modelValue ? [this.modelValue] : [])
232
293
 
233
294
  uploadedFiles = uploadedFiles.map((file, indexToDelete) => {
234
295
  return {
@@ -253,13 +314,13 @@ export default {
253
314
  }
254
315
 
255
316
  if (typeof file === 'string') {
256
- const fileName = this.fileName(file)
317
+ const fileName = this.getFileName(file)
257
318
  files[fileName] = { image: file, isUploaded: false, name: fileName }
258
319
  return
259
320
  }
260
321
 
261
322
  if (file.image) {
262
- const fileName = this.fileName(file.image)
323
+ const fileName = this.getFileName(file.image)
263
324
  files[fileName] = file
264
325
  }
265
326
  })
@@ -267,7 +328,7 @@ export default {
267
328
  return files
268
329
  },
269
330
 
270
- fileNameClass (isFailed) {
331
+ getFileNameClass (isFailed) {
271
332
  return isFailed ? 'text-negative' : 'text-grey-8'
272
333
  },
273
334
 
@@ -275,9 +336,69 @@ export default {
275
336
  return file.__status === 'failed'
276
337
  },
277
338
 
278
- colorFileIcon (file) {
339
+ getColorFileIcon (file) {
279
340
  return this.isFailed(file) ? 'negative' : 'primary'
341
+ },
342
+
343
+ async addFiles () {
344
+ const filesList = Array.from(this.hidenInputElement.files)
345
+ const processedFiles = []
346
+
347
+ filesList.forEach(file => processedFiles.push(this.resizeImage(file)))
348
+ this.uploader.addFiles(await Promise.all(processedFiles))
349
+ },
350
+
351
+ // Função para redimensionar imagens
352
+ async resizeImage (file) {
353
+ const { type } = file
354
+
355
+ if (!this.acceptResizeTypes.includes(type) || !this.useResize) return file
356
+
357
+ try {
358
+ const image = new Image()
359
+ const canvas = document.createElement('canvas')
360
+
361
+ image.src = URL.createObjectURL(file)
362
+
363
+ // Retorna largura e altura da original da imagem
364
+ const { width, height } = await getImageSize(image)
365
+
366
+ if (width <= this.sizeLimit) return file
367
+
368
+ // Retorna os novos tamanhos redimensionados
369
+ const resizedDimensions = getResizeDimensions(this.sizeLimit, width, height)
370
+
371
+ canvas.width = resizedDimensions.width
372
+ canvas.height = resizedDimensions.height
373
+
374
+ // Resolve problemas com cors
375
+ image.crossOrigin = ''
376
+
377
+ image.width = width
378
+ image.height = height
379
+
380
+ const pica = Pica()
381
+ const resizedImage = await pica.resize(image, canvas, this.defaultPicaResizeOptions)
382
+ const blob = await pica.toBlob(resizedImage, type, 0.90)
383
+
384
+ return new File([blob], file.name, { type })
385
+ } catch {
386
+ // Caso não consiga redimensionar retorna o arquivo original
387
+ return file
388
+ }
389
+ },
390
+
391
+ updateUploading (uploading) {
392
+ this.$emit('update:uploading', uploading)
280
393
  }
281
394
  }
282
395
  }
283
396
  </script>
397
+
398
+ <style lang="scss">
399
+ .qas-uploader {
400
+ &__input {
401
+ display: none;
402
+ }
403
+ }
404
+ </style>
@@ -1,4 +1,4 @@
1
- // Reapeat
1
+ // Repeat
2
2
  .bg-no-repeat {
3
3
  background-repeat: no-repeat !important;
4
4
  }
@@ -5,17 +5,18 @@
5
5
  }
6
6
  }
7
7
 
8
- @mixin setBorderColor($name, $color) {
8
+ @mixin set-border-color($name, $color) {
9
9
  .border-#{$name} {
10
- @extend %border-color;
11
10
  border: 0 solid $color !important;
11
+
12
+ @extend %border-color;
12
13
  }
13
14
  }
14
15
 
15
- @include setBorderColor(primary, $primary);
16
- @include setBorderColor(primary-contrast, $primary-contrast);
17
- @include setBorderColor(secondary, $secondary);
18
- @include setBorderColor(secondary-contrast, $secondary-contrast);
16
+ @include set-border-color(primary, $primary);
17
+ @include set-border-color(primary-contrast, $primary-contrast);
18
+ @include set-border-color(secondary, $secondary);
19
+ @include set-border-color(secondary-contrast, $secondary-contrast);
19
20
 
20
21
  // Direction
21
22
  .border-top {