Replace distribution JSON editor with form UI
This commit is contained in:
@@ -28,7 +28,7 @@ npm run admin
|
|||||||
```
|
```
|
||||||
|
|
||||||
- 기본 주소: `http://127.0.0.1:8787`
|
- 기본 주소: `http://127.0.0.1:8787`
|
||||||
- `distribution.json` 업로드 / 새로 만들기 / 직접 편집 가능
|
- `distribution.json` 업로드 / 새로 만들기 / 입력 폼 편집 가능
|
||||||
- 월드 ZIP 업로드 가능
|
- 월드 ZIP 업로드 가능
|
||||||
- 서버용 버킷 JAR 업로드 가능
|
- 서버용 버킷 JAR 업로드 가능
|
||||||
- 서버 메모리, 최대 인원수, 화이트리스트, 포트 설정 가능
|
- 서버 메모리, 최대 인원수, 화이트리스트, 포트 설정 가능
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ const state = {
|
|||||||
},
|
},
|
||||||
meta: null,
|
meta: null,
|
||||||
selectedProfileId: null,
|
selectedProfileId: null,
|
||||||
dirty: false
|
dirty: false,
|
||||||
|
distributionEditor: {
|
||||||
|
document: null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileList = document.getElementById('profileList')
|
const profileList = document.getElementById('profileList')
|
||||||
@@ -26,7 +29,9 @@ const createDistributionButton = document.getElementById('createDistributionButt
|
|||||||
const distributionEditorModal = document.getElementById('distributionEditorModal')
|
const distributionEditorModal = document.getElementById('distributionEditorModal')
|
||||||
const distributionEditorHint = document.getElementById('distributionEditorHint')
|
const distributionEditorHint = document.getElementById('distributionEditorHint')
|
||||||
const distributionEditorStatus = document.getElementById('distributionEditorStatus')
|
const distributionEditorStatus = document.getElementById('distributionEditorStatus')
|
||||||
const distributionEditorTextarea = document.getElementById('distributionEditorTextarea')
|
const distributionEditorForm = document.getElementById('distributionEditorForm')
|
||||||
|
const distributionModuleCount = document.getElementById('distributionModuleCount')
|
||||||
|
const distributionAdditionalServerCount = document.getElementById('distributionAdditionalServerCount')
|
||||||
const closeDistributionEditorButton = document.getElementById('closeDistributionEditorButton')
|
const closeDistributionEditorButton = document.getElementById('closeDistributionEditorButton')
|
||||||
const loadDistributionTemplateButton = document.getElementById('loadDistributionTemplateButton')
|
const loadDistributionTemplateButton = document.getElementById('loadDistributionTemplateButton')
|
||||||
const saveDistributionFileButton = document.getElementById('saveDistributionFileButton')
|
const saveDistributionFileButton = document.getElementById('saveDistributionFileButton')
|
||||||
@@ -49,6 +54,18 @@ const fieldElements = {
|
|||||||
serverWhitelistEnabled: document.getElementById('field-serverWhitelistEnabled')
|
serverWhitelistEnabled: document.getElementById('field-serverWhitelistEnabled')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const distributionFieldElements = {
|
||||||
|
version: document.getElementById('distribution-field-version'),
|
||||||
|
rss: document.getElementById('distribution-field-rss'),
|
||||||
|
serverId: document.getElementById('distribution-field-serverId'),
|
||||||
|
serverName: document.getElementById('distribution-field-serverName'),
|
||||||
|
serverDescription: document.getElementById('distribution-field-serverDescription'),
|
||||||
|
serverVersion: document.getElementById('distribution-field-serverVersion'),
|
||||||
|
minecraftVersion: document.getElementById('distribution-field-minecraftVersion'),
|
||||||
|
mainServer: document.getElementById('distribution-field-mainServer'),
|
||||||
|
autoconnect: document.getElementById('distribution-field-autoconnect')
|
||||||
|
}
|
||||||
|
|
||||||
function slugify(value){
|
function slugify(value){
|
||||||
return String(value ?? '')
|
return String(value ?? '')
|
||||||
.trim()
|
.trim()
|
||||||
@@ -61,6 +78,94 @@ function isRemoteUrl(value){
|
|||||||
return /^https?:\/\//i.test(String(value ?? '').trim())
|
return /^https?:\/\//i.test(String(value ?? '').trim())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deepClone(value){
|
||||||
|
return value == null ? value : JSON.parse(JSON.stringify(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDistributionTemplateProfileDefaults(profile){
|
||||||
|
const safeId = slugify(profile?.id || profile?.name || 'example-profile') || 'example-profile'
|
||||||
|
return {
|
||||||
|
version: '1.0.0',
|
||||||
|
rss: '',
|
||||||
|
servers: [
|
||||||
|
{
|
||||||
|
id: safeId,
|
||||||
|
name: profile?.name || '새 프로필',
|
||||||
|
description: profile?.description || '관리자 사이트에서 만든 distribution 템플릿입니다.',
|
||||||
|
version: '1.0.0',
|
||||||
|
minecraftVersion: '1.20.1',
|
||||||
|
mainServer: true,
|
||||||
|
autoconnect: false,
|
||||||
|
modules: []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeDistributionDocument(rawDocument, profile){
|
||||||
|
const baseDocument = deepClone(rawDocument) ?? {}
|
||||||
|
const defaultDocument = getDistributionTemplateProfileDefaults(profile)
|
||||||
|
const servers = Array.isArray(baseDocument.servers) && baseDocument.servers.length > 0
|
||||||
|
? baseDocument.servers
|
||||||
|
: defaultDocument.servers
|
||||||
|
const primaryServer = {
|
||||||
|
...defaultDocument.servers[0],
|
||||||
|
...(servers[0] ?? {})
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDocument.version = typeof baseDocument.version === 'string' && baseDocument.version.trim().length > 0
|
||||||
|
? baseDocument.version.trim()
|
||||||
|
: defaultDocument.version
|
||||||
|
baseDocument.rss = typeof baseDocument.rss === 'string' ? baseDocument.rss.trim() : ''
|
||||||
|
baseDocument.servers = [primaryServer, ...servers.slice(1)]
|
||||||
|
|
||||||
|
return baseDocument
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateDistributionEditorForm(documentData, profile){
|
||||||
|
const normalized = normalizeDistributionDocument(documentData, profile)
|
||||||
|
const primaryServer = normalized.servers[0]
|
||||||
|
|
||||||
|
state.distributionEditor.document = normalized
|
||||||
|
distributionFieldElements.version.value = normalized.version ?? ''
|
||||||
|
distributionFieldElements.rss.value = normalized.rss ?? ''
|
||||||
|
distributionFieldElements.serverId.value = primaryServer.id ?? ''
|
||||||
|
distributionFieldElements.serverName.value = primaryServer.name ?? ''
|
||||||
|
distributionFieldElements.serverDescription.value = primaryServer.description ?? ''
|
||||||
|
distributionFieldElements.serverVersion.value = primaryServer.version ?? ''
|
||||||
|
distributionFieldElements.minecraftVersion.value = primaryServer.minecraftVersion ?? ''
|
||||||
|
distributionFieldElements.mainServer.checked = primaryServer.mainServer !== false
|
||||||
|
distributionFieldElements.autoconnect.checked = primaryServer.autoconnect === true
|
||||||
|
|
||||||
|
const moduleCount = Array.isArray(primaryServer.modules) ? primaryServer.modules.length : 0
|
||||||
|
const additionalServerCount = Math.max(0, normalized.servers.length - 1)
|
||||||
|
distributionModuleCount.textContent = `${moduleCount}개`
|
||||||
|
distributionAdditionalServerCount.textContent = `${additionalServerCount}개`
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDistributionDocumentFromForm(profile){
|
||||||
|
const baseDocument = normalizeDistributionDocument(state.distributionEditor.document, profile)
|
||||||
|
const primaryServer = {
|
||||||
|
...baseDocument.servers[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
baseDocument.version = distributionFieldElements.version.value.trim() || '1.0.0'
|
||||||
|
baseDocument.rss = distributionFieldElements.rss.value.trim()
|
||||||
|
|
||||||
|
primaryServer.id = distributionFieldElements.serverId.value.trim() || slugify(profile?.id || profile?.name || 'example-profile') || 'example-profile'
|
||||||
|
primaryServer.name = distributionFieldElements.serverName.value.trim() || profile?.name || '새 프로필'
|
||||||
|
primaryServer.description = distributionFieldElements.serverDescription.value.trim()
|
||||||
|
primaryServer.version = distributionFieldElements.serverVersion.value.trim() || '1.0.0'
|
||||||
|
primaryServer.minecraftVersion = distributionFieldElements.minecraftVersion.value.trim() || '1.20.1'
|
||||||
|
primaryServer.mainServer = distributionFieldElements.mainServer.checked
|
||||||
|
primaryServer.autoconnect = distributionFieldElements.autoconnect.checked
|
||||||
|
primaryServer.modules = Array.isArray(primaryServer.modules) ? primaryServer.modules : []
|
||||||
|
|
||||||
|
baseDocument.servers = [primaryServer, ...baseDocument.servers.slice(1)]
|
||||||
|
state.distributionEditor.document = baseDocument
|
||||||
|
return baseDocument
|
||||||
|
}
|
||||||
|
|
||||||
function createProfile(){
|
function createProfile(){
|
||||||
const timestamp = Date.now()
|
const timestamp = Date.now()
|
||||||
return {
|
return {
|
||||||
@@ -415,7 +520,7 @@ async function loadDistributionTemplate(){
|
|||||||
throw new Error(result.message || 'distribution 샘플을 불러오지 못했습니다.')
|
throw new Error(result.message || 'distribution 샘플을 불러오지 못했습니다.')
|
||||||
}
|
}
|
||||||
|
|
||||||
distributionEditorTextarea.value = result.content
|
return JSON.parse(result.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadDistributionContent(requestedPath){
|
async function loadDistributionContent(requestedPath){
|
||||||
@@ -425,7 +530,7 @@ async function loadDistributionContent(requestedPath){
|
|||||||
throw new Error(result.message || 'distribution 파일을 불러오지 못했습니다.')
|
throw new Error(result.message || 'distribution 파일을 불러오지 못했습니다.')
|
||||||
}
|
}
|
||||||
|
|
||||||
distributionEditorTextarea.value = result.content
|
return JSON.parse(result.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openDistributionEditor(mode){
|
async function openDistributionEditor(mode){
|
||||||
@@ -436,7 +541,8 @@ async function openDistributionEditor(mode){
|
|||||||
}
|
}
|
||||||
|
|
||||||
openDistributionEditorModal()
|
openDistributionEditorModal()
|
||||||
distributionEditorTextarea.value = ''
|
distributionEditorForm.reset()
|
||||||
|
state.distributionEditor.document = null
|
||||||
updateDistributionEditorHint(profile)
|
updateDistributionEditorHint(profile)
|
||||||
showDistributionEditorStatus('distribution 내용을 준비하는 중...', 'info')
|
showDistributionEditorStatus('distribution 내용을 준비하는 중...', 'info')
|
||||||
|
|
||||||
@@ -444,7 +550,8 @@ async function openDistributionEditor(mode){
|
|||||||
const currentPath = String(profile.distributionUrl ?? '').trim()
|
const currentPath = String(profile.distributionUrl ?? '').trim()
|
||||||
|
|
||||||
if(mode === 'create' || currentPath.length === 0){
|
if(mode === 'create' || currentPath.length === 0){
|
||||||
await loadDistributionTemplate()
|
const templateDocument = await loadDistributionTemplate()
|
||||||
|
populateDistributionEditorForm(templateDocument, profile)
|
||||||
updateDistributionEditorHint(profile, '')
|
updateDistributionEditorHint(profile, '')
|
||||||
showStatus('distribution 템플릿을 불러왔습니다.', 'success')
|
showStatus('distribution 템플릿을 불러왔습니다.', 'success')
|
||||||
showDistributionEditorStatus('샘플을 불러왔습니다. 바로 수정한 뒤 저장하면 됩니다.', 'success')
|
showDistributionEditorStatus('샘플을 불러왔습니다. 바로 수정한 뒤 저장하면 됩니다.', 'success')
|
||||||
@@ -452,7 +559,8 @@ async function openDistributionEditor(mode){
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(isRemoteUrl(currentPath)){
|
if(isRemoteUrl(currentPath)){
|
||||||
await loadDistributionContent(currentPath)
|
const remoteDocument = await loadDistributionContent(currentPath)
|
||||||
|
populateDistributionEditorForm(remoteDocument, profile)
|
||||||
updateDistributionEditorHint(profile, currentPath)
|
updateDistributionEditorHint(profile, currentPath)
|
||||||
showStatus('원격 distribution 내용을 불러왔습니다.', 'success')
|
showStatus('원격 distribution 내용을 불러왔습니다.', 'success')
|
||||||
showDistributionEditorStatus('원격 distribution 내용을 불러왔습니다. 저장하면 로컬 파일로 복사됩니다.', 'success')
|
showDistributionEditorStatus('원격 distribution 내용을 불러왔습니다. 저장하면 로컬 파일로 복사됩니다.', 'success')
|
||||||
@@ -460,7 +568,8 @@ async function openDistributionEditor(mode){
|
|||||||
}
|
}
|
||||||
|
|
||||||
showStatus('distribution 파일을 불러오는 중...', 'info')
|
showStatus('distribution 파일을 불러오는 중...', 'info')
|
||||||
await loadDistributionContent(currentPath)
|
const localDocument = await loadDistributionContent(currentPath)
|
||||||
|
populateDistributionEditorForm(localDocument, profile)
|
||||||
updateDistributionEditorHint(profile, currentPath)
|
updateDistributionEditorHint(profile, currentPath)
|
||||||
showStatus('distribution 파일을 불러왔습니다.', 'success')
|
showStatus('distribution 파일을 불러왔습니다.', 'success')
|
||||||
showDistributionEditorStatus('현재 연결된 distribution 파일을 불러왔습니다.', 'success')
|
showDistributionEditorStatus('현재 연결된 distribution 파일을 불러왔습니다.', 'success')
|
||||||
@@ -479,6 +588,7 @@ async function saveDistributionFile(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const distributionDocument = buildDistributionDocumentFromForm(profile)
|
||||||
saveDistributionFileButton.disabled = true
|
saveDistributionFileButton.disabled = true
|
||||||
showStatus('distribution 파일 저장 중...', 'info')
|
showStatus('distribution 파일 저장 중...', 'info')
|
||||||
showDistributionEditorStatus('distribution 파일 저장 중...', 'info')
|
showDistributionEditorStatus('distribution 파일 저장 중...', 'info')
|
||||||
@@ -490,7 +600,7 @@ async function saveDistributionFile(){
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
profileId: profile.id,
|
profileId: profile.id,
|
||||||
content: distributionEditorTextarea.value
|
content: JSON.stringify(distributionDocument, null, 2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
const result = await response.json()
|
const result = await response.json()
|
||||||
@@ -515,6 +625,10 @@ async function saveDistributionFile(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function bindDistributionEditor(){
|
function bindDistributionEditor(){
|
||||||
|
distributionEditorForm.addEventListener('submit', (event) => {
|
||||||
|
event.preventDefault()
|
||||||
|
})
|
||||||
|
|
||||||
distributionEditorModal.addEventListener('click', (event) => {
|
distributionEditorModal.addEventListener('click', (event) => {
|
||||||
if(event.target === distributionEditorModal){
|
if(event.target === distributionEditorModal){
|
||||||
closeDistributionEditorModal()
|
closeDistributionEditorModal()
|
||||||
@@ -538,7 +652,8 @@ window.__launcherAdminCloseDistributionEditor = () => {
|
|||||||
|
|
||||||
window.__launcherAdminLoadDistributionTemplate = async () => {
|
window.__launcherAdminLoadDistributionTemplate = async () => {
|
||||||
try {
|
try {
|
||||||
await loadDistributionTemplate()
|
const templateDocument = await loadDistributionTemplate()
|
||||||
|
populateDistributionEditorForm(templateDocument, getSelectedProfile())
|
||||||
updateDistributionEditorHint(getSelectedProfile(), '')
|
updateDistributionEditorHint(getSelectedProfile(), '')
|
||||||
showStatus('distribution 템플릿을 다시 불러왔습니다.', 'success')
|
showStatus('distribution 템플릿을 다시 불러왔습니다.', 'success')
|
||||||
showDistributionEditorStatus('샘플을 다시 불러왔습니다.', 'success')
|
showDistributionEditorStatus('샘플을 다시 불러왔습니다.', 'success')
|
||||||
|
|||||||
@@ -116,12 +116,12 @@
|
|||||||
<div class="uploadField">
|
<div class="uploadField">
|
||||||
<input id="field-distributionUrl" type="text" autocomplete="off">
|
<input id="field-distributionUrl" type="text" autocomplete="off">
|
||||||
<button type="button" class="secondaryAction uploadButton" data-upload-target="distributionUrl" data-upload-accept=".json,application/json">JSON 업로드</button>
|
<button type="button" class="secondaryAction uploadButton" data-upload-target="distributionUrl" data-upload-accept=".json,application/json">JSON 업로드</button>
|
||||||
<button type="button" id="editDistributionButton" class="secondaryAction" onclick="window.__launcherAdminOpenDistributionEditor && window.__launcherAdminOpenDistributionEditor('edit')">JSON 편집</button>
|
<button type="button" id="editDistributionButton" class="secondaryAction" onclick="window.__launcherAdminOpenDistributionEditor && window.__launcherAdminOpenDistributionEditor('edit')">폼 편집</button>
|
||||||
<button type="button" id="createDistributionButton" class="secondaryAction" onclick="window.__launcherAdminOpenDistributionEditor && window.__launcherAdminOpenDistributionEditor('create')">새로 만들기</button>
|
<button type="button" id="createDistributionButton" class="secondaryAction" onclick="window.__launcherAdminOpenDistributionEditor && window.__launcherAdminOpenDistributionEditor('create')">새로 만들기</button>
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</label>
|
||||||
<div class="fieldHelpText fieldBlockFull">
|
<div class="fieldHelpText fieldBlockFull">
|
||||||
distribution.json은 여기서 직접 업로드하거나 JSON 편집기로 새로 만들 수 있습니다.
|
distribution.json은 여기서 직접 업로드하거나, 아래 편집기에서 설명을 보며 입력 폼으로 만들 수 있습니다.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -195,12 +195,88 @@
|
|||||||
<div>
|
<div>
|
||||||
<span class="eyebrow">Distribution Editor</span>
|
<span class="eyebrow">Distribution Editor</span>
|
||||||
<h3>distribution.json 편집</h3>
|
<h3>distribution.json 편집</h3>
|
||||||
<p id="distributionEditorHint">프로필에 연결할 distribution.json 내용을 사이트 안에서 직접 관리합니다.</p>
|
<p id="distributionEditorHint">프로필에 연결할 distribution.json 내용을 사이트 안에서 입력 폼으로 관리합니다.</p>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" id="closeDistributionEditorButton" class="secondaryAction" onclick="window.__launcherAdminCloseDistributionEditor && window.__launcherAdminCloseDistributionEditor()">닫기</button>
|
<button type="button" id="closeDistributionEditorButton" class="secondaryAction" onclick="window.__launcherAdminCloseDistributionEditor && window.__launcherAdminCloseDistributionEditor()">닫기</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="distributionEditorStatus" class="statusBanner" hidden></div>
|
<div id="distributionEditorStatus" class="statusBanner" hidden></div>
|
||||||
<textarea id="distributionEditorTextarea" class="distributionTextarea" spellcheck="false"></textarea>
|
<div id="distributionEditorSummary" class="fieldSection distributionSummary">
|
||||||
|
<div class="sectionHeader">
|
||||||
|
<h3>현재 보존되는 고급 정보</h3>
|
||||||
|
</div>
|
||||||
|
<div class="fieldGrid">
|
||||||
|
<div class="fieldBlock">
|
||||||
|
<span>모듈 수</span>
|
||||||
|
<div id="distributionModuleCount" class="distributionSummaryValue">0개</div>
|
||||||
|
</div>
|
||||||
|
<div class="fieldBlock">
|
||||||
|
<span>추가 서버 수</span>
|
||||||
|
<div id="distributionAdditionalServerCount" class="distributionSummaryValue">0개</div>
|
||||||
|
</div>
|
||||||
|
<div class="fieldHelpText fieldBlockFull">
|
||||||
|
이 편집기는 첫 번째 서버의 기본 정보만 수정합니다. 기존 모듈 목록과 추가 서버 정보는 저장할 때 그대로 보존됩니다.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<form id="distributionEditorForm" class="editorForm distributionEditorForm">
|
||||||
|
<section class="fieldSection">
|
||||||
|
<div class="sectionHeader">
|
||||||
|
<h3>기본 정보</h3>
|
||||||
|
</div>
|
||||||
|
<div class="fieldGrid">
|
||||||
|
<label class="fieldBlock">
|
||||||
|
<span>distribution 버전</span>
|
||||||
|
<input id="distribution-field-version" type="text" autocomplete="off" placeholder="1.0.0">
|
||||||
|
<div class="fieldHelpText">distribution 파일 자체 버전입니다.</div>
|
||||||
|
</label>
|
||||||
|
<label class="fieldBlock">
|
||||||
|
<span>뉴스 RSS 주소</span>
|
||||||
|
<input id="distribution-field-rss" type="text" autocomplete="off" placeholder="https://example.com/rss.xml">
|
||||||
|
<div class="fieldHelpText">선택값입니다. 비워두면 뉴스 피드를 사용하지 않습니다.</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="fieldSection">
|
||||||
|
<div class="sectionHeader">
|
||||||
|
<h3>서버 기본 정보</h3>
|
||||||
|
</div>
|
||||||
|
<div class="fieldGrid">
|
||||||
|
<label class="fieldBlock">
|
||||||
|
<span>서버 ID</span>
|
||||||
|
<input id="distribution-field-serverId" type="text" autocomplete="off" placeholder="my-profile">
|
||||||
|
<div class="fieldHelpText">영문 식별자입니다. 보통 프로필 ID와 비슷하게 맞춥니다.</div>
|
||||||
|
</label>
|
||||||
|
<label class="fieldBlock">
|
||||||
|
<span>서버 이름</span>
|
||||||
|
<input id="distribution-field-serverName" type="text" autocomplete="off" placeholder="My Profile">
|
||||||
|
<div class="fieldHelpText">런처 안에 표시될 이름입니다.</div>
|
||||||
|
</label>
|
||||||
|
<label class="fieldBlock fieldBlockFull">
|
||||||
|
<span>서버 설명</span>
|
||||||
|
<textarea id="distribution-field-serverDescription" rows="4" placeholder="이 distribution이 어떤 클라이언트인지 설명합니다."></textarea>
|
||||||
|
<div class="fieldHelpText">런처와 설치 흐름에서 사용할 설명입니다.</div>
|
||||||
|
</label>
|
||||||
|
<label class="fieldBlock">
|
||||||
|
<span>배포 버전</span>
|
||||||
|
<input id="distribution-field-serverVersion" type="text" autocomplete="off" placeholder="1.0.0">
|
||||||
|
<div class="fieldHelpText">이 프로필의 배포 버전 표기입니다.</div>
|
||||||
|
</label>
|
||||||
|
<label class="fieldBlock">
|
||||||
|
<span>마인크래프트 버전</span>
|
||||||
|
<input id="distribution-field-minecraftVersion" type="text" autocomplete="off" placeholder="1.20.1">
|
||||||
|
<div class="fieldHelpText">실제 마크 버전입니다.</div>
|
||||||
|
</label>
|
||||||
|
<label class="toggleBlock">
|
||||||
|
<input id="distribution-field-mainServer" type="checkbox">
|
||||||
|
<span>기본 서버로 사용</span>
|
||||||
|
</label>
|
||||||
|
<label class="toggleBlock">
|
||||||
|
<input id="distribution-field-autoconnect" type="checkbox">
|
||||||
|
<span>자동 접속 사용</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</form>
|
||||||
<div class="modalActions">
|
<div class="modalActions">
|
||||||
<button type="button" id="loadDistributionTemplateButton" class="secondaryAction" onclick="window.__launcherAdminLoadDistributionTemplate && window.__launcherAdminLoadDistributionTemplate()">샘플 불러오기</button>
|
<button type="button" id="loadDistributionTemplateButton" class="secondaryAction" onclick="window.__launcherAdminLoadDistributionTemplate && window.__launcherAdminLoadDistributionTemplate()">샘플 불러오기</button>
|
||||||
<button type="button" id="saveDistributionFileButton" class="primaryAction" onclick="window.__launcherAdminSaveDistributionFile && window.__launcherAdminSaveDistributionFile()">distribution 저장</button>
|
<button type="button" id="saveDistributionFileButton" class="primaryAction" onclick="window.__launcherAdminSaveDistributionFile && window.__launcherAdminSaveDistributionFile()">distribution 저장</button>
|
||||||
|
|||||||
@@ -376,12 +376,30 @@ textarea {
|
|||||||
background: #111412;
|
background: #111412;
|
||||||
box-shadow: var(--shadow);
|
box-shadow: var(--shadow);
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#distributionEditorStatus {
|
#distributionEditorStatus {
|
||||||
margin-top: -4px;
|
margin-top: -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.distributionSummary {
|
||||||
|
padding-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.distributionSummaryValue {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||||
|
border-radius: 14px;
|
||||||
|
background: var(--panel-strong);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.distributionEditorForm {
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.modalHeader {
|
.modalHeader {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ npm run admin
|
|||||||
|
|
||||||
- 프로필 추가 / 수정 / 삭제 / 복제
|
- 프로필 추가 / 수정 / 삭제 / 복제
|
||||||
- `맵` 기본 + `모드`, `플러그인`, `서버` 체크 조합
|
- `맵` 기본 + `모드`, `플러그인`, `서버` 체크 조합
|
||||||
- `distribution.json` 업로드 / 새로 만들기 / 직접 편집
|
- `distribution.json` 업로드 / 새로 만들기 / 입력 폼 편집
|
||||||
- 월드 ZIP 업로드
|
- 월드 ZIP 업로드
|
||||||
- 서버용 버킷 JAR 업로드
|
- 서버용 버킷 JAR 업로드
|
||||||
- 서버 포트 / 메모리 / 최대 인원수 / 화이트리스트 설정
|
- 서버 포트 / 메모리 / 최대 인원수 / 화이트리스트 설정
|
||||||
@@ -32,8 +32,10 @@ npm run admin
|
|||||||
## distribution 편집
|
## distribution 편집
|
||||||
|
|
||||||
- `distribution 파일` 칸에서 JSON 업로드 가능
|
- `distribution 파일` 칸에서 JSON 업로드 가능
|
||||||
- `JSON 편집` 버튼으로 현재 연결된 로컬 `distribution.json` 수정 가능
|
- `폼 편집` 버튼으로 현재 연결된 `distribution.json`을 입력 폼으로 수정 가능
|
||||||
- `새로 만들기` 버튼으로 샘플 템플릿에서 새 `distribution.json` 생성 가능
|
- `새로 만들기` 버튼으로 샘플 템플릿에서 새 `distribution.json` 생성 가능
|
||||||
|
- 편집기에서는 버전, RSS, 서버 이름, 마인크래프트 버전 같은 기본 정보를 설명과 함께 입력합니다.
|
||||||
|
- 기존 모듈 목록과 추가 서버 정보는 저장 시 그대로 보존됩니다.
|
||||||
- 저장된 distribution 파일은 아래에 생성됩니다.
|
- 저장된 distribution 파일은 아래에 생성됩니다.
|
||||||
- `admin/data/distributions/`
|
- `admin/data/distributions/`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user