1- import { Palette } from "lucide-react" ;
1+ import { Palette , RotateCw } from "lucide-react" ;
22import { useRef , useState } from "react" ;
33
44import {
55 COLORABLE_BASE_ITEMS ,
6+ type FloorDecoyRotationDeg ,
67 type LayerItem ,
8+ creatorPaletteKeyForLookup ,
79 getColoredVariant ,
810 getGridItems ,
11+ getRotatedFloorPlacementItem ,
12+ isRotatableFloorBaseKey ,
13+ paletteDisplayLabel ,
14+ parseRotatableFloorKey ,
915} from "../../../../constants/layer-items" ;
1016import { getUIBlockBackgroundData } from "../../../../utils/tileset-utils" ;
1117import { renderTilesetLayer } from "../../shared/render-tileset-layer" ;
@@ -15,12 +21,16 @@ interface CreatorLayerOptionsGridProps {
1521 layerKey : string ;
1622 activeBlock : LayerItem | null ;
1723 setActiveBlock : ( block : LayerItem | null ) => void ;
24+ floorCableRotationByBase : Record < string , FloorDecoyRotationDeg > ;
25+ rotateCableAtBase : ( baseKey : string ) => void ;
1826}
1927
2028export function CreatorLayerOptionsGrid ( {
2129 layerKey,
2230 activeBlock,
2331 setActiveBlock,
32+ floorCableRotationByBase,
33+ rotateCableAtBase,
2434} : CreatorLayerOptionsGridProps ) {
2535 const items = getGridItems ( layerKey ) ;
2636
@@ -35,13 +45,24 @@ export function CreatorLayerOptionsGrid({
3545 const pickerAnchorRef = useRef < HTMLDivElement | null > ( null ) ;
3646
3747 const getDisplayItem = ( item : LayerItem ) : LayerItem => {
38- if ( ! item . colorable || ! item . baseKey ) return item ;
39- const colorIdx = selectedColors [ item . baseKey ] ?? 0 ;
40- return getColoredVariant ( item . baseKey , colorIdx ) ?? item ;
48+ let out = item ;
49+ if ( out . colorable && out . baseKey ) {
50+ const colorIdx = selectedColors [ out . baseKey ] ?? 0 ;
51+ out = getColoredVariant ( out . baseKey , colorIdx ) ?? out ;
52+ }
53+ if ( item . supportsRotation && isRotatableFloorBaseKey ( item . key ) ) {
54+ const deg = floorCableRotationByBase [ item . key ] ?? 0 ;
55+ return getRotatedFloorPlacementItem ( item . key , deg ) ?? out ;
56+ }
57+ return out ;
4158 } ;
4259
4360 const isItemActive = ( item : LayerItem ) : boolean => {
4461 if ( ! activeBlock ) return false ;
62+ if ( item . supportsRotation ) {
63+ const p = parseRotatableFloorKey ( activeBlock . key ) ;
64+ return p ?. baseKey === item . key ;
65+ }
4566 if ( item . colorable && item . baseKey ) {
4667 return activeBlock . baseKey === item . baseKey ;
4768 }
@@ -67,6 +88,16 @@ export function CreatorLayerOptionsGrid({
6788 setOpenPickerBaseKey ( ( prev ) => ( prev === baseKey ? null : baseKey ) ) ;
6889 } ;
6990
91+ const handleRotateClick = ( e : React . MouseEvent , baseKey : string ) => {
92+ e . stopPropagation ( ) ;
93+ if ( isRotatableFloorBaseKey ( baseKey ) ) {
94+ rotateCableAtBase ( baseKey ) ;
95+ }
96+ } ;
97+
98+ const paletteRowKeyForItem = ( item : LayerItem ) =>
99+ item . colorable && item . baseKey ? item . baseKey : item . key ;
100+
70101 return (
71102 < div className = "custom-scrollbar flex min-h-0 flex-1 flex-wrap content-start items-start justify-center gap-4 overflow-y-auto rounded-lg bg-violet-900/40 p-4" >
72103 { items . map ( ( item ) => {
@@ -75,23 +106,45 @@ export function CreatorLayerOptionsGrid({
75106 const isColorable = Boolean ( item . colorable && item . baseKey ) ;
76107 const hasVariants =
77108 isColorable && ( COLORABLE_BASE_ITEMS [ item . baseKey ! ] ?. length ?? 0 ) > 1 ;
109+ const canRotate =
110+ Boolean ( item . supportsRotation ) && isRotatableFloorBaseKey ( item . key ) ;
111+
112+ const paletteRowKey = creatorPaletteKeyForLookup (
113+ paletteRowKeyForItem ( item ) ,
114+ ) ;
78115
79116 const useCompositeBlend =
80117 displayItem . baseFrame !== undefined || Boolean ( displayItem . color ) ;
81118
119+ const needsRotatePreview =
120+ displayItem . rotationDeg != null &&
121+ displayItem . rotationDeg % 360 !== 0 ;
122+
123+ const previewUsesTilesetLayers =
124+ useCompositeBlend || needsRotatePreview ;
125+
82126 const { bgUrl, bgPosX, bgPosY } = getUIBlockBackgroundData (
83127 displayItem . frame ,
84128 24 ,
85129 import . meta. env . BASE_URL ,
86130 ) ;
87131
132+ const paletteName = paletteDisplayLabel ( displayItem ) ;
133+ const rotatedKey = parseRotatableFloorKey ( displayItem . key ) ;
134+ const tileHoverTitle =
135+ rotatedKey !== null &&
136+ rotatedKey . rotationDeg % 360 !== 0 &&
137+ item . supportsRotation
138+ ? `${ paletteName } · ${ rotatedKey . rotationDeg } °`
139+ : paletteName ;
140+
88141 return (
89142 < div
90- key = { item . baseKey ?? item . key }
143+ key = { paletteRowKey }
91144 ref = {
92145 openPickerBaseKey === item . baseKey ? pickerAnchorRef : undefined
93146 }
94- className = "relative"
147+ className = "relative w-[5rem] shrink-0 "
95148 >
96149 < div
97150 role = "button"
@@ -110,10 +163,10 @@ export function CreatorLayerOptionsGrid({
110163 ? "bg-amber-400/30 ring-2 ring-amber-400"
111164 : "hover:bg-violet-500/40"
112165 } `}
113- title = { displayItem . label }
166+ title = { tileHoverTitle }
114167 >
115168 < div className = "relative h-20 w-20 overflow-hidden border-4 border-emerald-950 bg-blue-400" >
116- { ! useCompositeBlend ? (
169+ { ! previewUsesTilesetLayers ? (
117170 < div
118171 className = "h-6 w-6"
119172 style = { {
@@ -141,26 +194,54 @@ export function CreatorLayerOptionsGrid({
141194 renderTilesetLayer ( {
142195 frameId : displayItem . baseFrame ,
143196 direction : displayItem . direction ,
197+ rotationDeg : displayItem . rotationDeg ,
144198 } ) }
145199 { renderTilesetLayer ( {
146200 frameId : displayItem . frame ,
147201 color : displayItem . color ,
148202 direction : displayItem . direction ,
203+ rotationDeg : displayItem . rotationDeg ,
149204 } ) }
150205 </ div >
151206 ) }
152207
153- { hasVariants && (
154- < button
155- type = "button"
156- onClick = { ( e ) => {
157- handleExpandClick ( e , item . baseKey ! ) ;
158- } }
159- className = "absolute bottom-0.5 right-0.5 flex h-5 w-5 cursor-pointer items-center justify-center rounded bg-violet-900/80 text-violet-200 transition-colors hover:bg-violet-700/90 hover:text-white"
160- aria-label = "Choose palette color"
161- title = "Choose color"
162- >
163- < Palette className = "h-4 w-4" aria-hidden strokeWidth = { 2 } /> </ button >
208+ { ( canRotate || hasVariants ) && (
209+ < div className = "absolute bottom-0.5 right-0.5 flex flex-row-reverse items-center gap-0.5" >
210+ { hasVariants && (
211+ < button
212+ type = "button"
213+ onClick = { ( e ) => {
214+ handleExpandClick ( e , item . baseKey ! ) ;
215+ } }
216+ className = "flex h-5 w-5 cursor-pointer items-center justify-center rounded bg-violet-900/80 text-violet-200 transition-colors hover:bg-violet-700/90 hover:text-white"
217+ aria-label = "Choose palette color"
218+ title = "Choose color"
219+ >
220+ < Palette
221+ className = "h-4 w-4"
222+ aria-hidden
223+ strokeWidth = { 2 }
224+ />
225+ </ button >
226+ ) }
227+ { canRotate && (
228+ < button
229+ type = "button"
230+ onClick = { ( e ) => {
231+ handleRotateClick ( e , item . key ) ;
232+ } }
233+ className = "flex h-5 w-5 cursor-pointer items-center justify-center rounded bg-violet-900/80 text-violet-200 transition-colors hover:bg-violet-700/90 hover:text-white"
234+ aria-label = "Rotate cable (R)"
235+ title = "Rotate (R)"
236+ >
237+ < RotateCw
238+ className = "h-4 w-4"
239+ aria-hidden
240+ strokeWidth = { 2 }
241+ />
242+ </ button >
243+ ) }
244+ </ div >
164245 ) }
165246
166247 { Boolean ( isColorable && displayItem . color ) && (
@@ -170,8 +251,8 @@ export function CreatorLayerOptionsGrid({
170251 />
171252 ) }
172253 </ div >
173- < span className = "max-w-[96px] text-center text-[11px ] leading-tight font-medium text-violet-200" >
174- { displayItem . label }
254+ < span className = "block w-full text-balance break-words px-0.5 text- center text-[10px ] leading-tight font-medium text-violet-200 hyphens-auto " >
255+ { paletteName }
175256 </ span >
176257 </ div >
177258
0 commit comments