릴리스: v1.3.62 에디터 저장 순서와 삭제 조건 정리

This commit is contained in:
2026-04-02 13:57:23 +09:00
parent d3c5eeae6a
commit b98a3d5a6d
4 changed files with 51 additions and 17 deletions

View File

@@ -119,10 +119,7 @@ const copiedFromLabel = computed(() => {
if (sourceSnapshotAuthor.value) parts.push(sourceSnapshotAuthor.value)
return parts.join(' · ') || '복사해 온 티어표'
})
const customItems = computed(() =>
Object.values(itemsById.value)
.filter((item) => item?.origin === 'custom')
)
const customItems = computed(() => getOrderedItems().filter((item) => item?.origin === 'custom'))
const hasSavedTierList = computed(() => !!(persistedTierListId.value || (tierListId.value && tierListId.value !== 'new')))
const canRequestTemplateCreate = computed(
() => canEdit.value && hasSavedTierList.value && gameId.value === 'freeform' && customItems.value.length > 0
@@ -166,6 +163,29 @@ function formatExportDate(ts) {
})
}
function getOrderedItemIds() {
const orderedIds = []
const seen = new Set()
const pushId = (itemId) => {
if (!itemId || seen.has(itemId) || !itemsById.value[itemId]) return
seen.add(itemId)
orderedIds.push(itemId)
}
pool.value.forEach(pushId)
groups.value.forEach((group) => {
;(group.cells || []).forEach((cell) => {
;(cell || []).forEach(pushId)
})
})
Object.keys(itemsById.value).forEach(pushId)
return orderedIds
}
function getOrderedItems() {
return getOrderedItemIds().map((itemId) => itemsById.value[itemId]).filter(Boolean)
}
function setIconSize(nextSize) {
iconSize.value = nextSize
}
@@ -655,7 +675,7 @@ function buildPayload(existingId) {
sourceSnapshotTitle: sourceSnapshotTitle.value || '',
sourceSnapshotAuthor: sourceSnapshotAuthor.value || '',
groups: buildGroupPayload(),
pool: Object.values(itemsById.value),
pool: getOrderedItems(),
}
}
@@ -722,6 +742,7 @@ function closeTemplateUpdateModal() {
}
function openDeleteModal() {
if (!hasSavedTierList.value) return
isDeleteModalOpen.value = true
}
@@ -730,11 +751,12 @@ function closeDeleteModal() {
}
async function confirmDeleteTierList() {
if (!canEdit.value || isNewTierList.value || isDeleting.value) return
const currentTierListId = persistedTierListId.value || (tierListId.value && tierListId.value !== 'new' ? tierListId.value : '')
if (!canEdit.value || !currentTierListId || isDeleting.value) return
error.value = ''
try {
isDeleting.value = true
await api.deleteTierList(tierListId.value)
await api.deleteTierList(currentTierListId)
closeDeleteModal()
toast.success('티어표를 삭제했어요.')
router.push(gameId.value === 'freeform' ? '/me' : `/games/${gameId.value}`)
@@ -792,7 +814,7 @@ async function requestTemplate(type) {
isPublic: !!isPublic.value,
showCharacterNames: !!showCharacterNames.value,
groups: buildGroupPayload(),
boardItems: Object.values(itemsById.value),
boardItems: getOrderedItems(),
})
if (type === 'create') closeTemplateRequestModal()
@@ -1185,10 +1207,10 @@ onUnmounted(() => {
@dragleave="onDragLeave"
@drop.prevent="onDropFiles"
>
<div>
<div class="dropzone__iconWrap">
<SvgIcon :src="addPhotoAlternateIcon" alt="" class="dropzone__icon" />
</div>
<div>
<div class="dropzone__title">커스텀 이미지 추가</div>
<div class="dropzone__desc">이곳으로 이미지를 드래그하거나 파일 선택으로 번에 추가할 있어요.</div>
</div>
@@ -1302,7 +1324,7 @@ onUnmounted(() => {
<button v-if="canEdit" class="btn btn--save editorSidebar__button" :disabled="isSaving" @click="save">{{ isSaving ? '저장중...' : '저장' }}</button>
</div>
<div class="editorSidebar__utilityLinks">
<button v-if="canEdit && !isNewTierList" class="editorSidebar__utilityLink editorSidebar__utilityLink--danger" @click="openDeleteModal">삭제하기</button>
<button v-if="canEdit && hasSavedTierList" class="editorSidebar__utilityLink editorSidebar__utilityLink--danger" @click="openDeleteModal">삭제하기</button>
<button v-if="canDuplicate" class="editorSidebar__utilityLink" @click="duplicateCurrentTierList">복사해서 티어표로 가져오기</button>
<button
v-if="canRequestTemplateCreate"
@@ -2110,20 +2132,21 @@ onUnmounted(() => {
.dropzone__button {
min-width: 124px;
min-height: 34px;
font-size: 11px;
}
.dropzone__iconWrap {
/* .dropzone__iconWrap {
width: 52px;
height: 52px;
display: grid;
place-items: center;
border-radius: 16px;
background: color-mix(in srgb, var(--theme-text) 10%, transparent);
}
} */
.dropzone__icon {
width: 28px;
height: 28px;
width: 48px;
height: 48px;
opacity: 0.86;
}
@@ -2334,10 +2357,11 @@ onUnmounted(() => {
}
.dropzone {
margin-top: 12px;
padding: 14px;
padding: 28px 22px;
border-radius: 16px;
border: 1px dashed rgba(255, 255, 255, 0.18);
background: var(--theme-surface-soft);
border: 2px dashed color-mix(in srgb, var(--theme-border-strong) 82%, rgba(255, 255, 255, 0.12));
background: linear-gradient(180deg, color-mix(in srgb, var(--theme-card-bg) 88%, white 4%), color-mix(in srgb, var(--theme-card-bg-hover) 82%, white 6%)),
radial-gradient(circle at top, color-mix(in srgb, var(--theme-accent) 12%, transparent), transparent 60%);
}
.dropzone--active {
border-color: rgba(110, 231, 183, 0.6);