import {createUniverseHome} from './universe-home.js?v=universe-home-1'; import {createUniverseLibrary} from './universe-library.js?v=universe-home-1'; import {readOrbitRoute,formatRoute,UNIVERSE_ROUTE} from './orbit-route.js?v=universe-home-1'; const $=id=>document.getElementById(id); let orbit=null,orbitFlight=null,routeGeneration=0,summary=null,pendingSavedId=null; let fullDataDirty=false,refreshFlight=null; const home=createUniverseHome({container:$('universeHome'),onOpenFormat:format=>navigate(formatRoute(format)),onWorlds:()=>void showWorlds(),onDiscover:()=>$('discoverNav').click(),onOtherFormats:formats=>showOtherFormats(formats)}); home.update({view:'loading',works:null}); const library=createUniverseLibrary({onUpdate:payload=>{summary=payload;home.update(payload);$('workCount').textContent=Array.isArray(payload.works)?payload.works.length+' SAVED':'';if(payload.unauthorized){fullDataDirty=true;orbit?.suspendOrbit();$('worldsMenu').hidden=true;}else if(orbit&&Number(payload.updatedAt)>orbit.libraryUpdatedAt())fullDataDirty=true;},onStatus:(message,state)=>{if(document.body.dataset.view==='universe'){$('syncStatus').textContent=message;$('syncStatus').dataset.state=state;}}}); function navigate(hash){if(location.hash===hash)void renderRoute();else location.hash=hash;} async function getOrbit(){ if(orbit)return orbit;if(orbitFlight)return orbitFlight; orbitFlight=import('./orbit-app.js?v=orbit-cleanup-1').then(async module=>{ orbit=module; await orbit.initializeOrbitApp({onHome:({keepDetail=false}={})=>{if(keepDetail){routeGeneration++;history.pushState(null,'',UNIVERSE_ROUTE);applyMode({kind:'universe'});}else navigate(UNIVERSE_ROUTE);},onOpenFormat:(format,{alreadyActivated=false}={})=>{ const hash=formatRoute(format);if(alreadyActivated){routeGeneration++;if(location.hash!==hash)history.pushState(null,'',hash);applyMode({kind:'orbit',format});$('sceneStatus').textContent=$('field').dataset.workCount+' SAVED WORKS';}else navigate(hash); },onSummary:payload=>{if(payload.unauthorized)library.clearSession(payload.error);else if(!summary||Number(payload.updatedAt)>=Number(summary.updatedAt||0))library.accept(payload);}}); return orbit; }).catch(error=>{orbit=null;orbitFlight=null;throw error;}); return orbitFlight; } function applyMode(route){ const isHome=route.kind==='universe';document.body.dataset.view=isHome?'universe':'orbit'; $('universeHome').hidden=!isHome;$('orbitBreadcrumb').hidden=isHome; $('universeNav').setAttribute('aria-current',isHome?'page':'false'); $('orbitFormat').textContent=(route.format||'UNSORTED').toUpperCase(); $('worldsMenu').hidden=true;$('sceneStatus').textContent=''; // The sole sync control moves with the screen; no duplicate listener/button. (isHome?$('mastSync'):document.querySelector('.console')).append($('syncControl')); document.title=isHome?'MEDIA ORBIT — Your universe':(route.format||'Unsorted')+' ORBIT — MEDIA ORBIT'; } async function renderRoute(){ const generation=++routeGeneration,route=readOrbitRoute(location.hash);applyMode(route); if(route.kind==='universe'){ orbit?.suspendOrbit();if(summary){home.update(summary);if(Array.isArray(summary.works)){$('syncStatus').textContent=(summary.stale?'保存済みデータ · 古い可能性があります · ':'NOTION SYNC · ')+summary.works.length+' WORKS';$('syncStatus').dataset.state=summary.stale?'offline':'ready';}}void library.refresh(false);return; } $('sceneStatus').textContent='作品を読み込んでいます…'; try { const app=await getOrbit();if(generation!==routeGeneration)return; if(fullDataDirty){await app.refreshFullLibrary(true);fullDataDirty=Number(summary?.updatedAt||0)>app.libraryUpdatedAt();if(generation!==routeGeneration)return;} await app.openFormat(route.format);if(generation!==routeGeneration){if(document.body.dataset.view==='universe')app.suspendOrbit();return;} $('sceneStatus').textContent=$('field').dataset.workCount+' SAVED WORKS'; }catch(error){if(generation===routeGeneration){$('sceneStatus').textContent=error.message+' · ↻で再試行';$('syncStatus').dataset.state='error';}} } async function showWorlds(){ const generation=routeGeneration; $('sceneStatus').textContent='WORLDを読み込んでいます…'; try{const app=await getOrbit();if(generation!==routeGeneration||document.body.dataset.view!=='universe')return;if(fullDataDirty){await app.refreshFullLibrary(true);fullDataDirty=Number(summary?.updatedAt||0)>app.libraryUpdatedAt();}if(generation!==routeGeneration||document.body.dataset.view!=='universe')return;await app.openHomeWorlds();if(generation!==routeGeneration)$('worldsMenu').hidden=true;} catch(error){$('sceneStatus').textContent=error.message;return;} $('sceneStatus').textContent=''; } function showOtherFormats(formats){ const box=$('worldsMenu');box.replaceChildren();box.hidden=false; const title=document.createElement('h2');title.textContent='OTHER FORMATS';const note=document.createElement('p');note.className='muted';note.textContent='Notionの分類をそのまま表示しています。'; const close=document.createElement('button');close.className='drawer-close';close.textContent='×';close.setAttribute('aria-label','その他のFormatを閉じる');close.onclick=()=>box.hidden=true;box.append(title,close,note); for(const item of formats||[]){const button=document.createElement('button');button.className='home-world-row';button.textContent=(item.label||item.format||'UNSORTED')+' · '+(item.saved??item.count??item.works?.length??0)+' SAVED';button.onclick=()=>navigate(formatRoute(item.format==='Unknown format'?'':(item.format??item.key??'')));box.append(button);} } async function refresh(force=false){ if(refreshFlight)return refreshFlight;$('refresh').disabled=true; refreshFlight=(async()=>{ if(pendingSavedId){const app=await getOrbit();while(pendingSavedId){const id=pendingSavedId;await app.refreshFullLibrary(true);const followed=await app.followSavedWork(id);if(pendingSavedId!==id)continue;if(followed)pendingSavedId=null;else{$('syncStatus').textContent='保存済み · Notionの一覧への反映を待っています · ↻で再確認';$('syncStatus').dataset.state='offline';break;}}} else if(document.body.dataset.view==='orbit'||!$('drawer').hidden||!$('worldsMenu').hidden&&orbit){const app=await getOrbit();await app.refreshFullLibrary(force);fullDataDirty=Number(summary?.updatedAt||0)>app.libraryUpdatedAt();} else await library.refresh(force); })().catch(error=>{$('syncStatus').textContent=error.message;$('syncStatus').dataset.state='error';}).finally(()=>{refreshFlight=null;$('refresh').disabled=false;}); return refreshFlight; } $('refresh').addEventListener('click',()=>void refresh(true)); $('home').onclick=()=>navigate(UNIVERSE_ROUTE); window.addEventListener('hashchange',()=>void renderRoute());window.addEventListener('popstate',()=>void renderRoute()); window.addEventListener('mediaorbit:refresh',()=>void refresh(true)); window.addEventListener('mediaorbit:work-saved',e=>{pendingSavedId=e.detail.pageId;void refresh(true);}); async function onReturn(){ if(document.hidden)return; void refresh(false); } window.addEventListener('focus',()=>void onReturn());document.addEventListener('visibilitychange',()=>void onReturn()); let refreshInterval=null; function startRefreshInterval(){if(refreshInterval===null)refreshInterval=setInterval(()=>{if(!document.hidden)void refresh(false);},5*60*1000);} startRefreshInterval(); window.addEventListener('pagehide',event=>{clearInterval(refreshInterval);refreshInterval=null;orbit?.suspendOrbit();if(!event.persisted)home.dispose();}); window.addEventListener('pageshow',event=>{if(event.persisted){startRefreshInterval();void renderRoute();}}); void renderRoute();