릴리스: v1.2.71 공개 티어표 복사와 보기 상태 보정

This commit is contained in:
2026-03-31 16:52:17 +09:00
parent 6dce53db7a
commit 5b047b0458
6 changed files with 179 additions and 11 deletions

View File

@@ -90,6 +90,7 @@ export const api = {
favoriteTierList: (id) => request(`/api/tierlists/${encodeURIComponent(id)}/favorite`, { method: 'POST' }),
unfavoriteTierList: (id) => request(`/api/tierlists/${encodeURIComponent(id)}/favorite`, { method: 'DELETE' }),
deleteTierList: (id) => request(`/api/tierlists/${encodeURIComponent(id)}`, { method: 'DELETE' }),
duplicateTierList: (id) => request(`/api/tierlists/${encodeURIComponent(id)}/duplicate`, { method: 'POST' }),
requestTierListTemplate: (id, payload) => request(`/api/tierlists/${encodeURIComponent(id)}/template-request`, { method: 'POST', body: payload }),
saveTierList: (payload) => request('/api/tierlists', { method: 'POST', body: payload }),
uploadTierListThumbnail: async (file) => {

View File

@@ -213,8 +213,9 @@ function submitSearch() {
}
.list {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
grid-template-columns: repeat(auto-fit, minmax(280px, 320px));
gap: 18px;
justify-content: start;
}
.boardCard {
border-radius: 22px;
@@ -271,25 +272,34 @@ function submitSearch() {
min-width: 0;
font-size: 18px;
line-height: 1.35;
white-space: nowrap;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.boardCard__head {
min-width: 0;
padding: 16px 18px 18px;
display: grid;
gap: 6px;
gap: 8px;
}
.boardCard__titleRow,
.boardCard__metaRow {
min-width: 0;
display: flex;
gap: 10px;
align-items: center;
justify-content: space-between;
}
.boardCard__titleRow {
align-items: flex-start;
}
.boardCard__metaRow {
align-items: flex-end;
flex-wrap: wrap;
}
.boardCard__author {
min-width: 0;
@@ -323,6 +333,7 @@ function submitSearch() {
.boardCard__date,
.favoriteStat {
flex: 0 0 auto;
min-width: 0;
font-size: 13px;
color: rgba(255, 255, 255, 0.64);
white-space: nowrap;

View File

@@ -49,6 +49,9 @@ const ownerId = ref('')
const authorName = ref('')
const authorAccountName = ref('')
const updatedAt = ref(0)
const sourceTierListId = ref('')
const sourceSnapshotTitle = ref('')
const sourceSnapshotAuthor = ref('')
const isDragActive = ref(false)
const isThumbnailDragActive = ref(false)
const iconSize = ref(80)
@@ -95,6 +98,14 @@ const untitledWarning = computed(
'제목 없이 저장된 티어표는 무분별한 도배 방지를 위해 관리자에 의해 임의 삭제될 수 있어요.'
)
const canFavorite = computed(() => !!auth.user && !isNewTierList.value && !canEdit.value)
const canDuplicate = computed(() => !!auth.user && !isNewTierList.value && !canEdit.value)
const copiedFromLabel = computed(() => {
if (!sourceTierListId.value) return ''
const parts = []
if (sourceSnapshotTitle.value) parts.push(`원본 ${sourceSnapshotTitle.value}`)
if (sourceSnapshotAuthor.value) parts.push(sourceSnapshotAuthor.value)
return parts.join(' · ') || '복사해 온 티어표'
})
const customItems = computed(() =>
Object.values(itemsById.value)
.filter((item) => item?.origin === 'custom')
@@ -473,6 +484,9 @@ function buildPayload(existingId) {
description: (description.value || '').trim(),
isPublic: !!isPublic.value,
showCharacterNames: !!showCharacterNames.value,
sourceTierListId: sourceTierListId.value || '',
sourceSnapshotTitle: sourceSnapshotTitle.value || '',
sourceSnapshotAuthor: sourceSnapshotAuthor.value || '',
groups: groups.value.map((g) => ({ id: g.id, name: g.name, itemIds: g.itemIds })),
pool: Object.values(itemsById.value),
}
@@ -561,6 +575,19 @@ async function confirmDeleteTierList() {
}
}
async function duplicateCurrentTierList() {
if (!canDuplicate.value) return
try {
const data = await api.duplicateTierList(tierListId.value)
const duplicatedId = data.tierList?.id
if (!duplicatedId) throw new Error('duplicate_failed')
toast.success('티어표를 복사해 내 작업으로 가져왔어요.')
router.push(`/editor/${gameId.value}/${duplicatedId}`)
} catch (e) {
error.value = '티어표 복사에 실패했어요.'
}
}
async function toggleFavorite() {
if (!canFavorite.value || isFavoriteBusy.value) return
try {
@@ -653,6 +680,9 @@ onMounted(() => {
authorName.value = t.authorName || ''
authorAccountName.value = t.authorAccountName || ''
updatedAt.value = Number(t.updatedAt || 0)
sourceTierListId.value = t.sourceTierListId || ''
sourceSnapshotTitle.value = t.sourceSnapshotTitle || ''
sourceSnapshotAuthor.value = t.sourceSnapshotAuthor || ''
favoriteCount.value = Number(t.favoriteCount || 0)
isFavorited.value = !!t.isFavorited
groups.value = t.groups
@@ -699,7 +729,7 @@ onUnmounted(() => {
<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">
<div v-for="id in pool" :key="id" class="previewOnly__poolItem previewOnly__poolItem--inactive">
<img :src="resolveItemSrc(itemsById[id])" class="thumb" :alt="itemsById[id]?.label || id" />
</div>
</div>
@@ -816,6 +846,10 @@ onUnmounted(() => {
공개된 티어표를 보는 중입니다. 로그인한 작성자만 수정할 있어요.
</template>
</div>
<div v-if="sourceTierListId" class="editorMain__sourceNote">
<span>복사본</span>
<button class="editorMain__sourceLink" type="button" @click="router.push(`/editor/${gameId}/${sourceTierListId}`)">{{ copiedFromLabel }}</button>
</div>
</div>
</section>
@@ -911,9 +945,10 @@ onUnmounted(() => {
{{ canEdit ? '등록된 아이템 리스트입니다. 드래그해서 표에 넣을 수 있습니다.' : '공개 티어표는 보기 전용입니다.' }}
</div>
<div ref="poolEl" class="pool" data-list-type="pool">
<div v-for="id in pool" :key="id" class="poolItem" :data-item-id="id">
<div v-for="id in pool" :key="id" class="poolItem" :class="{ 'poolItem--readonly': !canEdit }" :data-item-id="id">
<img :src="resolveItemSrc(itemsById[id])" class="thumb" :alt="itemsById[id]?.label || id" />
<div class="poolItem__label">{{ itemsById[id]?.label || id }}</div>
<div v-if="!canEdit" class="poolItem__state">미배치</div>
</div>
</div>
</div>
@@ -1005,6 +1040,7 @@ onUnmounted(() => {
</div>
<div class="editorSidebar__utilityLinks">
<button v-if="canEdit && !isNewTierList" class="editorSidebar__utilityLink editorSidebar__utilityLink--danger" @click="openDeleteModal">삭제하기</button>
<button v-if="canDuplicate" class="editorSidebar__utilityLink" @click="duplicateCurrentTierList">복사해서 티어표로 가져오기</button>
<button
v-if="canRequestTemplateCreate"
class="editorSidebar__utilityLink"
@@ -1055,6 +1091,23 @@ onUnmounted(() => {
font-size: 13px;
line-height: 1.5;
}
.editorMain__sourceNote {
margin-top: 4px;
display: inline-flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
font-size: 12px;
color: rgba(255, 255, 255, 0.62);
}
.editorMain__sourceLink {
border: 0;
padding: 0;
background: transparent;
color: rgba(191, 219, 254, 0.94);
font: inherit;
cursor: pointer;
}
.previewOnly {
min-height: 100vh;
padding: 20px;
@@ -1133,6 +1186,10 @@ onUnmounted(() => {
display: inline-flex;
position: relative;
}
.previewOnly__poolItem--inactive {
opacity: 0.52;
filter: grayscale(0.22) brightness(0.78);
}
.toggleSwitch {
display: inline-flex;
align-items: center;
@@ -1845,6 +1902,10 @@ onUnmounted(() => {
border: 1px solid rgba(255, 255, 255, 0.10);
background: rgba(0, 0, 0, 0.18);
}
.poolItem--readonly {
opacity: 0.58;
filter: grayscale(0.25) brightness(0.78);
}
.poolItem .thumb {
width: 100%;
max-width: var(--thumb-size, 80px);
@@ -1863,6 +1924,13 @@ onUnmounted(() => {
text-overflow: ellipsis;
white-space: nowrap;
}
.poolItem__state {
font-size: 10px;
font-weight: 800;
letter-spacing: 0.04em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.58);
}
.hidden {
display: none;
}