Adds a new "생성되는 리소스팩 이름" admin field saved to the pack manifest and consumed by the rp installer when naming the final zip. Empty value falls back to <packKey>_musicquiz; Windows-invalid chars are sanitized to '_'. Bumps version 0.1.1 → 0.2.0 (new feature). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
230 lines
10 KiB
Plaintext
230 lines
10 KiB
Plaintext
<!doctype html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title><%= t('editor.browserTitle', { name: pack.name }) %></title>
|
|
<link rel="stylesheet" href="/static/styles.css" />
|
|
</head>
|
|
<body class="siteBody">
|
|
<%- include('../partials/navbar', { userId }) %>
|
|
|
|
<main class="pageWrap">
|
|
<section class="editorHeader">
|
|
<div>
|
|
<p class="eyebrow"><%= t('editor.eyebrow') %></p>
|
|
<h1><%= pack.name %></h1>
|
|
</div>
|
|
<a class="ghostLink" href="/op/dashboard"><%= t('common.backToList') %></a>
|
|
</section>
|
|
|
|
<form method="post" class="editorForm" id="editorForm">
|
|
<div class="gridTwo">
|
|
<label>
|
|
<span><%= t('editor.displayName') %></span>
|
|
<input name="displayName" value="<%= pack.name %>" required />
|
|
</label>
|
|
<label>
|
|
<span><%= t('editor.fileName') %></span>
|
|
<input name="fileName" value="<%= packKey %>" required pattern="[a-zA-Z0-9_\-]+" />
|
|
</label>
|
|
</div>
|
|
|
|
<div class="gridTwo">
|
|
<label>
|
|
<span><%= t('editor.mcVersion') %></span>
|
|
<select name="mcVersion" required>
|
|
<% releases.forEach(function (release) { %>
|
|
<option value="<%= release %>" <%= release === pack.mcVersion ? 'selected' : '' %>><%= release %></option>
|
|
<% }) %>
|
|
</select>
|
|
</label>
|
|
<label>
|
|
<span><%= t('editor.platformType') %></span>
|
|
<select name="platformType" id="platformType">
|
|
<% ['vanilla','forge','fabric','neoforge'].forEach(function (loader) { %>
|
|
<option value="<%= loader %>" <%= pack.platform.type === loader ? 'selected' : '' %>><%= loader %></option>
|
|
<% }) %>
|
|
</select>
|
|
</label>
|
|
<label class="fullSpan" id="platformDownloadField"<%= pack.platform.type === 'vanilla' ? ' hidden' : '' %>>
|
|
<span><%= t('editor.platformDownloadUrl') %></span>
|
|
<input name="platformDownloadUrl" value="<%= pack.platform.downloadUrl || '' %>" placeholder="/forge-installer.jar 또는 https://example.com/forge-installer.jar" />
|
|
<small class="muted"><%- t('editor.platformDownloadHint') %></small>
|
|
</label>
|
|
<label class="fullSpan" id="platformLoaderField"<%= pack.platform.type === 'fabric' ? '' : ' hidden' %>>
|
|
<span><%= t('editor.platformLoaderVersion') %></span>
|
|
<select name="platformLoaderVersion" id="platformLoaderVersion" data-current="<%= pack.platform.loaderVersion || '' %>">
|
|
<option value=""><%= t('common.loading') %></option>
|
|
</select>
|
|
<small class="muted"><%= t('editor.platformLoaderHint') %></small>
|
|
</label>
|
|
<label>
|
|
<span><%= t('editor.serverMinRam') %></span>
|
|
<input type="number" name="serverMinRam" value="<%= pack.serverMinRam %>" min="512" required />
|
|
</label>
|
|
<label>
|
|
<span><%= t('editor.serverMaxRam') %></span>
|
|
<input type="number" name="serverMaxRam" value="<%= pack.serverMaxRam %>" min="512" required />
|
|
</label>
|
|
<label>
|
|
<span><%= t('editor.clientMinRam') %></span>
|
|
<input type="number" name="clientMinRam" value="<%= pack.clientMinRam %>" min="512" required />
|
|
</label>
|
|
<label>
|
|
<span><%= t('editor.clientRecommendedRam') %></span>
|
|
<input type="number" name="clientRecommendedRam" value="<%= pack.clientRecommendedRam %>" min="512" required />
|
|
</label>
|
|
<label>
|
|
<span><%= t('editor.mapPath') %></span>
|
|
<input name="mapPath" value="<%= pack.mapPath %>" placeholder="my-map.zip" pattern=".+\.zip" />
|
|
<small class="muted"><%= t('editor.mapPathHint') %></small>
|
|
</label>
|
|
<label>
|
|
<span><%= t('editor.serverPath') %></span>
|
|
<input name="serverPath" value="<%= pack.serverPath %>" placeholder="my-server.zip" pattern=".+\.zip" />
|
|
<small class="muted"><%= t('editor.serverPathHint') %></small>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="gridTwo">
|
|
<label>
|
|
<span><%= t('editor.modsFolder') %></span>
|
|
<input name="modsFolder" value="<%= pack.modsFolder %>" placeholder="my-pack" pattern="[a-zA-Z0-9_\-]*" />
|
|
<small class="muted"><%- t('editor.modsFolderHint') %></small>
|
|
</label>
|
|
<label>
|
|
<span><%= t('editor.resourcepackPath') %></span>
|
|
<input name="resourcepackPath" value="<%= pack.resourcepackPath %>" placeholder="my-pack.zip" pattern=".*\.zip|" />
|
|
<small class="muted"><%= t('editor.resourcepackHint') %></small>
|
|
</label>
|
|
<label class="fullSpan">
|
|
<span><%= t('editor.outputPackName') %></span>
|
|
<input name="outputPackName" value="<%= pack.outputPackName %>" placeholder="<%= t('editor.outputPackNamePlaceholder') %>" />
|
|
<small class="muted"><%= t('editor.outputPackNameHint') %></small>
|
|
</label>
|
|
</div>
|
|
|
|
<button class="primaryButton" type="submit"><%= t('common.save') %></button>
|
|
</form>
|
|
</main>
|
|
|
|
<script>
|
|
var I18N = {
|
|
ramOrderInvalid: <%- JSON.stringify(t('editor.ramOrderInvalid')) %>,
|
|
fabricLoaderRequired: <%- JSON.stringify(t('editor.fabricLoaderRequired')) %>,
|
|
loaderEmpty: <%- JSON.stringify(t('editor.platformLoaderEmpty')) %>,
|
|
loaderPickMc: <%- JSON.stringify(t('editor.platformLoaderPickMc')) %>,
|
|
loaderLoadFailedPrefix: <%- JSON.stringify(t('editor.platformLoaderLoadFailed', { message: '__M__' })) %>,
|
|
loading: <%- JSON.stringify(t('common.loading')) %>
|
|
}
|
|
function formatLoaderLoadFailed(message) {
|
|
return I18N.loaderLoadFailedPrefix.replace('__M__', message)
|
|
}
|
|
</script>
|
|
<script>
|
|
(function () {
|
|
var platformSelect = document.getElementById('platformType')
|
|
var mcVersionSelect = document.querySelector('select[name="mcVersion"]')
|
|
var downloadField = document.getElementById('platformDownloadField')
|
|
var loaderField = document.getElementById('platformLoaderField')
|
|
var loaderSelect = document.getElementById('platformLoaderVersion')
|
|
var currentLoader = loaderSelect.getAttribute('data-current') || ''
|
|
var loaderCache = {} // mcVersion -> [loader versions]
|
|
var loaderFetchSeq = 0
|
|
|
|
function syncPlatformVisibility() {
|
|
var type = platformSelect.value
|
|
if (type === 'fabric') {
|
|
downloadField.removeAttribute('hidden')
|
|
loaderField.removeAttribute('hidden')
|
|
loadFabricLoaders()
|
|
} else if (type === 'vanilla') {
|
|
downloadField.setAttribute('hidden', '')
|
|
loaderField.setAttribute('hidden', '')
|
|
downloadField.querySelector('input').value = ''
|
|
loaderSelect.innerHTML = '<option value=""></option>'
|
|
} else {
|
|
downloadField.removeAttribute('hidden')
|
|
loaderField.setAttribute('hidden', '')
|
|
loaderSelect.innerHTML = '<option value=""></option>'
|
|
}
|
|
}
|
|
|
|
function populateLoaderOptions(versions, preselect) {
|
|
if (!versions || versions.length === 0) {
|
|
loaderSelect.innerHTML = '<option value="">' + I18N.loaderEmpty + '</option>'
|
|
return
|
|
}
|
|
var html = ''
|
|
for (var i = 0; i < versions.length; i++) {
|
|
var v = versions[i]
|
|
var sel = v.version === preselect ? ' selected' : ''
|
|
var label = v.version + (v.stable ? '' : ' (beta)')
|
|
html += '<option value="' + v.version + '"' + sel + '>' + label + '</option>'
|
|
}
|
|
loaderSelect.innerHTML = html
|
|
// 사용자가 저장해둔 값이 목록에 없으면 첫 번째(최신) 자동 선택.
|
|
if (preselect && !versions.some(function (v) { return v.version === preselect })) {
|
|
loaderSelect.value = versions[0].version
|
|
}
|
|
}
|
|
|
|
function loadFabricLoaders() {
|
|
var mc = (mcVersionSelect && mcVersionSelect.value) || ''
|
|
if (!mc) {
|
|
loaderSelect.innerHTML = '<option value="">' + I18N.loaderPickMc + '</option>'
|
|
return
|
|
}
|
|
if (loaderCache[mc]) {
|
|
populateLoaderOptions(loaderCache[mc], currentLoader)
|
|
return
|
|
}
|
|
var seq = ++loaderFetchSeq
|
|
loaderSelect.innerHTML = '<option value="">' + I18N.loading + '</option>'
|
|
fetch('https://meta.fabricmc.net/v2/versions/loader/' + encodeURIComponent(mc))
|
|
.then(function (res) {
|
|
if (!res.ok) throw new Error('HTTP ' + res.status)
|
|
return res.json()
|
|
})
|
|
.then(function (list) {
|
|
if (seq !== loaderFetchSeq) return // 더 새로운 요청이 들어왔으면 무시
|
|
// 응답: [{ loader: { version, stable, ... }, intermediary: {...} }, ...]
|
|
var versions = (list || []).map(function (item) {
|
|
return { version: item.loader.version, stable: !!item.loader.stable }
|
|
})
|
|
loaderCache[mc] = versions
|
|
populateLoaderOptions(versions, currentLoader)
|
|
})
|
|
.catch(function (err) {
|
|
if (seq !== loaderFetchSeq) return
|
|
var msg = (err && err.message) ? err.message : String(err)
|
|
loaderSelect.innerHTML = '<option value="">' + formatLoaderLoadFailed(msg) + '</option>'
|
|
})
|
|
}
|
|
|
|
platformSelect.addEventListener('change', syncPlatformVisibility)
|
|
if (mcVersionSelect) mcVersionSelect.addEventListener('change', function () {
|
|
if (platformSelect.value === 'fabric') loadFabricLoaders()
|
|
})
|
|
syncPlatformVisibility()
|
|
|
|
var form = document.getElementById('editorForm')
|
|
form.addEventListener('submit', function (event) {
|
|
var clientMin = Number(form.clientMinRam.value)
|
|
var clientReco = Number(form.clientRecommendedRam.value)
|
|
if (clientMin > clientReco) {
|
|
event.preventDefault()
|
|
alert(I18N.ramOrderInvalid)
|
|
return
|
|
}
|
|
if (platformSelect.value === 'fabric' && !loaderSelect.value) {
|
|
event.preventDefault()
|
|
alert(I18N.fabricLoaderRequired)
|
|
}
|
|
})
|
|
})()
|
|
</script>
|
|
</body>
|
|
</html>
|