@joewinke/jatui 0.1.25 → 0.1.26

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 (242) hide show
  1. package/dist/actions/collapse.d.ts +80 -0
  2. package/dist/actions/collapse.js +134 -0
  3. package/dist/actions/railNav.d.ts +167 -0
  4. package/dist/actions/railNav.js +351 -0
  5. package/dist/components/AnnotationLayer.svelte +108 -0
  6. package/dist/components/AnnotationLayer.svelte.d.ts +12 -0
  7. package/dist/components/AnnotationPanel.svelte +319 -0
  8. package/dist/components/AnnotationPanel.svelte.d.ts +13 -0
  9. package/dist/components/AudioWaveform.svelte +701 -0
  10. package/dist/components/AudioWaveform.svelte.d.ts +19 -0
  11. package/dist/components/AvailabilityModal.svelte +177 -0
  12. package/dist/components/AvailabilityModal.svelte.d.ts +14 -0
  13. package/dist/components/AvatarUpload.svelte +234 -0
  14. package/dist/components/AvatarUpload.svelte.d.ts +23 -0
  15. package/dist/components/Badge.svelte +38 -0
  16. package/dist/components/Badge.svelte.d.ts +14 -0
  17. package/dist/components/BookingForm.svelte +278 -0
  18. package/dist/components/BookingForm.svelte.d.ts +37 -0
  19. package/dist/components/BurndownChart.svelte +778 -0
  20. package/dist/components/BurndownChart.svelte.d.ts +17 -0
  21. package/dist/components/Button.svelte +81 -0
  22. package/dist/components/Button.svelte.d.ts +21 -0
  23. package/dist/components/CalendarPicker.svelte +284 -0
  24. package/dist/components/CalendarPicker.svelte.d.ts +22 -0
  25. package/dist/components/Card.svelte +67 -0
  26. package/dist/components/Card.svelte.d.ts +17 -0
  27. package/dist/components/CharacterCounter.svelte +82 -0
  28. package/dist/components/CharacterCounter.svelte.d.ts +10 -0
  29. package/dist/components/ChipInput.svelte +609 -0
  30. package/dist/components/ChipInput.svelte.d.ts +55 -0
  31. package/dist/components/Collapse.svelte +91 -0
  32. package/dist/components/Collapse.svelte.d.ts +18 -0
  33. package/dist/components/ColorSelector.svelte +167 -0
  34. package/dist/components/ColorSelector.svelte.d.ts +9 -0
  35. package/dist/components/CommentThread.svelte +773 -0
  36. package/dist/components/CommentThread.svelte.d.ts +32 -0
  37. package/dist/components/ConfirmDialog.svelte +348 -0
  38. package/dist/components/ConfirmDialog.svelte.d.ts +3 -0
  39. package/dist/components/ConfirmModal.svelte +142 -0
  40. package/dist/components/ConfirmModal.svelte.d.ts +15 -0
  41. package/dist/components/ContextMenu.svelte +188 -0
  42. package/dist/components/ContextMenu.svelte.d.ts +31 -0
  43. package/dist/components/CountdownTimer.svelte +94 -0
  44. package/dist/components/CountdownTimer.svelte.d.ts +10 -0
  45. package/dist/components/DateRangePicker.svelte +194 -0
  46. package/dist/components/DateRangePicker.svelte.d.ts +10 -0
  47. package/dist/components/Drawer.svelte +143 -0
  48. package/dist/components/Drawer.svelte.d.ts +33 -0
  49. package/dist/components/EmojiPicker.svelte +159 -0
  50. package/dist/components/EmojiPicker.svelte.d.ts +8 -0
  51. package/dist/components/EntityPreviewCard.svelte +104 -0
  52. package/dist/components/EntityPreviewCard.svelte.d.ts +9 -0
  53. package/dist/components/FileDropzone.svelte +493 -0
  54. package/dist/components/FileDropzone.svelte.d.ts +40 -0
  55. package/dist/components/FilePicker.svelte +488 -0
  56. package/dist/components/FilePicker.svelte.d.ts +20 -0
  57. package/dist/components/FileThumbnail.svelte +80 -0
  58. package/dist/components/FileThumbnail.svelte.d.ts +12 -0
  59. package/dist/components/FilterDropdown.svelte +203 -0
  60. package/dist/components/FilterDropdown.svelte.d.ts +48 -0
  61. package/dist/components/FloatingActionBar.svelte +354 -0
  62. package/dist/components/FloatingActionBar.svelte.d.ts +51 -0
  63. package/dist/components/GPSTracker.svelte +202 -0
  64. package/dist/components/GPSTracker.svelte.d.ts +34 -0
  65. package/dist/components/HunkDiffView.svelte +348 -0
  66. package/dist/components/HunkDiffView.svelte.d.ts +9 -0
  67. package/dist/components/ImageLightbox.svelte +274 -0
  68. package/dist/components/ImageLightbox.svelte.d.ts +25 -0
  69. package/dist/components/ImageUpload.svelte +147 -0
  70. package/dist/components/ImageUpload.svelte.d.ts +10 -0
  71. package/dist/components/InlineEdit.svelte +291 -0
  72. package/dist/components/InlineEdit.svelte.d.ts +45 -0
  73. package/dist/components/InputDialog.svelte +327 -0
  74. package/dist/components/InputDialog.svelte.d.ts +3 -0
  75. package/dist/components/KeyboardShortcutsOverlay.svelte +296 -0
  76. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +42 -0
  77. package/dist/components/LazyImage.svelte +123 -0
  78. package/dist/components/LazyImage.svelte.d.ts +14 -0
  79. package/dist/components/LinkShortener.svelte +274 -0
  80. package/dist/components/LinkShortener.svelte.d.ts +27 -0
  81. package/dist/components/LoadingSpinner.svelte +106 -0
  82. package/dist/components/LoadingSpinner.svelte.d.ts +21 -0
  83. package/dist/components/LocationMap.svelte +186 -0
  84. package/dist/components/LocationMap.svelte.d.ts +11 -0
  85. package/dist/components/MapView.svelte +341 -0
  86. package/dist/components/MapView.svelte.d.ts +20 -0
  87. package/dist/components/MarkupEditor.svelte +485 -0
  88. package/dist/components/MarkupEditor.svelte.d.ts +10 -0
  89. package/dist/components/MarkupOverlay.svelte +55 -0
  90. package/dist/components/MarkupOverlay.svelte.d.ts +8 -0
  91. package/dist/components/MediaWorkbench.svelte +871 -0
  92. package/dist/components/MediaWorkbench.svelte.d.ts +9 -0
  93. package/dist/components/MilestoneCard.svelte +98 -0
  94. package/dist/components/MilestoneCard.svelte.d.ts +16 -0
  95. package/dist/components/MilestoneTimeline.svelte +91 -0
  96. package/dist/components/MilestoneTimeline.svelte.d.ts +15 -0
  97. package/dist/components/Modal.svelte +244 -0
  98. package/dist/components/Modal.svelte.d.ts +35 -0
  99. package/dist/components/PDFViewer.svelte +105 -0
  100. package/dist/components/PDFViewer.svelte.d.ts +9 -0
  101. package/dist/components/PdfThumbnail.svelte +63 -0
  102. package/dist/components/PdfThumbnail.svelte.d.ts +9 -0
  103. package/dist/components/PhoneInput.svelte +212 -0
  104. package/dist/components/PhoneInput.svelte.d.ts +25 -0
  105. package/dist/components/ResizableDivider.svelte +307 -0
  106. package/dist/components/ResizableDivider.svelte.d.ts +26 -0
  107. package/dist/components/ResizablePanel.svelte +305 -0
  108. package/dist/components/ResizablePanel.svelte.d.ts +18 -0
  109. package/dist/components/SearchDropdown.svelte +421 -0
  110. package/dist/components/SearchDropdown.svelte.d.ts +24 -0
  111. package/dist/components/SelectInput.svelte +237 -0
  112. package/dist/components/SelectInput.svelte.d.ts +26 -0
  113. package/dist/components/SidebarUserFooter.svelte +34 -0
  114. package/dist/components/SidebarUserFooter.svelte.d.ts +16 -0
  115. package/dist/components/SignaturePad.svelte +175 -0
  116. package/dist/components/SignaturePad.svelte.d.ts +17 -0
  117. package/dist/components/SmartImageEditor.svelte +720 -0
  118. package/dist/components/SmartImageEditor.svelte.d.ts +24 -0
  119. package/dist/components/SortDropdown.svelte +156 -0
  120. package/dist/components/SortDropdown.svelte.d.ts +40 -0
  121. package/dist/components/Sparkline.svelte +116 -0
  122. package/dist/components/Sparkline.svelte.d.ts +12 -0
  123. package/dist/components/SpeechForm.svelte +114 -0
  124. package/dist/components/SpeechForm.svelte.d.ts +17 -0
  125. package/dist/components/StatusBadge.svelte +157 -0
  126. package/dist/components/StatusBadge.svelte.d.ts +24 -0
  127. package/dist/components/TaskTypeIcon.svelte +54 -0
  128. package/dist/components/TaskTypeIcon.svelte.d.ts +17 -0
  129. package/dist/components/TextArea.svelte +162 -0
  130. package/dist/components/TextArea.svelte.d.ts +30 -0
  131. package/dist/components/TextInput.svelte +131 -0
  132. package/dist/components/TextInput.svelte.d.ts +29 -0
  133. package/dist/components/ThemeSelector.svelte +206 -0
  134. package/dist/components/ThemeSelector.svelte.d.ts +14 -0
  135. package/dist/components/TimeSlotPicker.svelte +162 -0
  136. package/dist/components/TimeSlotPicker.svelte.d.ts +18 -0
  137. package/dist/components/UserAvatar.svelte +72 -0
  138. package/dist/components/UserAvatar.svelte.d.ts +11 -0
  139. package/dist/components/VariablePicker.svelte +170 -0
  140. package/dist/components/VariablePicker.svelte.d.ts +40 -0
  141. package/dist/components/VoicePlayer.svelte +422 -0
  142. package/dist/components/VoicePlayer.svelte.d.ts +18 -0
  143. package/dist/components/linked-columns/LinkedColumns.svelte +526 -0
  144. package/dist/components/linked-columns/LinkedColumns.svelte.d.ts +130 -0
  145. package/dist/components/markup.d.ts +47 -0
  146. package/dist/components/markup.js +201 -0
  147. package/dist/components/messaging/Avatar.svelte +81 -0
  148. package/dist/components/messaging/Avatar.svelte.d.ts +11 -0
  149. package/dist/components/messaging/ChannelInfoModal.svelte +163 -0
  150. package/dist/components/messaging/ChannelInfoModal.svelte.d.ts +18 -0
  151. package/dist/components/messaging/ChannelList.svelte +107 -0
  152. package/dist/components/messaging/ChannelList.svelte.d.ts +11 -0
  153. package/dist/components/messaging/ChannelMemberAvatarStack.svelte +69 -0
  154. package/dist/components/messaging/ChannelMemberAvatarStack.svelte.d.ts +11 -0
  155. package/dist/components/messaging/ChannelMembersModal.svelte +182 -0
  156. package/dist/components/messaging/ChannelMembersModal.svelte.d.ts +16 -0
  157. package/dist/components/messaging/CreateChannelModal.svelte +190 -0
  158. package/dist/components/messaging/CreateChannelModal.svelte.d.ts +14 -0
  159. package/dist/components/messaging/DirectMessageList.svelte +145 -0
  160. package/dist/components/messaging/DirectMessageList.svelte.d.ts +12 -0
  161. package/dist/components/messaging/EmojiSelector.svelte +261 -0
  162. package/dist/components/messaging/EmojiSelector.svelte.d.ts +19 -0
  163. package/dist/components/messaging/MentionAutocomplete.svelte +193 -0
  164. package/dist/components/messaging/MentionAutocomplete.svelte.d.ts +17 -0
  165. package/dist/components/messaging/MessageAttachment.svelte +273 -0
  166. package/dist/components/messaging/MessageAttachment.svelte.d.ts +13 -0
  167. package/dist/components/messaging/MessageAttachmentUpload.svelte +243 -0
  168. package/dist/components/messaging/MessageAttachmentUpload.svelte.d.ts +19 -0
  169. package/dist/components/messaging/MessageInput.svelte +452 -0
  170. package/dist/components/messaging/MessageInput.svelte.d.ts +28 -0
  171. package/dist/components/messaging/MessageItem.svelte +341 -0
  172. package/dist/components/messaging/MessageItem.svelte.d.ts +41 -0
  173. package/dist/components/messaging/MessageThread.svelte +306 -0
  174. package/dist/components/messaging/MessageThread.svelte.d.ts +30 -0
  175. package/dist/components/messaging/NotificationSettingsModal.svelte +234 -0
  176. package/dist/components/messaging/NotificationSettingsModal.svelte.d.ts +13 -0
  177. package/dist/components/messaging/QuotedMessageDisplay.svelte +123 -0
  178. package/dist/components/messaging/QuotedMessageDisplay.svelte.d.ts +10 -0
  179. package/dist/components/messaging/StartDMModal.svelte +100 -0
  180. package/dist/components/messaging/StartDMModal.svelte.d.ts +14 -0
  181. package/dist/components/messaging/ThreadPanel.svelte +153 -0
  182. package/dist/components/messaging/ThreadPanel.svelte.d.ts +16 -0
  183. package/dist/components/pipeline/DESIGN.md +361 -0
  184. package/dist/components/pipeline/Pipeline.svelte +391 -0
  185. package/dist/components/pipeline/Pipeline.svelte.d.ts +28 -0
  186. package/dist/components/pipeline/PipelineCard.svelte +94 -0
  187. package/dist/components/pipeline/PipelineCard.svelte.d.ts +13 -0
  188. package/dist/components/pipeline/PipelineColumn.svelte +163 -0
  189. package/dist/components/pipeline/PipelineColumn.svelte.d.ts +30 -0
  190. package/dist/components/replay/ChapterTimeline.svelte +326 -0
  191. package/dist/components/replay/ChapterTimeline.svelte.d.ts +39 -0
  192. package/dist/components/session-nav/transcriptModel.d.ts +132 -0
  193. package/dist/components/session-nav/transcriptModel.js +285 -0
  194. package/dist/data/emojis.d.ts +18 -0
  195. package/dist/data/emojis.js +1196 -0
  196. package/dist/index.d.ts +142 -0
  197. package/dist/index.js +142 -0
  198. package/dist/stores/confirmDialog.svelte.d.ts +21 -0
  199. package/dist/stores/confirmDialog.svelte.js +31 -0
  200. package/dist/stores/inputDialog.svelte.d.ts +24 -0
  201. package/dist/stores/inputDialog.svelte.js +32 -0
  202. package/dist/styles/rail.css +63 -0
  203. package/dist/types/annotation.d.ts +32 -0
  204. package/dist/types/annotation.js +3 -0
  205. package/dist/types/booking.d.ts +115 -0
  206. package/dist/types/booking.js +24 -0
  207. package/dist/types/comments.d.ts +65 -0
  208. package/dist/types/comments.js +22 -0
  209. package/dist/types/entityPreview.d.ts +42 -0
  210. package/dist/types/entityPreview.js +10 -0
  211. package/dist/types/filePicker.d.ts +15 -0
  212. package/dist/types/filePicker.js +1 -0
  213. package/dist/types/googleMaps.d.ts +51 -0
  214. package/dist/types/maps.d.ts +43 -0
  215. package/dist/types/maps.js +4 -0
  216. package/dist/types/messaging.d.ts +282 -0
  217. package/dist/types/messaging.js +100 -0
  218. package/dist/types/milestone.d.ts +20 -0
  219. package/dist/types/milestone.js +7 -0
  220. package/dist/types/pipeline.d.ts +115 -0
  221. package/dist/types/pipeline.js +15 -0
  222. package/dist/types/smartImageEditor.d.ts +42 -0
  223. package/dist/types/smartImageEditor.js +1 -0
  224. package/dist/types/templateVars.d.ts +34 -0
  225. package/dist/types/templateVars.js +12 -0
  226. package/dist/utils/currency.d.ts +8 -0
  227. package/dist/utils/currency.js +17 -0
  228. package/dist/utils/daisyuiColors.d.ts +37 -0
  229. package/dist/utils/daisyuiColors.js +152 -0
  230. package/dist/utils/dateFormatters.d.ts +69 -0
  231. package/dist/utils/dateFormatters.js +199 -0
  232. package/dist/utils/googleMapsLoader.d.ts +19 -0
  233. package/dist/utils/googleMapsLoader.js +121 -0
  234. package/dist/utils/mentionParser.d.ts +67 -0
  235. package/dist/utils/mentionParser.js +132 -0
  236. package/dist/utils/phone.d.ts +13 -0
  237. package/dist/utils/phone.js +67 -0
  238. package/dist/utils/phoneFormat.d.ts +26 -0
  239. package/dist/utils/phoneFormat.js +69 -0
  240. package/dist/utils/taskUtils.d.ts +37 -0
  241. package/dist/utils/taskUtils.js +71 -0
  242. package/package.json +3 -2
@@ -0,0 +1,701 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy } from "svelte"
3
+ import type { Snippet } from "svelte"
4
+
5
+ import {
6
+ getDaisyUIColor,
7
+ lightenColor,
8
+ darkenColor,
9
+ watchThemeChanges,
10
+ } from "../utils/daisyuiColors"
11
+
12
+ let {
13
+ audioUrl,
14
+ height = 80,
15
+ waveColor = "neutral",
16
+ progressColor = "accent",
17
+ backgroundColor = "base-100",
18
+ waveColorHex = "",
19
+ progressColorHex = "",
20
+ backgroundColorHex = "",
21
+ interactive = true,
22
+ showTrimControls = false,
23
+ onTrim,
24
+ providedDuration,
25
+ speedControls,
26
+ } = $props<{
27
+ audioUrl: string
28
+ height?: number
29
+ waveColor?: string
30
+ progressColor?: string
31
+ backgroundColor?: string
32
+ waveColorHex?: string
33
+ progressColorHex?: string
34
+ backgroundColorHex?: string
35
+ interactive?: boolean
36
+ showTrimControls?: boolean
37
+ onTrim?: (startTime: number, endTime: number) => void
38
+ providedDuration?: number
39
+ speedControls?: Snippet
40
+ }>()
41
+
42
+ let canvas = $state<HTMLCanvasElement | null>(null)
43
+ let audio = $state<HTMLAudioElement | null>(null)
44
+ let canvasContext: CanvasRenderingContext2D | null = null
45
+
46
+ let isPlaying = $state(false)
47
+ let currentTime = $state(0)
48
+ let duration = $state(0)
49
+ let isLoading = $state(true)
50
+ let waveformData: number[] = []
51
+
52
+ let hoverX = $state(0)
53
+ let isHovering = $state(false)
54
+ let hoverTime = $state(0)
55
+
56
+ let trimStart = $state(0)
57
+ let trimEnd = $state(0)
58
+ let isDraggingStart = $state(false)
59
+ let isDraggingEnd = $state(false)
60
+ let isInTrimMode = $state(false)
61
+
62
+ function resolveColor(daisyUIName: string, hexFallback: string): string {
63
+ if (typeof window === "undefined") return hexFallback
64
+ try {
65
+ if (daisyUIName.startsWith("#")) return daisyUIName
66
+ return getDaisyUIColor(daisyUIName)
67
+ } catch {
68
+ return hexFallback
69
+ }
70
+ }
71
+
72
+ // svelte-ignore state_referenced_locally
73
+ let resolvedWaveColor = $state(resolveColor(waveColor, waveColorHex))
74
+ // svelte-ignore state_referenced_locally
75
+ let resolvedProgressColor = $state(resolveColor(progressColor, progressColorHex))
76
+ // svelte-ignore state_referenced_locally
77
+ let resolvedBackgroundColor = $state(resolveColor(backgroundColor, backgroundColorHex))
78
+
79
+ function updateResolvedColors() {
80
+ resolvedWaveColor = resolveColor(waveColor, waveColorHex)
81
+ resolvedProgressColor = resolveColor(progressColor, progressColorHex)
82
+ resolvedBackgroundColor = resolveColor(backgroundColor, backgroundColorHex)
83
+ }
84
+
85
+ onMount(() => {
86
+ const cleanupThemeWatcher = watchThemeChanges(() => {
87
+ updateResolvedColors()
88
+ if (canvasContext && canvas && waveformData.length > 0) {
89
+ drawWaveform()
90
+ }
91
+ })
92
+
93
+ if (canvas) {
94
+ canvasContext = canvas.getContext("2d")
95
+
96
+ setTimeout(() => {
97
+ if (canvas && canvasContext) {
98
+ const container = canvas.parentElement
99
+ if (container) {
100
+ const containerRect = container.getBoundingClientRect()
101
+ const displayWidth = containerRect.width
102
+ const displayHeight = height
103
+
104
+ canvas.width = displayWidth
105
+ canvas.height = displayHeight
106
+ canvas.style.width = displayWidth + "px"
107
+ canvas.style.height = displayHeight + "px"
108
+
109
+ updateResolvedColors()
110
+ loadAudioAndGenerateWaveform()
111
+ }
112
+ }
113
+ }, 100)
114
+ }
115
+
116
+ if (audio) {
117
+ audio.load()
118
+ }
119
+
120
+ return cleanupThemeWatcher
121
+ })
122
+
123
+ async function loadAudioAndGenerateWaveform() {
124
+ try {
125
+ isLoading = true
126
+ generateSimpleWaveform()
127
+ isLoading = false
128
+ } catch {
129
+ isLoading = false
130
+ }
131
+ }
132
+
133
+ function generateSimpleWaveform() {
134
+ if (!canvas) return
135
+
136
+ const canvasWidth = canvas.width
137
+
138
+ if (waveformData.length === 0 || waveformData.length !== canvasWidth) {
139
+ waveformData = []
140
+
141
+ const seed = audioUrl
142
+ ? audioUrl.split("").reduce((acc: number, char: string) => acc + char.charCodeAt(0), 0)
143
+ : 12345
144
+
145
+ for (let i = 0; i < canvasWidth; i++) {
146
+ const progress = i / canvasWidth
147
+ const baseAmplitude = Math.sin(progress * Math.PI * 2 + seed * 0.001) * 0.3 + 0.5
148
+ const speechPattern = Math.sin(progress * Math.PI * 20 + seed * 0.002) * 0.2
149
+ const variation = Math.sin(progress * Math.PI * 8 + seed * 0.003) * 0.1
150
+
151
+ let amplitude = baseAmplitude + speechPattern + variation
152
+ amplitude = Math.max(0.1, Math.min(0.9, amplitude))
153
+
154
+ waveformData.push(amplitude)
155
+ }
156
+ }
157
+
158
+ drawWaveform()
159
+ }
160
+
161
+ function createWaveformGradient() {
162
+ if (!canvasContext || !canvas) return null
163
+
164
+ const gradient = canvasContext.createLinearGradient(0, 0, 0, canvas.height)
165
+ const neutralColor = resolvedWaveColor
166
+ const neutralContentColor = getDaisyUIColor("neutral-content")
167
+ const lighterNeutral = lightenColor(neutralColor, 0.2)
168
+
169
+ gradient.addColorStop(0, neutralColor)
170
+ gradient.addColorStop(0.7, neutralColor)
171
+ gradient.addColorStop(0.71, neutralContentColor)
172
+ gradient.addColorStop(0.72, neutralContentColor)
173
+ gradient.addColorStop(0.73, lighterNeutral)
174
+ gradient.addColorStop(1, lighterNeutral)
175
+
176
+ return gradient
177
+ }
178
+
179
+ function createProgressGradient() {
180
+ if (!canvasContext || !canvas) return null
181
+
182
+ const gradient = canvasContext.createLinearGradient(0, 0, 0, canvas.height)
183
+ const accentColor = resolvedProgressColor
184
+ const accentContentColor = getDaisyUIColor("accent-content")
185
+ const darkerAccent = darkenColor(accentColor, 0.1)
186
+ const lighterAccent = lightenColor(accentColor, 0.3)
187
+
188
+ gradient.addColorStop(0, accentColor)
189
+ gradient.addColorStop(0.7, darkerAccent)
190
+ gradient.addColorStop(0.71, accentContentColor)
191
+ gradient.addColorStop(0.72, accentContentColor)
192
+ gradient.addColorStop(0.73, lighterAccent)
193
+ gradient.addColorStop(1, lighterAccent)
194
+
195
+ return gradient
196
+ }
197
+
198
+ function drawBaseWaveform() {
199
+ if (!canvasContext || !canvas || waveformData.length === 0) return
200
+
201
+ const { width, height } = canvas
202
+ canvasContext.clearRect(0, 0, width, height)
203
+
204
+ canvasContext.fillStyle = resolvedBackgroundColor
205
+ canvasContext.fillRect(0, 0, width, height)
206
+
207
+ const barWidth = 2
208
+ const barSpacing = 1
209
+ const totalBarWidth = barWidth + barSpacing
210
+ const numBars = Math.floor(width / totalBarWidth)
211
+ const centerY = height / 2
212
+
213
+ const validDuration = isFinite(duration) && duration > 0
214
+ const progressX = validDuration ? (currentTime / duration) * width : 0
215
+ const trimStartX = validDuration ? (trimStart / duration) * width : 0
216
+ const trimEndX = validDuration ? (trimEnd / duration) * width : width
217
+
218
+ const waveGradient = createWaveformGradient()
219
+ const progressGradient = createProgressGradient()
220
+
221
+ for (let i = 0; i < numBars; i++) {
222
+ const dataIndex = Math.floor((i / numBars) * waveformData.length)
223
+ const amplitude = waveformData[dataIndex] || 0.1
224
+ const barHeight = Math.max(4, amplitude * height * 0.8)
225
+ const x = i * totalBarWidth
226
+ const y = centerY - barHeight / 2
227
+
228
+ let fillStyle = waveGradient || waveColor
229
+ if (showTrimControls && isInTrimMode) {
230
+ if (x < trimStartX || x > trimEndX) {
231
+ canvasContext.globalAlpha = 0.3
232
+ } else if (x < progressX) {
233
+ fillStyle = progressGradient || progressColor
234
+ canvasContext.globalAlpha = 1
235
+ } else {
236
+ canvasContext.globalAlpha = 1
237
+ }
238
+ } else if (x < progressX) {
239
+ fillStyle = progressGradient || progressColor
240
+ canvasContext.globalAlpha = 1
241
+ } else {
242
+ canvasContext.globalAlpha = 1
243
+ }
244
+
245
+ canvasContext.fillStyle = fillStyle
246
+ canvasContext.fillRect(x, y, barWidth, barHeight)
247
+ }
248
+
249
+ canvasContext.globalAlpha = 1
250
+ }
251
+
252
+ function drawOverlays() {
253
+ if (!canvasContext || !canvas) return
254
+
255
+ const { width, height } = canvas
256
+
257
+ const validDuration = isFinite(duration) && duration > 0
258
+ const progressX = validDuration ? (currentTime / duration) * width : 0
259
+ const trimStartX = validDuration ? (trimStart / duration) * width : 0
260
+ const trimEndX = validDuration ? (trimEnd / duration) * width : width
261
+
262
+ if (isHovering && hoverX > 0) {
263
+ const baseContent = getDaisyUIColor("base-content")
264
+ canvasContext.fillStyle = `color-mix(in oklch, ${baseContent} 15%, transparent)`
265
+ const rect = canvas.getBoundingClientRect()
266
+ const canvasHoverX = (hoverX / rect.width) * width
267
+ canvasContext.fillRect(0, 0, canvasHoverX, height)
268
+
269
+ canvasContext.strokeStyle = `color-mix(in oklch, ${baseContent} 60%, transparent)`
270
+ canvasContext.lineWidth = 1
271
+ canvasContext.beginPath()
272
+ canvasContext.moveTo(canvasHoverX, 0)
273
+ canvasContext.lineTo(canvasHoverX, height)
274
+ canvasContext.stroke()
275
+ }
276
+
277
+ if (progressX > 0) {
278
+ canvasContext.strokeStyle = resolvedProgressColor
279
+ canvasContext.lineWidth = 2
280
+ canvasContext.beginPath()
281
+ canvasContext.moveTo(progressX, 0)
282
+ canvasContext.lineTo(progressX, height)
283
+ canvasContext.stroke()
284
+ }
285
+
286
+ if (showTrimControls && isInTrimMode) {
287
+ const errorColor = getDaisyUIColor("error")
288
+ const infoColor = getDaisyUIColor("info")
289
+ canvasContext.strokeStyle = errorColor
290
+ canvasContext.lineWidth = 3
291
+ canvasContext.beginPath()
292
+ canvasContext.moveTo(trimStartX, 0)
293
+ canvasContext.lineTo(trimStartX, height)
294
+ canvasContext.stroke()
295
+
296
+ canvasContext.strokeStyle = errorColor
297
+ canvasContext.lineWidth = 3
298
+ canvasContext.beginPath()
299
+ canvasContext.moveTo(trimEndX, 0)
300
+ canvasContext.lineTo(trimEndX, height)
301
+ canvasContext.stroke()
302
+
303
+ canvasContext.fillStyle = `color-mix(in oklch, ${infoColor} 10%, transparent)`
304
+ canvasContext.fillRect(trimStartX, 0, trimEndX - trimStartX, height)
305
+ }
306
+ }
307
+
308
+ function drawWaveform() {
309
+ drawBaseWaveform()
310
+ drawOverlays()
311
+ }
312
+
313
+ function handleCanvasMouseDown(event: MouseEvent) {
314
+ if (!interactive || !canvas || !isFinite(duration) || duration <= 0) return
315
+
316
+ const rect = canvas.getBoundingClientRect()
317
+ const x = event.clientX - rect.left
318
+ const clickPosition = x / rect.width
319
+ const clickTime = clickPosition * duration
320
+
321
+ if (showTrimControls && isInTrimMode) {
322
+ const trimStartX = (trimStart / duration) * rect.width
323
+ const trimEndX = (trimEnd / duration) * rect.width
324
+ const threshold = 10
325
+
326
+ if (Math.abs(x - trimStartX) < threshold) {
327
+ isDraggingStart = true
328
+ return
329
+ } else if (Math.abs(x - trimEndX) < threshold) {
330
+ isDraggingEnd = true
331
+ return
332
+ }
333
+ }
334
+
335
+ if (audio) {
336
+ audio.currentTime = clickTime
337
+ currentTime = clickTime
338
+ drawWaveform()
339
+ }
340
+ }
341
+
342
+ function handleCanvasMouseMove(event: MouseEvent) {
343
+ if (!canvas || !isFinite(duration) || duration <= 0) return
344
+
345
+ const rect = canvas.getBoundingClientRect()
346
+ const x = event.clientX - rect.left
347
+ const position = x / rect.width
348
+ const time = Math.max(0, Math.min(duration, position * duration))
349
+
350
+ if (interactive && !isDraggingStart && !isDraggingEnd) {
351
+ const wasHovering = isHovering
352
+ const oldHoverX = hoverX
353
+
354
+ isHovering = true
355
+ hoverX = x
356
+ hoverTime = time
357
+
358
+ if (!wasHovering || Math.abs(oldHoverX - hoverX) > 2) {
359
+ drawBaseWaveform()
360
+ drawOverlays()
361
+ }
362
+ }
363
+
364
+ if (isDraggingStart) {
365
+ trimStart = Math.min(time, trimEnd - 0.1)
366
+ drawWaveform()
367
+ } else if (isDraggingEnd) {
368
+ trimEnd = Math.max(time, trimStart + 0.1)
369
+ drawWaveform()
370
+ }
371
+
372
+ if (showTrimControls && isInTrimMode) {
373
+ const trimStartX = (trimStart / duration) * rect.width
374
+ const trimEndX = (trimEnd / duration) * rect.width
375
+ const threshold = 10
376
+
377
+ if (Math.abs(x - trimStartX) < threshold || Math.abs(x - trimEndX) < threshold) {
378
+ canvas.style.cursor = "ew-resize"
379
+ } else {
380
+ canvas.style.cursor = "pointer"
381
+ }
382
+ } else {
383
+ canvas.style.cursor = "pointer"
384
+ }
385
+ }
386
+
387
+ function handleCanvasMouseUp() {
388
+ isDraggingStart = false
389
+ isDraggingEnd = false
390
+ }
391
+
392
+ function handleCanvasMouseLeave() {
393
+ if (isHovering) {
394
+ isHovering = false
395
+ hoverX = 0
396
+ hoverTime = 0
397
+ drawBaseWaveform()
398
+ drawOverlays()
399
+ }
400
+ }
401
+
402
+ function handlePlay() { isPlaying = true }
403
+ function handlePause() { isPlaying = false }
404
+
405
+ function handleTimeUpdate() {
406
+ if (audio) {
407
+ currentTime = audio.currentTime
408
+ drawWaveform()
409
+ }
410
+ }
411
+
412
+ function handleLoadedMetadata() {
413
+ if (audio && isFinite(audio.duration) && audio.duration > 0) {
414
+ duration = audio.duration
415
+ trimStart = 0
416
+ trimEnd = duration
417
+ isLoading = false
418
+ drawWaveform()
419
+ } else if (providedDuration && providedDuration > 0) {
420
+ duration = providedDuration
421
+ trimStart = 0
422
+ trimEnd = duration
423
+ isLoading = false
424
+ drawWaveform()
425
+ } else {
426
+ duration = 40
427
+ trimStart = 0
428
+ trimEnd = duration
429
+ isLoading = false
430
+ drawWaveform()
431
+ }
432
+ }
433
+
434
+ function handleEnded() {
435
+ isPlaying = false
436
+ currentTime = 0
437
+ drawWaveform()
438
+ }
439
+
440
+ function handleCanPlayThrough() {
441
+ if (audio && isFinite(audio.duration) && audio.duration > 0) {
442
+ duration = audio.duration
443
+ trimStart = 0
444
+ trimEnd = duration
445
+ isLoading = false
446
+ drawWaveform()
447
+ }
448
+ }
449
+
450
+ function handleAudioError() {
451
+ isLoading = false
452
+ }
453
+
454
+ function togglePlayback() {
455
+ if (!audio) return
456
+ if (isPlaying) {
457
+ audio.pause()
458
+ } else {
459
+ audio.play()
460
+ }
461
+ }
462
+
463
+ function formatTime(seconds: number): string {
464
+ if (!isFinite(seconds) || isNaN(seconds) || seconds < 0) return "0:00"
465
+ const mins = Math.floor(seconds / 60)
466
+ const secs = Math.floor(seconds % 60)
467
+ return `${mins}:${secs.toString().padStart(2, "0")}`
468
+ }
469
+
470
+ function startTrimMode() {
471
+ if (!isFinite(duration) || duration <= 0) return
472
+ isInTrimMode = true
473
+ trimStart = 0
474
+ trimEnd = duration
475
+ drawWaveform()
476
+ }
477
+
478
+ function cancelTrim() {
479
+ isInTrimMode = false
480
+ trimStart = 0
481
+ trimEnd = duration
482
+ drawWaveform()
483
+ }
484
+
485
+ function applyTrim() {
486
+ if (onTrim) onTrim(trimStart, trimEnd)
487
+ isInTrimMode = false
488
+ }
489
+
490
+ function previewTrimSelection() {
491
+ if (audio) {
492
+ audio.currentTime = trimStart
493
+ currentTime = trimStart
494
+ audio.play()
495
+
496
+ const checkTime = () => {
497
+ if (!audio) return
498
+ if (audio.currentTime >= trimEnd) {
499
+ audio.pause()
500
+ } else if (isPlaying) {
501
+ requestAnimationFrame(checkTime)
502
+ }
503
+ }
504
+ requestAnimationFrame(checkTime)
505
+ }
506
+ }
507
+
508
+ $effect(() => {
509
+ if (canvas && canvasContext) {
510
+ const rect = canvas.getBoundingClientRect()
511
+ canvas.width = rect.width
512
+ canvas.height = height
513
+ canvas.style.width = rect.width + "px"
514
+ canvas.style.height = height + "px"
515
+ generateSimpleWaveform()
516
+ }
517
+ })
518
+
519
+ function resizeCanvas() {
520
+ if (!canvas || !canvasContext) return
521
+ const container = canvas.parentElement
522
+ if (!container) return
523
+
524
+ const containerRect = container.getBoundingClientRect()
525
+ let displayWidth = containerRect.width
526
+ const displayHeight = height
527
+
528
+ if (displayWidth <= 0) {
529
+ let parent = container.parentElement
530
+ while (parent && displayWidth <= 0) {
531
+ displayWidth = parent.getBoundingClientRect().width
532
+ parent = parent.parentElement
533
+ }
534
+ }
535
+
536
+ if (displayWidth > 0 && Math.abs(canvas.width - displayWidth) > 2) {
537
+ canvas.width = displayWidth
538
+ canvas.height = displayHeight
539
+ canvas.style.width = displayWidth + "px"
540
+ canvas.style.height = displayHeight + "px"
541
+ waveformData = []
542
+ generateSimpleWaveform()
543
+ }
544
+ }
545
+
546
+ $effect(() => {
547
+ if (!canvas) return
548
+
549
+ const handleResize = () => {
550
+ setTimeout(() => resizeCanvas(), 50)
551
+ }
552
+
553
+ window.addEventListener("resize", handleResize)
554
+
555
+ const observer = new MutationObserver(() => handleResize())
556
+
557
+ if (canvas.parentElement) {
558
+ observer.observe(canvas.parentElement, {
559
+ attributes: true,
560
+ childList: true,
561
+ subtree: true,
562
+ })
563
+ }
564
+
565
+ setTimeout(() => resizeCanvas(), 200)
566
+
567
+ return () => {
568
+ window.removeEventListener("resize", handleResize)
569
+ observer.disconnect()
570
+ }
571
+ })
572
+
573
+ $effect(() => {
574
+ if (audio && audioUrl) {
575
+ currentTime = 0
576
+ duration = 0
577
+ isLoading = true
578
+ audio.load()
579
+ }
580
+ })
581
+
582
+ onDestroy(() => {
583
+ if (audio) audio.pause()
584
+ })
585
+ </script>
586
+
587
+ <div class="audio-waveform w-full">
588
+ <audio
589
+ bind:this={audio}
590
+ src={audioUrl}
591
+ onplay={handlePlay}
592
+ onpause={handlePause}
593
+ ontimeupdate={handleTimeUpdate}
594
+ onloadedmetadata={handleLoadedMetadata}
595
+ oncanplaythrough={handleCanPlayThrough}
596
+ onended={handleEnded}
597
+ onerror={handleAudioError}
598
+ preload="metadata"
599
+ crossorigin="anonymous"
600
+ ></audio>
601
+
602
+ <div class="relative w-full" style="height: {height}px;">
603
+ {#if isLoading}
604
+ <div class="absolute inset-0 flex items-center justify-center bg-base-200 rounded">
605
+ <span class="loading loading-dots loading-sm"></span>
606
+ <span class="ml-2 text-sm">Loading waveform...</span>
607
+ </div>
608
+ {/if}
609
+
610
+ <canvas
611
+ bind:this={canvas}
612
+ class="rounded border border-base-300"
613
+ style="display: block;"
614
+ onmousedown={handleCanvasMouseDown}
615
+ onmousemove={handleCanvasMouseMove}
616
+ onmouseup={handleCanvasMouseUp}
617
+ onmouseleave={handleCanvasMouseLeave}
618
+ ></canvas>
619
+
620
+ {#if isHovering && interactive && hoverX > 0}
621
+ <div
622
+ class="absolute pointer-events-none bg-neutral text-neutral-content text-xs px-2 py-1 rounded z-10"
623
+ style="left: {hoverX - 20}px; top: -30px;"
624
+ >
625
+ {formatTime(hoverTime)}
626
+ </div>
627
+ {/if}
628
+ </div>
629
+
630
+ <div class="flex items-center justify-between mt-3 gap-4">
631
+ <div class="flex items-center gap-3">
632
+ <button
633
+ class="btn btn-circle btn-primary btn-sm"
634
+ onclick={togglePlayback}
635
+ disabled={isLoading}
636
+ aria-label={isPlaying ? "Pause" : "Play"}
637
+ >
638
+ {#if isLoading}
639
+ <span class="loading loading-spinner loading-xs"></span>
640
+ {:else if isPlaying}
641
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
642
+ {:else}
643
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="6 3 20 12 6 21 6 3"/></svg>
644
+ {/if}
645
+ </button>
646
+
647
+ <div class="text-sm font-mono">
648
+ {formatTime(currentTime)} / {formatTime(duration)}
649
+ </div>
650
+ </div>
651
+
652
+ <div class="flex items-center">
653
+ {#if speedControls}
654
+ {@render speedControls()}
655
+ {/if}
656
+ </div>
657
+ </div>
658
+
659
+ {#if showTrimControls}
660
+ <div class="mt-4">
661
+ {#if !isInTrimMode}
662
+ <button
663
+ class="btn btn-outline btn-sm"
664
+ onclick={startTrimMode}
665
+ disabled={isLoading || !isFinite(duration) || duration <= 0}
666
+ >
667
+ Trim Audio
668
+ </button>
669
+ {:else}
670
+ <div class="space-y-3">
671
+ <div class="text-sm">
672
+ <div class="flex justify-between items-center">
673
+ <span>Selection: {formatTime(trimStart)} - {formatTime(trimEnd)}</span>
674
+ <span class="badge badge-neutral">{formatTime(trimEnd - trimStart)} duration</span>
675
+ </div>
676
+ </div>
677
+
678
+ <div class="flex gap-2">
679
+ <button class="btn btn-outline btn-sm" onclick={previewTrimSelection} disabled={isLoading}>
680
+ Preview
681
+ </button>
682
+ <button class="btn btn-primary btn-sm" onclick={applyTrim} disabled={isLoading || trimEnd <= trimStart}>
683
+ Apply Trim
684
+ </button>
685
+ <button class="btn btn-ghost btn-sm" onclick={cancelTrim}>
686
+ Cancel
687
+ </button>
688
+ </div>
689
+
690
+ <div class="text-xs text-base-content/60">
691
+ Drag the red lines to select the audio section you want to keep
692
+ </div>
693
+ </div>
694
+ {/if}
695
+ </div>
696
+ {/if}
697
+ </div>
698
+
699
+ <style>
700
+ .audio-waveform canvas { display: block; }
701
+ </style>
@@ -0,0 +1,19 @@
1
+ import type { Snippet } from "svelte";
2
+ type $$ComponentProps = {
3
+ audioUrl: string;
4
+ height?: number;
5
+ waveColor?: string;
6
+ progressColor?: string;
7
+ backgroundColor?: string;
8
+ waveColorHex?: string;
9
+ progressColorHex?: string;
10
+ backgroundColorHex?: string;
11
+ interactive?: boolean;
12
+ showTrimControls?: boolean;
13
+ onTrim?: (startTime: number, endTime: number) => void;
14
+ providedDuration?: number;
15
+ speedControls?: Snippet;
16
+ };
17
+ declare const AudioWaveform: import("svelte").Component<$$ComponentProps, {}, "">;
18
+ type AudioWaveform = ReturnType<typeof AudioWaveform>;
19
+ export default AudioWaveform;