@@ -11,6 +11,7 @@ import {
1111 validateObjectDefinition ,
1212} from "../../../../src/engine/object-model/index.js" ;
1313import { createAssetToolApiRepository } from "../../../js/shared/assets-api-client.js" ;
14+ import { listMessages } from "../../../../toolbox/messages/messages-api-client.js" ;
1415
1516const constants = readServerToolConstants ( "objects" ) ;
1617
@@ -53,10 +54,13 @@ let editingRow = null;
5354let storageIssue = null ;
5455let selectedObjectKey = "" ;
5556let detailDraftBaseline = null ;
57+ let messageRecords = null ;
58+ let messageLoadIssue = null ;
5659
5760const elements = {
5861 addRow : document . querySelector ( "[data-objects-add-row]" ) ,
5962 assetStatus : document . querySelector ( "[data-objects-asset-status]" ) ,
63+ assetLinks : document . querySelector ( "[data-objects-asset-links]" ) ,
6064 count : document . querySelector ( "[data-objects-count]" ) ,
6165 detailActive : document . querySelector ( "[data-objects-detail-active]" ) ,
6266 detailAudio : document . querySelector ( "[data-objects-detail-audio]" ) ,
@@ -65,6 +69,7 @@ const elements = {
6569 detailDescription : document . querySelector ( "[data-objects-detail-description]" ) ,
6670 detailEmpty : document . querySelector ( "[data-objects-details-empty]" ) ,
6771 detailForm : document . querySelector ( "[data-objects-details-form]" ) ,
72+ detailMessage : document . querySelector ( "[data-objects-detail-message]" ) ,
6873 detailName : document . querySelector ( "[data-objects-detail-name]" ) ,
6974 detailSave : document . querySelector ( "[data-objects-detail-save]" ) ,
7075 detailSelected : document . querySelector ( "[data-objects-detail-selected]" ) ,
@@ -153,6 +158,7 @@ function normalizeObjectDetails(source = {}) {
153158 audioReference : normalizeText ( details . audioReference ?? source . audioReference ) ,
154159 defaultValues : normalizeText ( details . defaultValues ?? source . defaultValues ) ,
155160 description : normalizeText ( details . description ?? source . description ) ,
161+ messageReference : normalizeText ( details . messageReference ?? source . messageReference ) ,
156162 spriteReference : normalizeText ( details . spriteReference ?? source . spriteReference ) || normalizeText ( source . render ?. assetKey ) ,
157163 tags : Object . freeze ( parseTags ( details . tags ?? source . tags ) ) ,
158164 visible : normalizeBoolean ( details . visible ?? source . visible , true ) ,
@@ -481,6 +487,7 @@ function detailFieldElements() {
481487 elements . detailAudio ,
482488 elements . detailDefaults ,
483489 elements . detailDescription ,
490+ elements . detailMessage ,
484491 elements . detailName ,
485492 elements . detailSprite ,
486493 elements . detailTags ,
@@ -503,6 +510,7 @@ function detailSnapshotForObject(object = {}) {
503510 audioReference : details . audioReference ,
504511 defaultValues : details . defaultValues ,
505512 description : details . description ,
513+ messageReference : details . messageReference ,
506514 name : normalizeText ( object . name ) ,
507515 spriteReference : details . spriteReference ,
508516 tags : [ ...details . tags ] ,
@@ -517,6 +525,7 @@ function detailSnapshotFromForm() {
517525 audioReference : normalizeText ( elements . detailAudio ?. value ) ,
518526 defaultValues : normalizeText ( elements . detailDefaults ?. value ) ,
519527 description : normalizeText ( elements . detailDescription ?. value ) ,
528+ messageReference : normalizeText ( elements . detailMessage ?. value ) ,
520529 name : normalizeText ( elements . detailName ?. value ) ,
521530 spriteReference : normalizeText ( elements . detailSprite ?. value ) ,
522531 tags : parseTags ( elements . detailTags ?. value ) ,
@@ -606,6 +615,9 @@ function populateDetailForm(object) {
606615 if ( elements . detailAudio ) {
607616 elements . detailAudio . value = snapshot . audioReference ;
608617 }
618+ if ( elements . detailMessage ) {
619+ elements . detailMessage . value = snapshot . messageReference ;
620+ }
609621 if ( elements . detailDefaults ) {
610622 elements . detailDefaults . value = snapshot . defaultValues ;
611623 }
@@ -661,6 +673,20 @@ function objectDetailFindings(snapshot, originalId = "") {
661673 label : "Sprite reference" ,
662674 } ) ;
663675 }
676+ if ( snapshot . audioReference && ! linkedAudioAsset ( snapshot . audioReference ) ) {
677+ findings . push ( {
678+ action : "Use an existing audio asset reference, or clear the Audio reference before saving Object Details." ,
679+ label : "Audio reference" ,
680+ } ) ;
681+ }
682+ if ( snapshot . messageReference && ! linkedMessageRecord ( snapshot . messageReference ) ) {
683+ findings . push ( {
684+ action : messageLoadIssue
685+ ? "Messages could not be checked. Reload Objects after the API is available, or clear the Message reference before saving Object Details."
686+ : "Use an existing message reference, or clear the Message reference before saving Object Details." ,
687+ label : "Message reference" ,
688+ } ) ;
689+ }
664690 return findings ;
665691}
666692
@@ -680,6 +706,7 @@ function objectFromDetailSnapshot(object, snapshot) {
680706 audioReference : snapshot . audioReference ,
681707 defaultValues : snapshot . defaultValues ,
682708 description : snapshot . description ,
709+ messageReference : snapshot . messageReference ,
683710 spriteReference : snapshot . spriteReference ,
684711 tags : snapshot . tags ,
685712 visible : snapshot . visible ,
@@ -857,12 +884,97 @@ function listAssetRecords() {
857884 return Array . isArray ( tables ?. asset_library_items ) ? tables . asset_library_items : [ ] ;
858885}
859886
887+ function assetReferenceValues ( asset = { } ) {
888+ return [
889+ asset . id ,
890+ asset . key ,
891+ asset . name ,
892+ asset . fileName ,
893+ asset . originalName ,
894+ asset . reference ,
895+ asset . path ,
896+ asset . storedPath ,
897+ asset . storageObjectKey ,
898+ asset . targetFilePath ,
899+ ] . map ( normalizeText ) . filter ( Boolean ) ;
900+ }
901+
902+ function assetHasReference ( asset , reference ) {
903+ const key = normalizeText ( reference ) ;
904+ return Boolean ( key ) && assetReferenceValues ( asset ) . some ( ( value ) => value === key ) ;
905+ }
906+
907+ function assetRoleText ( asset = { } ) {
908+ return [
909+ asset . assetRole ,
910+ asset . assetRoleLabel ,
911+ asset . assetType ,
912+ asset . mimeType ,
913+ asset . role ,
914+ asset . type ,
915+ asset . usage ,
916+ ] . map ( normalizeText ) . join ( " " ) . toLowerCase ( ) ;
917+ }
918+
919+ function isSpriteAsset ( asset = { } ) {
920+ const text = assetRoleText ( asset ) ;
921+ return text . includes ( "sprite" ) || text . includes ( "image" ) || text . includes ( "png" ) || text . includes ( "jpeg" ) || text . includes ( "webp" ) ;
922+ }
923+
924+ function isAudioAsset ( asset = { } ) {
925+ const text = assetRoleText ( asset ) ;
926+ return text . includes ( "audio" ) || text . includes ( "sound" ) || text . includes ( "music" ) || text . includes ( "voice" ) || text . includes ( "wav" ) || text . includes ( "mpeg" ) || text . includes ( "ogg" ) ;
927+ }
928+
929+ function linkedAssetByReference ( reference , predicate ) {
930+ const key = normalizeText ( reference ) ;
931+ if ( ! key ) {
932+ return null ;
933+ }
934+ return listAssetRecords ( ) . find ( ( asset ) => assetHasReference ( asset , key ) && predicate ( asset ) ) || null ;
935+ }
936+
860937function linkedSpriteAsset ( assetKey ) {
861- const key = normalizeText ( assetKey ) ;
938+ return linkedAssetByReference ( assetKey , isSpriteAsset ) ;
939+ }
940+
941+ function linkedAudioAsset ( reference ) {
942+ return linkedAssetByReference ( reference , isAudioAsset ) ;
943+ }
944+
945+ function listMessageRecords ( ) {
946+ if ( messageRecords !== null ) {
947+ return messageRecords ;
948+ }
949+ try {
950+ const result = listMessages ( ) ;
951+ messageRecords = Array . isArray ( result ?. messages ) ? result . messages : [ ] ;
952+ messageLoadIssue = null ;
953+ } catch {
954+ messageRecords = [ ] ;
955+ messageLoadIssue = {
956+ action : "Messages could not be checked. Reload Objects after the API is available." ,
957+ label : "Message references" ,
958+ } ;
959+ }
960+ return messageRecords ;
961+ }
962+
963+ function messageReferenceValues ( message = { } ) {
964+ return [
965+ message . key ,
966+ message . id ,
967+ message . name ,
968+ message . slug ,
969+ ] . map ( normalizeText ) . filter ( Boolean ) ;
970+ }
971+
972+ function linkedMessageRecord ( reference ) {
973+ const key = normalizeText ( reference ) ;
862974 if ( ! key ) {
863975 return null ;
864976 }
865- return listAssetRecords ( ) . find ( ( asset ) => asset . id === key ) || null ;
977+ return listMessageRecords ( ) . find ( ( message ) => messageReferenceValues ( message ) . some ( ( value ) => value === key ) ) || null ;
866978}
867979
868980function assetDisplayText ( asset , fallbackKey = "" ) {
@@ -1177,6 +1289,107 @@ function renderOutput(objects, findings) {
11771289 }
11781290}
11791291
1292+ function messageDisplayText ( message , fallbackKey = "" ) {
1293+ const key = normalizeText ( message ?. key || message ?. id || fallbackKey ) ;
1294+ const name = normalizeText ( message ?. name ) ;
1295+ if ( key && name && name !== key ) {
1296+ return `${ name } (${ key } )` ;
1297+ }
1298+ return name || key || "Message missing" ;
1299+ }
1300+
1301+ function referenceStatusItem ( {
1302+ action = null ,
1303+ label,
1304+ linkDataName = "" ,
1305+ linkText = "" ,
1306+ linkValue = "" ,
1307+ linkedText = "" ,
1308+ missingText = "" ,
1309+ reference,
1310+ warningName = "" ,
1311+ } ) {
1312+ const item = document . createElement ( "li" ) ;
1313+ const heading = document . createElement ( "strong" ) ;
1314+ heading . textContent = `${ label } :` ;
1315+ item . append ( heading , " " ) ;
1316+ if ( ! reference ) {
1317+ item . append ( "No reference set." ) ;
1318+ return item ;
1319+ }
1320+ if ( ! linkedText ) {
1321+ item . dataset . objectsReferenceWarning = warningName || label ;
1322+ item . append ( missingText ) ;
1323+ if ( action ) {
1324+ item . append ( " " , actionLink ( linkText , action , linkDataName , linkValue ) ) ;
1325+ }
1326+ return item ;
1327+ }
1328+ item . append ( linkedText ) ;
1329+ if ( action ) {
1330+ item . append ( " " , actionLink ( linkText , action , linkDataName , linkValue ) ) ;
1331+ }
1332+ return item ;
1333+ }
1334+
1335+ function renderAssetLinks ( ) {
1336+ if ( ! elements . assetLinks ) {
1337+ return ;
1338+ }
1339+ elements . assetLinks . replaceChildren ( ) ;
1340+ const object = selectedObject ( ) ;
1341+ if ( ! object ) {
1342+ elements . assetLinks . append ( listItem ( "Select an object to review sprite, audio, and message links." ) ) ;
1343+ return ;
1344+ }
1345+
1346+ const details = detailsForObject ( object ) ;
1347+ const spriteAsset = details . spriteReference ? linkedSpriteAsset ( details . spriteReference ) : null ;
1348+ const audioAsset = details . audioReference ? linkedAudioAsset ( details . audioReference ) : null ;
1349+ const messageRecord = details . messageReference ? linkedMessageRecord ( details . messageReference ) : null ;
1350+
1351+ elements . assetLinks . append (
1352+ referenceStatusItem ( {
1353+ action : spriteAsset ? spriteEditorHref ( {
1354+ ...object ,
1355+ render : { assetKey : details . spriteReference , type : "Sprite" } ,
1356+ } ) : null ,
1357+ label : "Sprite" ,
1358+ linkDataName : "objectsAssetLinkSprite" ,
1359+ linkText : "Edit Sprite" ,
1360+ linkValue : objectId ( object ) ,
1361+ linkedText : spriteAsset ? assetDisplayText ( spriteAsset , details . spriteReference ) : "" ,
1362+ missingText : `Sprite reference "${ details . spriteReference } " is not linked to an existing sprite asset.` ,
1363+ reference : details . spriteReference ,
1364+ warningName : "sprite" ,
1365+ } ) ,
1366+ referenceStatusItem ( {
1367+ action : details . audioReference ? "/toolbox/assets/index.html" : null ,
1368+ label : "Audio" ,
1369+ linkDataName : "objectsAssetLinkAudio" ,
1370+ linkText : "Open Assets" ,
1371+ linkValue : normalizeText ( audioAsset ?. id || details . audioReference ) ,
1372+ linkedText : audioAsset ? assetDisplayText ( audioAsset , details . audioReference ) : "" ,
1373+ missingText : `Audio reference "${ details . audioReference } " is not linked to an existing audio asset.` ,
1374+ reference : details . audioReference ,
1375+ warningName : "audio" ,
1376+ } ) ,
1377+ referenceStatusItem ( {
1378+ action : details . messageReference ? "/toolbox/messages/index.html" : null ,
1379+ label : "Message" ,
1380+ linkDataName : "objectsMessageLink" ,
1381+ linkText : "Open Messages" ,
1382+ linkValue : normalizeText ( messageRecord ?. key || details . messageReference ) ,
1383+ linkedText : messageRecord ? messageDisplayText ( messageRecord , details . messageReference ) : "" ,
1384+ missingText : messageLoadIssue
1385+ ? "Message references could not be checked. Reload Objects after the API is available."
1386+ : `Message reference "${ details . messageReference } " is not linked to an existing Message.` ,
1387+ reference : details . messageReference ,
1388+ warningName : "message" ,
1389+ } ) ,
1390+ ) ;
1391+ }
1392+
11801393function renderTemplateCatalog ( ) {
11811394 if ( elements . templateCatalog ) {
11821395 elements . templateCatalog . replaceChildren ( ) ;
@@ -1229,6 +1442,7 @@ function render() {
12291442 renderOutput ( draftedObjects , findings ) ;
12301443 renderValidation ( findings ) ;
12311444 renderDetailsPanel ( { preserveDirty : true } ) ;
1445+ renderAssetLinks ( ) ;
12321446 if ( elements . addRow ) {
12331447 elements . addRow . disabled = Boolean ( editingRow ) ;
12341448 }
@@ -1428,6 +1642,8 @@ function resetTable() {
14281642}
14291643
14301644function refreshLinkedRenderAssetDisplay ( ) {
1645+ messageRecords = null ;
1646+ messageLoadIssue = null ;
14311647 if ( ! editingRow ) {
14321648 render ( ) ;
14331649 return ;
0 commit comments