PageSourceSearch

https://fairus-stepaudio-3-music.hf.space/studio/assets/js/main.js

js fairus-stepaudio-3-music.hf.space collected 2026-10-03 16:51:23 UTC 5,346 bytes, 133 lines download raw bytes

1/* =============================================================================
2   main.js · 启动、hash 路由、语言切换、侧栏、音符粒子
3   -----------------------------------------------------------------------------
4   用 hash 路由而不是 History API:GitHub Pages 无法配置 rewrite,
5   History 路由在直接访问子路径时会 404。
6   ============================================================================= */
7
8import { t, getLang, setLang, onLangChange } from './i18n.js';
9import { applyI18n, initNotes, toast } from './ui.js';
10import { initPlayer } from './player.js';
11import { renderHome, renderListen, renderCreate, renderLibrary, markCurrent,
12         applyHandoff } from './pages.js';
13import { prefs } from './store.js';
14import { openApiKeyDialog, onKeyChange } from './apikey.js';
15import { canGenerate, needsUserKey } from './config.js';
16
17const $ = id => document.getElementById(id);
18
19const ROUTES = {
20  home:    { view: 'view-home',    render: renderHome,    once: false },
21  listen:  { view: 'view-listen',  render: renderListen,  once: true  },
22  create:  { view: 'view-create',  render: renderCreate,  once: true  },
23  library: { view: 'view-library', render: renderLibrary, once: false },
24};
25const rendered = new Set();
26let current = null;
27
28function routeName() {
29  const h = (location.hash || '#/').replace(/^#\/?/, '').split('?')[0];
30  return ROUTES[h] ? h : 'home';
31}
32
33function navigate() {
34  const name = routeName();
35  const r = ROUTES[name];
36
37  for (const [k, v] of Object.entries(ROUTES)) {
38    $(v.view).classList.toggle('on', k === name);
39  }
40  document.querySelectorAll('.nav-item').forEach(a => {
41    if (a.dataset.route === name) a.setAttribute('aria-current', 'page');
42    else a.removeAttribute('aria-current');
43  });
44
45  if (!r.once || !rendered.has(name)) {
46    r.render();
47    rendered.add(name);
48  } else {
49    markCurrent();
50    // 创作页是 once:true(保留会话任务与已上传音频),但从首页带过来的
51    // 创意每次都要生效,所以单独调一次。
52    if (name === 'create') applyHandoff();
53  }
54
55  document.title = name === 'home'
56    ? t('app.title')
57    : `${t('nav.' + name)} · StepAudio-3-Music`;
58
59  // 切页回到顶部,但不要打断正在播放的音频
60  if (current !== name) $('main').scrollTo({ top: 0, behavior: 'instant' });
61  current = name;
62}
63
64/* ── 侧栏收起 ──────────────────────────────────────────────────────────── */
65function applyNav() {
66  const mini = prefs.navMini;
67  $('app').dataset.nav = mini ? 'mini' : 'full';
68  $('navToggleIcon').setAttribute('href', mini ? '#i-panel-open' : '#i-panel-close');
69  const btn = $('navToggle');
70  btn.dataset.tipI18n = mini ? 'nav.expand' : 'nav.collapse';
71  btn.querySelector('.nav-label').textContent = t(mini ? 'nav.expand' : 'nav.collapse');
72  btn.dataset.tip = t(mini ? 'nav.expand' : 'nav.collapse');
73}
74
75/* ── API Key 状态点 ─────────────────────────────────────────────────────────
76   侧栏那个小圆点:没配 Key 时亮琥珀色,让访客一眼看到还差一步。
77   proxy 模式下额度由平台供给,不需要访客配 Key,就别显示了。 */
78function applyKeyDot() {
79  const dot = $('navKeyDot');
80  if (!dot) return;
81  const need = needsUserKey() && !canGenerate();
82  dot.hidden = !need;
83  dot.classList.toggle('warn', need);
84}
85
86/* ── 语言 ──────────────────────────────────────────────────────────────── */
87function applyLang() {
88  const lang = getLang();
89  document.documentElement.lang = lang === 'zh' ? 'zh-CN' : 'en';
90  document.querySelectorAll('.lang-switch button').forEach(b => {
91    b.setAttribute('aria-pressed', String(b.dataset.lang === lang));
92  });
93  applyI18n(document);
94  applyNav();
95  applyKeyDot();
96}
97
98/* ── 启动 ──────────────────────────────────────────────────────────────── */
99function boot() {
100  applyLang();
101  initPlayer();
102  initNotes();
103
104  $('navToggle').onclick = () => { prefs.navMini = !prefs.navMini; applyNav(); };
105  $('navApiKey').onclick = openApiKeyDialog;
106  onKeyChange(applyKeyDot);
107  applyKeyDot();
108  document.querySelectorAll('.lang-switch button').forEach(b => {
109    b.onclick = () => setLang(b.dataset.lang);
110  });
111
112  // 占位链接:真实地址待定,点了给个明确反馈而不是静默跳 #
113  document.querySelectorAll('[data-placeholder-link]').forEach(a => {
114    a.onclick = e => { e.preventDefault(); toast(getLang() === 'zh' ? '链接待补充' : 'Link coming soon'); };
115  });
116
117  onLangChange(() => {
118    applyLang();
119    rendered.clear();           // 语言变了,页面内容要重建
120    navigate();
121  });
122
123  addEventListener('hashchange', navigate);
124  navigate();
125
126  addEventListener('offline', () => toast(t('err.offline')));
127}
128
129if (document.readyState === 'loading') {
130  document.addEventListener('DOMContentLoaded', boot, { once: true });
131} else {
132  boot();
133}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.