- /op/dashboard: remove underline from <a class="secondaryButton"> by adding
text-decoration:none + inline-flex centering on .primaryButton/.secondaryButton/
.dangerButton, plus margin-left:auto on .dashboardActions so the buttons stick
to the right side of the row even when the header is laid out as flex+wrap.
- /op/list/<pack>: fix the duplicate "save/clear + playlist URL" UI showing in
the active tab. .tabPanel had a baseline `display: block;` that overrode the
browser default `[hidden] { display: none }`. Add an explicit
`.tabPanel[hidden] { display: none !important }` rule so the inactive panel
actually hides.
- /op/list, /op/list/<pack>, /op/datapack: bump the gap between the "돌아가기"
ghost link and the page title from 8px to 20px.
- docs/yt-dlp-setup.md: install guide (single-binary curl + pipx + apt) since
the server doesn't have pip/pipx/yt-dlp installed. Server keeps the graceful
"수동 입력으로 진행" fallback when yt-dlp is missing.
122 lines
5.0 KiB
Plaintext
122 lines
5.0 KiB
Plaintext
<!doctype html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||
<title>데이터팩 수정</title>
|
||
<link rel="stylesheet" href="/static/styles.css" />
|
||
</head>
|
||
<body class="siteBody">
|
||
<%- include('../partials/navbar', { userId }) %>
|
||
|
||
<main class="pageWrap">
|
||
<section class="dashboardHeader">
|
||
<div>
|
||
<a class="ghostLink" href="/op/dashboard">← 돌아가기</a>
|
||
<h1 style="margin-top:20px;">데이터팩 수정</h1>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="dpControls">
|
||
<button type="button" class="primaryButton" id="pickPackBtn">음악퀴즈 선택</button>
|
||
<span class="muted" id="pickedLabel">선택된 음악퀴즈 없음</span>
|
||
</section>
|
||
|
||
<p class="muted" id="countLabel"></p>
|
||
|
||
<section class="dpActions" hidden id="dpActions">
|
||
<button type="button" class="secondaryButton" id="exportBtn">데이터팩 출력</button>
|
||
<button type="button" class="secondaryButton" id="copyBtn">복사</button>
|
||
<span class="statusText" id="dp-status"></span>
|
||
</section>
|
||
|
||
<pre class="codeBlock" id="codeOut" hidden></pre>
|
||
</main>
|
||
|
||
<!-- 음악퀴즈 선택 팝업 -->
|
||
<div class="modalOverlay" id="pickModal" hidden>
|
||
<div class="modalCard">
|
||
<header><h3>음악퀴즈 선택</h3>
|
||
<button class="modalClose" type="button" data-modal-close>×</button>
|
||
</header>
|
||
<div class="modalBody">
|
||
<div class="cardRow horizontalScroll" id="pickList">
|
||
<% items.forEach(function (item) { %>
|
||
<article class="packCard pickable" data-key="<%= item.key %>" data-name="<%= item.definition ? item.definition.name : item.key %>">
|
||
<h2><%= item.definition ? item.definition.name : item.key %></h2>
|
||
<p class="muted"><%= item.key %>.json</p>
|
||
<% if (item.definition) { %>
|
||
<ul class="metaList">
|
||
<li>MC <%= item.definition.mcVersion %></li>
|
||
<li>플랫폼 <%= item.definition.platform.type %></li>
|
||
</ul>
|
||
<% } %>
|
||
</article>
|
||
<% }) %>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
(function () {
|
||
var pickModal = document.getElementById('pickModal')
|
||
var pickedKey = ''
|
||
document.getElementById('pickPackBtn').addEventListener('click', function () {
|
||
pickModal.hidden = false
|
||
})
|
||
document.querySelectorAll('[data-modal-close]').forEach(function (b) {
|
||
b.addEventListener('click', function () { pickModal.hidden = true })
|
||
})
|
||
pickModal.addEventListener('click', function (e) {
|
||
if (e.target === pickModal) pickModal.hidden = true
|
||
})
|
||
document.querySelectorAll('#pickList .pickable').forEach(function (card) {
|
||
card.addEventListener('click', function () {
|
||
pickedKey = card.getAttribute('data-key')
|
||
var name = card.getAttribute('data-name')
|
||
document.getElementById('pickedLabel').textContent = '선택: ' + name
|
||
pickModal.hidden = true
|
||
document.getElementById('dpActions').hidden = false
|
||
// 곡 수 미리 가져오기
|
||
fetch('/op/list/' + encodeURIComponent(pickedKey)).catch(function () {})
|
||
// 더 직접적으로: generate 호출 시점에 카운트도 나옴. 일단 비워둠.
|
||
document.getElementById('countLabel').textContent = ''
|
||
document.getElementById('codeOut').hidden = true
|
||
})
|
||
})
|
||
document.getElementById('exportBtn').addEventListener('click', function () {
|
||
if (!pickedKey) return
|
||
var s = document.getElementById('dp-status')
|
||
s.textContent = '출력 중…'; s.classList.remove('error')
|
||
fetch('/op/datapack/' + encodeURIComponent(pickedKey) + '/generate')
|
||
.then(function (r) { return r.text().then(function (t) { return { ok: r.ok, text: t } }) })
|
||
.then(function (res) {
|
||
if (!res.ok) {
|
||
s.textContent = '실패: ' + res.text; s.classList.add('error')
|
||
return
|
||
}
|
||
var out = document.getElementById('codeOut')
|
||
out.textContent = res.text
|
||
out.hidden = false
|
||
// 첫줄/둘째줄에서 카운트 가져와 표기
|
||
var m = res.text.match(/총\s+(\d+)곡/)
|
||
if (m) document.getElementById('countLabel').textContent = '총 ' + m[1] + '개의 음악을 찾았습니다.'
|
||
s.textContent = '출력 완료'
|
||
})
|
||
.catch(function (err) { s.textContent = '실패: ' + err.message; s.classList.add('error') })
|
||
})
|
||
document.getElementById('copyBtn').addEventListener('click', function () {
|
||
var out = document.getElementById('codeOut')
|
||
if (out.hidden) return
|
||
navigator.clipboard.writeText(out.textContent).then(function () {
|
||
var s = document.getElementById('dp-status')
|
||
s.textContent = '복사됨'
|
||
s.classList.remove('error')
|
||
})
|
||
})
|
||
})()
|
||
</script>
|
||
</body>
|
||
</html>
|