Compare commits

..

1 Commits

Author SHA1 Message Date
a1afa658c2 릴리스: v0.1.15 다운로드와 반응형 UI 보정 2026-03-19 17:35:38 +09:00
4 changed files with 100 additions and 21 deletions

View File

@@ -91,3 +91,10 @@
- **커스텀 아이템 조회 강화**: 사용자 커스텀 아이템 목록에 파일명 검색, `50/200` 단위 페이지네이션, 다운로드 흐름 추가
- **회원 비밀번호 초기화 추가**: 관리자 페이지와 API에서 회원 비밀번호를 직접 재설정할 수 있도록 기능 추가
- **가변 티어 행 지원**: 티어표 에디터에서 `S~D` 고정 5단이 아니라 티어 행을 직접 추가/삭제할 수 있도록 보강
## 2026-03-19 v0.1.14
- **커스텀 아이템 카드 반응형 수정**: 관리자 아이템 관리 탭의 커스텀 아이템 카드에서 이미지 폭을 유동값으로 조정하고, 텍스트 영역에 `min-width: 0`과 강제 줄바꿈 기준을 추가해 카드 바깥 overflow를 방지
## 2026-03-19 v0.1.15
- **셀렉트 화살표 여백 정리**: 전역 `select` 스타일에 커스텀 화살표 위치와 오른쪽 여백을 추가해 텍스트와 화살표가 지나치게 붙지 않도록 조정
- **티어표 다운로드 결과 개선**: `TierEditorView`의 이미지 저장을 Blob 다운로드 방식으로 바꾸고, 캡처 대상을 보드 영역만 포함하는 전용 export 뷰로 분리해 우측 아이템 영역과 편집용 버튼/입력 UI가 저장 이미지에 섞이지 않도록 수정

View File

@@ -54,6 +54,21 @@ body {
margin: 0;
}
select {
appearance: none;
-webkit-appearance: none;
-moz-appearance: none;
background-image:
linear-gradient(45deg, transparent 50%, rgba(255, 255, 255, 0.78) 50%),
linear-gradient(135deg, rgba(255, 255, 255, 0.78) 50%, transparent 50%);
background-position:
calc(100% - 18px) calc(50% - 2px),
calc(100% - 12px) calc(50% - 2px);
background-size: 6px 6px, 6px 6px;
background-repeat: no-repeat;
padding-right: 36px;
}
h1,
h2 {
font-family: var(--heading);

View File

@@ -497,12 +497,10 @@ function fmt(ts) {
<button class="btn btn--ghost toolbar__button" @click="submitCustomItemSearch">검색</button>
<select :value="customItemLimit" class="select toolbar__select" @change="changeCustomItemLimit(Number($event.target.value))">
<option :value="50">50개씩 보기</option>
<option :value="200">200개씩 보기</option>
<option :value="100">100개씩 보기</option>
</select>
</div>
<div class="hint">현재 목록은 사용자 커스텀 이미지 전용입니다. 여기서 보는 항목은 게임 기본 아이템 삭제와 연결되지 않아요.</div>
<div v-if="!customItems.length" class="hint">조건에 맞는 커스텀 아이템이 없어요.</div>
<div v-else class="customItemGrid">
<article v-for="item in customItems" :key="item.id" class="customItemCard">
@@ -726,6 +724,7 @@ function fmt(ts) {
margin-top: 0;
}
.btn {
font-size: 12px;
margin-top: 12px;
padding: 10px 12px;
border-radius: 12px;
@@ -751,9 +750,6 @@ function fmt(ts) {
.btn--ghost {
background: rgba(255, 255, 255, 0.03);
}
.btn--small {
width: 100%;
}
.detailHead {
display: flex;
gap: 12px;
@@ -866,7 +862,7 @@ function fmt(ts) {
.customItemGrid {
margin-top: 14px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.customItemCard {
@@ -874,27 +870,39 @@ function fmt(ts) {
border-radius: 16px;
background: rgba(255, 255, 255, 0.04);
overflow: hidden;
display: flex;
gap: 12px;
align-items: flex-start;
padding: 12px;
min-width: 0;
}
.customItemCard__image {
width: min(100%, 150px);
width: clamp(88px, 22vw, 150px);
flex: 0 1 150px;
aspect-ratio: 1 / 1;
object-fit: cover;
display: block;
margin: 12px auto 0;
border-radius: 12px;
background: rgba(0, 0, 0, 0.18);
}
.customItemCard__body {
display: grid;
display: flex;
flex-direction: column;
gap: 6px;
padding: 12px;
min-width: 0;
flex: 1 1 auto;
}
.customItemCard__title {
min-width: 0;
overflow-wrap: anywhere;
word-break: break-word;
font-weight: 900;
}
.customItemCard__meta {
opacity: 0.72;
font-size: 13px;
min-width: 0;
overflow-wrap: anywhere;
word-break: break-word;
}
.pager {
@@ -979,5 +987,12 @@ function fmt(ts) {
.userList {
grid-template-columns: 1fr;
}
.customItemCard {
align-items: stretch;
}
.customItemCard__image {
width: clamp(72px, 28vw, 120px);
flex-basis: 120px;
}
}
</style>

View File

@@ -30,10 +30,12 @@ const description = ref('')
const isPublic = ref(false)
const error = ref('')
const isSaving = ref(false)
const isExporting = ref(false)
const ownerId = ref('')
const isDragActive = ref(false)
const boardEl = ref(null)
const exportBoardEl = ref(null)
const groupListEl = ref(null)
const poolEl = ref(null)
const groupDropEls = ref({})
@@ -211,11 +213,25 @@ function onDropFiles(event) {
async function downloadImage() {
if (!boardEl.value) return
const dataUrl = await htmlToImage.toPng(boardEl.value, { pixelRatio: 2, backgroundColor: '#0b1220' })
const a = document.createElement('a')
a.href = dataUrl
a.download = `${(title.value || gameName.value || 'tierlist').trim()}.png`
a.click()
isExporting.value = true
await nextTick()
try {
const targetEl = exportBoardEl.value || boardEl.value
const blob = await htmlToImage.toBlob(targetEl, { pixelRatio: 2, backgroundColor: '#0b1220' })
if (!blob) throw new Error('image_export_failed')
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `${(title.value || gameName.value || 'tierlist').trim()}.png`
document.body.appendChild(a)
a.click()
a.remove()
URL.revokeObjectURL(url)
} finally {
isExporting.value = false
}
}
async function uploadPendingCustomItems() {
@@ -375,15 +391,22 @@ onUnmounted(() => {
<section class="layout">
<div ref="boardEl" class="board">
<div v-if="canEdit" class="boardTools">
<div v-if="canEdit && !isExporting" class="boardTools">
<button class="btn btn--ghost" @click="addGroup">티어 추가</button>
</div>
<div ref="groupListEl" class="rows">
<div ref="exportBoardEl" class="exportBoard" :class="{ 'exportBoard--active': isExporting }">
<div v-if="isExporting" class="exportBoard__title">{{ title || gameName || gameId }}</div>
<div ref="groupListEl" class="rows">
<div v-for="g in groups" :key="g.id" class="row">
<div class="row__label">
<span class="grab" title="드래그로 순서 변경" data-group-handle></span>
<input v-model="g.name" class="groupName" :readonly="!canEdit" />
<button v-if="canEdit" class="rowRemoveBtn" :disabled="groups.length <= 1" @click="removeGroup(g.id)">삭제</button>
<template v-if="isExporting">
<div class="row__exportName">{{ g.name }}</div>
</template>
<template v-else>
<span class="grab" title="드래그로 순서 변경" data-group-handle></span>
<input v-model="g.name" class="groupName" :readonly="!canEdit" />
<button v-if="canEdit" class="rowRemoveBtn" :disabled="groups.length <= 1" @click="removeGroup(g.id)">삭제</button>
</template>
</div>
<div
class="row__drop"
@@ -398,6 +421,7 @@ onUnmounted(() => {
</div>
</div>
</div>
</div>
</div>
<div class="sidebar">
@@ -522,6 +546,7 @@ onUnmounted(() => {
display: grid;
grid-template-columns: 1fr 320px;
gap: 14px;
align-items: start;
}
.error {
margin: 10px 0 14px;
@@ -535,12 +560,23 @@ onUnmounted(() => {
background: rgba(255, 255, 255, 0.04);
border-radius: 16px;
padding: 12px;
align-self: start;
}
.boardTools {
display: flex;
justify-content: flex-end;
margin-bottom: 10px;
}
.exportBoard--active {
display: grid;
gap: 12px;
}
.exportBoard__title {
font-size: 28px;
font-weight: 900;
letter-spacing: -0.03em;
text-align: left;
}
.rows {
display: grid;
gap: 10px;
@@ -587,6 +623,12 @@ onUnmounted(() => {
outline: none;
min-width: 0;
}
.row__exportName {
width: 100%;
text-align: left;
font-weight: 900;
word-break: break-word;
}
.rowRemoveBtn {
padding: 6px 10px;
border-radius: 10px;