릴리스: v0.1.52 프리뷰 전용화와 썸네일 자동 생성

This commit is contained in:
2026-03-27 12:15:16 +09:00
parent 7b4a80f47d
commit 2bee78ba5e
8 changed files with 232 additions and 85 deletions

View File

@@ -10,6 +10,7 @@ const router = useRouter()
const auth = useAuthStore()
const { toasts, dismissToast } = useToast()
const isAdmin = computed(() => !!auth.user?.isAdmin)
const isPreviewMode = computed(() => route.query.preview === '1')
const avatarUrl = computed(() => {
if (!auth.user?.avatarSrc) return ''
return toApiUrl(auth.user.avatarSrc)
@@ -57,7 +58,7 @@ async function logout() {
<template>
<div class="app-shell">
<header class="app-header">
<header v-if="!isPreviewMode" class="app-header">
<div class="brand" @click="$router.push('/')">
<span class="brand__title">Tier Maker</span>
<span class="brand__sub">by zenn</span>
@@ -81,10 +82,10 @@ async function logout() {
</div>
</nav>
</header>
<main class="app-main">
<main class="app-main" :class="{ 'app-main--preview': isPreviewMode }">
<RouterView />
</main>
<div class="toastStack" aria-live="polite" aria-atomic="true">
<div class="toastStack" :class="{ 'toastStack--preview': isPreviewMode }" aria-live="polite" aria-atomic="true">
<div v-for="item in toasts" :key="item.id" class="toast" :class="[`toast--${item.type}`, { 'toast--closing': item.isClosing }]">
<div class="toast__body">
<div class="toast__message">{{ item.message }}</div>
@@ -158,6 +159,10 @@ async function logout() {
width: 100%;
box-sizing: border-box;
}
.app-main--preview {
padding: 0;
min-height: 100vh;
}
.user {
position: relative;
@@ -219,6 +224,9 @@ async function logout() {
gap: 10px;
width: min(360px, calc(100vw - 24px));
}
.toastStack--preview {
top: 12px;
}
.toast {
display: flex;
gap: 12px;

View File

@@ -662,7 +662,7 @@ function closePreviewModal() {
function previewTierListUrl(tierList) {
if (!tierList?.gameId || !tierList?.id) return ''
return `/editor/${tierList.gameId}/${tierList.id}`
return `/editor/${tierList.gameId}/${tierList.id}?preview=1`
}
function openTierListImportModal(tierList, items) {
@@ -1274,10 +1274,7 @@ async function saveFeaturedOrder() {
<div v-if="previewModalOpen" class="modalOverlay" @click.self="closePreviewModal">
<div class="modalCard modalCard--preview" role="dialog" aria-modal="true">
<div class="modalCard__titleRow">
<div>
<div class="modalCard__title">{{ previewTierList?.title || '티어표 미리보기' }}</div>
<div class="modalCard__desc">관리 화면을 벗어나지 않고 완성본만 확인할 있어요.</div>
</div>
<div class="modalCard__title">{{ previewTierList?.title || '티어표 미리보기' }}</div>
<button class="btn btn--ghost btn--small" @click="closePreviewModal">닫기</button>
</div>
<iframe

View File

@@ -14,6 +14,7 @@ const auth = useAuthStore()
const toast = useToast()
const gameId = computed(() => route.params.gameId)
const tierListId = computed(() => route.params.tierListId)
const previewMode = computed(() => route.query.preview === '1')
const gameName = ref('')
const groups = ref([
@@ -602,6 +603,32 @@ onUnmounted(() => {
</script>
<template>
<section v-if="previewMode" class="previewOnly">
<div class="previewOnly__sheet">
<div class="previewOnly__title">{{ effectiveTitle }}</div>
<div v-if="description" class="previewOnly__description">{{ description }}</div>
<div class="previewOnly__rows">
<div v-for="g in groups" :key="g.id" class="previewOnly__row">
<div class="previewOnly__label">{{ g.name }}</div>
<div class="previewOnly__drop">
<div v-for="id in g.itemIds" :key="id" class="previewOnly__cell">
<img :src="resolveItemSrc(itemsById[id])" class="thumb" :alt="itemsById[id]?.label || id" />
</div>
</div>
</div>
</div>
<div v-if="pool.length" class="previewOnly__pool">
<div class="previewOnly__poolTitle">남은 아이템</div>
<div class="previewOnly__poolGrid">
<div v-for="id in pool" :key="id" class="previewOnly__poolItem">
<img :src="resolveItemSrc(itemsById[id])" class="thumb" :alt="itemsById[id]?.label || id" />
</div>
</div>
</div>
</div>
</section>
<template v-else>
<section class="head">
<div class="heroCard">
<div class="heroCard__main">
@@ -824,6 +851,7 @@ onUnmounted(() => {
<button v-if="canEdit" class="btn btn--ghost" @click="openFile">파일 선택</button>
</div>
</section>
</template>
</template>
<style scoped>
@@ -832,6 +860,82 @@ onUnmounted(() => {
gap: 14px;
padding: 6px 2px 14px;
}
.previewOnly {
min-height: 100vh;
padding: 20px;
box-sizing: border-box;
background:
radial-gradient(circle at top, rgba(96, 165, 250, 0.14), transparent 38%),
rgba(11, 18, 32, 0.98);
}
.previewOnly__sheet {
display: grid;
gap: 16px;
width: 100%;
max-width: 1280px;
margin: 0 auto;
}
.previewOnly__title {
font-size: 28px;
font-weight: 900;
letter-spacing: -0.03em;
}
.previewOnly__description {
margin-top: -8px;
font-size: 14px;
line-height: 1.6;
opacity: 0.76;
}
.previewOnly__rows {
display: grid;
gap: 10px;
}
.previewOnly__row {
display: grid;
grid-template-columns: 180px 1fr;
gap: 10px;
}
.previewOnly__label {
display: grid;
place-items: center;
padding: 10px 8px;
text-align: center;
font-weight: 900;
border-radius: 14px;
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.12);
}
.previewOnly__drop {
border-radius: 14px;
background: rgba(0, 0, 0, 0.18);
border: 1px solid rgba(255, 255, 255, 0.1);
min-height: calc(var(--thumb-size, 80px) + 24px);
padding: 10px;
display: flex;
flex-wrap: wrap;
gap: 8px;
align-content: flex-start;
}
.previewOnly__cell {
display: inline-flex;
}
.previewOnly__pool {
display: grid;
gap: 10px;
padding-top: 8px;
}
.previewOnly__poolTitle {
font-weight: 900;
opacity: 0.82;
}
.previewOnly__poolGrid {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.previewOnly__poolItem {
display: inline-flex;
}
.heroCard {
display: grid;
grid-template-columns: minmax(0, 1.5fr) minmax(280px, 360px);
@@ -1424,6 +1528,9 @@ onUnmounted(() => {
border-radius: 14px;
}
@media (max-width: 980px) {
.previewOnly__row {
grid-template-columns: 140px 1fr;
}
.heroCard {
grid-template-columns: 1fr;
}
@@ -1461,4 +1568,12 @@ onUnmounted(() => {
width: 100%;
}
}
@media (max-width: 720px) {
.previewOnly {
padding: 14px;
}
.previewOnly__row {
grid-template-columns: 1fr;
}
}
</style>