릴리스: v1.2.69 관리자 게임 관리 재배치와 셸 전환 보정
This commit is contained in:
@@ -107,8 +107,10 @@ router.post('/games/:gameId/images', requireAdmin, upload.array('images', 50), a
|
|||||||
const game = await findGameById(req.params.gameId)
|
const game = await findGameById(req.params.gameId)
|
||||||
if (!game) return res.status(404).json({ error: 'not_found' })
|
if (!game) return res.status(404).json({ error: 'not_found' })
|
||||||
|
|
||||||
const manualLabel = typeof req.body?.label === 'string' ? req.body.label.trim() : ''
|
const labelsRaw = req.body?.labels
|
||||||
if (manualLabel && manualLabel.length > 60) return res.status(400).json({ error: 'bad_request' })
|
const labels = Array.isArray(labelsRaw) ? labelsRaw : labelsRaw ? [labelsRaw] : []
|
||||||
|
const normalizedLabels = labels.map((label) => (typeof label === 'string' ? label.trim().slice(0, 60) : ''))
|
||||||
|
if (normalizedLabels.some((label) => label.length > 60)) return res.status(400).json({ error: 'bad_request' })
|
||||||
|
|
||||||
const items = await Promise.all(
|
const items = await Promise.all(
|
||||||
files.map((file, index) =>
|
files.map((file, index) =>
|
||||||
@@ -116,7 +118,7 @@ router.post('/games/:gameId/images', requireAdmin, upload.array('images', 50), a
|
|||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
gameId: game.id,
|
gameId: game.id,
|
||||||
src: `/uploads/games/${file.filename}`,
|
src: `/uploads/games/${file.filename}`,
|
||||||
label: index === 0 && manualLabel ? manualLabel : buildItemLabelFromFilename(file),
|
label: normalizedLabels[index] || buildItemLabelFromFilename(file),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# 업데이트 로그
|
# 업데이트 로그
|
||||||
|
|
||||||
|
## 2026-03-31 v1.2.69
|
||||||
|
- 좌우 사이드 축소/확대 시 텍스트를 즉시 `display:none` 처리하던 방식을 줄이고, 폭·투명도 기반 전환으로 바꿔 아이콘이 떨리는 듯한 느낌을 완화함.
|
||||||
|
- 관리자 게임 관리는 오른쪽 사이드에서 게임 선택과 썸네일 지정을 담당하도록 재배치하고, 본문은 기본 아이템 추가/이름 입력/목록 관리에 집중하도록 정리함.
|
||||||
|
- 게임 기본 아이템 추가는 업로드 직후 각 파일 이름을 바로 수정할 수 있는 draft 입력 행을 넣고, 선택한 이름이 서버에 함께 저장되도록 관리자 업로드 API를 확장함.
|
||||||
|
|
||||||
## 2026-03-31 v1.2.68
|
## 2026-03-31 v1.2.68
|
||||||
- 내 티어표 카드 그리드는 각 카드가 화면 전체 너비를 과도하게 먹지 않도록 최대 폭을 제한해, 1~2개만 있을 때도 적당한 카드 크기를 유지하도록 조정함.
|
- 내 티어표 카드 그리드는 각 카드가 화면 전체 너비를 과도하게 먹지 않도록 최대 폭을 제한해, 1~2개만 있을 때도 적당한 카드 크기를 유지하도록 조정함.
|
||||||
- 새 티어표 기본 그룹은 기존 S/A/B/C/D 5줄 대신 S/A/B/C 4줄로 시작하게 바꾸고, 좌우 사이드 토글 아이콘 버튼은 외곽선과 배경을 제거해 더 가볍게 정리함.
|
- 새 티어표 기본 그룹은 기존 S/A/B/C/D 5줄 대신 S/A/B/C 4줄로 시작하게 바꾸고, 좌우 사이드 토글 아이콘 버튼은 외곽선과 배경을 제거해 더 가볍게 정리함.
|
||||||
|
|||||||
@@ -608,7 +608,9 @@ function submitGlobalSearch() {
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
transition: opacity 180ms ease;
|
max-width: 180px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: opacity 180ms ease, max-width 220ms ease, transform 220ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appUserCard__name {
|
.appUserCard__name {
|
||||||
@@ -642,12 +644,14 @@ function submitGlobalSearch() {
|
|||||||
.searchStub__input {
|
.searchStub__input {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
max-width: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: rgba(255, 255, 255, 0.92);
|
color: rgba(255, 255, 255, 0.92);
|
||||||
outline: none;
|
outline: none;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
transition: opacity 180ms ease, width 180ms ease;
|
overflow: hidden;
|
||||||
|
transition: opacity 180ms ease, max-width 220ms ease, transform 220ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchStub__input::placeholder {
|
.searchStub__input::placeholder {
|
||||||
@@ -691,7 +695,10 @@ function submitGlobalSearch() {
|
|||||||
|
|
||||||
.leftNav__label {
|
.leftNav__label {
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
max-width: 140px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: opacity 180ms ease, max-width 220ms ease, transform 220ms ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leftNav__item--active,
|
.leftNav__item--active,
|
||||||
@@ -726,13 +733,15 @@ function submitGlobalSearch() {
|
|||||||
.appShell--leftCollapsed .appUserCard__button,
|
.appShell--leftCollapsed .appUserCard__button,
|
||||||
.appShell--leftCollapsed .appUserCard__guest {
|
.appShell--leftCollapsed .appUserCard__guest {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 6px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.appShell--leftCollapsed .appUserCard__meta,
|
.appShell--leftCollapsed .appUserCard__meta,
|
||||||
.appShell--leftCollapsed .leftNav__label,
|
.appShell--leftCollapsed .leftNav__label,
|
||||||
.appShell--leftCollapsed .searchStub__input {
|
.appShell--leftCollapsed .searchStub__input {
|
||||||
display: none;
|
opacity: 0;
|
||||||
|
max-width: 0;
|
||||||
|
transform: translateX(-4px);
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appShell--leftCollapsed .appUserCard__avatar {
|
.appShell--leftCollapsed .appUserCard__avatar {
|
||||||
@@ -742,11 +751,10 @@ function submitGlobalSearch() {
|
|||||||
|
|
||||||
.appShell--leftCollapsed .searchStub {
|
.appShell--leftCollapsed .searchStub {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 10px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.appShell--leftCollapsed .searchStub__iconButton {
|
.appShell--leftCollapsed .searchStub__iconButton {
|
||||||
width: 100%;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.appShell--leftCollapsed .leftNav {
|
.appShell--leftCollapsed .leftNav {
|
||||||
@@ -755,7 +763,6 @@ function submitGlobalSearch() {
|
|||||||
|
|
||||||
.appShell--leftCollapsed .leftNav__item {
|
.appShell--leftCollapsed .leftNav__item {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 11px 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.appShell--leftCollapsed .leftRail__bottom {
|
.appShell--leftCollapsed .leftRail__bottom {
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ const newGameId = ref('')
|
|||||||
const newGameName = ref('')
|
const newGameName = ref('')
|
||||||
|
|
||||||
const uploadFiles = ref([])
|
const uploadFiles = ref([])
|
||||||
|
const uploadItemDrafts = ref([])
|
||||||
const thumbFile = ref(null)
|
const thumbFile = ref(null)
|
||||||
const itemPreviewUrls = ref([])
|
const itemPreviewUrls = ref([])
|
||||||
const isItemDragOver = ref(false)
|
const isItemDragOver = ref(false)
|
||||||
@@ -80,7 +81,7 @@ const gameCreateModalOpen = ref(false)
|
|||||||
|
|
||||||
const hasSelectedGame = computed(() => !!selectedGame.value?.game?.id)
|
const hasSelectedGame = computed(() => !!selectedGame.value?.game?.id)
|
||||||
const canApplyThumbnail = computed(() => !!thumbFile.value && !!selectedGameId.value)
|
const canApplyThumbnail = computed(() => !!thumbFile.value && !!selectedGameId.value)
|
||||||
const canAddItem = computed(() => uploadFiles.value.length > 0 && !!selectedGameId.value)
|
const canAddItem = computed(() => uploadItemDrafts.value.length > 0 && uploadItemDrafts.value.every((item) => !!item.label.trim()) && !!selectedGameId.value)
|
||||||
const customItemPageCount = computed(() => Math.max(1, Math.ceil(customItemTotal.value / customItemLimit.value)))
|
const customItemPageCount = computed(() => Math.max(1, Math.ceil(customItemTotal.value / customItemLimit.value)))
|
||||||
const adminTierListPageCount = computed(() => Math.max(1, Math.ceil(adminTierListTotal.value / adminTierListLimit.value)))
|
const adminTierListPageCount = computed(() => Math.max(1, Math.ceil(adminTierListTotal.value / adminTierListLimit.value)))
|
||||||
const featuredGames = computed(() =>
|
const featuredGames = computed(() =>
|
||||||
@@ -437,6 +438,7 @@ async function refreshUsers() {
|
|||||||
|
|
||||||
function resetUploadState() {
|
function resetUploadState() {
|
||||||
uploadFiles.value = []
|
uploadFiles.value = []
|
||||||
|
uploadItemDrafts.value = []
|
||||||
thumbFile.value = null
|
thumbFile.value = null
|
||||||
resetFileInput('item')
|
resetFileInput('item')
|
||||||
resetFileInput('thumb')
|
resetFileInput('thumb')
|
||||||
@@ -537,8 +539,14 @@ function handleItemFiles(fileList) {
|
|||||||
const files = Array.from(fileList || []).filter((file) => (file.type || '').startsWith('image/'))
|
const files = Array.from(fileList || []).filter((file) => (file.type || '').startsWith('image/'))
|
||||||
uploadFiles.value = files
|
uploadFiles.value = files
|
||||||
clearPreviewUrl('item')
|
clearPreviewUrl('item')
|
||||||
|
uploadItemDrafts.value = []
|
||||||
if (!files.length) return
|
if (!files.length) return
|
||||||
itemPreviewUrls.value = files.map((file) => URL.createObjectURL(file))
|
itemPreviewUrls.value = files.map((file) => URL.createObjectURL(file))
|
||||||
|
uploadItemDrafts.value = files.map((file, index) => ({
|
||||||
|
file,
|
||||||
|
previewUrl: itemPreviewUrls.value[index],
|
||||||
|
label: (file.name || '').replace(/\.[^.]+$/, '').replace(/[_-]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 60),
|
||||||
|
}))
|
||||||
resetFileInput('item')
|
resetFileInput('item')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -548,6 +556,7 @@ function openItemFilePicker() {
|
|||||||
|
|
||||||
function clearItemFiles() {
|
function clearItemFiles() {
|
||||||
uploadFiles.value = []
|
uploadFiles.value = []
|
||||||
|
uploadItemDrafts.value = []
|
||||||
clearPreviewUrl('item')
|
clearPreviewUrl('item')
|
||||||
resetFileInput('item')
|
resetFileInput('item')
|
||||||
}
|
}
|
||||||
@@ -602,15 +611,18 @@ async function uploadThumbnail() {
|
|||||||
|
|
||||||
async function uploadItem() {
|
async function uploadItem() {
|
||||||
resetMessages()
|
resetMessages()
|
||||||
if (!uploadFiles.value.length || !selectedGameId.value) {
|
if (!uploadItemDrafts.value.length || !selectedGameId.value) {
|
||||||
error.value = '아이템 파일을 선택해주세요.'
|
error.value = '아이템 파일을 선택해주세요.'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fd = new FormData()
|
const fd = new FormData()
|
||||||
uploadFiles.value.forEach((file) => fd.append('images', file))
|
uploadItemDrafts.value.forEach((entry) => {
|
||||||
const uploadCount = uploadFiles.value.length
|
fd.append('images', entry.file)
|
||||||
|
fd.append('labels', entry.label.trim())
|
||||||
|
})
|
||||||
|
const uploadCount = uploadItemDrafts.value.length
|
||||||
const res = await fetch(toApiUrl(`/api/admin/games/${encodeURIComponent(selectedGameId.value)}/images`), {
|
const res = await fetch(toApiUrl(`/api/admin/games/${encodeURIComponent(selectedGameId.value)}/images`), {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
credentials: 'include',
|
credentials: 'include',
|
||||||
@@ -1240,17 +1252,10 @@ async function saveFeaturedOrder() {
|
|||||||
<button class="btn btn--primary" @click="openGameCreateModal">새 게임 생성</button>
|
<button class="btn btn--primary" @click="openGameCreateModal">새 게임 생성</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gameManagerGrid">
|
<div class="gameManagerGrid gameManagerGrid--single">
|
||||||
<section class="adminCard">
|
<section class="adminCard adminCard--muted">
|
||||||
<div class="section__title">등록된 게임 선택</div>
|
<div class="section__title">아이템 관리 안내</div>
|
||||||
<div class="gameManagerCard__body">
|
<div class="hint hint--tight">게임 선택과 썸네일 지정은 오른쪽 사이드에서 진행하고, 이 본문에서는 기본 아이템 추가와 현재 아이템 관리만 담당합니다.</div>
|
||||||
<select :value="selectedGameId" class="select" @change="handleSelectedGameChange">
|
|
||||||
<option value="">게임을 선택해주세요</option>
|
|
||||||
<option v-for="game in games" :key="game.id" :value="game.id">{{ game.name }} ({{ game.id }})</option>
|
|
||||||
</select>
|
|
||||||
<div class="hint hint--tight">선택하면 아래 상세 영역에서 썸네일과 기본 아이템을 바로 수정할 수 있어요.</div>
|
|
||||||
<div v-if="selectedGameId && !hasSelectedGame && !isGameLoading" class="hint hint--tight">선택된 게임 ID: {{ selectedGameId }}</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1268,37 +1273,9 @@ async function saveFeaturedOrder() {
|
|||||||
<div class="selectedGame__name">{{ selectedGame.game.name }}</div>
|
<div class="selectedGame__name">{{ selectedGame.game.name }}</div>
|
||||||
<div class="selectedGame__id">{{ selectedGame.game.id }}</div>
|
<div class="selectedGame__id">{{ selectedGame.game.id }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="detailHead__actions">
|
|
||||||
<button class="btn btn--danger" @click="removeGame">게임 삭제</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section section--topGrid">
|
<div class="section">
|
||||||
<section class="adminCard">
|
|
||||||
<div class="section__title">썸네일 적용</div>
|
|
||||||
<input ref="thumbFileInput" type="file" accept="image/*" class="srOnlyInput" @change="onThumb" />
|
|
||||||
<button
|
|
||||||
class="thumbDropZone"
|
|
||||||
:class="{ 'thumbDropZone--active': isThumbDragOver }"
|
|
||||||
type="button"
|
|
||||||
@click="openThumbFilePicker"
|
|
||||||
@dragenter="onThumbDragEnter"
|
|
||||||
@dragover="onThumbDragOver"
|
|
||||||
@dragleave="onThumbDragLeave"
|
|
||||||
@drop="onThumbDrop"
|
|
||||||
>
|
|
||||||
<img v-if="displayThumbnailUrl" class="selectedThumb" :src="displayThumbnailUrl" :alt="selectedGame.game.name" />
|
|
||||||
<div v-else class="selectedThumb selectedThumb--empty">대표 썸네일</div>
|
|
||||||
<div class="thumbDropZone__copy">
|
|
||||||
<div class="thumbDropZone__title">클릭하거나 드래그해서 썸네일 추가</div>
|
|
||||||
<div class="thumbDropZone__desc">다른 업로드 화면처럼 이미지 한 장을 바로 지정할 수 있어요.</div>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
<div class="uploadControls">
|
|
||||||
<button class="btn" :disabled="!canApplyThumbnail" @click="uploadThumbnail">썸네일 적용</button>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="adminCard">
|
<section class="adminCard">
|
||||||
<div class="section__title">기본 아이템 추가</div>
|
<div class="section__title">기본 아이템 추가</div>
|
||||||
<div class="itemComposer">
|
<div class="itemComposer">
|
||||||
@@ -1320,18 +1297,24 @@ async function saveFeaturedOrder() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn" :disabled="!canAddItem" @click="uploadItem">
|
<button class="btn" :disabled="!canAddItem" @click="uploadItem">
|
||||||
아이템 {{ uploadFiles.length || 0 }}개 추가
|
아이템 {{ uploadItemDrafts.length || 0 }}개 추가
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="itemPreviewCard">
|
<div class="itemPreviewCard">
|
||||||
<div v-if="itemPreviewUrls.length" class="itemPreviewGrid">
|
<div v-if="uploadItemDrafts.length" class="itemDraftList">
|
||||||
<div v-for="(previewUrl, index) in itemPreviewUrls.slice(0, 6)" :key="previewUrl" class="itemPreviewFrame">
|
<div v-for="draft in uploadItemDrafts" :key="draft.previewUrl || draft.file.name" class="itemDraftRow">
|
||||||
<img class="itemPreviewImage" :src="previewUrl" :alt="uploadFiles[index]?.name || 'item preview'" />
|
<div class="itemDraftRow__preview">
|
||||||
|
<img class="itemPreviewImage" :src="draft.previewUrl" :alt="draft.file.name || 'item preview'" />
|
||||||
|
</div>
|
||||||
|
<div class="itemDraftRow__body">
|
||||||
|
<input v-model="draft.label" class="input input--labelEdit input--dense" maxlength="60" placeholder="아이템 이름" />
|
||||||
|
<div class="hint hint--tight">{{ draft.file.name }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="itemPreviewEmpty">선택한 기본 아이템 미리보기가 여기에 표시됩니다.</div>
|
<div v-else class="itemPreviewEmpty">선택한 기본 아이템 미리보기가 여기에 표시됩니다.</div>
|
||||||
<div class="thumbLabel thumbLabel--preview">
|
<div class="thumbLabel thumbLabel--preview">
|
||||||
{{ uploadFiles.length ? `선택된 파일 ${uploadFiles.length}개` : '아직 선택된 파일이 없어요.' }}
|
{{ uploadItemDrafts.length ? `추가 예정 아이템 ${uploadItemDrafts.length}개` : '아직 선택된 파일이 없어요.' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1764,7 +1747,44 @@ async function saveFeaturedOrder() {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section v-if="activeTab === 'items'" class="adminSidebar__panel">
|
<section v-if="activeTab === 'game-admin'" class="adminSidebar__panel">
|
||||||
|
<div class="adminSidebar__label">Game</div>
|
||||||
|
<div class="adminSidebar__group">
|
||||||
|
<select :value="selectedGameId" class="select" @change="handleSelectedGameChange">
|
||||||
|
<option value="">게임을 선택해주세요</option>
|
||||||
|
<option v-for="game in games" :key="game.id" :value="game.id">{{ game.name }} ({{ game.id }})</option>
|
||||||
|
</select>
|
||||||
|
<div class="hint hint--tight">선택한 게임의 썸네일과 기본 아이템 관리가 본문과 연동됩니다.</div>
|
||||||
|
<div v-if="selectedGameId && !hasSelectedGame && !isGameLoading" class="hint hint--tight">선택된 게임 ID: {{ selectedGameId }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="hasSelectedGame" class="adminSidebar__group">
|
||||||
|
<div class="selectedGameSidebar__name">{{ selectedGame.game.name }}</div>
|
||||||
|
<div class="selectedGameSidebar__id">{{ selectedGame.game.id }}</div>
|
||||||
|
<input ref="thumbFileInput" type="file" accept="image/*" class="srOnlyInput" @change="onThumb" />
|
||||||
|
<button
|
||||||
|
class="thumbDropZone"
|
||||||
|
:class="{ 'thumbDropZone--active': isThumbDragOver }"
|
||||||
|
type="button"
|
||||||
|
@click="openThumbFilePicker"
|
||||||
|
@dragenter="onThumbDragEnter"
|
||||||
|
@dragover="onThumbDragOver"
|
||||||
|
@dragleave="onThumbDragLeave"
|
||||||
|
@drop="onThumbDrop"
|
||||||
|
>
|
||||||
|
<img v-if="displayThumbnailUrl" class="selectedThumb selectedThumb--sidebar" :src="displayThumbnailUrl" :alt="selectedGame.game.name" />
|
||||||
|
<div v-else class="selectedThumb selectedThumb--empty selectedThumb--sidebar">대표 썸네일</div>
|
||||||
|
<div class="thumbDropZone__copy">
|
||||||
|
<div class="thumbDropZone__title">클릭하거나 드래그해서 썸네일 추가</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<div class="adminSidebar__actions adminSidebar__actions--stack">
|
||||||
|
<button class="btn" :disabled="!canApplyThumbnail" @click="uploadThumbnail">썸네일 적용</button>
|
||||||
|
<button class="btn btn--danger" @click="removeGame">게임 삭제</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section v-else-if="activeTab === 'items'" class="adminSidebar__panel">
|
||||||
<div class="adminSidebar__label">Filters</div>
|
<div class="adminSidebar__label">Filters</div>
|
||||||
<div class="adminSidebar__group">
|
<div class="adminSidebar__group">
|
||||||
<input v-model="customItemQuery" class="input" placeholder="파일명, 라벨, 업로더 검색" @keydown.enter.prevent="submitCustomItemSearch" />
|
<input v-model="customItemQuery" class="input" placeholder="파일명, 라벨, 업로더 검색" @keydown.enter.prevent="submitCustomItemSearch" />
|
||||||
@@ -1944,6 +1964,9 @@ async function saveFeaturedOrder() {
|
|||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
.adminSidebar__actions--stack .btn {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
.adminSidebar__groupTitle {
|
.adminSidebar__groupTitle {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 800;
|
font-weight: 800;
|
||||||
@@ -2174,6 +2197,9 @@ async function saveFeaturedOrder() {
|
|||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
.gameManagerGrid--single {
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
.gameManagerCard__body {
|
.gameManagerCard__body {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -2186,6 +2212,9 @@ async function saveFeaturedOrder() {
|
|||||||
padding: 16px;
|
padding: 16px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
.adminCard--muted {
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
.sectionHeader {
|
.sectionHeader {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -2240,6 +2269,11 @@ async function saveFeaturedOrder() {
|
|||||||
.input--labelEdit {
|
.input--labelEdit {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
|
.input--dense {
|
||||||
|
margin-top: 0;
|
||||||
|
padding-top: 9px;
|
||||||
|
padding-bottom: 9px;
|
||||||
|
}
|
||||||
.hint {
|
.hint {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
opacity: 0.78;
|
opacity: 0.78;
|
||||||
@@ -2351,6 +2385,18 @@ async function saveFeaturedOrder() {
|
|||||||
place-items: center;
|
place-items: center;
|
||||||
color: rgba(255, 255, 255, 0.62);
|
color: rgba(255, 255, 255, 0.62);
|
||||||
}
|
}
|
||||||
|
.selectedThumb--sidebar {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.selectedGameSidebar__name {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
.selectedGameSidebar__id {
|
||||||
|
font-size: 12px;
|
||||||
|
opacity: 0.68;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
.thumbDropZone {
|
.thumbDropZone {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -2433,6 +2479,29 @@ async function saveFeaturedOrder() {
|
|||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
.itemDraftList {
|
||||||
|
display: grid;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.itemDraftRow {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 72px minmax(0, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.itemDraftRow__preview {
|
||||||
|
width: 72px;
|
||||||
|
height: 72px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
background: rgba(0, 0, 0, 0.18);
|
||||||
|
}
|
||||||
|
.itemDraftRow__body {
|
||||||
|
min-width: 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
.itemPreviewFrame {
|
.itemPreviewFrame {
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
|
|||||||
Reference in New Issue
Block a user