Back to Napcraft
SOURCE / PINNED RELEASE

Made of little things.

Napcraft

Release
ee47ac6a25df…
Author-recorded commit
410dea87e109…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-52f9e22f5ce

Archive hash verified: 9e6e0cabb18d0d46…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/directory-ui.ts
import { identity } from '@napplet/sdk';
import { openWorldDirectory, distinctWorlds, listingId, type ListedWorld, type ListingChange, type WorldDirectory } from './world-directory';

let connection: Promise<WorldDirectory> | undefined;
function directory() {
  return connection ??= openWorldDirectory().catch(error=>{connection=undefined;throw error;});
}
function message(error: unknown) {
  const text = error instanceof Error ? error.message : String(error);
  if (/app-data-unavailable|app-data-no-relays/.test(text))
    return 'Public worlds are unavailable on this host. You can still create, practice or join with a code.';
  return text;
}

export function mountWorldList(root: HTMLElement, join: (code:string)=>Promise<void>, viewer: string) {
  root.innerHTML='<div class="directory-heading"><h2>Public worlds</h2><button class="directory-refresh" type="button">↻ Refresh</button></div><p class="directory-caption">Pick an island and make yourself at home. Listed worlds stay here when everyone leaves.</p><label class="sr-only" for="worldSearch">Search loaded worlds</label><input id="worldSearch" type="search" placeholder="Find an island…" maxlength="80"/><p class="directory-status" role="status" aria-live="polite"></p><div class="world-list"></div>';
  const refresh=root.querySelector<HTMLButtonElement>('button')!, search=root.querySelector<HTMLInputElement>('input')!;
  const status=root.querySelector<HTMLElement>('.directory-status')!, rows=root.querySelector<HTMLElement>('.world-list')!;
  let records:ListedWorld[]=[], loaded=false, note='', request=0, joining=false;
  const render=()=>{
    rows.replaceChildren();
    const filtered=records.filter(r=>r.title.toLocaleLowerCase().includes(search.value.trim().toLocaleLowerCase()));
    for(const record of filtered) {
      const row=document.createElement('article');row.className='world-row';
      const emblem=document.createElement('span');emblem.className='world-emblem';emblem.textContent='▧';emblem.setAttribute('aria-hidden','true');
      const copy=document.createElement('div');copy.className='world-copy';
      const title=document.createElement('strong');title.textContent=record.title;
      const subtitle=document.createElement('span');subtitle.textContent=record.author===viewer?'Shared by you':`Shared by ${record.author.slice(0,8)}…`;
      copy.append(title,subtitle);
      const button=document.createElement('button');button.className='world-join primary';button.textContent='Join';button.setAttribute('aria-label',`Join ${record.title}`);
      button.disabled=joining;
      button.onclick=async()=>{
        if(joining)return;joining=true;button.textContent='Joining…';
        root.querySelectorAll<HTMLButtonElement>('.world-join').forEach(b=>b.disabled=true);
        try{await join(record.data!.code)}finally{joining=false;if(root.isConnected)render()}
      };row.append(emblem,copy,button);rows.append(row);
    }
    status.textContent=note || (!loaded?'Looking for islands…':!records.length?'No public worlds yet. Create one, then choose Invite → List publicly.':!filtered.length?'No matching islands in the loaded list.':`${filtered.length} recently listed ${filtered.length===1?'world':'worlds'} · anyone can join and build`);
  };
  const load=async()=>{
    const current=++request;refresh.disabled=true;note='Looking for islands…';render();
    try {
      const result=await(await directory()).list({limit:40});
      if(!root.isConnected||current!==request)return;
      records=distinctWorlds(result.records,viewer);loaded=true;
      note=[result.incomplete?'Some listings could not be checked. Refresh to try again.':'',result.invalid?`${result.invalid} invalid or unsupported listings hidden.`:'',result.nextUntil?'Showing a recent selection, not every public world.':''].filter(Boolean).join(' ');
    }catch(error){if(!root.isConnected||current!==request)return;note=`${message(error)}${records.length?' Showing earlier results.':''}`;}
    finally{if(root.isConnected&&current===request){refresh.disabled=false;render();}}
  };
  search.oninput=render;refresh.onclick=()=>void load();void load();
}

export function mountListingControl(root: HTMLElement, code: string, title: string) {
  root.innerHTML='<h3>Make room for more builders</h3><p>List this island on Napcraft’s opening screen. Anyone can join and edit, even after you leave. Only list a world you want to share widely.</p><button id="listWorld" class="wide" disabled>Checking your listing…</button><p class="listing-status footnote" role="status" aria-live="polite"></p>';
  const button=root.querySelector<HTMLButtonElement>('button')!, status=root.querySelector<HTMLElement>('.listing-status')!;
  const load=async()=>{
    let prepared:ListingChange|undefined;
    button.disabled=true;
    try {
      const author=await identity.getPublicKey();
      if(!root.isConnected)return;
      if(!author){button.textContent='Sign in to list publicly';status.textContent='Connect your account in the host. Browsing and joining need no sign-in.';return;}
      const collection=await directory(), id=await listingId(code), lookup=await collection.get(author,id);
      if(!root.isConnected)return;
      if(lookup.incomplete)throw new Error('Could not fully check your listing. Retry before changing it.');
      const listed=Boolean(lookup.record&&!lookup.record.deleted);
      button.textContent=listed?'Remove my listing':'List publicly';button.disabled=false;
      status.textContent=listed?'Your listing stays available when the world is empty. Removing it keeps the world and its code intact. Others may have shared their own listing.':'Listing is a separate public announcement. Your world is already saved.';
      button.onclick=async()=>{
        if(button.disabled)return;
        button.disabled=true;button.textContent='Waiting for approval…';status.textContent='Approve the public listing change in your host.';
        try {
          // Retry the same prepared template after an uncertain publication.
          // Reopening this panel first checks the stable record's current revision.
          prepared ??= await collection.prepare({id,title:title.replace(/[\u0000-\u001f\u007f\u202a-\u202e\u2066-\u2069]/g,'').trim()||'Shared island',base:lookup.record,data:{code},deleted:listed});
          await prepared.publish();
          if(!root.isConnected)return;
          prepared=undefined;await load();
        }catch(error){if(root.isConnected){status.textContent=`${message(error)} Your world is still saved. Retry this listing change or reopen Invite to check its current status.`;button.textContent=listed?'Retry removing listing':'Retry listing';button.disabled=false;}}
      };
    }catch(error){if(root.isConnected){status.textContent=message(error);button.textContent='Retry checking listing';button.disabled=false;button.onclick=()=>void load();}}
  };
  void load();
}