172 lines
9.0 KiB
Vue
172 lines
9.0 KiB
Vue
<script setup>
|
|
import { toApiUrl } from '../../lib/runtime'
|
|
|
|
const props = defineProps({
|
|
activeTemplateRequest: { type: Object, default: null },
|
|
templateRequestSourceUrl: { type: Function, required: true },
|
|
stagedRequestDraftCount: { type: Number, required: true },
|
|
openGameCreateModal: { type: Function, required: true },
|
|
isGameLoading: { type: Boolean, required: true },
|
|
hasSelectedGame: { type: Boolean, required: true },
|
|
selectedGame: { type: Object, default: null },
|
|
itemFileInputRef: { type: Function, required: true },
|
|
onFile: { type: Function, required: true },
|
|
isItemDragOver: { type: Boolean, required: true },
|
|
onItemDragEnter: { type: Function, required: true },
|
|
onItemDragOver: { type: Function, required: true },
|
|
onItemDragLeave: { type: Function, required: true },
|
|
onItemDrop: { type: Function, required: true },
|
|
openItemFilePicker: { type: Function, required: true },
|
|
uploadItemDrafts: { type: Array, required: true },
|
|
clearItemFiles: { type: Function, required: true },
|
|
canAddItem: { type: Boolean, required: true },
|
|
uploadItem: { type: Function, required: true },
|
|
removeUploadDraft: { type: Function, required: true },
|
|
hasGameItemOrderChanges: { type: Boolean, required: true },
|
|
saveGameItemOrder: { type: Function, required: true },
|
|
gameItemListRef: { type: Function, required: true },
|
|
saveGameItemLabel: { type: Function, required: true },
|
|
removeGameItem: { type: Function, required: true },
|
|
selectedGameId: { type: String, default: '' },
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="props.activeTemplateRequest" class="panel requestWorkspace">
|
|
<div class="requestWorkspace__head">
|
|
<div>
|
|
<div class="panel__title">진행 중인 요청 작업</div>
|
|
<div class="requestWorkspace__title">{{ props.activeTemplateRequest.sourceTierListTitle || '템플릿 요청' }}</div>
|
|
<div class="hint hint--tight">
|
|
{{ props.activeTemplateRequest.type === 'create' ? '새 게임을 만든 뒤 필요한 아이템만 골라 저장하세요.' : '필요한 아이템만 남긴 뒤 기본 아이템 추가 버튼으로 반영하세요.' }}
|
|
</div>
|
|
</div>
|
|
<div class="requestWorkspace__stats">
|
|
<span class="pill pill--accent">{{ props.activeTemplateRequest.type === 'create' ? '신규 게임 요청' : '기존 게임 업데이트' }}</span>
|
|
<span class="pill">요청 아이템 {{ props.stagedRequestDraftCount }}개</span>
|
|
</div>
|
|
</div>
|
|
<div class="requestWorkspace__actions">
|
|
<a
|
|
v-if="props.templateRequestSourceUrl(props.activeTemplateRequest)"
|
|
class="btn btn--ghost btn--small"
|
|
:href="props.templateRequestSourceUrl(props.activeTemplateRequest)"
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
요청 티어표 보기
|
|
</a>
|
|
<button v-if="props.activeTemplateRequest.type === 'create'" class="btn btn--ghost btn--small" type="button" @click="props.openGameCreateModal">
|
|
새 게임 만들기
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="props.isGameLoading" class="panel panel--empty">
|
|
<div class="emptyState">
|
|
<div class="emptyState__title">게임 정보를 불러오는 중이에요.</div>
|
|
<div class="emptyState__desc">선택한 게임의 썸네일과 기본 아이템을 곧 표시합니다.</div>
|
|
</div>
|
|
</div>
|
|
<div v-else-if="props.hasSelectedGame" class="panel">
|
|
<div class="detailHead">
|
|
<div>
|
|
<div class="panel__title">선택된 게임 정보</div>
|
|
<div class="selectedGame__name">{{ props.selectedGame.game.name }}</div>
|
|
<div class="selectedGame__id">{{ props.selectedGame.game.id }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<section class="adminCard">
|
|
<div class="section__title">기본 아이템 추가</div>
|
|
<div class="itemComposer">
|
|
<div class="itemComposer__form">
|
|
<input :ref="props.itemFileInputRef" type="file" accept="image/*" multiple class="srOnlyInput" @change="props.onFile" />
|
|
<div
|
|
class="dropZone"
|
|
:class="{ 'dropZone--active': props.isItemDragOver }"
|
|
@dragenter="props.onItemDragEnter"
|
|
@dragover="props.onItemDragOver"
|
|
@dragleave="props.onItemDragLeave"
|
|
@drop="props.onItemDrop"
|
|
>
|
|
<div class="dropZone__title">이미지를 드래그해서 기본 아이템으로 추가</div>
|
|
<div class="dropZone__desc">
|
|
여러 파일을 한 번에 올릴 수 있고, 저장 라벨은 파일명으로 자동 생성됩니다.
|
|
<span v-if="props.stagedRequestDraftCount"> 현재 요청에서 가져온 아이템 {{ props.stagedRequestDraftCount }}개도 함께 검토 중이에요.</span>
|
|
</div>
|
|
<div class="dropZone__actions">
|
|
<button class="btn btn--ghost btn--small" type="button" @click="props.openItemFilePicker">파일 선택</button>
|
|
<button class="btn btn--danger btn--small" type="button" :disabled="!props.uploadItemDrafts.length" @click="props.clearItemFiles">선택 비우기</button>
|
|
</div>
|
|
</div>
|
|
<button class="btn" :disabled="!props.canAddItem" @click="props.uploadItem">
|
|
아이템 {{ props.uploadItemDrafts.length || 0 }}개 추가
|
|
</button>
|
|
</div>
|
|
<div class="itemPreviewCard">
|
|
<div v-if="props.uploadItemDrafts.length" class="itemDraftList">
|
|
<div
|
|
v-for="draft in props.uploadItemDrafts"
|
|
:key="draft.kind + ':' + (draft.itemId || draft.file?.name || draft.previewUrl)"
|
|
class="itemDraftRow"
|
|
>
|
|
<div class="itemDraftRow__preview">
|
|
<img class="itemPreviewImage" :src="draft.previewUrl" :alt="draft.sourceName || '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.sourceName }}</div>
|
|
<div class="itemDraftRow__meta">
|
|
<span class="pill pill--soft">{{ draft.kind === 'request' ? '요청 아이템' : '직접 추가 파일' }}</span>
|
|
<button class="btn btn--danger btn--small" type="button" @click="props.removeUploadDraft(draft)">제외</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="itemPreviewEmpty">등록한 기본 아이템 미리보기가 여기에 표시됩니다.</div>
|
|
<div class="thumbLabel thumbLabel--preview">
|
|
{{ props.uploadItemDrafts.length ? `추가 예정 아이템 ${props.uploadItemDrafts.length}개` : '아직 선택된 파일이 없어요.' }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<div class="sectionHeader">
|
|
<div>
|
|
<div class="section__title">현재 기본 아이템 목록</div>
|
|
<div class="hint hint--tight">드래그해서 기본 노출 순서를 바꿀 수 있어요. 저장 전까지는 화면에서만 순서가 바뀝니다.</div>
|
|
</div>
|
|
<button class="btn btn--primary btn--small" :disabled="!props.hasGameItemOrderChanges" @click="props.saveGameItemOrder">순서 저장</button>
|
|
</div>
|
|
<div v-if="!props.selectedGame?.items?.length" class="hint">아직 등록된 기본 아이템이 없어요.</div>
|
|
<div v-else :ref="props.gameItemListRef" class="thumbGrid">
|
|
<div v-for="item in props.selectedGame.items" :key="item.id" class="thumbCard" :data-game-item-id="item.id">
|
|
<img class="thumb thumb--game" :src="toApiUrl(item.src)" :alt="item.label" />
|
|
<input v-model="item.draftLabel" class="input input--labelEdit" placeholder="아이템 이름" />
|
|
<div class="thumbCard__actions">
|
|
<button
|
|
class="btn btn--ghost btn--small"
|
|
:disabled="item.isSavingLabel || !item.draftLabel?.trim() || item.draftLabel.trim() === item.label"
|
|
@click="props.saveGameItemLabel(item)"
|
|
>
|
|
{{ item.isSavingLabel ? '저장중...' : '이름 저장' }}
|
|
</button>
|
|
<button class="btn btn--danger btn--small" @click="props.removeGameItem(item.id)">아이템 삭제</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div v-else class="panel panel--empty">
|
|
<div class="emptyState">
|
|
<div class="emptyState__title">게임을 선택해 주세요.</div>
|
|
<div v-if="props.activeTemplateRequest?.type === 'create'" class="hint hint--tight">진행 중인 신규 게임 요청이 있어요. 위의 `새 게임 만들기`로 게임을 만든 뒤 아이템을 추가할 수 있습니다.</div>
|
|
<div v-if="props.selectedGameId" class="hint hint--tight">선택한 게임을 찾지 못했거나 로딩 중 오류가 발생했어요. 다시 선택해보세요.</div>
|
|
</div>
|
|
</div>
|
|
</template>
|