Commit Graph

9 Commits

Author SHA1 Message Date
7ac07a58ef Stable drop-preview drag + image captions
Drag-and-drop UX rewrite:
- The old "highlight target row + margin-grow animation" approach was
  driven by per-row dragenter/dragleave events. Those fire in a noisy
  enter/leave/enter cascade as the pointer crosses sub-elements and as
  the row itself grows under the pointer, which is why the gap was
  pulsing open/closed.
- New approach: a single container-level dragover handler. On dragstart
  the source row is briefly cloned into a translucent "placeholder"
  element (dashed outline, 45% opacity, pointer-events:none) inserted in
  the source's slot; the original is then hidden (display:none) right
  after dragstart so the browser can still capture it as the drag image.
  As the cursor moves over the container we compute which sibling's
  midpoint the pointer just crossed and insertBefore the placeholder
  accordingly. The list length stays constant the whole time, so there
  is no growing/shrinking gap to fight with — what the user sees is the
  dragged item itself shown semi-transparently at the exact drop slot.
  On drop, splice the array using the placeholder's index among the
  non-source children, then re-render.
- The bindContainerDnd helper handles both lists; image grid uses
  vertical Y math (same midpoint rule as the track list since cards
  flow row-by-row in the auto-fill grid). attachDraggable now only sets
  up dragstart/dragend/contextmenu per row; no more dragenter/dragleave.

Image grid:
- Image cards now have a caption below the thumbnail. When the same
  URL appears in the music list, the music entry's title/artist are
  borrowed via captionForImage(url); otherwise "(제목 없음)" muted text.
  Layout changed from a square aspect-ratio card to a flex column:
  .imgWrap holds the square thumbnail, .cardCaption sits underneath
  with single-line title + smaller muted artist line.

CSS cleanup:
- Drop the old .dropAbove margin-grow rules and .dragOver border rule
  on .trackRow/.imageCard. Replaced with .dragPlaceholder +
  .hiddenWhileDragging.
- .imageCard no longer uses aspect-ratio on itself; aspect lives on
  .imgWrap so caption can extend the card vertically.
2026-05-12 13:51:33 +09:00
7316477e23 Inline edit, URL-driven meta refresh, drag gap animation, copy-from-music
Music list tab:
- Title + artist are now contenteditable in-place. Typing updates state on
  every input event; Enter blurs (no embedded newlines), and on Save the
  current DOM text is re-synced into state.music[i].title/.artist before
  the JSON POST so a quick-save right after typing doesn't drop the last
  keystroke. While focused, the field is highlighted with the accent
  outline and unclamped so long titles wrap. Empty cells show a muted
  placeholder via :empty::before { content: attr(data-placeholder) }.
- Edit modal "save" now POSTs the new URL to /op/list/<pack>/video-meta
  (new endpoint backed by yt-dlp --dump-json --no-playlist) and patches
  state.music[i] with the returned title/channel/durationSec. If yt-dlp
  is unavailable or the lookup fails, the modal asks the user whether to
  apply just the URL change without metadata.
- Drag-and-drop UX: replaced the simple "highlight target" feedback with a
  "gap opens above the target" animation. The hovered row gets a
  .dropAbove class that animates margin-top to 64px via CSS transition,
  visually carving out the slot the dragged item will land in. Insertion
  math is now strictly "drop before the hover target" (with the
  srcIdx<dstIdx → dstIdx-1 adjustment after the splice removal), matching
  what the gap implies. Contenteditable spans no longer hijack drag start
  — on focus the parent <li>.draggable is flipped off so the user can
  freely select text, and back on at blur.

Image list tab:
- New "음악목록에서 가져오기" button. Copies state.music[*].url into
  state.images, which (via the existing thumbUrl() helper) renders each
  song's YouTube thumbnail as the image. Behind a confirm prompt because
  it replaces the entire image list.
- Same drag gap animation (.dropAbove → margin-left 80px) applied to the
  grid cards for consistency.

Server:
- youtube.ts: add fetchVideoMeta(url) using the same ensureYtDlp() path
  (auto-installed binary under %appdata%/.mc_custom). Returns one
  YtPlaylistEntry or null.
- routes/op.ts: POST /op/list/:packName/video-meta. 400 on missing URL,
  503 NO_YTDLP if the auto-install failed, 500 on other yt-dlp errors,
  200 { ok: true, entry } otherwise.

Smoke test (Rick Astley URL) returns
title=Rick Astley - Never Gonna..., channel=Rick Astley, durationSec=213.
2026-05-12 13:38:29 +09:00
a532ce5507 Fix op page UI: button underline, tab panel duplication, header spacing
- /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.
2026-05-12 13:11:44 +09:00
a2817c921d Add /op/list, /op/list/:pack, /op/datapack web admin + spec lock
docs/add.md
 - 사진 PNG 규격을 1024×1024 (4×4 블록 슬롯 × ×16 배율) 로 못박음
 - 짧은 변 기준 가운데 정사각 크롭 + 1024 초과 시만 축소, 미만은 native 유지

신규 라우트 (모두 requireAuth):
 - GET  /op/list                          → manifest 카드 목록
 - GET  /op/list/:pack                    → 음악목록·사진목록 탭 편집기
 - POST /op/list/:pack                    → file/list/<pack>.json 저장 (JSON)
 - POST /op/list/:pack/playlist           → yt-dlp 로 플레이리스트 펼치기
 - GET  /op/datapack                      → 음악퀴즈 선택 + 출력
 - GET  /op/datapack/:pack/generate       → 임시 포맷 mcfunction 텍스트

shared/types.ts: PackList / MusicListEntry / ImageListEntry
shared/store.ts: loadPackList, savePackList, normalizePackList
shared/paths.ts: fileListDirPath = file/list/
server/youtube.ts: yt-dlp 플레이리스트 펼치기 (--flat-playlist --dump-json),
  설치 안 됐을 때 NO_YTDLP 코드로 503.

UI:
 - views/op/list.ejs: 가로 카드 목록 + 돌아가기 버튼
 - views/op/listEditor.ejs + public/listEditor.js: 탭 전환, 드래그 정렬,
   우클릭 컨텍스트 메뉴(수정/삭제), 사진 수정 모달의 [유튜브 / 이미지] 토글,
   목록 저장·초기화·플레이리스트 불러오기 확인 팝업
 - views/op/datapack.ejs: 음악퀴즈 카드 선택 팝업 → 데이터팩 출력 + 복사
 - views/op/dashboard.ejs: 상단에 [음악목록 수정] [데이터팩 수정] 버튼
 - public/styles.css: 탭, 트랙 로우, 이미지 그리드, 컨텍스트 메뉴, 모달, 코드블록

.gitignore: conversations/ 추가.

스모크: login → /op/list 렌더, POST 저장 라운드트립 OK,
/op/datapack/.../generate 텍스트 출력 OK, 플레이리스트 fetch는 yt-dlp 미설치
환경에서 503 NO_YTDLP 메시지 정상.

Section 1 (리소스팩 설치기 EXE: yt-dlp 음악 다운로드, painting variant
텍스처 패키징, 리소스팩 zip 배치) 은 후속 커밋에서 작업.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 11:38:30 +09:00
8fd7cfaaef Build music-quiz installer and management site per spec
Implements the full spec described in README.md:

Management site (Node + TypeScript + Express + EJS):
- Public main page lists packs registered in manifest.json.
- /op login (account.json, internal-only), /op/dashboard manages packs
  with horizontal-scroll cards, add/select-and-delete flow, and the
  /op/dashboard/:packName editor (Mojang release dropdown, dynamic
  mods/resourcepacks lists, platform/RAM fields, file rename).
- Routes for /manifest.json (public) and /file/* (server pack files).
- Middleware blocks /account.json and /manifest/* directory access.

Installer (Electron):
- Five page renderer driven by IPC (preload contextBridge API):
  pack pick → single/multi → server install (path no-Korean check, JDK
  detect, file download, EULA, RAM gating, local web config editor,
  UPnP/port-forward check) → client install (.mc_custom mods +
  resourcepacks + launcher_profiles.json gameDir/javaArgs) → finish
  toggles (server folder, shortcut, server start, launcher start).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 21:34:27 +09:00
cd79378f3c Reset repository to README title only
Approach is changing entirely; clearing prior implementation
to start over from a clean slate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 20:41:19 +09:00
4453dbd8f3 Add client apply flow and asset uploads 2026-05-08 20:03:07 +09:00
5ff4e20b5e Align dashboard and installer flows with spec 2026-05-08 19:02:50 +09:00
af6e559682 Build installer and management site from spec 2026-05-07 23:22:34 +09:00