릴리스: v1.4.42 홈 템플릿 정렬 및 클릭 배치 지원
This commit is contained in:
@@ -30,6 +30,9 @@ const templates = computed(() => {
|
||||
const rankA = a.displayRank == null ? Number.MAX_SAFE_INTEGER : a.displayRank
|
||||
const rankB = b.displayRank == null ? Number.MAX_SAFE_INTEGER : b.displayRank
|
||||
if (rankA !== rankB) return rankA - rankB
|
||||
if (Number(a.createdAt || 0) !== Number(b.createdAt || 0)) {
|
||||
return Number(b.createdAt || 0) - Number(a.createdAt || 0)
|
||||
}
|
||||
return (a.name || '').localeCompare(b.name || '', 'ko')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -74,6 +74,8 @@ const isFavorited = ref(false)
|
||||
const isRequestingTemplate = ref(false)
|
||||
const isDeleting = ref(false)
|
||||
const poolSearchQuery = ref('')
|
||||
const selectedItemId = ref('')
|
||||
const recentDragFinishedAt = ref(0)
|
||||
|
||||
const boardEl = ref(null)
|
||||
const exportBoardEl = ref(null)
|
||||
@@ -122,7 +124,7 @@ const canSwitchToEditMode = computed(() => isOwnTierList.value && hasSavedTierLi
|
||||
const copiedFromLabel = computed(() => {
|
||||
if (!sourceTierListId.value) return ''
|
||||
const parts = []
|
||||
if (sourceSnapshotTitle.value) parts.push(`원본 ${sourceSnapshotTitle.value}`)
|
||||
if (sourceSnapshotTitle.value) parts.push(sourceSnapshotTitle.value)
|
||||
if (sourceSnapshotAuthor.value) parts.push(sourceSnapshotAuthor.value)
|
||||
return parts.join(' · ') || '복사해 온 티어표'
|
||||
})
|
||||
@@ -287,6 +289,87 @@ function removeItemFromGroup(groupId, columnIndex, itemId) {
|
||||
targetGroup.cells = nextCells
|
||||
syncGroupItemIds(targetGroup)
|
||||
pool.value = [itemId, ...pool.value.filter((id) => id !== itemId)]
|
||||
if (selectedItemId.value === itemId) selectedItemId.value = ''
|
||||
}
|
||||
|
||||
function shouldIgnoreItemClick() {
|
||||
return Date.now() - recentDragFinishedAt.value < 180
|
||||
}
|
||||
|
||||
function getItemLocation(itemId) {
|
||||
if (!itemId) return { type: null, groupId: '', columnIndex: -1, index: -1 }
|
||||
|
||||
const poolIndex = pool.value.findIndex((id) => id === itemId)
|
||||
if (poolIndex >= 0) {
|
||||
return { type: 'pool', groupId: '', columnIndex: -1, index: poolIndex }
|
||||
}
|
||||
|
||||
for (const group of groups.value) {
|
||||
for (let columnIndex = 0; columnIndex < columns.value.length; columnIndex += 1) {
|
||||
const index = getGroupCellIds(group, columnIndex).findIndex((id) => id === itemId)
|
||||
if (index >= 0) {
|
||||
return { type: 'group', groupId: group.id, columnIndex, index }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { type: null, groupId: '', columnIndex: -1, index: -1 }
|
||||
}
|
||||
|
||||
function detachItemById(itemId) {
|
||||
if (!itemId) return
|
||||
pool.value = pool.value.filter((id) => id !== itemId)
|
||||
groups.value.forEach((group) => {
|
||||
group.cells = (group.cells || []).map((cell) => (cell || []).filter((id) => id !== itemId))
|
||||
syncGroupItemIds(group)
|
||||
})
|
||||
}
|
||||
|
||||
function selectItemByClick(itemId) {
|
||||
if (!canEdit.value || !itemId || shouldIgnoreItemClick()) return
|
||||
selectedItemId.value = selectedItemId.value === itemId ? '' : itemId
|
||||
}
|
||||
|
||||
function placeSelectedItemInGroup(groupId, columnIndex) {
|
||||
if (!canEdit.value || !selectedItemId.value || !groupId || !Number.isInteger(columnIndex)) return
|
||||
if (shouldIgnoreItemClick()) return
|
||||
|
||||
const targetGroup = groups.value.find((group) => group.id === groupId)
|
||||
if (!targetGroup) return
|
||||
|
||||
const selectedId = selectedItemId.value
|
||||
const currentLocation = getItemLocation(selectedId)
|
||||
const sameTarget =
|
||||
currentLocation.type === 'group' &&
|
||||
currentLocation.groupId === groupId &&
|
||||
currentLocation.columnIndex === columnIndex
|
||||
|
||||
if (sameTarget) {
|
||||
selectedItemId.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
detachItemById(selectedId)
|
||||
const nextCells = [...targetGroup.cells]
|
||||
nextCells[columnIndex] = [...getGroupCellIds(targetGroup, columnIndex), selectedId]
|
||||
targetGroup.cells = nextCells
|
||||
syncGroupItemIds(targetGroup)
|
||||
selectedItemId.value = ''
|
||||
}
|
||||
|
||||
function moveSelectedItemToPool() {
|
||||
if (!canEdit.value || !selectedItemId.value || shouldIgnoreItemClick()) return
|
||||
|
||||
const selectedId = selectedItemId.value
|
||||
const currentLocation = getItemLocation(selectedId)
|
||||
if (currentLocation.type === 'pool') {
|
||||
selectedItemId.value = ''
|
||||
return
|
||||
}
|
||||
|
||||
detachItemById(selectedId)
|
||||
pool.value = [selectedId, ...pool.value]
|
||||
selectedItemId.value = ''
|
||||
}
|
||||
|
||||
function setGroupDropEl(groupId, columnIndex, el) {
|
||||
@@ -360,6 +443,12 @@ async function initSortables() {
|
||||
draggable: '[data-item-id]',
|
||||
ghostClass: 'ghost',
|
||||
chosenClass: 'chosen',
|
||||
onStart: () => {
|
||||
selectedItemId.value = ''
|
||||
},
|
||||
onEnd: () => {
|
||||
recentDragFinishedAt.value = Date.now()
|
||||
},
|
||||
onSort: () => normalizeSort(poolEl.value),
|
||||
onAdd: () => normalizeSort(poolEl.value),
|
||||
})
|
||||
@@ -371,6 +460,12 @@ async function initSortables() {
|
||||
draggable: '[data-item-id]',
|
||||
ghostClass: 'ghost',
|
||||
chosenClass: 'chosen',
|
||||
onStart: () => {
|
||||
selectedItemId.value = ''
|
||||
},
|
||||
onEnd: () => {
|
||||
recentDragFinishedAt.value = Date.now()
|
||||
},
|
||||
onSort: () => normalizeSort(el),
|
||||
onAdd: () => normalizeSort(el),
|
||||
})
|
||||
@@ -1213,7 +1308,7 @@ onUnmounted(() => {
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="sourceTierListId" class="editorMain__sourceNote">
|
||||
<span>복사본</span>
|
||||
<span>원본</span>
|
||||
<button class="editorMain__sourceLink" type="button" @click="router.push(editorPath(templateId, sourceTierListId))">{{ copiedFromLabel }}</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1294,10 +1389,19 @@ onUnmounted(() => {
|
||||
:data-group-id="g.id"
|
||||
:data-column-index="columnIndex"
|
||||
:ref="(el) => setGroupDropEl(g.id, columnIndex, el)"
|
||||
:class="{ 'row__drop--clickTarget': canEdit && !!selectedItemId }"
|
||||
@click="placeSelectedItemInGroup(g.id, columnIndex)"
|
||||
>
|
||||
<div v-if="columns.length > 1" class="row__columnBadge">{{ column.name || '열 ' + (columnIndex + 1) }}</div>
|
||||
<div v-if="!isExporting" class="row__empty" v-show="getGroupCellIds(g, columnIndex).length === 0">여기로 드래그해서 배치</div>
|
||||
<div v-for="id in getGroupCellIds(g, columnIndex)" :key="id" class="cell" :data-item-id="id">
|
||||
<div
|
||||
v-for="id in getGroupCellIds(g, columnIndex)"
|
||||
:key="id"
|
||||
class="cell"
|
||||
:class="{ 'cell--selected': selectedItemId === id }"
|
||||
:data-item-id="id"
|
||||
@click.stop="selectItemByClick(id)"
|
||||
>
|
||||
<img :src="resolveItemSrc(itemsById[id])" class="thumb" :alt="itemsById[id]?.label || id" />
|
||||
<div v-if="showCharacterNames" class="itemNameOverlay">{{ itemsById[id]?.label || id }}</div>
|
||||
<button
|
||||
@@ -1351,7 +1455,7 @@ onUnmounted(() => {
|
||||
<div class="sidebar__count">{{ visiblePoolCount }} / {{ pool.length }}</div>
|
||||
</div>
|
||||
<div class="sidebar__hint">
|
||||
{{ canEdit ? '등록된 아이템 리스트입니다. 드래그해서 표에 넣을 수 있습니다.' : '공개 티어표는 보기 전용입니다.' }}
|
||||
{{ canEdit ? '아이템을 드래그하거나, 클릭으로 선택한 뒤 원하는 셀/풀을 클릭해서 옮길 수 있어요.' : '공개 티어표는 보기 전용입니다.' }}
|
||||
</div>
|
||||
<input
|
||||
v-model="poolSearchQuery"
|
||||
@@ -1360,13 +1464,24 @@ onUnmounted(() => {
|
||||
maxlength="60"
|
||||
placeholder="아이템 이름 검색"
|
||||
/>
|
||||
<div ref="poolEl" class="pool" data-list-type="pool">
|
||||
<div
|
||||
ref="poolEl"
|
||||
class="pool"
|
||||
:class="{ 'pool--clickTarget': canEdit && !!selectedItemId }"
|
||||
data-list-type="pool"
|
||||
@click.self="moveSelectedItemToPool"
|
||||
>
|
||||
<div
|
||||
v-for="id in pool"
|
||||
:key="id"
|
||||
class="poolItem"
|
||||
:class="{ 'poolItem--readonly': !canEdit, 'poolItem--hidden': !isPoolItemVisible(id) }"
|
||||
:class="{
|
||||
'poolItem--readonly': !canEdit,
|
||||
'poolItem--hidden': !isPoolItemVisible(id),
|
||||
'poolItem--selected': selectedItemId === id,
|
||||
}"
|
||||
:data-item-id="id"
|
||||
@click.stop="selectItemByClick(id)"
|
||||
>
|
||||
<img :src="resolveItemSrc(itemsById[id])" class="thumb" :alt="itemsById[id]?.label || id" />
|
||||
<div class="poolItem__label">{{ itemsById[id]?.label || id }}</div>
|
||||
@@ -2214,6 +2329,11 @@ onUnmounted(() => {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
.row__drop--clickTarget {
|
||||
cursor: copy;
|
||||
border-color: rgba(96, 165, 250, 0.42);
|
||||
box-shadow: inset 0 0 0 1px rgba(96, 165, 250, 0.12);
|
||||
}
|
||||
.row__empty {
|
||||
opacity: 0.6;
|
||||
font-size: 13px;
|
||||
@@ -2227,6 +2347,11 @@ onUnmounted(() => {
|
||||
display: inline-flex;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.cell--selected {
|
||||
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.92), 0 0 0 6px rgba(96, 165, 250, 0.18);
|
||||
}
|
||||
.itemNameOverlay {
|
||||
position: absolute;
|
||||
@@ -2606,6 +2731,9 @@ onUnmounted(() => {
|
||||
gap: 10px;
|
||||
align-content: start;
|
||||
}
|
||||
.pool--clickTarget {
|
||||
cursor: copy;
|
||||
}
|
||||
.poolItem {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
@@ -2617,11 +2745,17 @@ onUnmounted(() => {
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.10);
|
||||
background: var(--theme-pill-bg);
|
||||
cursor: pointer;
|
||||
}
|
||||
.poolItem--readonly {
|
||||
cursor: default;
|
||||
opacity: 0.58;
|
||||
filter: grayscale(0.25) brightness(0.78);
|
||||
}
|
||||
.poolItem--selected {
|
||||
border-color: rgba(96, 165, 250, 0.58);
|
||||
box-shadow: 0 0 0 2px rgba(96, 165, 250, 0.92), 0 0 0 6px rgba(96, 165, 250, 0.18);
|
||||
}
|
||||
.poolItem .thumb {
|
||||
width: 100%;
|
||||
max-width: var(--thumb-size, 80px);
|
||||
|
||||
Reference in New Issue
Block a user