릴리스: v1.4.28 관리자 템플릿 내부 이름층 추가 정리
This commit is contained in:
@@ -788,7 +788,7 @@ async function getTopicDetail(topicId) {
|
|||||||
const topic = await findTopicById(topicId)
|
const topic = await findTopicById(topicId)
|
||||||
if (!topic) return null
|
if (!topic) return null
|
||||||
const items = await listTopicItems(topicId)
|
const items = await listTopicItems(topicId)
|
||||||
return { topic, game: topic, items }
|
return { topic, template: topic, items }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTopic({ id, name, isPublic = true }) {
|
async function createTopic({ id, name, isPublic = true }) {
|
||||||
@@ -1193,7 +1193,7 @@ async function cleanupMissingUploadReferences() {
|
|||||||
|
|
||||||
for (const row of gameItemRows) {
|
for (const row of gameItemRows) {
|
||||||
if (await fileExistsForUploadSrc(row.src)) continue
|
if (await fileExistsForUploadSrc(row.src)) continue
|
||||||
await deleteGameItem(row.id)
|
await deleteTopicItem(row.id)
|
||||||
stats.deletedGameItems += 1
|
stats.deletedGameItems += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ router.post('/templates/:templateId/thumbnail', requireAdmin, upload.single('thu
|
|||||||
|
|
||||||
const optimized = await writeOptimizedImage({
|
const optimized = await writeOptimizedImage({
|
||||||
file: req.file,
|
file: req.file,
|
||||||
directory: 'games',
|
directory: 'topics',
|
||||||
width: 1280,
|
width: 1280,
|
||||||
height: 1280,
|
height: 1280,
|
||||||
fit: 'inside',
|
fit: 'inside',
|
||||||
@@ -214,7 +214,7 @@ router.post('/templates/:templateId/images', requireAdmin, upload.array('images'
|
|||||||
files.map(async (file, index) => {
|
files.map(async (file, index) => {
|
||||||
const optimized = await writeOptimizedImage({
|
const optimized = await writeOptimizedImage({
|
||||||
file,
|
file,
|
||||||
directory: 'games',
|
directory: 'topics',
|
||||||
width: 512,
|
width: 512,
|
||||||
height: 512,
|
height: 512,
|
||||||
fit: 'inside',
|
fit: 'inside',
|
||||||
@@ -593,7 +593,7 @@ async function createTemplateFromTierList({ tierList, templateId, templateName }
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { game: await findTopicById(templateId), items: createdItems }
|
return { template: await findTopicById(templateId), items: createdItems }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createTemplateFromRequest({ templateRequest, templateId, templateName }) {
|
async function createTemplateFromRequest({ templateRequest, templateId, templateName }) {
|
||||||
@@ -609,7 +609,7 @@ async function createTemplateFromRequest({ templateRequest, templateId, template
|
|||||||
templateId,
|
templateId,
|
||||||
})
|
})
|
||||||
|
|
||||||
return { game: await findTopicById(templateId), items }
|
return { template: await findTopicById(templateId), items }
|
||||||
}
|
}
|
||||||
|
|
||||||
router.delete('/custom-items/:itemId', requireAdmin, async (req, res) => {
|
router.delete('/custom-items/:itemId', requireAdmin, async (req, res) => {
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
# 의사결정 이력
|
# 의사결정 이력
|
||||||
|
|
||||||
|
## 2026-04-02 v1.4.28
|
||||||
|
- 이 시점 이후 코드 검색에 남는 `game`는 대부분 레거시 데이터 마이그레이션, 옛 주소 redirect, 저장 데이터의 `origin` 호환처럼 의도된 층이므로, 무리하게 전부 0으로 만들기보다 기능을 깨뜨리지 않는 선에서 의미 있는 이름층만 더 줄이는 편이 맞다고 판단했다.
|
||||||
|
- 관리자 화면 내부 상태명(`selectedTemplate.game`, `isGameLoading`, `gameVisibilitySaving`)은 실제 기능 의미와 어긋나므로, QA 전에 한 번 더 `template` 기준으로 옮겨두는 편이 이후 유지보수에 더 유리하다고 정리했다.
|
||||||
|
|
||||||
## 2026-04-02 v1.4.27
|
## 2026-04-02 v1.4.27
|
||||||
- 공개/관리자 API 표면까지 `topic/template`로 정리된 뒤에는, 관리자 내부 상태 이름과 DB export alias에 남은 `game` 흔적도 계속 유지할 이유가 작아졌으므로 이 단계에서 함께 걷어내는 편이 맞다고 판단했다.
|
- 공개/관리자 API 표면까지 `topic/template`로 정리된 뒤에는, 관리자 내부 상태 이름과 DB export alias에 남은 `game` 흔적도 계속 유지할 이유가 작아졌으므로 이 단계에서 함께 걷어내는 편이 맞다고 판단했다.
|
||||||
- 다만 외부에서 직접 참조할 수 있는 공개 북마크와 달리, `adminGames`, `game-admin`, `favoriteGame` 같은 이름은 내부 구현 용어라서 이번 단계에서 정리해도 위험이 낮다고 정리했다.
|
- 다만 외부에서 직접 참조할 수 있는 공개 북마크와 달리, `adminGames`, `game-admin`, `favoriteGame` 같은 이름은 내부 구현 용어라서 이번 단계에서 정리해도 위험이 낮다고 정리했다.
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
# 할 일 및 이슈
|
# 할 일 및 이슈
|
||||||
|
|
||||||
## 단기 확인
|
## 단기 확인
|
||||||
|
- `v1.4.28`에서 관리자 템플릿 상세 상태와 기본 아이템 정렬 상태 이름을 `template` 기준으로 더 정리했으므로, 관리자 템플릿 선택/공개 전환/기본 아이템 정렬 저장이 그대로 정상인지 한 번 더 확인한다.
|
||||||
|
- 새 템플릿 썸네일/기본 아이템 업로드는 이제 `topics` 디렉터리로 저장되므로, 실제 업로드 후 최적화 작업 분류와 관리자 최근 작업 표시가 자연스럽게 보이는지 확인한다.
|
||||||
|
- 현재 코드 검색에 남는 `game`는 레거시 redirect, DB 마이그레이션, `origin: 'game'` 호환이 중심이므로, 이 층까지 실제로 없앨지 여부는 `v1.4` QA 후 안정성 기준으로 다시 판단한다.
|
||||||
- `v1.4.27`에서 관리자 내부 탭/라우트 이름과 DB alias export까지 더 정리했으므로, 관리자 템플릿 탭 이동, 커스텀 아이템에서 템플릿 관리로 점프, 템플릿 요청 확인하기 이동이 모두 정상인지 한 번 더 확인한다.
|
- `v1.4.27`에서 관리자 내부 탭/라우트 이름과 DB alias export까지 더 정리했으므로, 관리자 템플릿 탭 이동, 커스텀 아이템에서 템플릿 관리로 점프, 템플릿 요청 확인하기 이동이 모두 정상인지 한 번 더 확인한다.
|
||||||
- `v1.4.26`에서 관리자 기본 경로를 `/admin/templates`로 바꾸고 `/api/admin/templates`만 남겼으므로, 관리자 진입/새로고침/뒤로가기와 템플릿 생성·썸네일 업로드·아이템 추가가 모두 정상인지 확인한다.
|
- `v1.4.26`에서 관리자 기본 경로를 `/admin/templates`로 바꾸고 `/api/admin/templates`만 남겼으므로, 관리자 진입/새로고침/뒤로가기와 템플릿 생성·썸네일 업로드·아이템 추가가 모두 정상인지 확인한다.
|
||||||
- `v1.4.26`에서 공개 API `/api/games`를 제거했으므로, 실제 서버 재시작 후 홈/주제 상세/티어표 편집기에서 `/api/topics`만으로 모두 정상 동작하는지 확인한다.
|
- `v1.4.26`에서 공개 API `/api/games`를 제거했으므로, 실제 서버 재시작 후 홈/주제 상세/티어표 편집기에서 `/api/topics`만으로 모두 정상 동작하는지 확인한다.
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
# 업데이트 로그
|
# 업데이트 로그
|
||||||
|
|
||||||
|
## 2026-04-02 v1.4.28
|
||||||
|
- 관리자 템플릿 상세 상태(`selectedTemplate.game`)와 관련 응답 키를 `template` 기준으로 정리해, 내부 코드 검색에서 남던 `game` 흔적을 더 줄였다.
|
||||||
|
- 관리자 기본 아이템 정렬/로딩 상태 이름도 `templateItem*`, `isTemplateLoading`, `templateVisibilitySaving` 기준으로 바꾸고, 새 템플릿 자산 업로드는 `topics` 디렉터리로 저장되게 맞췄다.
|
||||||
|
- 현재 코드 검색에서 남는 `game`는 주로 레거시 주소 redirect(`/games/:gameId`), DB 마이그레이션용 legacy 테이블/컬럼명, 기존 저장 데이터와 맞춘 `origin: 'game'` 값처럼 의도적으로 남겨둔 호환층만 남도록 정리했다.
|
||||||
|
|
||||||
## 2026-04-02 v1.4.27
|
## 2026-04-02 v1.4.27
|
||||||
- 관리자 내부 탭/라우트 이름도 `template-admin`, `adminTemplates`, `/admin/templates` 기준으로 더 정리해, 화면 상태값과 라우트 이름에 남아 있던 `game-admin`, `adminGames` 흔적을 줄였다.
|
- 관리자 내부 탭/라우트 이름도 `template-admin`, `adminTemplates`, `/admin/templates` 기준으로 더 정리해, 화면 상태값과 라우트 이름에 남아 있던 `game-admin`, `adminGames` 흔적을 줄였다.
|
||||||
- 더 이상 참조되지 않는 DB alias export(`listGames`, `createGame`, `favoriteGame` 등)와 `updateTemplateRequestTargetGame` 별칭도 제거해, 백엔드 모듈 표면에서 남아 있던 레거시 `game` 이름층을 더 걷어냈다.
|
- 더 이상 참조되지 않는 DB alias export(`listGames`, `createGame`, `favoriteGame` 등)와 `updateTemplateRequestTargetGame` 별칭도 제거해, 백엔드 모듈 표면에서 남아 있던 레거시 `game` 이름층을 더 걷어냈다.
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const showRightRailAction = computed(() => false)
|
|||||||
const showSettingsGuideButton = computed(() => route.name === 'profile')
|
const showSettingsGuideButton = computed(() => route.name === 'profile')
|
||||||
const guideSteps = [
|
const guideSteps = [
|
||||||
{
|
{
|
||||||
id: 'select-game',
|
id: 'select-topic',
|
||||||
title: '주제 또는 양식 선택',
|
title: '주제 또는 양식 선택',
|
||||||
summary: '주제 템플릿을 고르거나 커스텀 티어표 만들기로 바로 시작합니다.',
|
summary: '주제 템플릿을 고르거나 커스텀 티어표 만들기로 바로 시작합니다.',
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ const props = defineProps({
|
|||||||
stagedRequestDraftCount: { type: Number, required: true },
|
stagedRequestDraftCount: { type: Number, required: true },
|
||||||
appliedRequestItemCount: { type: Number, required: true },
|
appliedRequestItemCount: { type: Number, required: true },
|
||||||
openTemplateCreateModal: { type: Function, required: true },
|
openTemplateCreateModal: { type: Function, required: true },
|
||||||
isGameLoading: { type: Boolean, required: true },
|
isTemplateLoading: { type: Boolean, required: true },
|
||||||
hasSelectedTemplate: { type: Boolean, required: true },
|
hasSelectedTemplate: { type: Boolean, required: true },
|
||||||
selectedTemplate: { type: Object, default: null },
|
selectedTemplate: { type: Object, default: null },
|
||||||
displayThumbnailUrl: { type: String, default: '' },
|
displayThumbnailUrl: { type: String, default: '' },
|
||||||
canApplyThumbnail: { type: Boolean, required: true },
|
canApplyThumbnail: { type: Boolean, required: true },
|
||||||
gameVisibilitySaving: { type: Boolean, required: true },
|
templateVisibilitySaving: { type: Boolean, required: true },
|
||||||
thumbFileInputRef: { type: Function, required: true },
|
thumbFileInputRef: { type: Function, required: true },
|
||||||
openThumbFilePicker: { type: Function, required: true },
|
openThumbFilePicker: { type: Function, required: true },
|
||||||
onThumb: { type: Function, required: true },
|
onThumb: { type: Function, required: true },
|
||||||
@@ -41,14 +41,14 @@ const props = defineProps({
|
|||||||
removeUploadDraft: { type: Function, required: true },
|
removeUploadDraft: { type: Function, required: true },
|
||||||
hasTemplateItemOrderChanges: { type: Boolean, required: true },
|
hasTemplateItemOrderChanges: { type: Boolean, required: true },
|
||||||
saveTemplateItemOrder: { type: Function, required: true },
|
saveTemplateItemOrder: { type: Function, required: true },
|
||||||
gameItemListRef: { type: Function, required: true },
|
templateItemListRef: { type: Function, required: true },
|
||||||
saveTemplateItemLabel: { type: Function, required: true },
|
saveTemplateItemLabel: { type: Function, required: true },
|
||||||
removeTemplateItem: { type: Function, required: true },
|
removeTemplateItem: { type: Function, required: true },
|
||||||
selectedTemplateId: { type: String, default: '' },
|
selectedTemplateId: { type: String, default: '' },
|
||||||
})
|
})
|
||||||
|
|
||||||
function setGameItemListElement(el) {
|
function setGameItemListElement(el) {
|
||||||
props.gameItemListRef(el)
|
props.templateItemListRef(el)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setThumbFileElement(el) {
|
function setThumbFileElement(el) {
|
||||||
@@ -102,7 +102,7 @@ function setThumbFileElement(el) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="props.isGameLoading" class="panel panel--empty">
|
<div v-if="props.isTemplateLoading" class="panel panel--empty">
|
||||||
<div class="emptyState">
|
<div class="emptyState">
|
||||||
<div class="emptyState__title">템플릿 정보를 불러오는 중이에요.</div>
|
<div class="emptyState__title">템플릿 정보를 불러오는 중이에요.</div>
|
||||||
<div class="emptyState__desc">선택한 템플릿의 썸네일과 기본 아이템을 곧 표시합니다.</div>
|
<div class="emptyState__desc">선택한 템플릿의 썸네일과 기본 아이템을 곧 표시합니다.</div>
|
||||||
@@ -122,7 +122,7 @@ function setThumbFileElement(el) {
|
|||||||
@dragleave="props.onThumbDragLeave"
|
@dragleave="props.onThumbDragLeave"
|
||||||
@drop="props.onThumbDrop"
|
@drop="props.onThumbDrop"
|
||||||
>
|
>
|
||||||
<img v-if="props.displayThumbnailUrl" class="selectedThumb selectedThumb--sidebar" :src="props.displayThumbnailUrl" :alt="props.selectedTemplate.game.name" />
|
<img v-if="props.displayThumbnailUrl" class="selectedThumb selectedThumb--sidebar" :src="props.displayThumbnailUrl" :alt="props.selectedTemplate.template.name" />
|
||||||
<div v-else class="selectedThumb selectedThumb--empty selectedThumb--sidebar">대표 썸네일</div>
|
<div v-else class="selectedThumb selectedThumb--empty selectedThumb--sidebar">대표 썸네일</div>
|
||||||
<div class="thumbDropZone__copy">
|
<div class="thumbDropZone__copy">
|
||||||
<div v-if="!props.displayThumbnailUrl" class="thumbDropZone__iconWrap">
|
<div v-if="!props.displayThumbnailUrl" class="thumbDropZone__iconWrap">
|
||||||
@@ -134,10 +134,10 @@ function setThumbFileElement(el) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="gameSettingsCard__body">
|
<div class="gameSettingsCard__body">
|
||||||
<div class="panel__title">템플릿 설정</div>
|
<div class="panel__title">템플릿 설정</div>
|
||||||
<div class="gameSettingsCard__meta">{{ props.selectedTemplate.game.name }} · {{ props.selectedTemplate.game.id }}</div>
|
<div class="gameSettingsCard__meta">{{ props.selectedTemplate.template.name }} · {{ props.selectedTemplate.template.id }}</div>
|
||||||
<label class="toggleSwitch" :class="{ 'toggleSwitch--disabled': props.gameVisibilitySaving }">
|
<label class="toggleSwitch" :class="{ 'toggleSwitch--disabled': props.templateVisibilitySaving }">
|
||||||
<input :checked="!!props.selectedTemplate.game.isPublic" type="checkbox" @change="props.toggleSelectedTemplateVisibility($event.target.checked)" />
|
<input :checked="!!props.selectedTemplate.template.isPublic" type="checkbox" @change="props.toggleSelectedTemplateVisibility($event.target.checked)" />
|
||||||
<span class="toggleSwitch__label">{{ props.selectedTemplate.game.isPublic ? '템플릿 공개중' : '비공개 상태' }}</span>
|
<span class="toggleSwitch__label">{{ props.selectedTemplate.template.isPublic ? '템플릿 공개중' : '비공개 상태' }}</span>
|
||||||
<span class="toggleSwitch__track"><span class="toggleSwitch__thumb"></span></span>
|
<span class="toggleSwitch__track"><span class="toggleSwitch__thumb"></span></span>
|
||||||
</label>
|
</label>
|
||||||
<div class="gameSettingsCard__actions">
|
<div class="gameSettingsCard__actions">
|
||||||
@@ -216,8 +216,8 @@ function setThumbFileElement(el) {
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="!props.selectedTemplate?.items?.length" class="hint">아직 등록된 기본 아이템이 없어요.</div>
|
<div v-if="!props.selectedTemplate?.items?.length" class="hint">아직 등록된 기본 아이템이 없어요.</div>
|
||||||
<div v-else :ref="setGameItemListElement" class="thumbGrid">
|
<div v-else :ref="setGameItemListElement" class="thumbGrid">
|
||||||
<div v-for="item in props.selectedTemplate.items" :key="item.id" class="thumbCard" :data-game-item-id="item.id">
|
<div v-for="item in props.selectedTemplate.items" :key="item.id" class="thumbCard" :data-template-item-id="item.id">
|
||||||
<img class="thumb thumb--game" :src="toApiUrl(item.src)" :alt="item.label" draggable="false" />
|
<img class="thumb thumb--template" :src="toApiUrl(item.src)" :alt="item.label" draggable="false" />
|
||||||
<input v-model="item.draftLabel" class="input input--labelEdit" placeholder="아이템 이름" data-no-drag />
|
<input v-model="item.draftLabel" class="input input--labelEdit" placeholder="아이템 이름" data-no-drag />
|
||||||
<div class="thumbCard__actions">
|
<div class="thumbCard__actions">
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ export function useAdminGameManager({
|
|||||||
thumbFile,
|
thumbFile,
|
||||||
itemPreviewUrls,
|
itemPreviewUrls,
|
||||||
itemFileInput,
|
itemFileInput,
|
||||||
gameItemListEl,
|
templateItemListEl,
|
||||||
gameItemSortable,
|
templateItemSortable,
|
||||||
savedGameItemOrderIds,
|
savedTemplateItemOrderIds,
|
||||||
isGameLoading,
|
isTemplateLoading,
|
||||||
activeTemplateRequest,
|
activeTemplateRequest,
|
||||||
templateRequests,
|
templateRequests,
|
||||||
customItemModalOpen,
|
customItemModalOpen,
|
||||||
@@ -49,21 +49,21 @@ export function useAdminGameManager({
|
|||||||
return src.split('/').pop() || item.file?.name || 'item'
|
return src.split('/').pop() || item.file?.name || 'item'
|
||||||
}
|
}
|
||||||
|
|
||||||
function destroyGameItemSortable() {
|
function destroyTemplateItemSortable() {
|
||||||
if (gameItemSortable.value) {
|
if (templateItemSortable.value) {
|
||||||
gameItemSortable.value.destroy()
|
templateItemSortable.value.destroy()
|
||||||
gameItemSortable.value = null
|
templateItemSortable.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function syncGameItemSortable() {
|
async function syncTemplateItemSortable() {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
destroyGameItemSortable()
|
destroyTemplateItemSortable()
|
||||||
if (!gameItemListEl.value || !selectedTemplate.value?.items?.length) return
|
if (!templateItemListEl.value || !selectedTemplate.value?.items?.length) return
|
||||||
|
|
||||||
gameItemSortable.value = Sortable.create(gameItemListEl.value, {
|
templateItemSortable.value = Sortable.create(templateItemListEl.value, {
|
||||||
animation: 160,
|
animation: 160,
|
||||||
draggable: '[data-game-item-id]',
|
draggable: '[data-template-item-id]',
|
||||||
forceFallback: true,
|
forceFallback: true,
|
||||||
fallbackOnBody: false,
|
fallbackOnBody: false,
|
||||||
filter: '[data-no-drag]',
|
filter: '[data-no-drag]',
|
||||||
@@ -124,31 +124,30 @@ export function useAdminGameManager({
|
|||||||
|
|
||||||
if (!selectedTemplateId.value) {
|
if (!selectedTemplateId.value) {
|
||||||
selectedTemplate.value = null
|
selectedTemplate.value = null
|
||||||
savedGameItemOrderIds.value = []
|
savedTemplateItemOrderIds.value = []
|
||||||
destroyGameItemSortable()
|
destroyTemplateItemSortable()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
isGameLoading.value = true
|
isTemplateLoading.value = true
|
||||||
const data = await api.getTopic(selectedTemplateId.value)
|
const data = await api.getTopic(selectedTemplateId.value)
|
||||||
const loadedTemplate = data.template || data.topic || null
|
const loadedTemplate = data.template || data.topic || null
|
||||||
selectedTemplate.value = {
|
selectedTemplate.value = {
|
||||||
...data,
|
...data,
|
||||||
game: loadedTemplate,
|
|
||||||
template: loadedTemplate,
|
template: loadedTemplate,
|
||||||
items: (data.items || []).map((item) => ({
|
items: (data.items || []).map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
draftLabel: item.label,
|
draftLabel: item.label,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
savedGameItemOrderIds.value = (data.items || []).map((item) => item.id)
|
savedTemplateItemOrderIds.value = (data.items || []).map((item) => item.id)
|
||||||
await syncGameItemSortable()
|
await syncTemplateItemSortable()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
selectedTemplate.value = null
|
selectedTemplate.value = null
|
||||||
error.value = '템플릿 정보를 불러오지 못했어요.'
|
error.value = '템플릿 정보를 불러오지 못했어요.'
|
||||||
} finally {
|
} finally {
|
||||||
isGameLoading.value = false
|
isTemplateLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,8 +349,8 @@ export function useAdminGameManager({
|
|||||||
draftLabel: item.label,
|
draftLabel: item.label,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
savedGameItemOrderIds.value = (data.items || []).map((item) => item.id)
|
savedTemplateItemOrderIds.value = (data.items || []).map((item) => item.id)
|
||||||
await syncGameItemSortable()
|
await syncTemplateItemSortable()
|
||||||
success.value = '기본 아이템 순서를 저장했어요.'
|
success.value = '기본 아이템 순서를 저장했어요.'
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error.value = '기본 아이템 순서 저장에 실패했어요.'
|
error.value = '기본 아이템 순서 저장에 실패했어요.'
|
||||||
@@ -360,8 +359,8 @@ export function useAdminGameManager({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
requestItemFilename,
|
requestItemFilename,
|
||||||
destroyGameItemSortable,
|
destroyTemplateItemSortable,
|
||||||
syncGameItemSortable,
|
syncTemplateItemSortable,
|
||||||
mergeRequestItemsIntoDrafts,
|
mergeRequestItemsIntoDrafts,
|
||||||
removeUploadDraft,
|
removeUploadDraft,
|
||||||
loadTemplate,
|
loadTemplate,
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ const success = ref('')
|
|||||||
const newTemplateId = ref('')
|
const newTemplateId = ref('')
|
||||||
const newTemplateName = ref('')
|
const newTemplateName = ref('')
|
||||||
const newTemplateIsPublic = ref(false)
|
const newTemplateIsPublic = ref(false)
|
||||||
const gameVisibilitySaving = ref(false)
|
const templateVisibilitySaving = ref(false)
|
||||||
|
|
||||||
const uploadFiles = ref([])
|
const uploadFiles = ref([])
|
||||||
const uploadItemDrafts = ref([])
|
const uploadItemDrafts = ref([])
|
||||||
@@ -122,12 +122,12 @@ const itemFileInput = ref(null)
|
|||||||
const thumbFileInput = ref(null)
|
const thumbFileInput = ref(null)
|
||||||
const featuredListEl = ref(null)
|
const featuredListEl = ref(null)
|
||||||
const featuredSortable = ref(null)
|
const featuredSortable = ref(null)
|
||||||
const gameItemListEl = ref(null)
|
const templateItemListEl = ref(null)
|
||||||
const gameItemSortable = ref(null)
|
const templateItemSortable = ref(null)
|
||||||
let gameItemSortableSyncTimer = null
|
let templateItemSortableSyncTimer = null
|
||||||
const savedGameItemOrderIds = ref([])
|
const savedTemplateItemOrderIds = ref([])
|
||||||
const userAvatarInputs = ref({})
|
const userAvatarInputs = ref({})
|
||||||
const isGameLoading = ref(false)
|
const isTemplateLoading = ref(false)
|
||||||
const templateCreateModalOpen = ref(false)
|
const templateCreateModalOpen = ref(false)
|
||||||
const previousBodyOverflow = ref('')
|
const previousBodyOverflow = ref('')
|
||||||
|
|
||||||
@@ -144,20 +144,20 @@ function setThumbFileInputRef(el) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function scheduleGameItemSortableSync() {
|
function scheduleGameItemSortableSync() {
|
||||||
if (gameItemSortableSyncTimer) {
|
if (templateItemSortableSyncTimer) {
|
||||||
clearTimeout(gameItemSortableSyncTimer)
|
clearTimeout(templateItemSortableSyncTimer)
|
||||||
gameItemSortableSyncTimer = null
|
templateItemSortableSyncTimer = null
|
||||||
}
|
}
|
||||||
if (!gameItemListEl.value || !selectedTemplate.value?.items?.length) return
|
if (!templateItemListEl.value || !selectedTemplate.value?.items?.length) return
|
||||||
|
|
||||||
gameItemSortableSyncTimer = setTimeout(() => {
|
templateItemSortableSyncTimer = setTimeout(() => {
|
||||||
gameItemSortableSyncTimer = null
|
templateItemSortableSyncTimer = null
|
||||||
syncGameItemSortable()
|
syncTemplateItemSortable()
|
||||||
}, 0)
|
}, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setGameItemListRef(el) {
|
function setGameItemListRef(el) {
|
||||||
gameItemListEl.value = el
|
templateItemListEl.value = el
|
||||||
if (!el) return
|
if (!el) return
|
||||||
scheduleGameItemSortableSync()
|
scheduleGameItemSortableSync()
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,7 @@ function normalizeAdminSrc(src) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasSelectedTemplate = computed(() => !!selectedTemplate.value?.game?.id)
|
const hasSelectedTemplate = computed(() => !!selectedTemplate.value?.template?.id)
|
||||||
const canApplyThumbnail = computed(() => !!thumbFile.value && !!selectedTemplateId.value)
|
const canApplyThumbnail = computed(() => !!thumbFile.value && !!selectedTemplateId.value)
|
||||||
const canAddItem = computed(() => uploadItemDrafts.value.length > 0 && uploadItemDrafts.value.every((item) => !!item.label.trim()) && !!selectedTemplateId.value)
|
const canAddItem = computed(() => uploadItemDrafts.value.length > 0 && uploadItemDrafts.value.every((item) => !!item.label.trim()) && !!selectedTemplateId.value)
|
||||||
const stagedRequestDraftCount = computed(() => uploadItemDrafts.value.filter((item) => item.kind === 'request').length)
|
const stagedRequestDraftCount = computed(() => uploadItemDrafts.value.filter((item) => item.kind === 'request').length)
|
||||||
@@ -188,7 +188,7 @@ const appliedRequestItemCount = computed(() => {
|
|||||||
})
|
})
|
||||||
const hasTemplateItemOrderChanges = computed(() => {
|
const hasTemplateItemOrderChanges = computed(() => {
|
||||||
const currentIds = (selectedTemplate.value?.items || []).map((item) => item.id)
|
const currentIds = (selectedTemplate.value?.items || []).map((item) => item.id)
|
||||||
return currentIds.join('|') !== savedGameItemOrderIds.value.join('|')
|
return currentIds.join('|') !== savedTemplateItemOrderIds.value.join('|')
|
||||||
})
|
})
|
||||||
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)))
|
||||||
@@ -375,12 +375,12 @@ onUnmounted(() => {
|
|||||||
if (typeof document !== 'undefined') document.body.style.overflow = previousBodyOverflow.value || ''
|
if (typeof document !== 'undefined') document.body.style.overflow = previousBodyOverflow.value || ''
|
||||||
clearPreviewUrl('item')
|
clearPreviewUrl('item')
|
||||||
clearPreviewUrl('thumb')
|
clearPreviewUrl('thumb')
|
||||||
if (gameItemSortableSyncTimer) {
|
if (templateItemSortableSyncTimer) {
|
||||||
clearTimeout(gameItemSortableSyncTimer)
|
clearTimeout(templateItemSortableSyncTimer)
|
||||||
gameItemSortableSyncTimer = null
|
templateItemSortableSyncTimer = null
|
||||||
}
|
}
|
||||||
destroyFeaturedSortable()
|
destroyFeaturedSortable()
|
||||||
destroyGameItemSortable()
|
destroyTemplateItemSortable()
|
||||||
})
|
})
|
||||||
|
|
||||||
function clearPreviewUrl(kind) {
|
function clearPreviewUrl(kind) {
|
||||||
@@ -452,7 +452,7 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => selectedTemplate.value?.game?.id || '',
|
() => selectedTemplate.value?.template?.id || '',
|
||||||
async (templateId) => {
|
async (templateId) => {
|
||||||
await refreshSelectedTemplateTierListStats(templateId)
|
await refreshSelectedTemplateTierListStats(templateId)
|
||||||
},
|
},
|
||||||
@@ -481,7 +481,7 @@ watch(
|
|||||||
watch(
|
watch(
|
||||||
() => activeTab.value,
|
() => activeTab.value,
|
||||||
async (tab) => {
|
async (tab) => {
|
||||||
if (tab === 'template-admin' && selectedTemplateId.value && !selectedTemplate.value?.game?.id) {
|
if (tab === 'template-admin' && selectedTemplateId.value && !selectedTemplate.value?.template?.id) {
|
||||||
await loadTemplate()
|
await loadTemplate()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -524,7 +524,7 @@ watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => [selectedTemplate.value?.game?.id || '', selectedTemplate.value?.items?.length || 0, !!gameItemListEl.value],
|
() => [selectedTemplate.value?.template?.id || '', selectedTemplate.value?.items?.length || 0, !!templateItemListEl.value],
|
||||||
([templateId, itemCount, hasListEl]) => {
|
([templateId, itemCount, hasListEl]) => {
|
||||||
if (!templateId || !itemCount || !hasListEl) return
|
if (!templateId || !itemCount || !hasListEl) return
|
||||||
scheduleGameItemSortableSync()
|
scheduleGameItemSortableSync()
|
||||||
@@ -571,6 +571,7 @@ function formatImageJobSourceCategory(category) {
|
|||||||
return '커스텀 아이템'
|
return '커스텀 아이템'
|
||||||
case 'tierlists':
|
case 'tierlists':
|
||||||
return '티어표 썸네일'
|
return '티어표 썸네일'
|
||||||
|
case 'topics':
|
||||||
case 'games':
|
case 'games':
|
||||||
return '주제/템플릿 이미지'
|
return '주제/템플릿 이미지'
|
||||||
case 'avatars':
|
case 'avatars':
|
||||||
@@ -931,8 +932,8 @@ const {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
destroyGameItemSortable,
|
destroyTemplateItemSortable,
|
||||||
syncGameItemSortable,
|
syncTemplateItemSortable,
|
||||||
mergeRequestItemsIntoDrafts,
|
mergeRequestItemsIntoDrafts,
|
||||||
removeUploadDraft,
|
removeUploadDraft,
|
||||||
loadTemplate,
|
loadTemplate,
|
||||||
@@ -953,10 +954,10 @@ const {
|
|||||||
thumbFile,
|
thumbFile,
|
||||||
itemPreviewUrls,
|
itemPreviewUrls,
|
||||||
itemFileInput,
|
itemFileInput,
|
||||||
gameItemListEl,
|
templateItemListEl,
|
||||||
gameItemSortable,
|
templateItemSortable,
|
||||||
savedGameItemOrderIds,
|
savedTemplateItemOrderIds,
|
||||||
isGameLoading,
|
isTemplateLoading,
|
||||||
activeTemplateRequest,
|
activeTemplateRequest,
|
||||||
templateRequests,
|
templateRequests,
|
||||||
customItemModalOpen,
|
customItemModalOpen,
|
||||||
@@ -1167,17 +1168,17 @@ async function uploadThumbnail() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function saveTemplateVisibility() {
|
async function saveTemplateVisibility() {
|
||||||
if (!selectedTemplate.value?.game?.id) return
|
if (!selectedTemplate.value?.template?.id) return
|
||||||
try {
|
try {
|
||||||
gameVisibilitySaving.value = true
|
templateVisibilitySaving.value = true
|
||||||
const data = await api.updateAdminTemplate(selectedTemplate.value.game.id, {
|
const data = await api.updateAdminTemplate(selectedTemplate.value.template.id, {
|
||||||
isPublic: !!selectedTemplate.value.game.isPublic,
|
isPublic: !!selectedTemplate.value.template.isPublic,
|
||||||
})
|
})
|
||||||
const nextTemplate = data.template || {}
|
const nextTemplate = data.template || {}
|
||||||
selectedTemplate.value = {
|
selectedTemplate.value = {
|
||||||
...selectedTemplate.value,
|
...selectedTemplate.value,
|
||||||
game: {
|
template: {
|
||||||
...selectedTemplate.value.game,
|
...selectedTemplate.value.template,
|
||||||
...nextTemplate,
|
...nextTemplate,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1188,17 +1189,17 @@ async function saveTemplateVisibility() {
|
|||||||
error.value = '템플릿 공개 상태를 저장하지 못했어요.'
|
error.value = '템플릿 공개 상태를 저장하지 못했어요.'
|
||||||
return false
|
return false
|
||||||
} finally {
|
} finally {
|
||||||
gameVisibilitySaving.value = false
|
templateVisibilitySaving.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleSelectedTemplateVisibility(nextValue) {
|
async function toggleSelectedTemplateVisibility(nextValue) {
|
||||||
if (!selectedTemplate.value?.game?.id || gameVisibilitySaving.value) return
|
if (!selectedTemplate.value?.template?.id || templateVisibilitySaving.value) return
|
||||||
const previous = !!selectedTemplate.value.game.isPublic
|
const previous = !!selectedTemplate.value.template.isPublic
|
||||||
selectedTemplate.value = {
|
selectedTemplate.value = {
|
||||||
...selectedTemplate.value,
|
...selectedTemplate.value,
|
||||||
game: {
|
template: {
|
||||||
...selectedTemplate.value.game,
|
...selectedTemplate.value.template,
|
||||||
isPublic: !!nextValue,
|
isPublic: !!nextValue,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1206,8 +1207,8 @@ async function toggleSelectedTemplateVisibility(nextValue) {
|
|||||||
if (!saved) {
|
if (!saved) {
|
||||||
selectedTemplate.value = {
|
selectedTemplate.value = {
|
||||||
...selectedTemplate.value,
|
...selectedTemplate.value,
|
||||||
game: {
|
template: {
|
||||||
...selectedTemplate.value.game,
|
...selectedTemplate.value.template,
|
||||||
isPublic: previous,
|
isPublic: previous,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -1278,9 +1279,9 @@ async function saveTemplateItemLabel(item) {
|
|||||||
|
|
||||||
async function removeTemplate() {
|
async function removeTemplate() {
|
||||||
resetMessages()
|
resetMessages()
|
||||||
if (!selectedTemplateId.value || !selectedTemplate.value?.game) return
|
if (!selectedTemplateId.value || !selectedTemplate.value?.template) return
|
||||||
|
|
||||||
const ok = window.confirm(`"${selectedTemplate.value.game.name}" 템플릿을 삭제할까요? 관련 기본 아이템과 티어표도 함께 삭제됩니다.`)
|
const ok = window.confirm(`"${selectedTemplate.value.template.name}" 템플릿을 삭제할까요? 관련 기본 아이템과 티어표도 함께 삭제됩니다.`)
|
||||||
if (!ok) return
|
if (!ok) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -1290,7 +1291,7 @@ async function removeTemplate() {
|
|||||||
})
|
})
|
||||||
if (!res.ok) throw new Error('failed')
|
if (!res.ok) throw new Error('failed')
|
||||||
|
|
||||||
const deletedName = selectedTemplate.value.game.name
|
const deletedName = selectedTemplate.value.template.name
|
||||||
selectedTemplateId.value = ''
|
selectedTemplateId.value = ''
|
||||||
selectedTemplate.value = null
|
selectedTemplate.value = null
|
||||||
resetUploadState()
|
resetUploadState()
|
||||||
@@ -1432,7 +1433,7 @@ async function saveAdminTierListMeta() {
|
|||||||
adminTierLists.value = adminTierLists.value.map((tierList) => (tierList.id === updated.id ? { ...tierList, ...updated } : tierList))
|
adminTierLists.value = adminTierLists.value.map((tierList) => (tierList.id === updated.id ? { ...tierList, ...updated } : tierList))
|
||||||
if (previewTierList.value?.id === updated.id) previewTierList.value = { ...previewTierList.value, ...updated }
|
if (previewTierList.value?.id === updated.id) previewTierList.value = { ...previewTierList.value, ...updated }
|
||||||
modalTargetAdminTierList.value = updated
|
modalTargetAdminTierList.value = updated
|
||||||
await Promise.all([refreshAdminTierListStats(), refreshSelectedTemplateTierListStats(selectedTemplate.value?.game?.id || '')])
|
await Promise.all([refreshAdminTierListStats(), refreshSelectedTemplateTierListStats(selectedTemplate.value?.template?.id || '')])
|
||||||
success.value = '티어표 정보를 수정했어요.'
|
success.value = '티어표 정보를 수정했어요.'
|
||||||
closeAdminTierListManageModal()
|
closeAdminTierListManageModal()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -1454,7 +1455,7 @@ async function deleteAdminTierListEntry() {
|
|||||||
adminTierLists.value = adminTierLists.value.filter((tierList) => tierList.id !== modalTargetAdminTierList.value.id)
|
adminTierLists.value = adminTierLists.value.filter((tierList) => tierList.id !== modalTargetAdminTierList.value.id)
|
||||||
adminTierListTotal.value = Math.max(0, adminTierListTotal.value - 1)
|
adminTierListTotal.value = Math.max(0, adminTierListTotal.value - 1)
|
||||||
if (previewTierList.value?.id === modalTargetAdminTierList.value.id) previewTierList.value = null
|
if (previewTierList.value?.id === modalTargetAdminTierList.value.id) previewTierList.value = null
|
||||||
await Promise.all([refreshAdminTierListStats(), refreshSelectedTemplateTierListStats(selectedTemplate.value?.game?.id || '')])
|
await Promise.all([refreshAdminTierListStats(), refreshSelectedTemplateTierListStats(selectedTemplate.value?.template?.id || '')])
|
||||||
success.value = '티어표를 삭제했어요.'
|
success.value = '티어표를 삭제했어요.'
|
||||||
closeAdminTierListManageModal()
|
closeAdminTierListManageModal()
|
||||||
if (!adminTierLists.value.length && adminTierListPage.value > 1) {
|
if (!adminTierLists.value.length && adminTierListPage.value > 1) {
|
||||||
@@ -1641,7 +1642,7 @@ function templateRequestTargetLabel(request) {
|
|||||||
|
|
||||||
const displayThumbnailUrl = computed(() => {
|
const displayThumbnailUrl = computed(() => {
|
||||||
if (thumbPreviewUrl.value) return thumbPreviewUrl.value
|
if (thumbPreviewUrl.value) return thumbPreviewUrl.value
|
||||||
if (selectedTemplate.value?.game?.thumbnailSrc) return toApiUrl(selectedTemplate.value.game.thumbnailSrc)
|
if (selectedTemplate.value?.template?.thumbnailSrc) return toApiUrl(selectedTemplate.value.template.thumbnailSrc)
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1707,12 +1708,12 @@ function userAvatarFallback(user) {
|
|||||||
:staged-request-draft-count="stagedRequestDraftCount"
|
:staged-request-draft-count="stagedRequestDraftCount"
|
||||||
:applied-request-item-count="appliedRequestItemCount"
|
:applied-request-item-count="appliedRequestItemCount"
|
||||||
:open-template-create-modal="openTemplateCreateModal"
|
:open-template-create-modal="openTemplateCreateModal"
|
||||||
:is-game-loading="isGameLoading"
|
:is-template-loading="isTemplateLoading"
|
||||||
:has-selected-template="hasSelectedTemplate"
|
:has-selected-template="hasSelectedTemplate"
|
||||||
:selected-template="selectedTemplate"
|
:selected-template="selectedTemplate"
|
||||||
:display-thumbnail-url="displayThumbnailUrl"
|
:display-thumbnail-url="displayThumbnailUrl"
|
||||||
:can-apply-thumbnail="canApplyThumbnail"
|
:can-apply-thumbnail="canApplyThumbnail"
|
||||||
:game-visibility-saving="gameVisibilitySaving"
|
:template-visibility-saving="templateVisibilitySaving"
|
||||||
:thumb-file-input-ref="setThumbFileInputRef"
|
:thumb-file-input-ref="setThumbFileInputRef"
|
||||||
:open-thumb-file-picker="openThumbFilePicker"
|
:open-thumb-file-picker="openThumbFilePicker"
|
||||||
:on-thumb="onThumb"
|
:on-thumb="onThumb"
|
||||||
@@ -1739,7 +1740,7 @@ function userAvatarFallback(user) {
|
|||||||
:remove-upload-draft="removeUploadDraft"
|
:remove-upload-draft="removeUploadDraft"
|
||||||
:has-template-item-order-changes="hasTemplateItemOrderChanges"
|
:has-template-item-order-changes="hasTemplateItemOrderChanges"
|
||||||
:save-template-item-order="saveTemplateItemOrder"
|
:save-template-item-order="saveTemplateItemOrder"
|
||||||
:game-item-list-ref="setGameItemListRef"
|
:template-item-list-ref="setGameItemListRef"
|
||||||
:save-template-item-label="saveTemplateItemLabel"
|
:save-template-item-label="saveTemplateItemLabel"
|
||||||
:remove-template-item="removeTemplateItem"
|
:remove-template-item="removeTemplateItem"
|
||||||
:selected-template-id="selectedTemplateId"
|
:selected-template-id="selectedTemplateId"
|
||||||
@@ -1823,7 +1824,7 @@ function userAvatarFallback(user) {
|
|||||||
v-model="newTemplateId"
|
v-model="newTemplateId"
|
||||||
class="field__input"
|
class="field__input"
|
||||||
maxlength="120"
|
maxlength="120"
|
||||||
placeholder="game id (영문/숫자)"
|
placeholder="topic id (영문/숫자)"
|
||||||
@keydown.enter.prevent="createTemplate"
|
@keydown.enter.prevent="createTemplate"
|
||||||
/>
|
/>
|
||||||
<span class="field__hint">영문, 숫자, 하이픈 조합 권장 · {{ newTemplateId.length }}/120자</span>
|
<span class="field__hint">영문, 숫자, 하이픈 조합 권장 · {{ newTemplateId.length }}/120자</span>
|
||||||
@@ -2235,12 +2236,12 @@ function userAvatarFallback(user) {
|
|||||||
<div class="adminSidebar__group">
|
<div class="adminSidebar__group">
|
||||||
<button class="btn btn--primary" @click="openTemplateCreateModal">새 템플릿 생성</button>
|
<button class="btn btn--primary" @click="openTemplateCreateModal">새 템플릿 생성</button>
|
||||||
<button class="btn btn--ghost" @click="openTemplatePickerModal('template-admin')">템플릿 선택</button>
|
<button class="btn btn--ghost" @click="openTemplatePickerModal('template-admin')">템플릿 선택</button>
|
||||||
<div v-if="selectedTemplate?.game" class="adminSelectionCard">
|
<div v-if="selectedTemplate?.template" class="adminSelectionCard">
|
||||||
<div class="adminSelectionCard__label">선택한 템플릿</div>
|
<div class="adminSelectionCard__label">선택한 템플릿</div>
|
||||||
<div class="adminSelectionCard__title">{{ selectedTemplate.game.name }}</div>
|
<div class="adminSelectionCard__title">{{ selectedTemplate.template.name }}</div>
|
||||||
<div class="adminSelectionCard__meta">{{ selectedTemplate.game.id }}</div>
|
<div class="adminSelectionCard__meta">{{ selectedTemplate.template.id }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="selectedTemplateId && !hasSelectedTemplate && !isGameLoading" class="hint hint--tight">선택된 템플릿 ID: {{ selectedTemplateId }}</div>
|
<div v-if="selectedTemplateId && !hasSelectedTemplate && !isTemplateLoading" class="hint hint--tight">선택된 템플릿 ID: {{ selectedTemplateId }}</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -3330,7 +3331,7 @@ function userAvatarFallback(user) {
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
}
|
}
|
||||||
.adminUiScope .thumb--game {
|
.adminUiScope .thumb--template {
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
display: block;
|
display: block;
|
||||||
|
|||||||
Reference in New Issue
Block a user