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.
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&¤t===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();
}
