sveltekit-ui 1.1.55 → 1.1.57
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.
- package/dist/Components/YoutubeVideo/index.svelte +118 -26
- package/dist/Components/YoutubeVideo/index.svelte.js +97 -21
- package/package.json +2 -2
- package/src/lib/Components/YoutubeVideo/index.svelte +118 -26
- package/src/lib/Components/YoutubeVideo/index.svelte.js +97 -21
- package/src/routes/+page.svelte +3 -3
- package/src/routes/components/[component]/Showcase/Location/index.svelte +2 -2
|
@@ -5,42 +5,62 @@
|
|
|
5
5
|
let { manager } = $props()
|
|
6
6
|
|
|
7
7
|
onMount(() => {
|
|
8
|
-
if (typeof manager?.render_video
|
|
9
|
-
manager
|
|
8
|
+
if (typeof manager?.render_video === "function") {
|
|
9
|
+
manager.render_video()
|
|
10
10
|
}
|
|
11
11
|
})
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
|
-
<svelte:window onresize={() => manager.resize_video()} />
|
|
15
|
-
|
|
16
14
|
<div id={manager?.selector_id}>
|
|
17
|
-
{#if manager?.is_loading}
|
|
18
|
-
<div
|
|
19
|
-
style="width: 100%; height: 30rem; display: grid; place-items: center; background-color: var(--shadow2-t); border-radius: 1rem; margin-top: 2rem;"
|
|
20
|
-
>
|
|
21
|
-
<LoadingWheel size={8} />
|
|
22
|
-
<p>Loading Youtube Video</p>
|
|
23
|
-
</div>
|
|
24
|
-
{/if}
|
|
25
15
|
{#if manager?.error_message}
|
|
26
16
|
<div class="failed_to_load_box">
|
|
27
17
|
<p>{manager?.error_message}</p>
|
|
28
|
-
<a href=
|
|
18
|
+
<a href={manager?.youtube_url} target="_blank" rel="noreferrer">{manager?.youtube_url}</a>
|
|
29
19
|
</div>
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
20
|
+
{:else if manager?.use_poster_preview && !manager?.is_iframe_active}
|
|
21
|
+
<button
|
|
22
|
+
type="button"
|
|
23
|
+
class="video_preview_button"
|
|
24
|
+
aria-label={`Play ${manager?.title}`}
|
|
25
|
+
onclick={() => manager?.activate_video?.()}
|
|
26
|
+
>
|
|
27
|
+
<img
|
|
28
|
+
class="video_preview_image"
|
|
29
|
+
src={manager?.poster_src}
|
|
30
|
+
alt={manager?.title}
|
|
31
|
+
loading="lazy"
|
|
32
|
+
onerror={manager?.handle_poster_error}
|
|
33
|
+
/>
|
|
34
|
+
<span class="video_preview_overlay">
|
|
35
|
+
<span class="video_preview_play">
|
|
36
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
37
|
+
<path d="M8 6v12l10-6z"></path>
|
|
38
|
+
</svg>
|
|
39
|
+
</span>
|
|
40
|
+
</span>
|
|
41
|
+
</button>
|
|
42
|
+
{:else}
|
|
43
|
+
<div class="video_container" style={`border-radius: ${manager?.border_radius}rem;`}>
|
|
44
|
+
{#if manager?.is_loading}
|
|
45
|
+
<div class="loading_box">
|
|
46
|
+
<LoadingWheel size={8} />
|
|
47
|
+
<p>Loading Youtube Video</p>
|
|
48
|
+
</div>
|
|
49
|
+
{/if}
|
|
50
|
+
|
|
51
|
+
<div class="video_inner_container">
|
|
52
|
+
<iframe
|
|
53
|
+
class:is_hidden={!!manager?.error_message}
|
|
54
|
+
src={manager?.embed_src}
|
|
55
|
+
title={manager?.title}
|
|
56
|
+
loading="lazy"
|
|
57
|
+
frameborder="0"
|
|
58
|
+
allow="autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
59
|
+
allowfullscreen
|
|
60
|
+
></iframe>
|
|
61
|
+
</div>
|
|
42
62
|
</div>
|
|
43
|
-
|
|
63
|
+
{/if}
|
|
44
64
|
</div>
|
|
45
65
|
|
|
46
66
|
<style>
|
|
@@ -49,21 +69,93 @@
|
|
|
49
69
|
width: 100%;
|
|
50
70
|
overflow: hidden;
|
|
51
71
|
}
|
|
72
|
+
|
|
52
73
|
.video_inner_container {
|
|
53
74
|
position: relative;
|
|
54
75
|
padding-bottom: 56.25%;
|
|
55
76
|
height: 0;
|
|
56
77
|
overflow: hidden;
|
|
57
78
|
}
|
|
79
|
+
|
|
80
|
+
.video_inner_container iframe {
|
|
81
|
+
position: absolute;
|
|
82
|
+
inset: 0;
|
|
83
|
+
width: 100%;
|
|
84
|
+
height: 100%;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.video_preview_button {
|
|
88
|
+
position: relative;
|
|
89
|
+
display: block;
|
|
90
|
+
width: 100%;
|
|
91
|
+
border: 0;
|
|
92
|
+
padding: 0;
|
|
93
|
+
background: transparent;
|
|
94
|
+
border-radius: 1rem;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.video_preview_image {
|
|
100
|
+
display: block;
|
|
101
|
+
width: 100%;
|
|
102
|
+
aspect-ratio: 16 / 9;
|
|
103
|
+
object-fit: cover;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.video_preview_overlay {
|
|
107
|
+
position: absolute;
|
|
108
|
+
inset: 0;
|
|
109
|
+
display: grid;
|
|
110
|
+
place-items: center;
|
|
111
|
+
background: linear-gradient(to top, rgb(0 0 0 / 0.28), rgb(0 0 0 / 0.06));
|
|
112
|
+
transition: background 0.2s ease;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.video_preview_button:hover .video_preview_overlay,
|
|
116
|
+
.video_preview_button:focus-visible .video_preview_overlay {
|
|
117
|
+
background: linear-gradient(to top, rgb(0 0 0 / 0.42), rgb(0 0 0 / 0.12));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.video_preview_play {
|
|
121
|
+
display: grid;
|
|
122
|
+
place-items: center;
|
|
123
|
+
width: 4.25rem;
|
|
124
|
+
height: 4.25rem;
|
|
125
|
+
border-radius: 999px;
|
|
126
|
+
background: rgb(255 255 255 / 0.94);
|
|
127
|
+
color: #111827;
|
|
128
|
+
box-shadow: 0 0.5rem 2rem rgb(0 0 0 / 0.18);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.video_preview_play svg {
|
|
132
|
+
width: 1.8rem;
|
|
133
|
+
height: 1.8rem;
|
|
134
|
+
fill: currentColor;
|
|
135
|
+
transform: translateX(0.08rem);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.loading_box {
|
|
139
|
+
width: 100%;
|
|
140
|
+
height: 30rem;
|
|
141
|
+
display: grid;
|
|
142
|
+
place-items: center;
|
|
143
|
+
background-color: var(--shadow2-t);
|
|
144
|
+
border-radius: 1rem;
|
|
145
|
+
margin-top: 2rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
58
148
|
.failed_to_load_box {
|
|
59
149
|
margin: 1rem 0;
|
|
60
150
|
background-color: oklch(var(--l8-t) var(--c16) var(--h3) / var(--o3));
|
|
61
151
|
border-radius: 1rem;
|
|
62
152
|
padding: 1rem;
|
|
63
153
|
}
|
|
154
|
+
|
|
64
155
|
.failed_to_load_box p {
|
|
65
156
|
color: oklch(var(--l3-t) var(--c16) var(--h3));
|
|
66
157
|
}
|
|
158
|
+
|
|
67
159
|
.is_hidden {
|
|
68
160
|
max-width: 0;
|
|
69
161
|
max-height: 0;
|
|
@@ -1,57 +1,133 @@
|
|
|
1
1
|
import { create_unique_id } from "../../client/index.js"
|
|
2
2
|
|
|
3
3
|
export function create_youtube_video_manager(config) {
|
|
4
|
-
|
|
4
|
+
const id = create_unique_id(null, 20)
|
|
5
|
+
|
|
5
6
|
let theme = $derived(config?.is_dark_theme ? "dark" : "light")
|
|
7
|
+
let selector_id = $derived(config?.selector_id ?? `youtube_video_${id}`)
|
|
8
|
+
let use_poster_preview = $derived(config?.use_poster_preview ?? false)
|
|
9
|
+
let autoplay_on_activate = $derived(config?.autoplay_on_activate ?? true)
|
|
10
|
+
let border_radius = $derived(config?.border_radius ?? 1)
|
|
11
|
+
let title = $derived(config?.title ?? "YouTube video player")
|
|
6
12
|
|
|
7
13
|
let is_loading = $state(false)
|
|
8
14
|
let error_message = $state("")
|
|
15
|
+
let is_iframe_active = $state(!(config?.use_poster_preview ?? true))
|
|
16
|
+
|
|
17
|
+
function handle_poster_error(e) {
|
|
18
|
+
if (manager?.poster_fallback_src && e.currentTarget.src !== manager.poster_fallback_src) {
|
|
19
|
+
e.currentTarget.src = manager.poster_fallback_src
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function get_thumbnail_src(quality = "maxresdefault") {
|
|
24
|
+
if (!config?.video_id) return null
|
|
25
|
+
return `https://img.youtube.com/vi/${config.video_id}/${quality}.jpg`
|
|
26
|
+
}
|
|
9
27
|
|
|
10
28
|
async function render_video() {
|
|
11
29
|
error_message = ""
|
|
30
|
+
|
|
31
|
+
if (!config?.video_id) {
|
|
32
|
+
error_message = "YouTube video not found or failed to load."
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
12
36
|
is_loading = true
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
img
|
|
37
|
+
|
|
38
|
+
await new Promise((resolve) => {
|
|
39
|
+
const img = new Image()
|
|
40
|
+
|
|
16
41
|
img.onload = function () {
|
|
17
|
-
if (this.
|
|
42
|
+
if (this.naturalWidth === 120) {
|
|
18
43
|
error_message = "YouTube video not found or failed to load."
|
|
19
44
|
} else {
|
|
20
45
|
error_message = ""
|
|
21
|
-
resize_video()
|
|
22
46
|
}
|
|
47
|
+
is_loading = false
|
|
48
|
+
resolve()
|
|
23
49
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
50
|
+
|
|
51
|
+
img.onerror = function () {
|
|
52
|
+
error_message = "YouTube video not found or failed to load."
|
|
53
|
+
is_loading = false
|
|
54
|
+
resolve()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
img.src = get_thumbnail_src("hqdefault")
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function activate_video() {
|
|
62
|
+
if (!config?.video_id || error_message) return
|
|
63
|
+
is_iframe_active = true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function deactivate_video() {
|
|
67
|
+
is_iframe_active = !use_poster_preview
|
|
28
68
|
}
|
|
29
69
|
|
|
30
70
|
function resize_video() {
|
|
31
|
-
|
|
32
|
-
const max_width = video_container.clientWidth
|
|
33
|
-
const max_height = 15 * 16 // 20rem * 16px
|
|
34
|
-
const aspect_ratio = 16 / 9
|
|
35
|
-
const height = Math.min(max_width / aspect_ratio, max_height)
|
|
36
|
-
const width = height * aspect_ratio
|
|
37
|
-
if (video_container.style.height > height) {
|
|
38
|
-
video_container.style.height = `${height}px`
|
|
39
|
-
video_container.style.width = `${width}px`
|
|
40
|
-
}
|
|
71
|
+
// CSS handles sizing now.
|
|
41
72
|
}
|
|
42
73
|
|
|
43
74
|
return {
|
|
44
75
|
id,
|
|
76
|
+
get selector_id() {
|
|
77
|
+
return selector_id
|
|
78
|
+
},
|
|
45
79
|
get video_id() {
|
|
46
80
|
return config?.video_id
|
|
47
81
|
},
|
|
48
82
|
get theme() {
|
|
49
83
|
return theme
|
|
50
84
|
},
|
|
85
|
+
get title() {
|
|
86
|
+
return title
|
|
87
|
+
},
|
|
51
88
|
get border_radius() {
|
|
52
|
-
return
|
|
89
|
+
return border_radius
|
|
90
|
+
},
|
|
91
|
+
get is_loading() {
|
|
92
|
+
return is_loading
|
|
93
|
+
},
|
|
94
|
+
get error_message() {
|
|
95
|
+
return error_message
|
|
96
|
+
},
|
|
97
|
+
get use_poster_preview() {
|
|
98
|
+
return use_poster_preview
|
|
99
|
+
},
|
|
100
|
+
get is_iframe_active() {
|
|
101
|
+
return is_iframe_active
|
|
102
|
+
},
|
|
103
|
+
get poster_src() {
|
|
104
|
+
return config?.poster_src ?? get_thumbnail_src("maxresdefault")
|
|
105
|
+
},
|
|
106
|
+
get poster_fallback_src() {
|
|
107
|
+
return config?.poster_fallback_src ?? get_thumbnail_src("hqdefault")
|
|
108
|
+
},
|
|
109
|
+
get youtube_url() {
|
|
110
|
+
return config?.video_id ? `https://youtu.be/${config.video_id}` : ""
|
|
111
|
+
},
|
|
112
|
+
get embed_src() {
|
|
113
|
+
if (!config?.video_id) return ""
|
|
114
|
+
|
|
115
|
+
const params = new URLSearchParams({
|
|
116
|
+
rel: "0",
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const shouldAutoplay = use_poster_preview ? is_iframe_active && autoplay_on_activate : !!config?.autoplay
|
|
120
|
+
|
|
121
|
+
if (shouldAutoplay) {
|
|
122
|
+
params.set("autoplay", "1")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return `https://www.youtube.com/embed/${config.video_id}?${params.toString()}`
|
|
53
126
|
},
|
|
54
127
|
render_video,
|
|
128
|
+
activate_video,
|
|
129
|
+
deactivate_video,
|
|
55
130
|
resize_video,
|
|
131
|
+
handle_poster_error,
|
|
56
132
|
}
|
|
57
133
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sveltekit-ui",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.57",
|
|
4
4
|
"description": "A SvelteKit UI component library for building modern web applications",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
32
32
|
"@vercel/analytics": "^2.0.1",
|
|
33
33
|
"typescript": "^6.0.2",
|
|
34
|
-
"vite": "^8.0.
|
|
34
|
+
"vite": "^8.0.8"
|
|
35
35
|
},
|
|
36
36
|
"homepage": "https://www.sveltekit-ui.com",
|
|
37
37
|
"keywords": [
|
|
@@ -5,42 +5,62 @@
|
|
|
5
5
|
let { manager } = $props()
|
|
6
6
|
|
|
7
7
|
onMount(() => {
|
|
8
|
-
if (typeof manager?.render_video
|
|
9
|
-
manager
|
|
8
|
+
if (typeof manager?.render_video === "function") {
|
|
9
|
+
manager.render_video()
|
|
10
10
|
}
|
|
11
11
|
})
|
|
12
12
|
</script>
|
|
13
13
|
|
|
14
|
-
<svelte:window onresize={() => manager.resize_video()} />
|
|
15
|
-
|
|
16
14
|
<div id={manager?.selector_id}>
|
|
17
|
-
{#if manager?.is_loading}
|
|
18
|
-
<div
|
|
19
|
-
style="width: 100%; height: 30rem; display: grid; place-items: center; background-color: var(--shadow2-t); border-radius: 1rem; margin-top: 2rem;"
|
|
20
|
-
>
|
|
21
|
-
<LoadingWheel size={8} />
|
|
22
|
-
<p>Loading Youtube Video</p>
|
|
23
|
-
</div>
|
|
24
|
-
{/if}
|
|
25
15
|
{#if manager?.error_message}
|
|
26
16
|
<div class="failed_to_load_box">
|
|
27
17
|
<p>{manager?.error_message}</p>
|
|
28
|
-
<a href=
|
|
18
|
+
<a href={manager?.youtube_url} target="_blank" rel="noreferrer">{manager?.youtube_url}</a>
|
|
29
19
|
</div>
|
|
30
|
-
{
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
20
|
+
{:else if manager?.use_poster_preview && !manager?.is_iframe_active}
|
|
21
|
+
<button
|
|
22
|
+
type="button"
|
|
23
|
+
class="video_preview_button"
|
|
24
|
+
aria-label={`Play ${manager?.title}`}
|
|
25
|
+
onclick={() => manager?.activate_video?.()}
|
|
26
|
+
>
|
|
27
|
+
<img
|
|
28
|
+
class="video_preview_image"
|
|
29
|
+
src={manager?.poster_src}
|
|
30
|
+
alt={manager?.title}
|
|
31
|
+
loading="lazy"
|
|
32
|
+
onerror={manager?.handle_poster_error}
|
|
33
|
+
/>
|
|
34
|
+
<span class="video_preview_overlay">
|
|
35
|
+
<span class="video_preview_play">
|
|
36
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
37
|
+
<path d="M8 6v12l10-6z"></path>
|
|
38
|
+
</svg>
|
|
39
|
+
</span>
|
|
40
|
+
</span>
|
|
41
|
+
</button>
|
|
42
|
+
{:else}
|
|
43
|
+
<div class="video_container" style={`border-radius: ${manager?.border_radius}rem;`}>
|
|
44
|
+
{#if manager?.is_loading}
|
|
45
|
+
<div class="loading_box">
|
|
46
|
+
<LoadingWheel size={8} />
|
|
47
|
+
<p>Loading Youtube Video</p>
|
|
48
|
+
</div>
|
|
49
|
+
{/if}
|
|
50
|
+
|
|
51
|
+
<div class="video_inner_container">
|
|
52
|
+
<iframe
|
|
53
|
+
class:is_hidden={!!manager?.error_message}
|
|
54
|
+
src={manager?.embed_src}
|
|
55
|
+
title={manager?.title}
|
|
56
|
+
loading="lazy"
|
|
57
|
+
frameborder="0"
|
|
58
|
+
allow="autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
59
|
+
allowfullscreen
|
|
60
|
+
></iframe>
|
|
61
|
+
</div>
|
|
42
62
|
</div>
|
|
43
|
-
|
|
63
|
+
{/if}
|
|
44
64
|
</div>
|
|
45
65
|
|
|
46
66
|
<style>
|
|
@@ -49,21 +69,93 @@
|
|
|
49
69
|
width: 100%;
|
|
50
70
|
overflow: hidden;
|
|
51
71
|
}
|
|
72
|
+
|
|
52
73
|
.video_inner_container {
|
|
53
74
|
position: relative;
|
|
54
75
|
padding-bottom: 56.25%;
|
|
55
76
|
height: 0;
|
|
56
77
|
overflow: hidden;
|
|
57
78
|
}
|
|
79
|
+
|
|
80
|
+
.video_inner_container iframe {
|
|
81
|
+
position: absolute;
|
|
82
|
+
inset: 0;
|
|
83
|
+
width: 100%;
|
|
84
|
+
height: 100%;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.video_preview_button {
|
|
88
|
+
position: relative;
|
|
89
|
+
display: block;
|
|
90
|
+
width: 100%;
|
|
91
|
+
border: 0;
|
|
92
|
+
padding: 0;
|
|
93
|
+
background: transparent;
|
|
94
|
+
border-radius: 1rem;
|
|
95
|
+
overflow: hidden;
|
|
96
|
+
cursor: pointer;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.video_preview_image {
|
|
100
|
+
display: block;
|
|
101
|
+
width: 100%;
|
|
102
|
+
aspect-ratio: 16 / 9;
|
|
103
|
+
object-fit: cover;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.video_preview_overlay {
|
|
107
|
+
position: absolute;
|
|
108
|
+
inset: 0;
|
|
109
|
+
display: grid;
|
|
110
|
+
place-items: center;
|
|
111
|
+
background: linear-gradient(to top, rgb(0 0 0 / 0.28), rgb(0 0 0 / 0.06));
|
|
112
|
+
transition: background 0.2s ease;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.video_preview_button:hover .video_preview_overlay,
|
|
116
|
+
.video_preview_button:focus-visible .video_preview_overlay {
|
|
117
|
+
background: linear-gradient(to top, rgb(0 0 0 / 0.42), rgb(0 0 0 / 0.12));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.video_preview_play {
|
|
121
|
+
display: grid;
|
|
122
|
+
place-items: center;
|
|
123
|
+
width: 4.25rem;
|
|
124
|
+
height: 4.25rem;
|
|
125
|
+
border-radius: 999px;
|
|
126
|
+
background: rgb(255 255 255 / 0.94);
|
|
127
|
+
color: #111827;
|
|
128
|
+
box-shadow: 0 0.5rem 2rem rgb(0 0 0 / 0.18);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.video_preview_play svg {
|
|
132
|
+
width: 1.8rem;
|
|
133
|
+
height: 1.8rem;
|
|
134
|
+
fill: currentColor;
|
|
135
|
+
transform: translateX(0.08rem);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.loading_box {
|
|
139
|
+
width: 100%;
|
|
140
|
+
height: 30rem;
|
|
141
|
+
display: grid;
|
|
142
|
+
place-items: center;
|
|
143
|
+
background-color: var(--shadow2-t);
|
|
144
|
+
border-radius: 1rem;
|
|
145
|
+
margin-top: 2rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
58
148
|
.failed_to_load_box {
|
|
59
149
|
margin: 1rem 0;
|
|
60
150
|
background-color: oklch(var(--l8-t) var(--c16) var(--h3) / var(--o3));
|
|
61
151
|
border-radius: 1rem;
|
|
62
152
|
padding: 1rem;
|
|
63
153
|
}
|
|
154
|
+
|
|
64
155
|
.failed_to_load_box p {
|
|
65
156
|
color: oklch(var(--l3-t) var(--c16) var(--h3));
|
|
66
157
|
}
|
|
158
|
+
|
|
67
159
|
.is_hidden {
|
|
68
160
|
max-width: 0;
|
|
69
161
|
max-height: 0;
|
|
@@ -1,57 +1,133 @@
|
|
|
1
1
|
import { create_unique_id } from "$lib/client/index.js"
|
|
2
2
|
|
|
3
3
|
export function create_youtube_video_manager(config) {
|
|
4
|
-
|
|
4
|
+
const id = create_unique_id(null, 20)
|
|
5
|
+
|
|
5
6
|
let theme = $derived(config?.is_dark_theme ? "dark" : "light")
|
|
7
|
+
let selector_id = $derived(config?.selector_id ?? `youtube_video_${id}`)
|
|
8
|
+
let use_poster_preview = $derived(config?.use_poster_preview ?? false)
|
|
9
|
+
let autoplay_on_activate = $derived(config?.autoplay_on_activate ?? true)
|
|
10
|
+
let border_radius = $derived(config?.border_radius ?? 1)
|
|
11
|
+
let title = $derived(config?.title ?? "YouTube video player")
|
|
6
12
|
|
|
7
13
|
let is_loading = $state(false)
|
|
8
14
|
let error_message = $state("")
|
|
15
|
+
let is_iframe_active = $state(!(config?.use_poster_preview ?? true))
|
|
16
|
+
|
|
17
|
+
function handle_poster_error(e) {
|
|
18
|
+
if (manager?.poster_fallback_src && e.currentTarget.src !== manager.poster_fallback_src) {
|
|
19
|
+
e.currentTarget.src = manager.poster_fallback_src
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function get_thumbnail_src(quality = "maxresdefault") {
|
|
24
|
+
if (!config?.video_id) return null
|
|
25
|
+
return `https://img.youtube.com/vi/${config.video_id}/${quality}.jpg`
|
|
26
|
+
}
|
|
9
27
|
|
|
10
28
|
async function render_video() {
|
|
11
29
|
error_message = ""
|
|
30
|
+
|
|
31
|
+
if (!config?.video_id) {
|
|
32
|
+
error_message = "YouTube video not found or failed to load."
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
|
|
12
36
|
is_loading = true
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
img
|
|
37
|
+
|
|
38
|
+
await new Promise((resolve) => {
|
|
39
|
+
const img = new Image()
|
|
40
|
+
|
|
16
41
|
img.onload = function () {
|
|
17
|
-
if (this.
|
|
42
|
+
if (this.naturalWidth === 120) {
|
|
18
43
|
error_message = "YouTube video not found or failed to load."
|
|
19
44
|
} else {
|
|
20
45
|
error_message = ""
|
|
21
|
-
resize_video()
|
|
22
46
|
}
|
|
47
|
+
is_loading = false
|
|
48
|
+
resolve()
|
|
23
49
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
50
|
+
|
|
51
|
+
img.onerror = function () {
|
|
52
|
+
error_message = "YouTube video not found or failed to load."
|
|
53
|
+
is_loading = false
|
|
54
|
+
resolve()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
img.src = get_thumbnail_src("hqdefault")
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function activate_video() {
|
|
62
|
+
if (!config?.video_id || error_message) return
|
|
63
|
+
is_iframe_active = true
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function deactivate_video() {
|
|
67
|
+
is_iframe_active = !use_poster_preview
|
|
28
68
|
}
|
|
29
69
|
|
|
30
70
|
function resize_video() {
|
|
31
|
-
|
|
32
|
-
const max_width = video_container.clientWidth
|
|
33
|
-
const max_height = 15 * 16 // 20rem * 16px
|
|
34
|
-
const aspect_ratio = 16 / 9
|
|
35
|
-
const height = Math.min(max_width / aspect_ratio, max_height)
|
|
36
|
-
const width = height * aspect_ratio
|
|
37
|
-
if (video_container.style.height > height) {
|
|
38
|
-
video_container.style.height = `${height}px`
|
|
39
|
-
video_container.style.width = `${width}px`
|
|
40
|
-
}
|
|
71
|
+
// CSS handles sizing now.
|
|
41
72
|
}
|
|
42
73
|
|
|
43
74
|
return {
|
|
44
75
|
id,
|
|
76
|
+
get selector_id() {
|
|
77
|
+
return selector_id
|
|
78
|
+
},
|
|
45
79
|
get video_id() {
|
|
46
80
|
return config?.video_id
|
|
47
81
|
},
|
|
48
82
|
get theme() {
|
|
49
83
|
return theme
|
|
50
84
|
},
|
|
85
|
+
get title() {
|
|
86
|
+
return title
|
|
87
|
+
},
|
|
51
88
|
get border_radius() {
|
|
52
|
-
return
|
|
89
|
+
return border_radius
|
|
90
|
+
},
|
|
91
|
+
get is_loading() {
|
|
92
|
+
return is_loading
|
|
93
|
+
},
|
|
94
|
+
get error_message() {
|
|
95
|
+
return error_message
|
|
96
|
+
},
|
|
97
|
+
get use_poster_preview() {
|
|
98
|
+
return use_poster_preview
|
|
99
|
+
},
|
|
100
|
+
get is_iframe_active() {
|
|
101
|
+
return is_iframe_active
|
|
102
|
+
},
|
|
103
|
+
get poster_src() {
|
|
104
|
+
return config?.poster_src ?? get_thumbnail_src("maxresdefault")
|
|
105
|
+
},
|
|
106
|
+
get poster_fallback_src() {
|
|
107
|
+
return config?.poster_fallback_src ?? get_thumbnail_src("hqdefault")
|
|
108
|
+
},
|
|
109
|
+
get youtube_url() {
|
|
110
|
+
return config?.video_id ? `https://youtu.be/${config.video_id}` : ""
|
|
111
|
+
},
|
|
112
|
+
get embed_src() {
|
|
113
|
+
if (!config?.video_id) return ""
|
|
114
|
+
|
|
115
|
+
const params = new URLSearchParams({
|
|
116
|
+
rel: "0",
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const shouldAutoplay = use_poster_preview ? is_iframe_active && autoplay_on_activate : !!config?.autoplay
|
|
120
|
+
|
|
121
|
+
if (shouldAutoplay) {
|
|
122
|
+
params.set("autoplay", "1")
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return `https://www.youtube.com/embed/${config.video_id}?${params.toString()}`
|
|
53
126
|
},
|
|
54
127
|
render_video,
|
|
128
|
+
activate_video,
|
|
129
|
+
deactivate_video,
|
|
55
130
|
resize_video,
|
|
131
|
+
handle_poster_error,
|
|
56
132
|
}
|
|
57
133
|
}
|
package/src/routes/+page.svelte
CHANGED
|
@@ -349,13 +349,13 @@ below are the docs for each component individually
|
|
|
349
349
|
</script>
|
|
350
350
|
|
|
351
351
|
<div class="container">
|
|
352
|
-
<div
|
|
352
|
+
<div>
|
|
353
353
|
<YoutubeVideo manager={youtube_video_manager} />
|
|
354
354
|
</div>
|
|
355
355
|
<Content manager={content_manager} />
|
|
356
356
|
<Button manager={button_manager} />
|
|
357
357
|
<Button manager={copy_all_docs_button_manager} />
|
|
358
|
-
<div>
|
|
358
|
+
<div style="max-height: 30rem; overflow: scroll;">
|
|
359
359
|
<h3>All Docs</h3>
|
|
360
360
|
{all_docs}
|
|
361
361
|
</div>
|
|
@@ -365,7 +365,7 @@ below are the docs for each component individually
|
|
|
365
365
|
.container {
|
|
366
366
|
margin: auto;
|
|
367
367
|
padding: 0 2rem 10rem 2rem;
|
|
368
|
-
|
|
368
|
+
width: clamp(20rem, 100%, 70rem);
|
|
369
369
|
}
|
|
370
370
|
p {
|
|
371
371
|
margin-top: 2rem;
|