@@ -19,6 +19,11 @@ import {
1919 logToolUiLifecycle
2020} from "../shared/toolLoadDiagnostics.js" ;
2121import { ACTIVE_PROJECT_STORAGE_KEY } from "../shared/projectManifestContract.js" ;
22+ import {
23+ TOOL_UX_LIFECYCLE ,
24+ getUnifiedEmptyStateMessage ,
25+ setToolUxLifecycleState
26+ } from "../shared/unifiedToolUxContract.js" ;
2227
2328const APPROVED_DESTINATIONS = Object . freeze ( {
2429 "Vector Assets" : "games/<project>/assets/vectors/" ,
@@ -104,6 +109,48 @@ const state = {
104109 }
105110} ;
106111
112+ function setAssetBrowserLifecycle ( stateName , details = { } ) {
113+ setToolUxLifecycleState ( "asset-browser" , stateName , details ) ;
114+ }
115+
116+ function ensureFirstVisibleAssetSelection ( entries ) {
117+ const source = Array . isArray ( entries ) ? entries : [ ] ;
118+ if ( source . length <= 0 ) {
119+ state . selectedAssetId = "" ;
120+ return false ;
121+ }
122+ const hasCurrent = source . some ( ( entry ) => entry . id === state . selectedAssetId ) ;
123+ if ( hasCurrent ) {
124+ return false ;
125+ }
126+ state . selectedAssetId = source [ 0 ] . id ;
127+ return true ;
128+ }
129+
130+ function syncAssetBrowserUxContract ( options = { } ) {
131+ const interactive = options . interacting === true ;
132+ const approvedCount = Array . isArray ( state . assetCatalog ) ? state . assetCatalog . length : 0 ;
133+ const hasSelection = Boolean ( getSelectedAsset ( ) ) ;
134+ if ( refs . useAssetInToolButton instanceof HTMLButtonElement ) {
135+ refs . useAssetInToolButton . disabled = ! hasSelection ;
136+ }
137+ if ( interactive ) {
138+ setAssetBrowserLifecycle ( TOOL_UX_LIFECYCLE . INTERACTING , {
139+ approvedCount,
140+ hasSelection
141+ } ) ;
142+ return ;
143+ }
144+ if ( approvedCount <= 0 ) {
145+ setAssetBrowserLifecycle ( TOOL_UX_LIFECYCLE . READY_EMPTY , { approvedCount } ) ;
146+ return ;
147+ }
148+ setAssetBrowserLifecycle (
149+ hasSelection ? TOOL_UX_LIFECYCLE . READY_SELECTED : TOOL_UX_LIFECYCLE . READY_EMPTY ,
150+ { approvedCount, hasSelection }
151+ ) ;
152+ }
153+
107154function sanitizeText ( value ) {
108155 return typeof value === "string" ? value . trim ( ) : "" ;
109156}
@@ -834,6 +881,7 @@ function populateDestinationOptions(category) {
834881
835882function renderAssetList ( ) {
836883 const entries = getVisibleAssets ( ) ;
884+ ensureFirstVisibleAssetSelection ( entries ) ;
837885 refs . countText . textContent = buildApprovedAssetStatusText ( entries . length , state . catalogLoadInfo ) ;
838886 refs . assetList . innerHTML = entries . length > 0
839887 ? entries . map ( ( entry ) => {
@@ -846,20 +894,16 @@ function renderAssetList() {
846894 </button>
847895 ` ;
848896 } ) . join ( "" )
849- : `<p class="asset-browser__empty">${ buildApprovedAssetEmptyStateText ( state . catalogLoadInfo ) } </p>` ;
850-
851- if ( ! entries . some ( ( entry ) => entry . id === state . selectedAssetId ) ) {
852- state . selectedAssetId = "" ;
853- }
897+ : `<p class="asset-browser__empty">${ getUnifiedEmptyStateMessage ( ) } ${ buildApprovedAssetEmptyStateText ( state . catalogLoadInfo ) } </p>` ;
854898}
855899
856900async function renderPreview ( ) {
857901 const selectedAsset = getSelectedAsset ( ) ;
858902 if ( ! selectedAsset ) {
859903 refs . previewTitle . textContent = "Preview" ;
860- refs . previewMeta . textContent = "Select an approved asset from the catalog." ;
861- refs . previewCanvas . innerHTML = ' <p class="asset-browser__empty">No asset selected. </p>' ;
862- refs . previewText . textContent = "Choose a file to inspect metadata and content ." ;
904+ refs . previewMeta . textContent = getUnifiedEmptyStateMessage ( ) ;
905+ refs . previewCanvas . innerHTML = ` <p class="asset-browser__empty">${ getUnifiedEmptyStateMessage ( ) } </p>` ;
906+ refs . previewText . textContent = "Load or create asset ." ;
863907 return ;
864908 }
865909
@@ -1038,49 +1082,66 @@ function syncImportFormFromFile() {
10381082
10391083function bindEvents ( ) {
10401084 refs . categoryFilter . addEventListener ( "change" , ( ) => {
1085+ syncAssetBrowserUxContract ( { interacting : true } ) ;
10411086 state . selectedCategory = refs . categoryFilter . value ;
10421087 renderAssetList ( ) ;
10431088 renderPreview ( ) ;
10441089 emitAssetBrowserControlReadiness ( ) ;
1090+ syncAssetBrowserUxContract ( ) ;
10451091 } ) ;
10461092
10471093 refs . searchInput . addEventListener ( "input" , ( ) => {
1094+ syncAssetBrowserUxContract ( { interacting : true } ) ;
10481095 state . search = refs . searchInput . value ;
10491096 renderAssetList ( ) ;
10501097 renderPreview ( ) ;
10511098 emitAssetBrowserControlReadiness ( ) ;
1099+ syncAssetBrowserUxContract ( ) ;
10521100 } ) ;
10531101
10541102 refs . assetList . addEventListener ( "click" , ( event ) => {
10551103 const button = event . target instanceof Element ? event . target . closest ( "[data-asset-id]" ) : null ;
10561104 if ( ! ( button instanceof HTMLElement ) ) {
10571105 return ;
10581106 }
1107+ syncAssetBrowserUxContract ( { interacting : true } ) ;
10591108 state . selectedAssetId = button . dataset . assetId || "" ;
10601109 renderAssetList ( ) ;
10611110 renderPreview ( ) ;
10621111 emitAssetBrowserControlReadiness ( ) ;
1112+ syncAssetBrowserUxContract ( ) ;
10631113 } ) ;
10641114
1065- refs . importFileInput . addEventListener ( "change" , syncImportFormFromFile ) ;
1115+ refs . importFileInput . addEventListener ( "change" , ( ) => {
1116+ syncAssetBrowserUxContract ( { interacting : true } ) ;
1117+ syncImportFormFromFile ( ) ;
1118+ syncAssetBrowserUxContract ( ) ;
1119+ } ) ;
10661120 refs . importCategorySelect . addEventListener ( "change" , ( ) => {
1121+ syncAssetBrowserUxContract ( { interacting : true } ) ;
10671122 populateDestinationOptions ( refs . importCategorySelect . value ) ;
10681123 renderImportPlan ( ) ;
10691124 emitAssetBrowserControlReadiness ( ) ;
1125+ syncAssetBrowserUxContract ( ) ;
10701126 } ) ;
10711127 refs . importNameInput . addEventListener ( "input" , ( ) => {
1128+ syncAssetBrowserUxContract ( { interacting : true } ) ;
10721129 renderImportPlan ( ) ;
10731130 emitAssetBrowserControlReadiness ( ) ;
1131+ syncAssetBrowserUxContract ( ) ;
10741132 } ) ;
10751133 refs . validateImportButton . addEventListener ( "click" , ( ) => {
1134+ syncAssetBrowserUxContract ( { interacting : true } ) ;
10761135 renderImportPlan ( ) ;
10771136 emitAssetBrowserControlReadiness ( ) ;
1137+ syncAssetBrowserUxContract ( ) ;
10781138 } ) ;
10791139 refs . downloadImportPlanButton . addEventListener ( "click" , downloadImportPlan ) ;
10801140 refs . useAssetInToolButton . addEventListener ( "click" , useSelectedAssetInActiveTool ) ;
10811141}
10821142
10831143async function init ( ) {
1144+ setAssetBrowserLifecycle ( TOOL_UX_LIFECYCLE . LOADING ) ;
10841145 await hydrateApprovedAssetCatalog ( ) ;
10851146 const approvedAssetsState = String ( state . catalogLoadInfo ?. status || APPROVED_ASSET_STATUS . sourceMissing ) ;
10861147 const approvedClassification = approvedAssetsState === APPROVED_ASSET_STATUS . loadedEmpty
@@ -1114,6 +1175,7 @@ async function init() {
11141175 await renderPreview ( ) ;
11151176 renderImportPlan ( ) ;
11161177 emitAssetBrowserControlReadiness ( ) ;
1178+ syncAssetBrowserUxContract ( ) ;
11171179 bindEvents ( ) ;
11181180}
11191181
@@ -1150,6 +1212,7 @@ const assetBrowserApi = {
11501212
11511213function bootAssetBrowser ( ) {
11521214 if ( ! initialized ) {
1215+ setAssetBrowserLifecycle ( TOOL_UX_LIFECYCLE . INIT ) ;
11531216 void init ( ) . then ( ( ) => tryLoadPresetFromQuery ( ) ) ;
11541217 initialized = true ;
11551218 }
0 commit comments