Commit Graph

5 Commits

Author SHA1 Message Date
c2fcc2fbbf i18n: 서버 측 모든 UI 문구를 locales/server/ko-kr.json 으로 분리
- src/shared/i18n.ts: 공용 i18n 로더 (dotted-key + {{placeholder}} 보간)
- locales/server/ko-kr.json: 사이트 + 라우터 + 데이터팩 출력 사전
- EJS 뷰는 res.locals.t 미들웨어로 일괄 적용
- listEditor.js 등 클라이언트 JS 는 사전을 inline <script> 로 주입받아 tt() 헬퍼 사용
2026-05-13 03:43:04 +09:00
635c22c7ad Migrate tsconfig to Node16 module/moduleResolution
VS Code surfaced the TS deprecation:
  'moduleResolution=node10' is deprecated and won't work in TS 7.0.

Fix: switch the root tsconfig.json from
  module: CommonJS, moduleResolution: node
to
  module: Node16, moduleResolution: Node16

TypeScript 7 only supports node16/nodenext/bundler. node16 matches the
runtime semantics we already use (Node ≥ 16, CommonJS output via the
absence of "type": "module" in package.json), so the emit is unchanged.

Side effect of Node16 resolution: relative imports must carry the .js
extension. Added .js to every relative import across src/* (17 sites,
8 files). Bare module specifiers (express, electron, node:fs, ...) are
unaffected.

Verified:
- tsc -p tsconfig.server.json — 0 errors
- tsc -p tsconfig.installer.json — 0 errors
- node dist/server/app.js boots; /op login → 302, /op/list → 200
2026-05-12 13:41:58 +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
7349d4e71e Auto-install yt-dlp into %appdata%/.mc_custom on first use
The previous flow required the operator to manually install yt-dlp on the
server. Now the server downloads the correct binary for the current OS/arch
from GitHub Releases (latest) the first time a playlist fetch is requested,
caches it under %appdata%/.mc_custom (resolved per-OS), chmod +x's it on
POSIX, and skips the download on every subsequent call.

- shared/paths.ts: add getAppDataDir() + getMcCustomDir() that map %appdata%
  to ~/.config (Linux), ~/Library/Application Support (macOS), or
  process.env.APPDATA (Windows) so the path is OS-agnostic.
- server/youtube.ts: replace the old "probe PATH and bail" probeYtDlp with
  ensureYtDlp(): picks the right GitHub asset name per platform.arch
  (yt-dlp.exe / yt-dlp_macos / yt-dlp_linux / yt-dlp_linux_aarch64 /
  yt-dlp_linux_armv7l), downloads it with redirect-following https.get to
  the install path, chmods +x, then verifies with `--version` before
  reporting success. Uses an installPromise singleton so concurrent
  requests don't race the download. On any failure it cleans up the
  partial file and throws YtDlpUnavailableError with a real reason.
- docs/yt-dlp-setup.md: note that auto-install is now the default; manual
  install is only for environments where the auto-download fails.

Smoke-tested on this Linux x64 box: first call downloads to
~/.config/.mc_custom/yt-dlp_linux and reports `2026.03.17`; second call
takes ~700ms (just the version probe) and reuses the cached file.
2026-05-12 13:15:25 +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