@@ -155,6 +155,7 @@ <h4 class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-500 dark
155155
156156 < div id ="modal " class ="hidden fixed inset-0 z-40 grid place-items-center bg-black/50 p-6 backdrop-blur-sm "> </ div >
157157 < div id ="submitModal " class ="hidden fixed inset-0 z-40 grid place-items-center bg-black/50 p-6 backdrop-blur-sm "> </ div >
158+ < div id ="getAppModal " class ="hidden fixed inset-0 z-50 grid place-items-center bg-black/50 p-6 backdrop-blur-sm "> </ div >
158159 < div id ="toast " class ="pointer-events-none fixed bottom-6 left-1/2 z-50 -translate-x-1/2 rounded-2xl border border-gray-200 bg-white px-5 py-3 text-sm opacity-0 shadow-lg transition dark:border-white/10 dark:bg-basisbg "> </ div >
159160
160161< script >
@@ -465,11 +466,11 @@ <h2 class="flex flex-wrap items-center gap-2 text-xl font-bold">${esc(p.name)} <
465466 ${ p . unity ?`<span>Unity <b class="text-gray-900 dark:text-white">${ esc ( p . unity ) } </b></span>` :'' }
466467 ${ p . updated ?`<span>Updated <b class="text-gray-900 dark:text-white">${ esc ( p . updated ) } </b></span>` :'' }
467468 </div>
468- ${ p . dependencies ? ` <div><p class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Dependencies</p><div class="flex flex-wrap gap-1.5"> ${ Object . entries ( p . dependencies ) . map ( ( [ k , v ] ) => `<span class="rounded-full bg-gray-100 px-2 py-0.5 text-[11px] text-gray-600 dark:bg-white/5 dark:text-gray-300"> ${ esc ( k ) } ${ esc ( v ) } </span>` ) . join ( '' ) } </div ></div>` : '' }
469+ <div id="depSlot" data-for=" ${ esc ( p . id ) } " ></div>
469470 <div>
470471 <p class="mb-3 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">How to install</p>
471472 <a href="basispm://install?id=${ encodeURIComponent ( p . id ) } &name=${ encodeURIComponent ( p . name ) } &git=${ encodeURIComponent ( p . gitUrl || '' ) } &repo=${ encodeURIComponent ( p . repoUrl || '' ) } "
472- class="mb-3 flex w-full items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-brand to-purple-600 px-5 py-4 text-base font-semibold text-white shadow hover:shadow-lg"
473+ class="deeplink mb-3 flex w-full items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-brand to-purple-600 px-5 py-4 text-base font-semibold text-white shadow hover:shadow-lg"
473474 title="Opens the Basis Package Manager app and adds this package to your active project. Requires the app installed.">
474475 <svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12m0 0 4-4m-4 4-4-4"/><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/></svg>
475476 Install in the Basis Package Manager
@@ -487,6 +488,29 @@ <h2 class="flex flex-wrap items-center gap-2 text-xl font-bold">${esc(p.name)} <
487488 </div>` ;
488489 m . classList . remove ( 'hidden' ) ;
489490 m . querySelectorAll ( '.copy' ) . forEach ( b => b . onclick = e => { e . stopPropagation ( ) ; copy ( b . dataset . copy , b ) ; } ) ;
491+ m . querySelectorAll ( '.deeplink' ) . forEach ( a => a . addEventListener ( 'click' , onDeepLinkClick ) ) ;
492+ renderDeps ( p ) ;
493+ }
494+
495+ // Dependencies aren't in the catalog — they live in each package's package.json. Fetch it once
496+ // the modal is open and fill in the slot. The isConnected guard means a slow fetch from a package
497+ // you've since closed (or swapped for another) can't paint into the wrong modal.
498+ async function renderDeps ( p ) {
499+ const slot = document . getElementById ( 'depSlot' ) ;
500+ if ( ! slot ) return ;
501+ const paint = deps => {
502+ if ( ! slot . isConnected ) return ;
503+ const entries = deps ? Object . entries ( deps ) : [ ] ;
504+ slot . innerHTML = entries . length
505+ ? `<div><p class="mb-2 text-xs font-semibold uppercase tracking-wide text-gray-500 dark:text-gray-400">Dependencies</p><div class="flex flex-wrap gap-1.5">${ entries . map ( ( [ k , v ] ) => `<span class="rounded-full bg-gray-100 px-2 py-0.5 text-[11px] text-gray-600 dark:bg-white/5 dark:text-gray-300">${ esc ( k ) } ${ esc ( v ) } </span>` ) . join ( '' ) } </div></div>`
506+ : '' ;
507+ } ;
508+ // Already recorded in the catalog? Use that and skip the network.
509+ if ( p . dependencies && Object . keys ( p . dependencies ) . length ) { paint ( p . dependencies ) ; return ; }
510+ slot . innerHTML = `<p class="text-xs text-gray-400">Loading dependencies…</p>` ;
511+ let pj = null ;
512+ try { pj = await fetchPackageJson ( p . repoUrl , p . gitUrl ) ; } catch { }
513+ paint ( pj && pj . dependencies ) ;
490514}
491515
492516function packageListCard ( b ) {
@@ -575,7 +599,7 @@ <h2 class="text-xl font-bold">${esc(b.name)}</h2>
575599 <p class="mb-2 text-sm text-gray-600 dark:text-gray-300">Clone a fresh Basis${ branch ?` (<b class="text-gray-900 dark:text-white">${ branch } </b>)` :'' } and open it in Unity, then install:</p>
576600 <div class="relative"><pre class="whitespace-pre-wrap break-all rounded-xl border border-gray-200 bg-gray-50 p-3 pr-16 font-mono text-xs text-gray-800 dark:border-white/10 dark:bg-black/30 dark:text-gray-200">${ esc ( cloneCmd ) } </pre><button class="copy absolute right-2 top-2 rounded-lg border border-gray-200 px-2 py-1 text-xs hover:border-brand hover:text-brand dark:border-white/10" data-copy="${ esc ( cloneCmd ) } ">Copy</button></div>
577601 </div>
578- <a href="${ esc ( deep ) } " class="mb-2 flex w-full items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-brand to-purple-600 px-5 py-4 text-base font-semibold text-white shadow hover:shadow-lg"
602+ <a href="${ esc ( deep ) } " class="deeplink mb-2 flex w-full items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-brand to-purple-600 px-5 py-4 text-base font-semibold text-white shadow hover:shadow-lg"
579603 title="Opens the Basis Package Manager and adds every package to a project you pick.">
580604 <svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12m0 0 4-4m-4 4-4-4"/><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/></svg>
581605 Install package list in the app
@@ -590,6 +614,7 @@ <h2 class="text-xl font-bold">${esc(b.name)}</h2>
590614 </div>` ;
591615 m . classList . remove ( 'hidden' ) ;
592616 m . querySelectorAll ( '.copy' ) . forEach ( btn => btn . onclick = e => { e . stopPropagation ( ) ; copy ( btn . dataset . copy , btn ) ; } ) ;
617+ m . querySelectorAll ( '.deeplink' ) . forEach ( a => a . addEventListener ( 'click' , onDeepLinkClick ) ) ;
593618
594619 // Clone-a-fresh-Basis toggle. On → show the git-clone step and frame the button as the "then install"
595620 // second step; off → hide it and just add the packages to a project you already have. The deep link is
@@ -764,9 +789,78 @@ <h2 class="text-xl font-bold">${esc(b.name)}</h2>
764789 toast ( 'Opening a GitHub issue to submit…' , 'ok' ) ;
765790}
766791
767- function closeModals ( ) { document . getElementById ( 'modal' ) . classList . add ( 'hidden' ) ; document . getElementById ( 'submitModal' ) . classList . add ( 'hidden' ) ; }
792+ // ---- basispm:// deep links, with a "you don't have the app yet" fallback ----
793+ // The Install buttons hand off to the Basis Package Manager desktop app. If it
794+ // isn't installed the click just does nothing, which reads as broken. So we fire
795+ // the deep link, then watch for the tab losing focus/visibility — that only
796+ // happens when the app actually takes over. If it never does within ~1.5s, we
797+ // assume the app is missing and surface the download page instead.
798+ const APP_DOWNLOAD_URL = `https://github.com/${ REPO } /releases/latest` ;
799+ let lastDeepLink = null ;
800+
801+ function onDeepLinkClick ( e ) {
802+ // Leave modified/middle clicks to the browser; only hijack a plain left click.
803+ if ( e . button !== 0 || e . metaKey || e . ctrlKey || e . shiftKey || e . altKey ) return ;
804+ e . preventDefault ( ) ;
805+ launchApp ( e . currentTarget . getAttribute ( 'href' ) ) ;
806+ }
807+
808+ function launchApp ( deep ) {
809+ lastDeepLink = deep ;
810+ let settled = false ;
811+ const detach = ( ) => {
812+ window . removeEventListener ( 'blur' , onLeave ) ;
813+ window . removeEventListener ( 'pagehide' , onLeave ) ;
814+ document . removeEventListener ( 'visibilitychange' , onVis ) ;
815+ clearTimeout ( timer ) ;
816+ } ;
817+ // Tab lost focus / went to the background → the app opened. Nothing more to do.
818+ const onLeave = ( ) => { if ( settled ) return ; settled = true ; detach ( ) ; } ;
819+ const onVis = ( ) => { if ( document . hidden ) onLeave ( ) ; } ;
820+ window . addEventListener ( 'blur' , onLeave ) ;
821+ window . addEventListener ( 'pagehide' , onLeave ) ;
822+ document . addEventListener ( 'visibilitychange' , onVis ) ;
823+
824+ window . location . href = deep ; // hand off to the protocol handler
825+
826+ const timer = setTimeout ( ( ) => {
827+ if ( settled ) return ; settled = true ; detach ( ) ;
828+ // Still here, focused and visible: the handler almost certainly isn't installed.
829+ if ( ! document . hidden && document . hasFocus ( ) ) openGetApp ( ) ;
830+ } , 1500 ) ;
831+ }
832+
833+ function openGetApp ( ) {
834+ const m = document . getElementById ( 'getAppModal' ) ;
835+ m . innerHTML = `<div class="w-full max-w-md rounded-2xl border border-gray-200 bg-white shadow-2xl dark:border-white/10 dark:bg-basisbg" onclick="event.stopPropagation()">
836+ <div class="flex items-start gap-4 border-b border-gray-200 p-6 dark:border-white/10">
837+ <div class="inline-flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-brand/10 text-2xl">📦</div>
838+ <div class="min-w-0 flex-1">
839+ <h2 class="text-lg font-bold">Get the Basis Package Manager</h2>
840+ <p class="mt-1 text-sm text-gray-500 dark:text-gray-400">Nothing opened? That button hands off to the Basis Package Manager desktop app — and it doesn't look installed yet. Grab it below, then click Install again.</p>
841+ </div>
842+ <button onclick="closeGetApp()" class="text-2xl leading-none text-gray-400 hover:text-gray-700 dark:hover:text-gray-200">×</button>
843+ </div>
844+ <div class="space-y-3 p-6">
845+ <a href="${ esc ( APP_DOWNLOAD_URL ) } " target="_blank" rel="noopener noreferrer"
846+ class="flex w-full items-center justify-center gap-2 rounded-2xl bg-gradient-to-r from-brand to-purple-600 px-5 py-4 text-base font-semibold text-white shadow hover:shadow-lg">
847+ <svg class="h-5 w-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 3v12m0 0 4-4m-4 4-4-4"/><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"/></svg>
848+ Download the Package Manager
849+ </a>
850+ <button onclick="retryDeepLink()" class="w-full rounded-2xl border border-gray-200 px-5 py-3 text-sm font-semibold text-gray-600 hover:border-brand hover:text-brand dark:border-white/10 dark:text-gray-300">
851+ Already installed it? Try opening again
852+ </button>
853+ </div>
854+ </div>` ;
855+ m . classList . remove ( 'hidden' ) ;
856+ }
857+ function closeGetApp ( ) { document . getElementById ( 'getAppModal' ) . classList . add ( 'hidden' ) ; }
858+ function retryDeepLink ( ) { closeGetApp ( ) ; if ( lastDeepLink ) launchApp ( lastDeepLink ) ; }
859+
860+ function closeModals ( ) { document . getElementById ( 'modal' ) . classList . add ( 'hidden' ) ; document . getElementById ( 'submitModal' ) . classList . add ( 'hidden' ) ; closeGetApp ( ) ; }
768861document . getElementById ( 'modal' ) . onclick = closeModals ;
769862document . getElementById ( 'submitModal' ) . onclick = closeModals ;
863+ document . getElementById ( 'getAppModal' ) . onclick = closeGetApp ;
770864document . addEventListener ( 'keydown' , e => { if ( e . key === 'Escape' ) closeModals ( ) ; } ) ;
771865document . getElementById ( 'submitBtn' ) . onclick = openSubmit ;
772866document . getElementById ( 'tabPackages' ) . onclick = ( ) => switchView ( 'packages' ) ;
0 commit comments