sveltekit-ui 1.1.19 → 1.1.21

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.
@@ -8,12 +8,10 @@
8
8
  let { manager } = $props()
9
9
 
10
10
  onMount(() => {
11
- if(typeof manager?.handle_mount == "function"){
11
+ if (typeof manager?.handle_mount == "function") {
12
12
  manager?.handle_mount()
13
13
  }
14
14
  })
15
-
16
-
17
15
  </script>
18
16
 
19
17
  {#snippet settings()}
@@ -23,6 +21,9 @@
23
21
  <div>
24
22
  <Slider manager={manager?.volume_slider_manager} />
25
23
  <Slider manager={manager?.rate_slider_manager} />
24
+ {#if manager?.is_show_download_button}
25
+ <Button manager={manager?.download_audio_button_manager} />
26
+ {/if}
26
27
  </div>
27
28
  {/snippet}
28
29
  </Popover>
@@ -53,7 +54,7 @@ onabort={(e) => console.log('abort',e)} -->
53
54
  src={manager?.src}
54
55
  type={manager?.type}
55
56
  ></audio>
56
- {#if manager?.ui_type == 'square'}
57
+ {#if manager?.ui_type == "square"}
57
58
  <div
58
59
  class="container_square"
59
60
  style="margin-left: {manager?.ml}rem; margin-right: {manager?.mr}rem; margin-top: {manager?.mt}rem; margin-bottom: {manager?.mb}rem;"
@@ -81,8 +82,13 @@ onabort={(e) => console.log('abort',e)} -->
81
82
  </div>
82
83
  <div style="display: flex;">
83
84
  <Button manager={manager?.play_pause_button_manager} />
84
- <div class="slider_box" class:is_short={manager?.ui_type == 'short'} >
85
- <span>{manager?.formatted_current_time}{manager?.time_slider_manager?.max && manager?.time_slider_manager?.max != Infinity ? ` / ${manager?.formatted_duration}` : ""}</span>
85
+ <div class="slider_box" class:is_short={manager?.ui_type == "short"}>
86
+ <span
87
+ >{manager?.formatted_current_time}{manager?.time_slider_manager?.max &&
88
+ manager?.time_slider_manager?.max != Infinity
89
+ ? ` / ${manager?.formatted_duration}`
90
+ : ""}</span
91
+ >
86
92
  </div>
87
93
  {@render settings()}
88
94
  </div>
@@ -91,7 +97,7 @@ onabort={(e) => console.log('abort',e)} -->
91
97
  {:else}
92
98
  <div
93
99
  class="container_line"
94
- class:is_short={manager?.ui_type == 'short'}
100
+ class:is_short={manager?.ui_type == "short"}
95
101
  style="margin-left: {manager?.ml}rem; margin-right: {manager?.mr}rem; margin-top: {manager?.mt}rem; margin-bottom: {manager?.mb}rem;"
96
102
  >
97
103
  {#if manager?.image_src && !manager?.is_hide_image}
@@ -105,20 +111,20 @@ onabort={(e) => console.log('abort',e)} -->
105
111
  />
106
112
  {/if}
107
113
  {#if !manager?.src}
108
- <p style="display: flex; flex: 1;" class:is_short={manager?.ui_type == 'short'}>Missing Audio Source</p>
114
+ <p style="display: flex; flex: 1;" class:is_short={manager?.ui_type == "short"}>Missing Audio Source</p>
109
115
  {:else if manager?.is_loading && !manager?.time_slider_manager?.max}
110
- <div style="display: flex; flex: 1; margin-left: 1rem;" class:is_short={manager?.ui_type == 'short'}>
116
+ <div style="display: flex; flex: 1; margin-left: 1rem;" class:is_short={manager?.ui_type == "short"}>
111
117
  <LoadingWheel size={2} />
112
- {#if manager?.ui_type != 'standard'}
113
- <p style="margin-left: 1rem;" class:is_short={manager?.ui_type == 'short'}>Loading...</p>
118
+ {#if manager?.ui_type != "standard"}
119
+ <p style="margin-left: 1rem;" class:is_short={manager?.ui_type == "short"}>Loading...</p>
114
120
  {/if}
115
121
  </div>
116
122
  {:else}
117
123
  <Button manager={manager?.play_pause_button_manager} />
118
- <div class="slider_box" class:is_short={manager?.ui_type == 'short'}>
124
+ <div class="slider_box" class:is_short={manager?.ui_type == "short"}>
119
125
  <span>{manager?.formatted_current_time}</span>
120
- {#if manager?.ui_type == 'standard'}
121
- <div style="display: flex; flex: 1; flex-direction: column;" class:is_short={manager?.ui_type == 'short'}>
126
+ {#if manager?.ui_type == "standard"}
127
+ <div style="display: flex; flex: 1; flex-direction: column;" class:is_short={manager?.ui_type == "short"}>
122
128
  <Slider manager={manager?.time_slider_manager} />
123
129
  </div>
124
130
  {:else}
@@ -128,7 +134,7 @@ onabort={(e) => console.log('abort',e)} -->
128
134
  <span>{manager?.formatted_duration}</span>
129
135
  {/if}
130
136
  </div>
131
- {#if manager?.ui_type == 'standard'}
137
+ {#if manager?.ui_type == "standard"}
132
138
  {@render settings()}
133
139
  {/if}
134
140
  {/if}
@@ -157,7 +163,7 @@ onabort={(e) => console.log('abort',e)} -->
157
163
  0 0.2rem 1rem var(--shadow2),
158
164
  inset 0 0 0 1px var(--shadow7-t);
159
165
  }
160
- .container_square{
166
+ .container_square {
161
167
  display: flex;
162
168
  flex-direction: column;
163
169
  flex: 0;
@@ -27,6 +27,10 @@ export function create_audio_manager(config) {
27
27
  let settings_button_manager = $state(null)
28
28
  let volume_slider_manager = $state(null)
29
29
  let rate_slider_manager = $state(null)
30
+ let is_show_download_button = $derived(set_closurable(config?.is_show_download_button, false))
31
+ let download_audio_button_manager = $state(null)
32
+ let download_is_loading = $state(false)
33
+ let download_error_message = $state(null)
30
34
 
31
35
  let time_loaded_until = $state(0)
32
36
  let time_highlight_range_start_percent = $derived((100 * time_slider_manager?.val) / time_slider_manager?.max)
@@ -293,6 +297,68 @@ export function create_audio_manager(config) {
293
297
  is_loading = false
294
298
  }
295
299
 
300
+ function mime_to_ext(mime) {
301
+ const v = String(mime || "").toLowerCase()
302
+ if (v.includes("mpeg")) return "mp3"
303
+ if (v.includes("mp4")) return "mp4"
304
+ if (v.includes("webm")) return "webm"
305
+ if (v.includes("ogg")) return "ogg"
306
+ if (v.includes("wav")) return "wav"
307
+ if (v.includes("flac")) return "flac"
308
+ if (v.includes("aac")) return "aac"
309
+ return "audio"
310
+ }
311
+
312
+ function safe_file_base(input) {
313
+ const s = String(input || "").trim()
314
+ if (!s) return "audio"
315
+ return (
316
+ s
317
+ .replace(/\.[a-z0-9]+$/i, "")
318
+ .replace(/[^a-z0-9_\- ]/gi, "")
319
+ .trim()
320
+ .replace(/\s+/g, "_")
321
+ .slice(0, 80)
322
+ .toLowerCase() || "audio"
323
+ )
324
+ }
325
+
326
+ async function download_audio() {
327
+ if (!browser) return
328
+ download_error_message = null
329
+ if (!src) {
330
+ download_error_message = "missing src"
331
+ return
332
+ }
333
+ try {
334
+ download_is_loading = true
335
+ const res = await fetch(src)
336
+ if (!res.ok) {
337
+ const t = await res.text().catch(() => null)
338
+ download_error_message = t || `download failed (${res.status})`
339
+ return
340
+ }
341
+ const content_type = res.headers.get("content-type") || type || "audio/mpeg"
342
+ const blob = await res.blob()
343
+ const ext = mime_to_ext(content_type || blob.type)
344
+ const base = safe_file_base(title || album || artist || "recording")
345
+ const file_name = `${base}.${ext}`
346
+ const object_url = URL.createObjectURL(blob)
347
+ const a = document.createElement("a")
348
+ a.href = object_url
349
+ a.download = file_name
350
+ a.rel = "noopener"
351
+ document.body.appendChild(a)
352
+ a.click()
353
+ a.remove()
354
+ setTimeout(() => URL.revokeObjectURL(object_url), 1000)
355
+ } catch (e) {
356
+ download_error_message = e?.message || "download failed"
357
+ } finally {
358
+ download_is_loading = false
359
+ }
360
+ }
361
+
296
362
  function init(config) {
297
363
  storage_path = config?.storage_path
298
364
  src = get_src(config?.src, config?.storage_id)
@@ -364,6 +430,14 @@ export function create_audio_manager(config) {
364
430
  val: 1,
365
431
  on_change: (input) => handle_rate_change(input),
366
432
  })
433
+ download_audio_button_manager = create_button_manager({
434
+ type: "outlined",
435
+ mt: 1,
436
+ text: "Download",
437
+ support_icon: "download",
438
+ is_loading: () => download_is_loading,
439
+ on_click: () => download_audio(),
440
+ })
367
441
  }
368
442
 
369
443
  init(config)
@@ -442,6 +516,12 @@ export function create_audio_manager(config) {
442
516
  get is_loading() {
443
517
  return is_loading
444
518
  },
519
+ get download_audio_button_manager() {
520
+ return download_audio_button_manager
521
+ },
522
+ get is_show_download_button() {
523
+ return is_show_download_button
524
+ },
445
525
  handle_mount,
446
526
  handle_loaded_metadata,
447
527
  handle_progress,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sveltekit-ui",
3
- "version": "1.1.19",
3
+ "version": "1.1.21",
4
4
  "description": "A SvelteKit UI component library for building modern web applications",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -8,12 +8,10 @@
8
8
  let { manager } = $props()
9
9
 
10
10
  onMount(() => {
11
- if(typeof manager?.handle_mount == "function"){
11
+ if (typeof manager?.handle_mount == "function") {
12
12
  manager?.handle_mount()
13
13
  }
14
14
  })
15
-
16
-
17
15
  </script>
18
16
 
19
17
  {#snippet settings()}
@@ -23,6 +21,9 @@
23
21
  <div>
24
22
  <Slider manager={manager?.volume_slider_manager} />
25
23
  <Slider manager={manager?.rate_slider_manager} />
24
+ {#if manager?.is_show_download_button}
25
+ <Button manager={manager?.download_audio_button_manager} />
26
+ {/if}
26
27
  </div>
27
28
  {/snippet}
28
29
  </Popover>
@@ -53,7 +54,7 @@ onabort={(e) => console.log('abort',e)} -->
53
54
  src={manager?.src}
54
55
  type={manager?.type}
55
56
  ></audio>
56
- {#if manager?.ui_type == 'square'}
57
+ {#if manager?.ui_type == "square"}
57
58
  <div
58
59
  class="container_square"
59
60
  style="margin-left: {manager?.ml}rem; margin-right: {manager?.mr}rem; margin-top: {manager?.mt}rem; margin-bottom: {manager?.mb}rem;"
@@ -81,8 +82,13 @@ onabort={(e) => console.log('abort',e)} -->
81
82
  </div>
82
83
  <div style="display: flex;">
83
84
  <Button manager={manager?.play_pause_button_manager} />
84
- <div class="slider_box" class:is_short={manager?.ui_type == 'short'} >
85
- <span>{manager?.formatted_current_time}{manager?.time_slider_manager?.max && manager?.time_slider_manager?.max != Infinity ? ` / ${manager?.formatted_duration}` : ""}</span>
85
+ <div class="slider_box" class:is_short={manager?.ui_type == "short"}>
86
+ <span
87
+ >{manager?.formatted_current_time}{manager?.time_slider_manager?.max &&
88
+ manager?.time_slider_manager?.max != Infinity
89
+ ? ` / ${manager?.formatted_duration}`
90
+ : ""}</span
91
+ >
86
92
  </div>
87
93
  {@render settings()}
88
94
  </div>
@@ -91,7 +97,7 @@ onabort={(e) => console.log('abort',e)} -->
91
97
  {:else}
92
98
  <div
93
99
  class="container_line"
94
- class:is_short={manager?.ui_type == 'short'}
100
+ class:is_short={manager?.ui_type == "short"}
95
101
  style="margin-left: {manager?.ml}rem; margin-right: {manager?.mr}rem; margin-top: {manager?.mt}rem; margin-bottom: {manager?.mb}rem;"
96
102
  >
97
103
  {#if manager?.image_src && !manager?.is_hide_image}
@@ -105,20 +111,20 @@ onabort={(e) => console.log('abort',e)} -->
105
111
  />
106
112
  {/if}
107
113
  {#if !manager?.src}
108
- <p style="display: flex; flex: 1;" class:is_short={manager?.ui_type == 'short'}>Missing Audio Source</p>
114
+ <p style="display: flex; flex: 1;" class:is_short={manager?.ui_type == "short"}>Missing Audio Source</p>
109
115
  {:else if manager?.is_loading && !manager?.time_slider_manager?.max}
110
- <div style="display: flex; flex: 1; margin-left: 1rem;" class:is_short={manager?.ui_type == 'short'}>
116
+ <div style="display: flex; flex: 1; margin-left: 1rem;" class:is_short={manager?.ui_type == "short"}>
111
117
  <LoadingWheel size={2} />
112
- {#if manager?.ui_type != 'standard'}
113
- <p style="margin-left: 1rem;" class:is_short={manager?.ui_type == 'short'}>Loading...</p>
118
+ {#if manager?.ui_type != "standard"}
119
+ <p style="margin-left: 1rem;" class:is_short={manager?.ui_type == "short"}>Loading...</p>
114
120
  {/if}
115
121
  </div>
116
122
  {:else}
117
123
  <Button manager={manager?.play_pause_button_manager} />
118
- <div class="slider_box" class:is_short={manager?.ui_type == 'short'}>
124
+ <div class="slider_box" class:is_short={manager?.ui_type == "short"}>
119
125
  <span>{manager?.formatted_current_time}</span>
120
- {#if manager?.ui_type == 'standard'}
121
- <div style="display: flex; flex: 1; flex-direction: column;" class:is_short={manager?.ui_type == 'short'}>
126
+ {#if manager?.ui_type == "standard"}
127
+ <div style="display: flex; flex: 1; flex-direction: column;" class:is_short={manager?.ui_type == "short"}>
122
128
  <Slider manager={manager?.time_slider_manager} />
123
129
  </div>
124
130
  {:else}
@@ -128,7 +134,7 @@ onabort={(e) => console.log('abort',e)} -->
128
134
  <span>{manager?.formatted_duration}</span>
129
135
  {/if}
130
136
  </div>
131
- {#if manager?.ui_type == 'standard'}
137
+ {#if manager?.ui_type == "standard"}
132
138
  {@render settings()}
133
139
  {/if}
134
140
  {/if}
@@ -157,7 +163,7 @@ onabort={(e) => console.log('abort',e)} -->
157
163
  0 0.2rem 1rem var(--shadow2),
158
164
  inset 0 0 0 1px var(--shadow7-t);
159
165
  }
160
- .container_square{
166
+ .container_square {
161
167
  display: flex;
162
168
  flex-direction: column;
163
169
  flex: 0;
@@ -27,6 +27,10 @@ export function create_audio_manager(config) {
27
27
  let settings_button_manager = $state(null)
28
28
  let volume_slider_manager = $state(null)
29
29
  let rate_slider_manager = $state(null)
30
+ let is_show_download_button = $derived(set_closurable(config?.is_show_download_button, false))
31
+ let download_audio_button_manager = $state(null)
32
+ let download_is_loading = $state(false)
33
+ let download_error_message = $state(null)
30
34
 
31
35
  let time_loaded_until = $state(0)
32
36
  let time_highlight_range_start_percent = $derived((100 * time_slider_manager?.val) / time_slider_manager?.max)
@@ -293,6 +297,68 @@ export function create_audio_manager(config) {
293
297
  is_loading = false
294
298
  }
295
299
 
300
+ function mime_to_ext(mime) {
301
+ const v = String(mime || "").toLowerCase()
302
+ if (v.includes("mpeg")) return "mp3"
303
+ if (v.includes("mp4")) return "mp4"
304
+ if (v.includes("webm")) return "webm"
305
+ if (v.includes("ogg")) return "ogg"
306
+ if (v.includes("wav")) return "wav"
307
+ if (v.includes("flac")) return "flac"
308
+ if (v.includes("aac")) return "aac"
309
+ return "audio"
310
+ }
311
+
312
+ function safe_file_base(input) {
313
+ const s = String(input || "").trim()
314
+ if (!s) return "audio"
315
+ return (
316
+ s
317
+ .replace(/\.[a-z0-9]+$/i, "")
318
+ .replace(/[^a-z0-9_\- ]/gi, "")
319
+ .trim()
320
+ .replace(/\s+/g, "_")
321
+ .slice(0, 80)
322
+ .toLowerCase() || "audio"
323
+ )
324
+ }
325
+
326
+ async function download_audio() {
327
+ if (!browser) return
328
+ download_error_message = null
329
+ if (!src) {
330
+ download_error_message = "missing src"
331
+ return
332
+ }
333
+ try {
334
+ download_is_loading = true
335
+ const res = await fetch(src)
336
+ if (!res.ok) {
337
+ const t = await res.text().catch(() => null)
338
+ download_error_message = t || `download failed (${res.status})`
339
+ return
340
+ }
341
+ const content_type = res.headers.get("content-type") || type || "audio/mpeg"
342
+ const blob = await res.blob()
343
+ const ext = mime_to_ext(content_type || blob.type)
344
+ const base = safe_file_base(title || album || artist || "recording")
345
+ const file_name = `${base}.${ext}`
346
+ const object_url = URL.createObjectURL(blob)
347
+ const a = document.createElement("a")
348
+ a.href = object_url
349
+ a.download = file_name
350
+ a.rel = "noopener"
351
+ document.body.appendChild(a)
352
+ a.click()
353
+ a.remove()
354
+ setTimeout(() => URL.revokeObjectURL(object_url), 1000)
355
+ } catch (e) {
356
+ download_error_message = e?.message || "download failed"
357
+ } finally {
358
+ download_is_loading = false
359
+ }
360
+ }
361
+
296
362
  function init(config) {
297
363
  storage_path = config?.storage_path
298
364
  src = get_src(config?.src, config?.storage_id)
@@ -364,6 +430,14 @@ export function create_audio_manager(config) {
364
430
  val: 1,
365
431
  on_change: (input) => handle_rate_change(input),
366
432
  })
433
+ download_audio_button_manager = create_button_manager({
434
+ type: "outlined",
435
+ mt: 1,
436
+ text: "Download",
437
+ support_icon: "download",
438
+ is_loading: () => download_is_loading,
439
+ on_click: () => download_audio(),
440
+ })
367
441
  }
368
442
 
369
443
  init(config)
@@ -442,6 +516,12 @@ export function create_audio_manager(config) {
442
516
  get is_loading() {
443
517
  return is_loading
444
518
  },
519
+ get download_audio_button_manager() {
520
+ return download_audio_button_manager
521
+ },
522
+ get is_show_download_button() {
523
+ return is_show_download_button
524
+ },
445
525
  handle_mount,
446
526
  handle_loaded_metadata,
447
527
  handle_progress,
@@ -24,6 +24,7 @@
24
24
  type: "audio/aac",
25
25
  image_src: "https://dummyimage.com/1000x400",
26
26
  image_type: "image/png",
27
+ is_show_download_button: true,
27
28
  })
28
29
 
29
30
  let audio_manager3 = create_audio_manager({