|
|
""" |
|
|
์ฐ์ปด ๋ชจ๋ฌ ์ปดํฌ๋ํธ - YouTube ํํ ๋ฆฌ์ผ ์์ ํ์
|
|
|
์ฒซ ๋ฐฉ๋ฌธ ์ ์๋์ผ๋ก ํ์, "๋ค์ ๋ณด์ง ์๊ธฐ" ๊ธฐ๋ฅ ์ง์ |
|
|
|
|
|
localStorage ํค: 'welcome_modal_dismissed' (๊ฒ์ ์ํ์ ๋
๋ฆฝ์ ) |
|
|
""" |
|
|
|
|
|
import gradio as gr |
|
|
|
|
|
|
|
|
class WelcomeModal: |
|
|
""" |
|
|
์ฐ์ปด ๋ชจ๋ฌ - YouTube ํํ ๋ฆฌ์ผ ์์ ํ์ |
|
|
|
|
|
- ์ฒซ ๋ฐฉ๋ฌธ ์ ์๋ ํ์ |
|
|
- "๋ค์ ๋ณด์ง ์๊ธฐ" ํด๋ฆญ ์ localStorage์ ์ ์ฅ |
|
|
- ๊ฒ์ ์ด๊ธฐํ์ ๋
๋ฆฝ์ ์ผ๋ก ์ค์ ์ ์ง |
|
|
""" |
|
|
|
|
|
|
|
|
YOUTUBE_VIDEO_ID = "YbCf6x0B3fU" |
|
|
|
|
|
|
|
|
STORAGE_KEY = "welcome_modal_dismissed" |
|
|
|
|
|
HTML_TEMPLATE = """ |
|
|
${value && value.visible ? ` |
|
|
<div class="welcome-modal-overlay" data-welcome-overlay="true"> |
|
|
<div class="welcome-modal-container"> |
|
|
<div class="welcome-modal-content"> |
|
|
<h2 class="welcome-modal-title">Welcome to Voice Sementle!</h2> |
|
|
|
|
|
<div class="welcome-modal-video"> |
|
|
<iframe |
|
|
src="https://www.youtube.com/embed/${value.videoId || 'YbCf6x0B3fU'}?rel=0&modestbranding=1" |
|
|
title="Voice Sementle Tutorial" |
|
|
frameborder="0" |
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" |
|
|
referrerpolicy="strict-origin-when-cross-origin" |
|
|
allowfullscreen> |
|
|
</iframe> |
|
|
</div> |
|
|
|
|
|
<div class="welcome-modal-actions"> |
|
|
<button class="welcome-modal-btn welcome-modal-btn-primary" data-welcome-close="true"> |
|
|
Start Playing |
|
|
</button> |
|
|
<button class="welcome-modal-btn welcome-modal-btn-secondary" data-welcome-dismiss="true"> |
|
|
Don't show again |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
` : ''} |
|
|
""" |
|
|
|
|
|
CSS_TEMPLATE = """ |
|
|
/* ์ฐ์ปด ๋ชจ๋ฌ - ์ ์ญ ์คํ์ผ์ ์ํฅ ์๋๋ก ์ค์ฝํ */ |
|
|
.welcome-modal-overlay { |
|
|
position: fixed !important; |
|
|
top: 0 !important; |
|
|
left: 0 !important; |
|
|
right: 0 !important; |
|
|
bottom: 0 !important; |
|
|
width: 100vw !important; |
|
|
height: 100vh !important; |
|
|
background-color: rgba(0, 0, 0, 0.7) !important; |
|
|
backdrop-filter: blur(4px); |
|
|
z-index: 9999 !important; |
|
|
display: flex !important; |
|
|
justify-content: center !important; |
|
|
align-items: center !important; |
|
|
padding: 20px; |
|
|
box-sizing: border-box; |
|
|
font-family: inherit; |
|
|
} |
|
|
|
|
|
.welcome-modal-container { |
|
|
width: 100%; |
|
|
max-width: 1280px; |
|
|
max-height: 90vh; |
|
|
overflow-y: auto; |
|
|
position: relative; |
|
|
animation: welcomeModalFadeIn 0.3s ease-out; |
|
|
font-family: inherit; |
|
|
border-radius: 20px; |
|
|
} |
|
|
|
|
|
@keyframes welcomeModalFadeIn { |
|
|
from { |
|
|
opacity: 0; |
|
|
transform: scale(0.9) translateY(-20px); |
|
|
} |
|
|
to { |
|
|
opacity: 1; |
|
|
transform: scale(1) translateY(0); |
|
|
} |
|
|
} |
|
|
|
|
|
.welcome-modal-close { |
|
|
position: absolute; |
|
|
top: 16px; |
|
|
right: 16px; |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
border: 1px solid rgba(255, 255, 255, 0.2); |
|
|
border-radius: 50%; |
|
|
width: 40px; |
|
|
height: 40px; |
|
|
cursor: pointer; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
color: #ffffff; |
|
|
transition: all 0.2s ease; |
|
|
z-index: 10; |
|
|
padding: 0; |
|
|
font-family: inherit; |
|
|
} |
|
|
|
|
|
.welcome-modal-close:hover { |
|
|
background: rgba(255, 255, 255, 0.2); |
|
|
transform: scale(1.1); |
|
|
} |
|
|
|
|
|
.welcome-modal-content { |
|
|
padding: 32px; |
|
|
text-align: center; |
|
|
font-family: inherit; |
|
|
} |
|
|
|
|
|
.welcome-modal-title { |
|
|
font-size: 60px; |
|
|
font-weight: 700; |
|
|
color: #ffffff; |
|
|
margin: 0 0 24px 0; |
|
|
font-family: inherit; |
|
|
} |
|
|
|
|
|
.welcome-modal-subtitle { |
|
|
font-size: 16px; |
|
|
color: rgba(255, 255, 255, 0.7); |
|
|
margin: 0 0 24px 0; |
|
|
font-family: inherit; |
|
|
} |
|
|
|
|
|
.welcome-modal-video { |
|
|
position: relative; |
|
|
width: 100%; |
|
|
padding-bottom: 56.25%; /* 16:9 aspect ratio */ |
|
|
border-radius: 16px; |
|
|
overflow: hidden; |
|
|
background: #000; |
|
|
margin-bottom: 24px; |
|
|
} |
|
|
|
|
|
.welcome-modal-video iframe { |
|
|
position: absolute; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
} |
|
|
|
|
|
.welcome-modal-actions { |
|
|
display: flex; |
|
|
justify-content: center; |
|
|
gap: 12px; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.welcome-modal-btn { |
|
|
padding: 14px 32px; |
|
|
border-radius: 20px; |
|
|
font-size: 16px; |
|
|
font-weight: 600; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s ease; |
|
|
border: none; |
|
|
min-width: 200px; |
|
|
font-family: inherit; |
|
|
} |
|
|
|
|
|
.welcome-modal-btn-primary { |
|
|
background: #4db8ff !important; |
|
|
color: #ffffff !important; |
|
|
} |
|
|
|
|
|
.welcome-modal-btn-primary:hover { |
|
|
transform: translateY(-2px); |
|
|
} |
|
|
|
|
|
.welcome-modal-btn-secondary { |
|
|
background: transparent; |
|
|
color: rgba(255, 255, 255, 0.6); |
|
|
border: 1px solid rgba(255, 255, 255, 0.2); |
|
|
} |
|
|
|
|
|
.welcome-modal-btn-secondary:hover { |
|
|
background: rgba(255, 255, 255, 0.1); |
|
|
color: rgba(255, 255, 255, 0.9); |
|
|
} |
|
|
|
|
|
/* Mobile responsive */ |
|
|
@media (max-width: 600px) { |
|
|
.welcome-modal-container { |
|
|
max-width: 100%; |
|
|
border-radius: 16px; |
|
|
} |
|
|
|
|
|
.welcome-modal-content { |
|
|
padding: 24px 16px; |
|
|
} |
|
|
|
|
|
.welcome-modal-title { |
|
|
font-size: 22px; |
|
|
} |
|
|
|
|
|
.welcome-modal-btn { |
|
|
width: 100%; |
|
|
min-width: unset; |
|
|
} |
|
|
} |
|
|
""" |
|
|
|
|
|
JS_ON_LOAD = """ |
|
|
const STORAGE_KEY = 'welcome_modal_dismissed'; |
|
|
const videoId = props.value?.videoId || 'YbCf6x0B3fU'; |
|
|
|
|
|
// ํ์ด์ง ๋ก๋ ์ localStorage ์ฒดํฌ |
|
|
const isDismissed = localStorage.getItem(STORAGE_KEY) === 'true'; |
|
|
|
|
|
// "๋ค์ ๋ณด์ง ์๊ธฐ"๋ฅผ ์ ํํ์ง ์์ ๊ฒฝ์ฐ์๋ง ๋ชจ๋ฌ ํ์ |
|
|
if (!isDismissed) { |
|
|
// ํฐํธ๊ฐ ์์ ํ ๋ก๋๋ ํ ๋ชจ๋ฌ ํ์ |
|
|
document.fonts.ready.then(() => { |
|
|
// ํฐํธ ๋ก๋ ํ ์ฝ๊ฐ์ ๋๋ ์ด ์ถ๊ฐ (๋ ๋๋ง ์์ ํ) |
|
|
setTimeout(() => { |
|
|
props.value = { visible: true, videoId: videoId }; |
|
|
}, 100); |
|
|
}); |
|
|
} |
|
|
|
|
|
// ํด๋ฆญ ์ด๋ฒคํธ ํธ๋ค๋ฌ |
|
|
element.addEventListener('click', (e) => { |
|
|
// ๋ฐฐ๊ฒฝ ํด๋ฆญ์ผ๋ก ๋ซ๊ธฐ |
|
|
if (e.target.dataset.welcomeOverlay === 'true') { |
|
|
props.value = { visible: false, videoId: props.value?.videoId }; |
|
|
} |
|
|
|
|
|
// X ๋ฒํผ ๋๋ "Start Playing" ๋ฒํผ ํด๋ฆญ |
|
|
if (e.target.closest('[data-welcome-close="true"]')) { |
|
|
props.value = { visible: false, videoId: props.value?.videoId }; |
|
|
} |
|
|
|
|
|
// "Don't show again" ๋ฒํผ ํด๋ฆญ |
|
|
if (e.target.closest('[data-welcome-dismiss="true"]')) { |
|
|
localStorage.setItem(STORAGE_KEY, 'true'); |
|
|
props.value = { visible: false, videoId: props.value?.videoId }; |
|
|
} |
|
|
}); |
|
|
|
|
|
// ESC ํค๋ก ๋ซ๊ธฐ |
|
|
const handleKeydown = (e) => { |
|
|
if (e.key === 'Escape' && props.value && props.value.visible) { |
|
|
props.value = { visible: false, videoId: props.value?.videoId }; |
|
|
} |
|
|
}; |
|
|
document.addEventListener('keydown', handleKeydown); |
|
|
""" |
|
|
|
|
|
def __init__(self, video_id: str = None): |
|
|
""" |
|
|
์ฐ์ปด ๋ชจ๋ฌ ์ด๊ธฐํ |
|
|
|
|
|
Args: |
|
|
video_id: YouTube ์์ ID (๊ธฐ๋ณธ๊ฐ: YbCf6x0B3fU) |
|
|
""" |
|
|
self.video_id = video_id or self.YOUTUBE_VIDEO_ID |
|
|
self.component = None |
|
|
|
|
|
def render(self) -> gr.HTML: |
|
|
""" |
|
|
์ฐ์ปด ๋ชจ๋ฌ ์ปดํฌ๋ํธ ๋ ๋๋ง |
|
|
|
|
|
Returns: |
|
|
gr.HTML: ๋ ๋๋ง๋ ๋ชจ๋ฌ ์ปดํฌ๋ํธ |
|
|
""" |
|
|
|
|
|
self.component = gr.HTML( |
|
|
value={"visible": False, "videoId": self.video_id}, |
|
|
html_template=self.HTML_TEMPLATE, |
|
|
css_template=self.CSS_TEMPLATE, |
|
|
js_on_load=self.JS_ON_LOAD, |
|
|
elem_id="welcome-modal", |
|
|
) |
|
|
return self.component |
|
|
|
|
|
@staticmethod |
|
|
def show(video_id: str = "YbCf6x0B3fU") -> dict: |
|
|
"""๋ชจ๋ฌ ์ด๊ธฐ""" |
|
|
return {"visible": True, "videoId": video_id} |
|
|
|
|
|
@staticmethod |
|
|
def hide() -> dict: |
|
|
"""๋ชจ๋ฌ ๋ซ๊ธฐ""" |
|
|
return {"visible": False, "videoId": ""} |
|
|
|