11import { safeParseJson , toPrettyJson } from "../shared/debugInspectorData.js" ;
22import { registerToolBootContract } from "../shared/toolBootContract.js" ;
3+ import { readSharedPaletteHandoff } from "../shared/assetUsageIntegration.js" ;
34import {
45 clearGameSkinOverride ,
56 loadGameSkin ,
@@ -76,7 +77,8 @@ const state = {
7677 presetSkin : null ,
7778 selectedObjectKey : "" ,
7879 selectedObjectKeys : [ ] ,
79- selectedColorSwatch : ""
80+ selectedColorSwatch : "" ,
81+ skipAutoSelectOnce : false
8082} ;
8183
8284function normalizeText ( value ) {
@@ -118,11 +120,11 @@ function getGameOptionById(gameId) {
118120}
119121
120122function getSelectedGameOption ( ) {
121- return getGameOptionById ( state . activeGameId ) || GAME_OPTIONS [ 0 ] || null ;
123+ return getGameOptionById ( state . activeGameId ) ;
122124}
123125
124126function resolveActiveGameOption ( initialGameId = "" ) {
125- const selected = getGameOptionById ( initialGameId ) || GAME_OPTIONS [ 0 ] || null ;
127+ const selected = getGameOptionById ( initialGameId ) ;
126128 state . activeGameId = selected ? selected . id : "" ;
127129 return selected ;
128130}
@@ -132,6 +134,22 @@ function getObjectKeys() {
132134 return Object . keys ( objects ) ;
133135}
134136
137+ function getValidSelectedObjectKeys ( ) {
138+ const keys = getObjectKeys ( ) ;
139+ if ( ! keys . length || ! Array . isArray ( state . selectedObjectKeys ) ) {
140+ return [ ] ;
141+ }
142+ return Array . from ( new Set ( state . selectedObjectKeys . filter ( ( key ) => keys . includes ( key ) ) ) ) ;
143+ }
144+
145+ function updateFlattenButtonState ( ) {
146+ const validSelection = getValidSelectedObjectKeys ( ) ;
147+ state . selectedObjectKeys = validSelection ;
148+ if ( refs . flattenObjectsButton instanceof HTMLButtonElement ) {
149+ refs . flattenObjectsButton . disabled = validSelection . length < 2 ;
150+ }
151+ }
152+
135153function setStatus ( message ) {
136154 if ( refs . statusText instanceof HTMLElement ) {
137155 refs . statusText . textContent = message ;
@@ -249,18 +267,26 @@ function ensureSelectedObjectKey() {
249267 if ( ! keys . length ) {
250268 state . selectedObjectKey = "" ;
251269 state . selectedObjectKeys = [ ] ;
270+ updateFlattenButtonState ( ) ;
271+ return ;
272+ }
273+
274+ const normalizedSelection = getValidSelectedObjectKeys ( ) ;
275+ if ( state . skipAutoSelectOnce && ! normalizeText ( state . selectedObjectKey ) ) {
276+ state . skipAutoSelectOnce = false ;
277+ state . selectedObjectKeys = normalizedSelection ;
278+ updateFlattenButtonState ( ) ;
252279 return ;
253280 }
281+
254282 if ( ! keys . includes ( state . selectedObjectKey ) ) {
255283 state . selectedObjectKey = keys [ 0 ] ;
256284 }
257- const normalizedSelection = Array . isArray ( state . selectedObjectKeys )
258- ? state . selectedObjectKeys . filter ( ( key ) => keys . includes ( key ) )
259- : [ ] ;
260285 if ( ! normalizedSelection . includes ( state . selectedObjectKey ) ) {
261286 normalizedSelection . unshift ( state . selectedObjectKey ) ;
262287 }
263288 state . selectedObjectKeys = Array . from ( new Set ( normalizedSelection ) ) ;
289+ updateFlattenButtonState ( ) ;
264290}
265291
266292function parseHexForPicker ( value ) {
@@ -611,6 +637,7 @@ function syncSelectedObjectUiFromSelection() {
611637function selectObjectKey ( objectKey ) {
612638 state . selectedObjectKey = objectKey ;
613639 syncSelectedObjectUiFromSelection ( ) ;
640+ updateFlattenButtonState ( ) ;
614641 renderObjectList ( ) ;
615642 renderPaletteList ( ) ;
616643 renderObjectControls ( ) ;
@@ -624,6 +651,7 @@ function setObjectSelected(objectKey, selected) {
624651 : currentSelection . filter ( ( key ) => key !== objectKey ) ;
625652 state . selectedObjectKeys = nextSelection ;
626653 syncSelectedObjectUiFromSelection ( ) ;
654+ updateFlattenButtonState ( ) ;
627655 renderObjectList ( ) ;
628656 renderPaletteList ( ) ;
629657 renderObjectControls ( ) ;
@@ -691,38 +719,45 @@ function renderPaletteList() {
691719 return ;
692720 }
693721 refs . paletteList . innerHTML = "" ;
694- const objects = toObject ( state . activeSkin ?. objects ) ;
695- const colorMap = new Map ( ) ;
696- Object . entries ( objects ) . forEach ( ( [ objectKey , objectValue ] ) => {
697- const shapeObject = toObject ( objectValue ) ;
698- Object . entries ( shapeObject ) . forEach ( ( [ propertyKey , propertyValue ] ) => {
699- const color = typeof propertyValue === "string" ? normalizeText ( propertyValue ) : "" ;
722+ const sharedPalette = readSharedPaletteHandoff ( ) ;
723+ const entries = Array . isArray ( sharedPalette ?. colors ) ? sharedPalette . colors : [ ] ;
724+ const swatches = entries
725+ . map ( ( entry , index ) => {
726+ const color = normalizeText ( entry ?. hex ) ;
700727 if ( ! parseHexForPicker ( color ) ) {
701- return ;
702- }
703- const token = color . toLowerCase ( ) ;
704- if ( ! colorMap . has ( token ) ) {
705- colorMap . set ( token , {
706- id : `${ objectKey } .${ propertyKey } ` ,
707- label : `${ objectKey } .${ propertyKey } ` ,
708- color
709- } ) ;
728+ return null ;
710729 }
711- } ) ;
712- } ) ;
713- const swatches = Array . from ( colorMap . values ( ) ) ;
730+ const swatchName = normalizeText ( entry ?. name ) || `Swatch ${ index + 1 } ` ;
731+ const swatchSymbol = normalizeText ( entry ?. symbol ) ;
732+ const suffix = swatchSymbol ? ` [${ swatchSymbol } ]` : "" ;
733+ return {
734+ id : `${ sharedPalette ?. paletteId || "shared-palette" } .${ index } ` ,
735+ label : `${ swatchName } ${ suffix } ` ,
736+ color
737+ } ;
738+ } )
739+ . filter ( ( entry ) => Boolean ( entry ) ) ;
740+
741+ if ( ! sharedPalette ) {
742+ const empty = document . createElement ( "p" ) ;
743+ empty . className = "skin-editor-empty" ;
744+ empty . textContent = "No shared palette selected. Open Palette Browser and select Use in Workspace Manager." ;
745+ refs . paletteList . appendChild ( empty ) ;
746+ return ;
747+ }
714748
715749 if ( ! swatches . length ) {
716750 const empty = document . createElement ( "p" ) ;
717751 empty . className = "skin-editor-empty" ;
718- empty . textContent = "No object colors found ." ;
752+ empty . textContent = "Shared palette has no valid swatches ." ;
719753 refs . paletteList . appendChild ( empty ) ;
720754 return ;
721755 }
722756
723757 const paletteLabel = document . createElement ( "p" ) ;
724758 paletteLabel . className = "skin-editor-empty" ;
725- paletteLabel . textContent = `Palette rebuilt from object colors (${ swatches . length } ).` ;
759+ const paletteName = normalizeText ( sharedPalette . displayName ) || normalizeText ( sharedPalette . paletteId ) || "Shared Palette" ;
760+ paletteLabel . textContent = `Shared palette '${ paletteName } ' (${ swatches . length } ).` ;
726761 refs . paletteList . appendChild ( paletteLabel ) ;
727762
728763 const selectedObjectColor = normalizeText ( state . selectedColorSwatch || getSelectedObjectColorValue ( ) ) . toLowerCase ( ) ;
@@ -757,6 +792,9 @@ function renderObjectList() {
757792 }
758793 refs . objectList . innerHTML = "" ;
759794 const keys = getObjectKeys ( ) ;
795+ const selectedObjectKeys = getValidSelectedObjectKeys ( ) ;
796+ state . selectedObjectKeys = selectedObjectKeys ;
797+ updateFlattenButtonState ( ) ;
760798 if ( ! keys . length ) {
761799 const note = document . createElement ( "p" ) ;
762800 note . className = "skin-editor-empty" ;
@@ -772,7 +810,7 @@ function renderObjectList() {
772810 const checkbox = document . createElement ( "input" ) ;
773811 checkbox . type = "checkbox" ;
774812 checkbox . className = "skin-editor-object-check" ;
775- checkbox . checked = state . selectedObjectKeys . includes ( objectKey ) ;
813+ checkbox . checked = selectedObjectKeys . includes ( objectKey ) ;
776814 checkbox . addEventListener ( "click" , ( event ) => {
777815 event . stopPropagation ( ) ;
778816 } ) ;
@@ -1062,6 +1100,7 @@ function drawSelectedObjectPreview() {
10621100
10631101function renderWorkbench ( ) {
10641102 ensureSelectedObjectKey ( ) ;
1103+ updateFlattenButtonState ( ) ;
10651104 syncSelectedObjectUiFromSelection ( ) ;
10661105 renderObjectList ( ) ;
10671106 renderPaletteList ( ) ;
@@ -1077,14 +1116,15 @@ function setCurrentSkinDocument(rawSkin, source = "loaded") {
10771116 const normalized = sanitizePositiveDimensionsInDocument ( toObjectCentricSkinDocument ( game , rawSkin ) ) ;
10781117 state . activeSkin = deepClone ( normalized ) || normalized ;
10791118 state . selectedObjectKeys = [ ] ;
1119+ state . skipAutoSelectOnce = false ;
10801120 updateEditorFromState ( source ) ;
10811121 renderWorkbench ( ) ;
10821122}
10831123
10841124async function loadActiveSkinForSelectedGame ( ) {
10851125 const game = getSelectedGameOption ( ) ;
10861126 if ( ! game ) {
1087- setStatus ( "No supported game context was resolved ." ) ;
1127+ setStatus ( "Missing game context. Launch Skin Editor from a game/workspace link that includes a valid gameId ." ) ;
10881128 return ;
10891129 }
10901130 const fallbackSkin = {
@@ -1333,9 +1373,10 @@ function flattenSelectedObjects() {
13331373 state . activeSkin . objects = { } ;
13341374 }
13351375 const objects = state . activeSkin . objects ;
1336- const selectedKeys = Array . from (
1337- new Set ( state . selectedObjectKeys . filter ( ( key ) => Object . prototype . hasOwnProperty . call ( objects , key ) ) )
1338- ) ;
1376+ const selectedKeys = getValidSelectedObjectKeys ( )
1377+ . filter ( ( key ) => Object . prototype . hasOwnProperty . call ( objects , key ) ) ;
1378+ state . selectedObjectKeys = selectedKeys ;
1379+ updateFlattenButtonState ( ) ;
13391380 if ( selectedKeys . length < 2 ) {
13401381 setStatus ( "Select at least 2 objects to flatten." ) ;
13411382 return ;
@@ -1361,10 +1402,11 @@ function flattenSelectedObjects() {
13611402 color : firstColor ,
13621403 components
13631404 } ;
1364- state . selectedObjectKey = nextKey ;
1365- state . selectedObjectKeys = [ nextKey ] ;
1405+ state . selectedObjectKey = "" ;
1406+ state . selectedObjectKeys = [ ] ;
1407+ state . skipAutoSelectOnce = true ;
13661408 if ( refs . newShapeName instanceof HTMLInputElement ) {
1367- refs . newShapeName . value = nextKey ;
1409+ refs . newShapeName . value = "" ;
13681410 }
13691411 updateEditorFromState ( "visual-editor" ) ;
13701412 renderWorkbench ( ) ;
@@ -1416,6 +1458,7 @@ async function loadPresetFromQuery() {
14161458}
14171459
14181460function bindEvents ( ) {
1461+ updateFlattenButtonState ( ) ;
14191462 refs . loadButton ?. addEventListener ( "click" , ( ) => {
14201463 void loadActiveSkinForSelectedGame ( ) ;
14211464 } ) ;
@@ -1453,9 +1496,19 @@ function bindEvents() {
14531496
14541497async function bootSkinEditor ( ) {
14551498 const { gameId, presetLoaded } = await loadPresetFromQuery ( ) ;
1456- resolveActiveGameOption ( gameId ) ;
1499+ const resolvedGame = resolveActiveGameOption ( gameId ) ;
14571500 bindEvents ( ) ;
1501+ if ( ! resolvedGame ) {
1502+ setStatus ( "Missing game context. Launch Skin Editor from a game/workspace link that includes a valid gameId." ) ;
1503+ updateFlattenButtonState ( ) ;
1504+ renderPaletteList ( ) ;
1505+ return ;
1506+ }
14581507 await loadActiveSkinForSelectedGame ( ) ;
1508+ if ( ! readSharedPaletteHandoff ( ) ) {
1509+ setStatus ( "Shared palette is required. Open Palette Browser and use 'Use in Workspace Manager' first." ) ;
1510+ return ;
1511+ }
14591512 if ( presetLoaded ) {
14601513 setStatus ( "Loaded game preset. Object workbench is ready." ) ;
14611514 }
0 commit comments