fix. 런처실행 버튼 수정
This commit is contained in:
217
layers/components/blocks/Button/Download.vue
Normal file
217
layers/components/blocks/Button/Download.vue
Normal file
@@ -0,0 +1,217 @@
|
||||
<script setup lang="ts">
|
||||
import type { CSSProperties } from 'vue'
|
||||
import type {
|
||||
DownloadButtonType,
|
||||
ButtonVariant,
|
||||
} from '#layers/types/components/button'
|
||||
|
||||
type Platform = 'google_play' | 'app_store' | 'pc' | 'stove'
|
||||
|
||||
interface Props {
|
||||
platform: Platform
|
||||
type?: DownloadButtonType
|
||||
variant?: ButtonVariant
|
||||
backgroundColor?: string
|
||||
textColor?: string
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
type: 'default',
|
||||
variant: 'filled',
|
||||
disabled: false,
|
||||
})
|
||||
|
||||
const runtimeConfig = useRuntimeConfig()
|
||||
const { gameData } = useGameDataStore()
|
||||
const { isProcessing, validateLauncher } = useCheckGameStart()
|
||||
|
||||
const PLATFORM_ICON_MAP: Record<Platform, string> = {
|
||||
google_play: 'AtomsIconsLogoGoogle',
|
||||
app_store: 'AtomsIconsLogoApple',
|
||||
pc: 'AtomsIconsLogoWindow',
|
||||
stove: 'AtomsIconsLogoStove',
|
||||
} as const
|
||||
|
||||
const DUP_IMAGE_MAP: Record<Platform, string> = {
|
||||
google_play: '/images/common/btn_logo-google.svg',
|
||||
app_store: '/images/common/btn_logo-app.svg',
|
||||
pc: '/images/common/btn_logo-pc.svg',
|
||||
stove: '/images/common/btn_system_normal_stove_pc.svg',
|
||||
} as const
|
||||
|
||||
const componentTag = computed(() => {
|
||||
if (props.platform === 'stove') {
|
||||
return 'a'
|
||||
}
|
||||
return 'button'
|
||||
})
|
||||
const isDuplication = computed(() => props.type === 'duplication')
|
||||
const isSingle = computed(() => props.type === 'single')
|
||||
const isCustom = computed(() => props.type === 'custom')
|
||||
const platformIcon = computed(() => PLATFORM_ICON_MAP[props.platform])
|
||||
const duplicationImage = computed(() =>
|
||||
isDuplication.value ? DUP_IMAGE_MAP[props.platform] : ''
|
||||
)
|
||||
|
||||
const inlineStyle = computed<CSSProperties>(() => {
|
||||
const style: CSSProperties = {}
|
||||
|
||||
if (props.backgroundColor) {
|
||||
style.backgroundColor = props.backgroundColor
|
||||
}
|
||||
if (props.textColor) {
|
||||
style.color = props.textColor
|
||||
}
|
||||
if (props.type === 'duplication') {
|
||||
style.backgroundImage = `url(${duplicationImage.value})`
|
||||
}
|
||||
return style
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
if (props.platform === 'pc') {
|
||||
validateLauncher()
|
||||
return
|
||||
}
|
||||
if (props.platform === 'stove') {
|
||||
const stoveClientDownloadUrl = runtimeConfig.public.stoveClientDownloadUrl
|
||||
location.href = stoveClientDownloadUrl
|
||||
return
|
||||
}
|
||||
|
||||
const url = gameData?.market_json[props.platform]?.url || ''
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="componentTag"
|
||||
v-bind="$attrs"
|
||||
:class="[
|
||||
'btn-base',
|
||||
props.type,
|
||||
{ 'no-text': isSingle && !$slots.default },
|
||||
]"
|
||||
:data-variant="props.variant"
|
||||
:data-platform="props.platform"
|
||||
:style="inlineStyle"
|
||||
:disabled="disabled || isProcessing"
|
||||
@click="handleClick"
|
||||
>
|
||||
<span class="btn-content">
|
||||
<component
|
||||
:is="platformIcon"
|
||||
v-if="!isDuplication && !isCustom"
|
||||
class="icon-platform"
|
||||
/>
|
||||
<span class="text">
|
||||
<slot />
|
||||
</span>
|
||||
<span v-if="type === 'default'" class="icon-download">
|
||||
<AtomsIconsDownloadLine />
|
||||
</span>
|
||||
</span>
|
||||
</component>
|
||||
|
||||
<ClientOnly>
|
||||
<Teleport to="#teleports">
|
||||
<BlocksModalClient />
|
||||
</Teleport>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.btn-base {
|
||||
@apply overflow-hidden inline-block relative font-medium cursor-pointer
|
||||
before:content-[''] before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border before:border-white/10
|
||||
after:content-[''] after:absolute after:top-0 after:left-0 after:w-full after:h-full after:bg-white after:transition-opacity after:duration-300 after:ease-in-out after:opacity-0;
|
||||
}
|
||||
.btn-base:hover {
|
||||
@apply after:opacity-20;
|
||||
}
|
||||
.btn-content {
|
||||
@apply relative flex items-center z-[1] text-left;
|
||||
}
|
||||
.icon-platform {
|
||||
@apply w-5 h-5 flex-shrink-0;
|
||||
}
|
||||
|
||||
.btn-base[data-variant='filled'] {
|
||||
@apply bg-[#383838] text-[#ffffff];
|
||||
}
|
||||
.btn-base[data-variant='outlined'] {
|
||||
@apply bg-white text-[#1F1F1F]
|
||||
before:border-black/10
|
||||
after:hidden;
|
||||
}
|
||||
.btn-base[data-variant='outlined'][data-platform='app_store'] svg,
|
||||
.btn-base[data-variant='outlined'][data-platform='pc'] svg,
|
||||
.btn-base[data-variant='outlined'][data-platform='stove'] svg {
|
||||
@apply fill-[#1F1F1F];
|
||||
}
|
||||
|
||||
/* default */
|
||||
.btn-base.default {
|
||||
@apply w-[296px] py-3.5 px-5 rounded-[4px]
|
||||
md:w-[356px] md:py-4 md:px-6
|
||||
before:rounded-[4px] after:rounded-[4px];
|
||||
}
|
||||
.btn-base.default .text {
|
||||
@apply pl-2 pr-4 line-clamp-2 text-[14px]
|
||||
md:text-[16px];
|
||||
}
|
||||
.btn-base.default .icon-download {
|
||||
@apply border-l border-white/10 ml-auto pl-4;
|
||||
}
|
||||
.btn-base.default[data-platform='stove'] {
|
||||
@apply bg-[#FC4420];
|
||||
}
|
||||
.btn-base.default[data-variant='outlined'] .icon-download {
|
||||
@apply border-black/10;
|
||||
}
|
||||
.btn-base.default[data-variant='outlined'] .icon-download svg {
|
||||
@apply fill-[#1F1F1F];
|
||||
}
|
||||
|
||||
/* duplication */
|
||||
.btn-base.duplication {
|
||||
@apply items-start bg-[16px_50%] bg-[length:auto_28px] bg-no-repeat rounded-[4px]
|
||||
before:rounded-[4px] after:rounded-[4px]
|
||||
pt-[22px] pl-[47px] pr-[22px] pb-[7px] text-[11px]
|
||||
md:h-[64px] md:pt-[30px] md:pl-[64px] md:pr-[28px] md:pb-[11px] md:text-[12px] md:bg-[20px_50%] md:bg-[length:auto_40px];
|
||||
}
|
||||
.btn-base.duplication[data-platform='google_play'] {
|
||||
@apply min-w-[148px] md:min-w-[194px];
|
||||
}
|
||||
.btn-base.duplication[data-platform='app_store'] {
|
||||
@apply min-w-[132px] md:min-w-[174px];
|
||||
}
|
||||
.btn-base.duplication[data-platform='pc'] {
|
||||
@apply min-w-[113px] md:min-w-[150px];
|
||||
}
|
||||
.btn-base.duplication[data-platform='stove'] {
|
||||
@apply min-w-[113px] md:min-w-[150px];
|
||||
}
|
||||
.btn-base.duplication .text {
|
||||
@apply line-clamp-1;
|
||||
}
|
||||
|
||||
/* single */
|
||||
.btn-base.single {
|
||||
@apply justify-center items-center text-[14px]
|
||||
h-[40px] px-3.5 rounded-[4px]
|
||||
md:h-[48px] md:rounded-[8px]
|
||||
before:rounded-[4px] md:before:rounded-[8px];
|
||||
}
|
||||
.btn-base.single.no-text {
|
||||
@apply min-w-[40px] px-0 md:min-w-[48px];
|
||||
}
|
||||
.btn-base.single.no-text .icon-platform {
|
||||
@apply mx-auto;
|
||||
}
|
||||
.btn-base.single .icon-platform {
|
||||
@apply mr-1;
|
||||
}
|
||||
</style>
|
||||
@@ -1,87 +0,0 @@
|
||||
<script setup>
|
||||
const {
|
||||
isProcessing,
|
||||
isShowCheckLauncher,
|
||||
isShowDownloadLauncher,
|
||||
validateLauncher,
|
||||
downloadLauncher,
|
||||
} = useCheckGameStart()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AtomsButton
|
||||
button-size="size-small md:size-small"
|
||||
:class="$attrs?.class"
|
||||
:disabled="isProcessing"
|
||||
style="font-size: 16px"
|
||||
@click="validateLauncher"
|
||||
>
|
||||
<slot />
|
||||
</AtomsButton>
|
||||
|
||||
<ClientOnly>
|
||||
<Teleport to="#teleports">
|
||||
<BlocksModalLayer
|
||||
v-model:is-open="isShowCheckLauncher"
|
||||
:is-show-dimmed="true"
|
||||
:is-outside-close="false"
|
||||
:modal-name="'launcher'"
|
||||
area-class="max-w-[480px] pt-[56px] px-[24px] pb-[24px] rounded-[8px]"
|
||||
close-class="absolute top-[16px] right-[24px]"
|
||||
>
|
||||
<span class="ico-loading"></span>
|
||||
<!-- [TODO] i18n 적용 -->
|
||||
<!-- <p class="text-check">{{ tm('Common_Message_Check_Client').txt }}</p> -->
|
||||
<p class="text-check">pc 클라이언트 실행 중...</p>
|
||||
<Transition name="fade">
|
||||
<div v-if="isShowDownloadLauncher" class="client-area">
|
||||
<!-- <p
|
||||
v-dompurify-html="tm('Common_Message_Download_Client').txt"
|
||||
class="text-info"
|
||||
></p>
|
||||
<button type="button" class="btn-download" @click="downloadLauncher">
|
||||
{{ tm('Common_Message_Install').txt }}
|
||||
</button>
|
||||
<p
|
||||
v-dompurify-html="tm('Common_Message_Download_Close').txt"
|
||||
class="text-tip"
|
||||
></p> -->
|
||||
<p class="text-info">
|
||||
PC 클라이언트가 실행되지 않나요?
|
||||
<br />
|
||||
다운로드 전이라면 다운로드 후 진행해주세요
|
||||
</p>
|
||||
<AtomsButtonVariant class="max-w-[300px]" @click="downloadLauncher">
|
||||
다운로드
|
||||
</AtomsButtonVariant>
|
||||
<p
|
||||
v-dompurify-html="
|
||||
'*PC 클라이언트가 정상 실행되었다면 팝업을 닫아 주세요.'
|
||||
"
|
||||
class="text-tip"
|
||||
></p>
|
||||
</div>
|
||||
</Transition>
|
||||
</BlocksModalLayer>
|
||||
</Teleport>
|
||||
</ClientOnly>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ico-loading {
|
||||
@apply block mx-auto mb-4 w-[80px] h-[80px] bg-[url('/images/common/stove_loading_light.png')] bg-contain bg-center bg-no-repeat;
|
||||
}
|
||||
.text-check {
|
||||
@apply mb-6 text-center text-[20px] font-bold leading-[30px] tracking-[-0.6px] text-[#333333];
|
||||
}
|
||||
|
||||
.client-area {
|
||||
@apply pt-4 border-t border-[rgba(0,0,0,0.08)] text-center;
|
||||
}
|
||||
.text-info {
|
||||
@apply mb-3 text-[14px] font-medium leading-[24px] tracking-[-0.42px] text-[#333333];
|
||||
}
|
||||
.text-tip {
|
||||
@apply mt-4 text-[14px] leading-[20px] tracking-[-0.42px] text-[#999999];
|
||||
}
|
||||
</style>
|
||||
@@ -1,32 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useLoadingStore } from '#layers/stores/useLoadingStore'
|
||||
|
||||
const loadingStore = useLoadingStore()
|
||||
const { fullLoading } = storeToRefs(loadingStore)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<div v-if="fullLoading" class="spinner-container">
|
||||
<div class="spinner-line"></div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.spinner-container {
|
||||
@apply fixed inset-0 bg-black/90 flex items-center justify-center z-[900];
|
||||
}
|
||||
.spinner {
|
||||
@apply w-[80px] h-[80px] bg-cover bg-center bg-no-repeat bg-[url('/images/common/publisning_template_loader_black.png')];
|
||||
}
|
||||
|
||||
[data-theme='light'] {
|
||||
.spinner-container {
|
||||
@apply bg-white/90;
|
||||
}
|
||||
.spinner {
|
||||
@apply bg-[url('/images/common/publisning_template_loader_white.png')];
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,39 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useLoadingStore } from '#layers/stores/useLoadingStore'
|
||||
|
||||
const loadingStore = useLoadingStore()
|
||||
const localLoadings = computed(() => Object.entries(loadingStore.localLoadings))
|
||||
const canTeleport = (localId: string) => {
|
||||
if (!import.meta.client) {
|
||||
return false
|
||||
}
|
||||
return !!document.getElementById(localId)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<template v-for="[localId, loadingInfo] in localLoadings" :key="localId">
|
||||
<Teleport v-if="canTeleport(localId)" :to="`#${localId}`">
|
||||
<Transition name="fade">
|
||||
<div v-if="loadingInfo.active" class="spinner-container">
|
||||
<div class="spinner-line"></div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.spinner-container {
|
||||
@apply fixed inset-0 flex items-center justify-center z-[900];
|
||||
}
|
||||
.spinner {
|
||||
@apply w-[80px] h-[80px] bg-cover bg-center bg-no-repeat bg-[url('/images/common/publisning_template_loader_black.png')];
|
||||
}
|
||||
|
||||
[data-theme='light'] {
|
||||
.spinner {
|
||||
@apply bg-[url('/images/common/publisning_template_loader_white.png')];
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,65 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
interface props {
|
||||
isShowDimmed?: boolean
|
||||
contentText?: string
|
||||
confirmButtonText?: string
|
||||
isOutsideClose?: boolean
|
||||
modalName?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<props>(), {
|
||||
isShowDimmed: false,
|
||||
isOutsideClose: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['confirmButtonEvent'])
|
||||
|
||||
const isOpen = defineModel<boolean>('isOpen', { default: false })
|
||||
|
||||
const setButtonEvent = (event?: () => void | void) => {
|
||||
if (typeof event === 'function') {
|
||||
return event()
|
||||
}
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
const handleOutsideClick = () => {
|
||||
if (props.isOutsideClose) {
|
||||
isOpen.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="isOpen"
|
||||
:class="['modal-wrap', { dimmed: props.isShowDimmed }, props.modalName]"
|
||||
@click="handleOutsideClick"
|
||||
>
|
||||
<div class="modal-area" @click.stop>
|
||||
<div class="modal-content">
|
||||
<p
|
||||
v-if="props.contentText"
|
||||
v-dompurify-html="props.contentText"
|
||||
class="content-text"
|
||||
></p>
|
||||
<slot></slot>
|
||||
<div class="content-btns">
|
||||
<AtomsButtonVariant
|
||||
@click="setButtonEvent(() => emit('confirmButtonEvent'))"
|
||||
>
|
||||
{{ props.confirmButtonText || '확인' }}
|
||||
</AtomsButtonVariant>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-area {
|
||||
@apply max-w-[312px] p-6 bg-white rounded-[20px];
|
||||
}
|
||||
</style>
|
||||
68
layers/components/blocks/modal/Client.vue
Normal file
68
layers/components/blocks/modal/Client.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<script setup lang="ts">
|
||||
const { isShowCheckLauncher, isShowDownloadLauncher, downloadLauncher } =
|
||||
useCheckGameStart()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AtomsModalLayer
|
||||
v-model:is-open="isShowCheckLauncher"
|
||||
:is-show-dimmed="true"
|
||||
:is-outside-close="false"
|
||||
:modal-name="'launcher'"
|
||||
area-class="max-w-[480px] pt-[56px] px-[24px] pb-[24px] rounded-[8px]"
|
||||
close-class="absolute top-[16px] right-[24px]"
|
||||
>
|
||||
<span class="ico-loading"></span>
|
||||
<!-- [TODO] i18n 적용 -->
|
||||
<!-- <p class="text-check">{{ tm('Common_Message_Check_Client').txt }}</p> -->
|
||||
<p class="text-check">pc 클라이언트 실행 중...</p>
|
||||
<Transition name="fade">
|
||||
<div v-if="isShowDownloadLauncher" class="client-area">
|
||||
<!-- <p
|
||||
v-dompurify-html="tm('Common_Message_Download_Client').txt"
|
||||
class="text-info"
|
||||
></p>
|
||||
<button type="button" class="btn-download" @click="downloadLauncher">
|
||||
{{ tm('Common_Message_Install').txt }}
|
||||
</button>
|
||||
<p
|
||||
v-dompurify-html="tm('Common_Message_Download_Close').txt"
|
||||
class="text-tip"
|
||||
></p> -->
|
||||
<p class="text-info">
|
||||
PC 클라이언트가 실행되지 않나요?
|
||||
<br />
|
||||
다운로드 전이라면 다운로드 후 진행해주세요
|
||||
</p>
|
||||
<AtomsButtonVariant class="max-w-[300px]" @click="downloadLauncher">
|
||||
다운로드
|
||||
</AtomsButtonVariant>
|
||||
<p
|
||||
v-dompurify-html="
|
||||
'*PC 클라이언트가 정상 실행되었다면 팝업을 닫아 주세요.'
|
||||
"
|
||||
class="text-tip"
|
||||
></p>
|
||||
</div>
|
||||
</Transition>
|
||||
</AtomsModalLayer>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.ico-loading {
|
||||
@apply block mx-auto mb-4 w-[80px] h-[80px] bg-[url('/images/common/stove_loading_light.png')] bg-contain bg-center bg-no-repeat;
|
||||
}
|
||||
.text-check {
|
||||
@apply mb-6 text-center text-[20px] font-bold leading-[30px] tracking-[-0.6px] text-[#333333];
|
||||
}
|
||||
|
||||
.client-area {
|
||||
@apply pt-4 border-t border-[rgba(0,0,0,0.08)] text-center;
|
||||
}
|
||||
.text-info {
|
||||
@apply mb-3 text-[14px] font-medium leading-[24px] tracking-[-0.42px] text-[#333333];
|
||||
}
|
||||
.text-tip {
|
||||
@apply mt-4 text-[14px] leading-[20px] tracking-[-0.42px] text-[#999999];
|
||||
}
|
||||
</style>
|
||||
@@ -1,72 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
interface props {
|
||||
isShowDimmed?: boolean
|
||||
contentText?: string
|
||||
confirmButtonText?: string
|
||||
cancelButtonText?: string
|
||||
isOutsideClose?: boolean
|
||||
modalName?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<props>(), {
|
||||
isShowDimmed: false,
|
||||
isOutsideClose: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['cancelButtonEvent', 'confirmButtonEvent'])
|
||||
|
||||
const isOpen = defineModel<boolean>('isOpen', { default: false })
|
||||
|
||||
const setButtonEvent = (event?: () => void) => {
|
||||
if (event) {
|
||||
event()
|
||||
}
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
const handleOutsideClick = () => {
|
||||
if (props.isOutsideClose) {
|
||||
isOpen.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="isOpen"
|
||||
:class="['modal-wrap', { dimmed: props.isShowDimmed }, props.modalName]"
|
||||
@click="handleOutsideClick"
|
||||
>
|
||||
<div class="modal-area" @click.stop>
|
||||
<div class="modal-content">
|
||||
<p
|
||||
v-if="props.contentText"
|
||||
v-dompurify-html="props.contentText"
|
||||
class="content-text"
|
||||
></p>
|
||||
<slot></slot>
|
||||
<div class="content-btns">
|
||||
<AtomsButtonVariant
|
||||
variant="outlined"
|
||||
@click="setButtonEvent(() => emit('cancelButtonEvent'))"
|
||||
>
|
||||
{{ props.cancelButtonText || '취소' }}
|
||||
</AtomsButtonVariant>
|
||||
<AtomsButtonVariant
|
||||
@click="setButtonEvent(() => emit('confirmButtonEvent'))"
|
||||
>
|
||||
{{ props.confirmButtonText || '확인' }}
|
||||
</AtomsButtonVariant>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-area {
|
||||
@apply max-w-[312px] p-6 bg-white rounded-[20px];
|
||||
}
|
||||
</style>
|
||||
@@ -1,50 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
interface props {
|
||||
isShowDimmed?: boolean
|
||||
isOutsideClose?: boolean
|
||||
modalName?: string
|
||||
areaClass?: string
|
||||
closeClass?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<props>(), {
|
||||
isShowDimmed: false,
|
||||
isOutsideClose: false,
|
||||
})
|
||||
|
||||
const isOpen = defineModel<boolean>('isOpen', { default: false })
|
||||
|
||||
const handleCloseModal = () => {
|
||||
isOpen.value = false
|
||||
}
|
||||
|
||||
const handleOutsideClick = () => {
|
||||
if (props.isOutsideClose) {
|
||||
handleCloseModal()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="isOpen"
|
||||
:class="['modal-wrap', { dimmed: props.isShowDimmed }, props.modalName]"
|
||||
@click="handleOutsideClick"
|
||||
>
|
||||
<div :class="['modal-area', props.areaClass]" @click.stop>
|
||||
<div class="modal-content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
:class="['modal-close', props.closeClass]"
|
||||
@click="handleCloseModal"
|
||||
>
|
||||
<span class="sr-only">close</span>
|
||||
<AtomsIconsCloseLine size="24" color="#333333" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
@@ -1,88 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { getYouTubeEmbedUrl } from '@/layers/utils/youtubeUtil'
|
||||
|
||||
interface Props {
|
||||
youtubeUrl: string
|
||||
isOutsideClose?: boolean
|
||||
modalName?: string
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
youtubeUrl: '',
|
||||
isOutsideClose: false,
|
||||
})
|
||||
|
||||
const emit = defineEmits(['closeButtonEvent'])
|
||||
|
||||
const isOpen = defineModel<boolean>('isOpen', { default: false })
|
||||
|
||||
const embedUrl = computed(() => {
|
||||
return getYouTubeEmbedUrl(props.youtubeUrl)
|
||||
})
|
||||
|
||||
const handleCloseModal = () => {
|
||||
isOpen.value = false
|
||||
emit('closeButtonEvent')
|
||||
}
|
||||
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape' && isOpen.value) {
|
||||
handleCloseModal()
|
||||
}
|
||||
}
|
||||
|
||||
const handleOutsideClick = () => {
|
||||
if (props.isOutsideClose) {
|
||||
handleCloseModal()
|
||||
}
|
||||
}
|
||||
|
||||
// 키보드 이벤트 리스너 등록/해제
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', handleKeydown)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleKeydown)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Transition name="fade">
|
||||
<div
|
||||
v-if="isOpen"
|
||||
class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-75 z-[800]"
|
||||
:class="props.modalName"
|
||||
@click="handleOutsideClick"
|
||||
>
|
||||
<div
|
||||
class="relative mx-4 my-4"
|
||||
style="
|
||||
width: min(896px, 90vw, calc((90vh - 2rem) * 16 / 9));
|
||||
aspect-ratio: 16/9;
|
||||
"
|
||||
@click.stop
|
||||
>
|
||||
<!-- 헤더 -->
|
||||
<div class="flex justify-end mb-3 md:mb-4">
|
||||
<button type="button" @click="handleCloseModal">
|
||||
<AtomsIconsCloseLine />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 유튜브 영상 컨테이너 -->
|
||||
<div class="relative w-full h-full">
|
||||
<iframe
|
||||
v-if="embedUrl"
|
||||
:src="embedUrl"
|
||||
class="absolute top-0 left-0 w-full h-full"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowfullscreen
|
||||
title="YouTube video player"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
Reference in New Issue
Block a user