릴리스: v1.3.35 라이트모드와 아이템 모달 보정

This commit is contained in:
2026-04-01 16:11:24 +09:00
parent fb00ddb1d8
commit 7e80320e9f
8 changed files with 85 additions and 43 deletions

View File

@@ -56,6 +56,7 @@ const userDeleteModalOpen = ref(false)
const userRoleModalOpen = ref(false)
const customItemModalOpen = ref(false)
const customItemDeleteModalOpen = ref(false)
const customItemModalHistoryActive = ref(false)
const modalTargetUser = ref(null)
const modalPasswordDraft = ref('')
const modalRoleNextAdmin = ref(false)
@@ -194,13 +195,26 @@ const adminOverviewStats = computed(() => {
]
})
function handleAdminPopState() {
if (customItemDeleteModalOpen.value) {
customItemDeleteModalOpen.value = false
if (customItemModalOpen.value) pushCustomItemModalHistoryState()
return
}
if (customItemModalOpen.value) {
closeCustomItemModal({ fromPopState: true })
}
}
onMounted(async () => {
if (typeof window !== 'undefined') window.addEventListener('popstate', handleAdminPopState)
await auth.refresh()
await Promise.all([refreshGames(), refreshCustomItems(), refreshAdminTierLists(), refreshUsers(), refreshTemplateRequests(), refreshImageDiagnostics()])
await syncFeaturedSortable()
})
onUnmounted(() => {
if (typeof window !== 'undefined') window.removeEventListener('popstate', handleAdminPopState)
clearPreviewUrl('item')
clearPreviewUrl('thumb')
destroyFeaturedSortable()
@@ -1048,26 +1062,44 @@ function moveCustomItemPage(direction) {
refreshCustomItems()
}
function pushCustomItemModalHistoryState() {
if (typeof window === 'undefined') return
window.history.pushState({ ...(window.history.state || {}), adminCustomItemModal: true }, '', window.location.href)
customItemModalHistoryActive.value = true
}
function openCustomItemModal(item) {
modalTargetCustomItem.value = item || null
customItemModalTargetGameId.value = ''
customItemModalGameQuery.value = ''
customItemModalGameSort.value = 'recent'
customItemModalOpen.value = true
pushCustomItemModalHistoryState()
}
function closeCustomItemModal() {
function closeCustomItemModal({ fromPopState = false } = {}) {
customItemModalOpen.value = false
customItemDeleteModalOpen.value = false
modalTargetCustomItem.value = null
customItemModalTargetGameId.value = ''
customItemModalGameQuery.value = ''
customItemModalGameSort.value = 'recent'
if (fromPopState) {
customItemModalHistoryActive.value = false
return
}
if (customItemModalHistoryActive.value && typeof window !== 'undefined') {
customItemModalHistoryActive.value = false
window.history.back()
}
}
function openCustomItemDeleteModal(item) {
if (!item) return
if (item.usageCount > 0) {
error.value = '사용 중 사용자 업로드 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
if (item.sourceType === 'user' && (item.usageCount > 0 || item.linkedGames.length > 0)) {
error.value = '사용 중이거나 템플릿에 연결된 사용자 업로드 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
return
}
modalTargetCustomItem.value = item
@@ -1081,8 +1113,8 @@ function closeCustomItemDeleteModal() {
async function removeCustomItem(item = modalTargetCustomItem.value) {
resetMessages()
if (!item) return
if (item.usageCount > 0) {
error.value = '사용 중 사용자 업로드 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
if (item.sourceType === 'user' && (item.usageCount > 0 || item.linkedGames.length > 0)) {
error.value = '사용 중이거나 템플릿에 연결된 사용자 업로드 이미지는 먼저 참조를 정리해야 삭제할 수 있어요.'
return
}
@@ -1091,9 +1123,9 @@ async function removeCustomItem(item = modalTargetCustomItem.value) {
closeCustomItemDeleteModal()
closeCustomItemModal()
await refreshCustomItems()
success.value = '미사용 사용자 업로드 이미지를 삭제했어요.'
success.value = item.sourceType === 'template' ? '선택한 템플릿 아이템을 제거했어요.' : '사용자 업로드 이미지를 삭제했어요.'
} catch (e) {
error.value = '커스텀 이미지 삭제에 실패했어요.'
error.value = item.sourceType === 'template' ? '템플릿 아이템 제거에 실패했어요.' : '사용자 업로드 이미지 삭제에 실패했어요.'
}
}
@@ -1939,7 +1971,7 @@ async function saveFeaturedOrder() {
<div v-if="modalTargetCustomItem" class="customItemModal">
<aside class="customItemModal__pickerPanel">
<div class="customItemModal__pickerHead">
<div class="customItemModal__pickerEyebrow">GAME LIBRARY</div>
<div class="customItemModal__pickerEyebrow">GAME PICKER</div>
<div class="customItemModal__pickerTitle">템플릿으로 추가할 게임</div>
</div>
<div class="customItemModal__pickerControls">
@@ -1993,7 +2025,7 @@ async function saveFeaturedOrder() {
<button class="btn btn--ghost customItemModal__action" :disabled="!customItemModalTargetGameId || modalTargetCustomItem.isPromoting" @click="promoteCustomItem(modalTargetCustomItem)">
{{ modalTargetCustomItem.isPromoting ? '추가중...' : '기본 템플릿에 추가' }}
</button>
<button v-if="modalTargetCustomItem.canDelete" class="btn btn--danger customItemModal__action" :disabled="modalTargetCustomItem.usageCount > 0 || visibleLinkedGames.length > 0" @click="openCustomItemDeleteModal(modalTargetCustomItem)">삭제</button>
<button v-if="modalTargetCustomItem.canDelete" class="btn btn--danger customItemModal__action" :disabled="modalTargetCustomItem.sourceType === 'user' && (modalTargetCustomItem.usageCount > 0 || visibleLinkedGames.length > 0)" @click="openCustomItemDeleteModal(modalTargetCustomItem)">삭제</button>
</div>
</div>
</div>
@@ -2002,8 +2034,8 @@ async function saveFeaturedOrder() {
<div v-if="customItemDeleteModalOpen" class="modalOverlay" @click.self="closeCustomItemDeleteModal">
<div class="modalCard" role="dialog" aria-modal="true">
<div class="modalCard__title">커스텀 아이템 삭제</div>
<div class="modalCard__desc">{{ modalTargetCustomItem ? '"' + modalTargetCustomItem.label + '" 이미지를 삭제할까요? 사용자 업로드이면서 어디에도 연결되지 않은 이미지에만 삭제를 허용합니다.' : '' }}</div>
<div class="modalCard__title">아이템 삭제</div>
<div class="modalCard__desc">{{ !modalTargetCustomItem ? '' : modalTargetCustomItem.sourceType === 'template' ? '"' + modalTargetCustomItem.label + '" 항목을 현재 템플릿에서 제거할까요? 이미 저장된 같은 게임의 티어표에서도 함께 빠질 수 있어요.' : '"' + modalTargetCustomItem.label + '" 이미지를 삭제할까요? 사용자 업로드이면서 어디에도 연결되지 않은 이미지에만 삭제를 허용합니다.' }}</div>
<div class="modalCard__actions">
<button class="btn btn--ghost" @click="closeCustomItemDeleteModal">취소</button>
<button class="btn btn--danger" @click="removeCustomItem()">삭제</button>
@@ -2894,6 +2926,7 @@ async function saveFeaturedOrder() {
display: grid;
place-items: center;
color: var(--theme-text-soft);
background: var(--theme-card-bg);
}
.selectedThumb--sidebar {
width: 100%;
@@ -3134,12 +3167,12 @@ async function saveFeaturedOrder() {
font-weight: 800;
font-size: 13px;
line-height: 1.3;
color: #ffffff;
color: var(--theme-text);
}
.customItemModal {
display: grid;
grid-template-columns: minmax(220px, 260px) minmax(0, 1fr);
gap: 18px;
grid-template-columns: minmax(280px, 320px) minmax(0, 1fr);
gap: 24px;
align-items: start;
}
.customItemModal__pickerPanel {
@@ -3275,7 +3308,7 @@ async function saveFeaturedOrder() {
text-align: center;
}
.modalCard--customItem {
width: min(980px, 100%);
width: min(1160px, calc(100vw - 48px));
}
.pager {
margin-top: 16px;