renusify 2.5.1 → 3.0.0

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 (212) hide show
  1. package/components/app/index.vue +74 -22
  2. package/components/app/toast/index.vue +76 -71
  3. package/components/app/toast/toast.vue +62 -44
  4. package/components/avatar/index.vue +208 -84
  5. package/components/button/buttonConfirm.vue +53 -26
  6. package/components/button/buttonGroup.js +0 -2
  7. package/components/button/buttonGroup.vue +310 -62
  8. package/components/button/index.vue +584 -100
  9. package/components/calendar/index.js +0 -2
  10. package/components/calendar/index.vue +326 -262
  11. package/components/calendar/month.vue +64 -55
  12. package/components/calendar/year.vue +30 -25
  13. package/components/card/index.vue +139 -59
  14. package/components/codeEditor/highlightCss.vue +38 -39
  15. package/components/codeEditor/highlightHtml.vue +64 -64
  16. package/components/codeEditor/highlightJs.vue +37 -38
  17. package/components/codeEditor/index.vue +129 -79
  18. package/components/codeEditor/run.vue +225 -39
  19. package/components/codeEditor/useCodeFormatter.js +150 -0
  20. package/components/confirm/index.vue +140 -81
  21. package/components/container/col.vue +5 -4
  22. package/components/container/divider.vue +28 -19
  23. package/components/container/index.vue +34 -15
  24. package/components/container/row.vue +26 -9
  25. package/components/container/spacer.vue +2 -4
  26. package/components/container/style.scss +3 -0
  27. package/components/content/index.vue +49 -32
  28. package/components/cropper/index.vue +401 -244
  29. package/components/float/index.vue +542 -415
  30. package/components/form/addressInput/index.vue +184 -109
  31. package/components/form/camInput/index.vue +370 -244
  32. package/components/form/checkInput/index.vue +138 -71
  33. package/components/form/checkboxInput/index.vue +87 -47
  34. package/components/form/colorInput/Alpha.vue +81 -83
  35. package/components/form/colorInput/Hue.vue +91 -68
  36. package/components/form/colorInput/Preview.vue +43 -47
  37. package/components/form/colorInput/Saturation.vue +101 -86
  38. package/components/form/colorInput/index.vue +72 -40
  39. package/components/form/colorInput/picker.vue +111 -106
  40. package/components/form/colorInput/useColor.js +153 -0
  41. package/components/form/dateInput/index.vue +691 -356
  42. package/components/form/dateInput/month.vue +63 -54
  43. package/components/form/dateInput/year.vue +35 -25
  44. package/components/form/fileInput/index.js +0 -1
  45. package/components/form/fileInput/index.vue +263 -106
  46. package/components/form/fileInput/single.vue +323 -164
  47. package/components/form/groupInput/index.vue +199 -101
  48. package/components/form/index.vue +189 -83
  49. package/components/form/input/index.vue +416 -377
  50. package/components/form/jsonInput/JsonView.vue +54 -56
  51. package/components/form/jsonInput/index.vue +247 -165
  52. package/components/form/maskInput/index.vue +252 -132
  53. package/components/form/numberInput/index.js +0 -1
  54. package/components/form/numberInput/index.vue +226 -117
  55. package/components/form/passwordInput/index.js +2 -1
  56. package/components/form/passwordInput/index.vue +269 -102
  57. package/components/form/radioInput/index.vue +143 -72
  58. package/components/form/rangeInput/index.vue +280 -167
  59. package/components/form/ratingInput/index.vue +57 -57
  60. package/components/form/selectInput/index.js +1 -3
  61. package/components/form/selectInput/index.vue +584 -296
  62. package/components/form/switchInput/index.vue +73 -59
  63. package/components/form/telInput/index.js +0 -1
  64. package/components/form/telInput/index.vue +238 -135
  65. package/components/form/textArea/index.vue +72 -35
  66. package/components/form/textEditor/index.vue +739 -0
  67. package/components/form/{text-editor → textEditor}/style.scss +8 -16
  68. package/components/form/textInput/index.vue +54 -32
  69. package/components/form/timeInput/index.vue +83 -56
  70. package/components/form/timeInput/range.vue +116 -95
  71. package/components/form/timeInput/timepicker.vue +382 -449
  72. package/components/form/uniqueInput/index.vue +105 -48
  73. package/components/form/unitInput/index.vue +139 -84
  74. package/components/formCreator/index.js +0 -1
  75. package/components/formCreator/index.vue +314 -148
  76. package/components/highlight/index.vue +41 -25
  77. package/components/highlight/style.scss +2 -2
  78. package/components/highlight/{mixin.js → useHighlight.js} +181 -160
  79. package/components/icon/index.vue +79 -33
  80. package/components/img/index.vue +249 -147
  81. package/components/img/preview.vue +180 -198
  82. package/components/img/svgImg.vue +42 -39
  83. package/components/index.js +5 -20
  84. package/components/infinite/index.js +1 -2
  85. package/components/infinite/index.vue +248 -66
  86. package/components/map/index.vue +428 -261
  87. package/components/map/route.vue +794 -487
  88. package/components/map/select.vue +118 -58
  89. package/components/menu/index.vue +201 -91
  90. package/components/meta/meta.js +26 -3
  91. package/components/modal/index.vue +383 -158
  92. package/components/notify/index.vue +204 -86
  93. package/components/notify/notification.vue +38 -55
  94. package/components/progress/circle.vue +189 -70
  95. package/components/progress/line.vue +266 -46
  96. package/components/searchBox/index.js +1 -3
  97. package/components/searchBox/index.vue +194 -101
  98. package/components/skeleton/index.vue +45 -20
  99. package/components/slider/index.vue +318 -156
  100. package/components/swiper/index.vue +254 -106
  101. package/components/table/crud/footer.vue +77 -53
  102. package/components/table/crud/header.vue +71 -72
  103. package/components/table/crud/index.vue +631 -401
  104. package/components/table/index.vue +721 -278
  105. package/components/timeAgo/index.vue +145 -96
  106. package/components/tour/index.vue +338 -235
  107. package/components/tree/index.vue +235 -89
  108. package/components/tree/tree-element.vue +107 -106
  109. package/directive/animate/index.js +77 -0
  110. package/directive/clickOutSide/index.js +98 -0
  111. package/directive/drag/index.js +153 -0
  112. package/directive/index.js +11 -13
  113. package/directive/intersect/index.js +263 -0
  114. package/directive/mask/index.js +67 -0
  115. package/directive/parallax/index.js +78 -0
  116. package/directive/ripple/index.js +14 -0
  117. package/directive/scroll/index.js +244 -0
  118. package/directive/sortable/index.js +274 -0
  119. package/directive/title/index.js +75 -0
  120. package/directive/touch/index.js +268 -0
  121. package/index.js +10 -8
  122. package/package.json +5 -2
  123. package/plugins/validation/Validate.js +88 -79
  124. package/scripts/generate-docs.mjs +226 -0
  125. package/scripts/menu.mjs +240 -0
  126. package/scripts/parser.mjs +1086 -0
  127. package/style/_index.scss +7 -0
  128. package/style/app.scss +13 -65
  129. package/style/colors.scss +5 -22
  130. package/style/functions/index.scss +8 -0
  131. package/style/mixins/index.scss +17 -5
  132. package/style/variables/base.scss +154 -175
  133. package/style/variables/color.scss +0 -12
  134. package/style/variables/utilities.scss +0 -180
  135. package/tools/helper.js +0 -8
  136. package/tools/icons.js +6 -1
  137. package/tools/root.js +71 -0
  138. package/components/app/style.scss +0 -41
  139. package/components/app/toast/style.scss +0 -20
  140. package/components/avatar/style.scss +0 -32
  141. package/components/bar/bottomNav.js +0 -1
  142. package/components/bar/bottomNav.vue +0 -28
  143. package/components/bar/bottomNavigationCircle.js +0 -2
  144. package/components/bar/bottomNavigationCircle.vue +0 -99
  145. package/components/bar/scss/bottomNav.scss +0 -67
  146. package/components/bar/scss/toolbar.scss +0 -174
  147. package/components/bar/toolbar/index.js +0 -8
  148. package/components/bar/toolbar/index.vue +0 -35
  149. package/components/bar/toolbar/laptop.vue +0 -33
  150. package/components/bar/toolbar/menuChilds.vue +0 -41
  151. package/components/bar/toolbar/menuLaptop.vue +0 -41
  152. package/components/bar/toolbar/menuMob.vue +0 -39
  153. package/components/bar/toolbar/mixin.js +0 -43
  154. package/components/bar/toolbar/mobile.vue +0 -34
  155. package/components/breadcrumb/bredcrumbItem.vue +0 -39
  156. package/components/breadcrumb/index.js +0 -3
  157. package/components/breadcrumb/index.vue +0 -71
  158. package/components/breadcrumb/style.scss +0 -51
  159. package/components/button/style.scss +0 -411
  160. package/components/card/style.scss +0 -86
  161. package/components/chart/chart.js +0 -1
  162. package/components/chart/chart.vue +0 -69
  163. package/components/chart/worldMap.js +0 -2
  164. package/components/chart/worldMap.vue +0 -1112
  165. package/components/chat/MessageList.vue +0 -163
  166. package/components/chat/chatInput.vue +0 -150
  167. package/components/chat/chatMsg.vue +0 -276
  168. package/components/chat/index.js +0 -11
  169. package/components/chat/index.vue +0 -113
  170. package/components/chip/index.js +0 -3
  171. package/components/chip/index.vue +0 -77
  172. package/components/chip/style.scss +0 -199
  173. package/components/codeEditor/mixin.js +0 -145
  174. package/components/countdown/index.js +0 -1
  175. package/components/countdown/index.vue +0 -105
  176. package/components/form/colorInput/mixin.js +0 -132
  177. package/components/form/fileInput/file.js +0 -148
  178. package/components/form/telInput/assets/flags.png +0 -0
  179. package/components/form/telInput/assets/flags@2x.png +0 -0
  180. package/components/form/text-editor/index.vue +0 -710
  181. package/components/icon/style.scss +0 -17
  182. package/components/infinite/div.js +0 -6
  183. package/components/infinite/div.vue +0 -193
  184. package/components/infinite/page.js +0 -3
  185. package/components/infinite/page.vue +0 -105
  186. package/components/list/index.js +0 -3
  187. package/components/list/index.vue +0 -122
  188. package/components/list/style.scss +0 -66
  189. package/components/message/index.js +0 -4
  190. package/components/message/index.vue +0 -40
  191. package/components/modal/style.scss +0 -146
  192. package/components/nestable/NestableItem.vue +0 -307
  193. package/components/nestable/editable.js +0 -44
  194. package/components/nestable/index.js +0 -1
  195. package/components/nestable/index.vue +0 -226
  196. package/components/nestable/methods.js +0 -416
  197. package/components/progress/style.scss +0 -229
  198. package/components/table/style.scss +0 -338
  199. package/components/tabs/index.js +0 -3
  200. package/components/tabs/index.vue +0 -151
  201. package/components/timeline/index.js +0 -6
  202. package/components/timeline/index.vue +0 -76
  203. package/directive/resize/index.js +0 -30
  204. package/directive/skeleton/index.js +0 -27
  205. package/directive/skeleton/style.scss +0 -37
  206. package/plugins/request/Request.js +0 -68
  207. package/style/animation.scss +0 -94
  208. package/style/style.scss +0 -8
  209. package/tools/rootable.js +0 -75
  210. /package/components/form/{text-editor → textEditor}/index.js +0 -0
  211. /package/components/form/{text-editor → textEditor}/preview.js +0 -0
  212. /package/components/form/{text-editor → textEditor}/preview.vue +0 -0
@@ -2,10 +2,10 @@
2
2
  <r-input
3
3
  :class="[`${$r.prefix}cam-input`]"
4
4
  :model-value="files.length > 0 ? files : null"
5
- hide
6
- labelControlClass="label-active"
5
+ label-active
7
6
  >
8
7
  <div>
8
+ <!-- Slot for custom video holder display. Provides items scoped prop -->
9
9
  <slot name="holder" :items="modelValue">
10
10
  <span class="video-holder" v-for="(vid, i) in modelValue" :key="i">
11
11
  <video
@@ -44,6 +44,7 @@
44
44
  :width="width"
45
45
  :height="height"
46
46
  ></canvas>
47
+ <!-- Slot for custom control buttons. Provides uploadPercentage, cancelFile, start, stop, started scoped props -->
47
48
  <slot name="control"
48
49
  :uploadPercentage="uploadPercentage"
49
50
  :cancelFile="cancelFile"
@@ -105,257 +106,382 @@
105
106
  </r-input>
106
107
  </template>
107
108
 
108
- <script>
109
- export default {
110
- name: "r-cam-input",
111
- props: {
112
- uploadLink: String,
113
- width: {
114
- type: String,
115
- default: "300",
116
- },
117
- height: {
118
- type: String,
119
- default: "300",
120
- },
121
- audio: {
122
- type: Boolean,
123
- default: true,
124
- },
125
- video: {
126
- type: Boolean,
127
- default: true,
128
- },
129
- modelValue: Array,
130
- size: {
131
- default: 3,
132
- type: Number,
133
- },
134
- headers: Object
109
+ <script setup>
110
+ import {ref, watch, inject, onUnmounted} from 'vue'
111
+
112
+ const props = defineProps({
113
+ /**
114
+ * API endpoint URL for uploading recorded videos
115
+ * @type {String}
116
+ */
117
+ uploadLink: String,
118
+
119
+ /**
120
+ * Width of the camera view/video element
121
+ * @type {String|Number}
122
+ * @default "300"
123
+ */
124
+ width: {
125
+ type: [String, Number],
126
+ default: "300",
135
127
  },
136
- emits: ['update:modelValue', 'error'],
137
- data() {
138
- return {
139
- started: false,
140
- type: null,
141
- stream: null,
142
- mediaRecorder: null,
143
- recordedBlobs: [],
144
- uploadPercentage: 0,
145
- CancelTokenSource: null,
146
- files: this.modelValue || [],
147
- };
128
+
129
+ /**
130
+ * Height of the camera view/video element
131
+ * @type {String|Number}
132
+ * @default "300"
133
+ */
134
+ height: {
135
+ type: [String, Number],
136
+ default: "300",
148
137
  },
149
- methods: {
150
- visualize() {
151
- const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
152
- const analyser = audioCtx.createAnalyser();
153
- const source = audioCtx.createMediaStreamSource(this.stream);
154
- source.connect(analyser);
155
-
156
- analyser.fftSize = 2048;
157
- const bufferLength = analyser.frequencyBinCount;
158
- const dataArray = new Uint8Array(bufferLength);
159
- analyser.getByteTimeDomainData(dataArray);
160
- const canvas = this.$refs.visualizer;
161
- canvas.width = this.width;
162
- canvas.height = this.height;
163
- const canvasCtx = canvas.getContext("2d");
164
- canvasCtx.clearRect(0, 0, this.width, this.height);
165
- const that = this;
166
-
167
- const barHeight = 5;
168
- const barWidth = 4;
169
- const barSpacing = 10;
170
- const cx = canvas.width / 2;
171
- const cy = canvas.height / 2;
172
- const radius = cy / 2;
173
- const maxBarNum = Math.floor(
174
- (radius * 2 * Math.PI) / (barWidth + barSpacing)
175
- );
176
-
177
- const freqJump = Math.floor(dataArray.length / maxBarNum);
178
- let colors = [];
179
- for (let i = 0; i < maxBarNum; i++) {
180
- colors.push(that.getRandomColor());
181
- }
182
- const draw2 = function () {
183
- if (!that.started) {
184
- return;
185
- }
186
- canvasCtx.clearRect(0, 0, that.width, that.height);
187
- requestAnimationFrame(draw2);
188
- analyser.getByteTimeDomainData(dataArray);
189
-
190
- for (let i = 0; i < maxBarNum; i++) {
191
- const amplitude = 128 - dataArray[i * freqJump];
192
- const alfa = (i * 2 * Math.PI) / maxBarNum;
193
- const beta = ((3 * 45 - barWidth) * Math.PI) / 180;
194
- const x = 0;
195
- const y = radius;
196
- const w = barWidth;
197
- const h = Math.min(Math.max(barHeight, amplitude), radius / 2);
198
- canvasCtx.save();
199
- canvasCtx.translate(cx, cy);
200
- canvasCtx.rotate(alfa - beta);
201
- canvasCtx.fillStyle = colors[i];
202
- canvasCtx.fillRect(x, y, w, h);
203
- canvasCtx.restore();
204
- }
205
- };
206
- draw2();
207
- },
208
- getRandomColor() {
209
- const letters = "0123456789ABCDEF";
210
- let color = "#";
211
- for (let i = 0; i < 6; i++) {
212
- color += letters[Math.floor(Math.random() * 16)];
213
- }
214
- return color;
215
- },
216
- startRecording(stream) {
217
- this.recordedBlobs = [];
218
-
219
- try {
220
- this.mediaRecorder = new MediaRecorder(stream);
221
- } catch (e) {
222
- console.error("Exception while creating MediaRecorder:", e);
223
- return;
224
- }
225
138
 
226
- this.mediaRecorder.onstop = () => {
227
- console.log("Recorder stopped");
228
- this.download();
229
- };
230
- this.mediaRecorder.ondataavailable = this.handleDataAvailable;
231
- this.mediaRecorder.start();
232
- console.log("MediaRecorder started");
233
- },
234
- handleDataAvailable(event) {
235
- if (event.data && event.data.size > 0) {
236
- this.type = event.data.type;
237
- this.recordedBlobs.push(event.data);
238
- }
239
- },
240
- download() {
241
- this.CancelTokenSource = this.$axios.CancelToken.source();
242
- const blob = new Blob(this.recordedBlobs, {type: this.type});
243
- let fileData = new FormData();
244
- fileData.append("file", blob, "user.webm");
245
- let headers = this.headers
246
- if (!headers) {
247
- headers = {}
248
- }
249
- headers['Content-Type'] = 'multipart/form-data'
250
- this.$axios
251
- .post(this.uploadLink, fileData, {
252
- headers: headers,
253
- onUploadProgress: function (progressEvent) {
254
- this.uploadPercentage = Math.min(
255
- parseInt(
256
- Math.floor((progressEvent.loaded * 100) / progressEvent.total)
257
- ),
258
- 98
259
- );
260
- }.bind(this),
261
- cancelToken: this.CancelTokenSource.token,
262
- })
263
- .then(
264
- ({data}) => {
265
- this.uploadPercentage = 100;
266
- this.files.push(data.link);
267
- this.emit();
268
- },
269
- (err) => {
270
- this.uploadPercentage = 0;
271
- if (err.response) {
272
- this.$emit('error', err.response.data)
273
- } else {
274
- this.$emit('error', err)
275
- }
276
- }
277
- );
278
- },
279
- emit() {
280
- this.$emit("update:modelValue", this.files);
281
- },
282
- run() {
283
- try {
284
- this.uploadPercentage = 0;
285
- navigator.mediaDevices
286
- .getUserMedia({
287
- audio: this.audio,
288
- video: this.video
289
- ? {width: this.width, height: this.height}
290
- : false,
291
- })
292
- .then((stream) => {
293
- if (this.$refs.selfView) {
294
- this.$refs.selfView.srcObject = stream;
295
- }
296
- if (!this.video) {
297
- this.stream = stream;
298
- this.visualize();
299
- }
300
-
301
- this.startRecording(stream);
302
- });
303
- } catch (err) {
304
- this.started = false;
305
- console.error("startChat: " + err);
306
- }
307
- },
308
- start() {
309
- if (this.files.length >= this.size) {
310
- return;
311
- }
312
- this.started = true;
313
- this.run();
314
- },
315
- stop() {
316
- this.started = false;
317
- if (this.$refs["selfView"] && this.$refs["selfView"].srcObject) {
318
- this.$refs["selfView"].srcObject.getTracks().forEach(function (track) {
319
- track.stop();
320
- });
321
- this.$refs["selfView"].srcObject = null;
322
- } else if (this.stream) {
323
- this.stream.getTracks().forEach(function (track) {
324
- track.stop();
325
- });
326
- this.stream = null;
327
- }
139
+ /**
140
+ * Enable audio recording
141
+ * @type {Boolean}
142
+ * @default true
143
+ */
144
+ audio: {
145
+ type: Boolean,
146
+ default: true,
147
+ },
328
148
 
329
- if (this.mediaRecorder !== null) {
330
- this.mediaRecorder.stop();
331
- this.mediaRecorder = null;
332
- }
333
- },
334
- cancelFile() {
335
- this.CancelTokenSource.cancel();
336
- this.uploadPercentage = 0;
337
- },
338
- dlt(link) {
339
- const i = this.files.indexOf(link);
340
- this.$axios
341
- .delete(this.uploadLink, {
342
- data: {link: link},
343
- }, {headers: this.headers})
344
- .then(() => {
345
- this.files.splice(i, 1);
346
- this.emit();
347
- });
348
- },
149
+ /**
150
+ * Enable video recording
151
+ * @type {Boolean}
152
+ * @default true
153
+ */
154
+ video: {
155
+ type: Boolean,
156
+ default: true,
157
+ },
158
+
159
+ /**
160
+ * Array of uploaded video URLs
161
+ * @type {Array}
162
+ */
163
+ modelValue: Array,
164
+
165
+ /**
166
+ * Maximum number of videos allowed to record
167
+ * @type {Number}
168
+ * @default 3
169
+ */
170
+ size: {
171
+ default: 3,
172
+ type: Number,
349
173
  },
350
- };
174
+
175
+ /**
176
+ * Additional headers for upload/delete requests
177
+ * @type {Object}
178
+ */
179
+ headers: Object
180
+ })
181
+
182
+ const emit = defineEmits([
183
+ /**
184
+ * Emitted when the list of uploaded videos changes
185
+ * @param {Array} videos - Array of video URLs
186
+ */
187
+ 'update:modelValue',
188
+
189
+ /**
190
+ * Emitted when an error occurs during recording, upload, or delete
191
+ * @param {String|Object} error - Error message or error object
192
+ */
193
+ 'error'
194
+ ])
195
+
196
+ const $axios = inject('axios')
197
+
198
+ const started = ref(false)
199
+ const type = ref(null)
200
+ const stream = ref(null)
201
+ const mediaRecorder = ref(null)
202
+ const recordedBlobs = ref([])
203
+ const uploadPercentage = ref(0)
204
+ const CancelTokenSource = ref(null)
205
+ const files = ref(props.modelValue || [])
206
+
207
+ const visualizer = ref(null)
208
+ const selfView = ref(null)
209
+
210
+ watch(() => props.modelValue, (newValue) => {
211
+ files.value = newValue || []
212
+ })
213
+
214
+ /**
215
+ * Creates audio visualizer when video is disabled
216
+ */
217
+ const visualize = () => {
218
+ if (!stream.value) return
219
+
220
+ const audioCtx = new (window.AudioContext || window.webkitAudioContext)()
221
+ const analyser = audioCtx.createAnalyser()
222
+ const source = audioCtx.createMediaStreamSource(stream.value)
223
+ source.connect(analyser)
224
+
225
+ analyser.fftSize = 2048
226
+ const bufferLength = analyser.frequencyBinCount
227
+ const dataArray = new Uint8Array(bufferLength)
228
+ analyser.getByteTimeDomainData(dataArray)
229
+
230
+ const canvas = visualizer.value
231
+ if (!canvas) return
232
+
233
+ canvas.width = parseInt(props.width)
234
+ canvas.height = parseInt(props.height)
235
+ const canvasCtx = canvas.getContext("2d")
236
+ canvasCtx.clearRect(0, 0, canvas.width, canvas.height)
237
+
238
+ const barHeight = 5
239
+ const barWidth = 4
240
+ const barSpacing = 10
241
+ const cx = canvas.width / 2
242
+ const cy = canvas.height / 2
243
+ const radius = cy / 2
244
+ const maxBarNum = Math.floor(
245
+ (radius * 2 * Math.PI) / (barWidth + barSpacing)
246
+ )
247
+
248
+ const freqJump = Math.floor(dataArray.length / maxBarNum)
249
+ let colors = []
250
+ for (let i = 0; i < maxBarNum; i++) {
251
+ colors.push(getRandomColor())
252
+ }
253
+
254
+ const draw = () => {
255
+ if (!started.value) {
256
+ return
257
+ }
258
+ canvasCtx.clearRect(0, 0, canvas.width, canvas.height)
259
+ requestAnimationFrame(draw)
260
+ analyser.getByteTimeDomainData(dataArray)
261
+
262
+ for (let i = 0; i < maxBarNum; i++) {
263
+ const amplitude = 128 - dataArray[i * freqJump]
264
+ const alfa = (i * 2 * Math.PI) / maxBarNum
265
+ const beta = ((3 * 45 - barWidth) * Math.PI) / 180
266
+ const x = 0
267
+ const y = radius
268
+ const w = barWidth
269
+ const h = Math.min(Math.max(barHeight, amplitude), radius / 2)
270
+ canvasCtx.save()
271
+ canvasCtx.translate(cx, cy)
272
+ canvasCtx.rotate(alfa - beta)
273
+ canvasCtx.fillStyle = colors[i]
274
+ canvasCtx.fillRect(x, y, w, h)
275
+ canvasCtx.restore()
276
+ }
277
+ }
278
+ draw()
279
+ }
280
+
281
+ /**
282
+ * Generates a random color for visualizer bars
283
+ * @returns {String} Random hex color
284
+ */
285
+ const getRandomColor = () => {
286
+ const letters = "0123456789ABCDEF"
287
+ let color = "#"
288
+ for (let i = 0; i < 6; i++) {
289
+ color += letters[Math.floor(Math.random() * 16)]
290
+ }
291
+ return color
292
+ }
293
+
294
+ /**
295
+ * Starts media recording
296
+ * @param {MediaStream} stream - Media stream to record
297
+ */
298
+ const startRecording = (stream) => {
299
+ recordedBlobs.value = []
300
+
301
+ try {
302
+ mediaRecorder.value = new MediaRecorder(stream)
303
+ } catch (e) {
304
+ console.error("Exception while creating MediaRecorder:", e)
305
+ return
306
+ }
307
+
308
+ mediaRecorder.value.onstop = () => {
309
+ console.log("Recorder stopped")
310
+ download()
311
+ }
312
+
313
+ mediaRecorder.value.ondataavailable = handleDataAvailable
314
+ mediaRecorder.value.start()
315
+ console.log("MediaRecorder started")
316
+ }
317
+
318
+ /**
319
+ * Handles data available event from media recorder
320
+ * @param {BlobEvent} event - Data available event
321
+ */
322
+ const handleDataAvailable = (event) => {
323
+ if (event.data && event.data.size > 0) {
324
+ type.value = event.data.type
325
+ recordedBlobs.value.push(event.data)
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Uploads recorded video to server
331
+ */
332
+ const download = async () => {
333
+ if (!props.uploadLink || !$axios) {
334
+ console.error("Upload link or axios not available")
335
+ return
336
+ }
337
+
338
+ CancelTokenSource.value = $axios.CancelToken.source()
339
+ const blob = new Blob(recordedBlobs.value, {type: type.value})
340
+ let fileData = new FormData()
341
+ fileData.append("file", blob, "user.webm")
342
+
343
+ let headers = props.headers ? {...props.headers} : {}
344
+ headers['Content-Type'] = 'multipart/form-data'
345
+
346
+ try {
347
+ const response = await $axios.post(props.uploadLink, fileData, {
348
+ headers: headers,
349
+ onUploadProgress: (progressEvent) => {
350
+ uploadPercentage.value = Math.min(
351
+ parseInt(
352
+ Math.floor((progressEvent.loaded * 100) / progressEvent.total)
353
+ ),
354
+ 98
355
+ )
356
+ },
357
+ cancelToken: CancelTokenSource.value.token,
358
+ })
359
+
360
+ uploadPercentage.value = 100
361
+ files.value.push(response.data.link)
362
+ emitFiles()
363
+ } catch (err) {
364
+ uploadPercentage.value = 0
365
+ if (err.response) {
366
+ emit('error', err.response.data)
367
+ } else {
368
+ emit('error', err)
369
+ }
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Emits updated files array to parent
375
+ */
376
+ const emitFiles = () => {
377
+ emit("update:modelValue", files.value)
378
+ }
379
+
380
+ /**
381
+ * Initializes camera and starts recording
382
+ */
383
+ const run = async () => {
384
+ try {
385
+ uploadPercentage.value = 0
386
+ const mediaStream = await navigator.mediaDevices.getUserMedia({
387
+ audio: props.audio,
388
+ video: props.video
389
+ ? {width: parseInt(props.width), height: parseInt(props.height)}
390
+ : false,
391
+ })
392
+
393
+ if (selfView.value) {
394
+ selfView.value.srcObject = mediaStream
395
+ }
396
+
397
+ if (!props.video) {
398
+ stream.value = mediaStream
399
+ visualize()
400
+ }
401
+
402
+ startRecording(mediaStream)
403
+ } catch (err) {
404
+ started.value = false
405
+ console.error("startChat: " + err)
406
+ emit('error', err.message || err)
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Starts the recording process
412
+ */
413
+ const start = () => {
414
+ if (files.value.length >= props.size) {
415
+ return
416
+ }
417
+ started.value = true
418
+ run()
419
+ }
420
+
421
+ /**
422
+ * Stops recording and cleans up media resources
423
+ */
424
+ const stop = () => {
425
+ started.value = false
426
+
427
+ if (selfView.value?.srcObject) {
428
+ selfView.value.srcObject.getTracks().forEach(track => track.stop())
429
+ selfView.value.srcObject = null
430
+ } else if (stream.value) {
431
+ stream.value.getTracks().forEach(track => track.stop())
432
+ stream.value = null
433
+ }
434
+
435
+ if (mediaRecorder.value) {
436
+ mediaRecorder.value.stop()
437
+ mediaRecorder.value = null
438
+ }
439
+ }
440
+
441
+ /**
442
+ * Cancels current file upload
443
+ */
444
+ const cancelFile = () => {
445
+ if (CancelTokenSource.value) {
446
+ CancelTokenSource.value.cancel()
447
+ }
448
+ uploadPercentage.value = 0
449
+ }
450
+
451
+ /**
452
+ * Deletes an uploaded video
453
+ * @param {String} link - Video URL to delete
454
+ */
455
+ const dlt = async (link) => {
456
+ if (!props.uploadLink || !$axios) return
457
+
458
+ const i = files.value.indexOf(link)
459
+ try {
460
+ await $axios.delete(props.uploadLink, {
461
+ data: {link: link},
462
+ headers: props.headers
463
+ })
464
+ files.value.splice(i, 1)
465
+ emitFiles()
466
+ } catch (error) {
467
+ console.error("Error deleting file:", error)
468
+ emit('error', error.response?.data || error.message || error)
469
+ }
470
+ }
471
+
472
+ onUnmounted(() => {
473
+ stop()
474
+ cancelFile()
475
+ })
351
476
  </script>
352
477
 
353
478
  <style lang="scss">
354
- @use "../../../style/variables/base";
479
+ @use "../../../style" as *;
355
480
 
356
- .#{base.$prefix}cam-input {
357
- video {
358
- border: 1px solid var(--color-border);
481
+ .#{$prefix}cam-input {
482
+ .input-control {
483
+ height: auto;
484
+ width: auto;
359
485
  }
360
486
 
361
487
  .self-view {