@@ -15,7 +15,7 @@ import {
1515} from "lucide-react" ;
1616import { useEffect , useRef , useState , type KeyboardEvent , type ReactNode } from "react" ;
1717import * as control from "../device/controller" ;
18- import { isNoirKsnakeStatus , isNoirS1Status , noirBrandLabel } from "../device/noir.ts" ;
18+ import { isNoirE1Status , isNoirKsnakeStatus , isNoirS1Status , noirBrandLabel } from "../device/noir.ts" ;
1919import type { ControlSnapshot , WorkspaceTab } from "../device/types" ;
2020import { t , tp , connectionText , type I18nKey } from "../i18n" ;
2121import { Diagnostics , LogitechDetails } from "./Diagnostics" ;
@@ -104,6 +104,14 @@ const NOIR_S1_BUTTON_MARKER_POSITIONS: Readonly<Record<string, ButtonMarkerPosit
104104 dpi : { x : 0.5 , y : 0.42 } ,
105105} ;
106106
107+ const NOIR_E1_BUTTON_MARKER_POSITIONS : Readonly < Record < string , ButtonMarkerPosition > > = {
108+ left : { x : 0.28 , y : 0.2 } ,
109+ right : { x : 0.72 , y : 0.2 } ,
110+ middle : { x : 0.5 , y : 0.18 } ,
111+ forward : { x : 0.08 , y : 0.32 } ,
112+ backward : { x : 0.08 , y : 0.5 } ,
113+ } ;
114+
107115function m2NexButtonMarkerPosition ( button : string ) : ButtonMarkerPosition | null {
108116 return M2_NEX_BUTTON_MARKER_POSITIONS [ button . trim ( ) . toLowerCase ( ) ] ?? null ;
109117}
@@ -112,6 +120,10 @@ function noirS1ButtonMarkerPosition(button: string): ButtonMarkerPosition | null
112120 return NOIR_S1_BUTTON_MARKER_POSITIONS [ button . trim ( ) . toLowerCase ( ) ] ?? null ;
113121}
114122
123+ function noirE1ButtonMarkerPosition ( button : string ) : ButtonMarkerPosition | null {
124+ return NOIR_E1_BUTTON_MARKER_POSITIONS [ button . trim ( ) . toLowerCase ( ) ] ?? null ;
125+ }
126+
115127// Device artwork requests go straight to the documented GitHub issue form
116128// (public/devices/README.md, .github/ISSUE_TEMPLATE/device-artwork.yml).
117129const ARTWORK_ISSUE_URL = "https://github.com/OpenMouse-Project/openmouse/issues/new?template=device-artwork.yml" ;
@@ -247,12 +259,12 @@ function M2NexProfileOverview({ snapshot }: { snapshot: ControlSnapshot }): Reac
247259 >
248260 < div className = "m2nex-profile-overview-title" >
249261 < span > { t ( locale , "m2nex.overline" ) } </ span >
250- < strong > { t ( locale , "m2nex.label" ) } </ strong >
262+ < strong > { status . name } </ strong >
251263 </ div >
252264 < label className = "m2nex-profile-overview-picker" htmlFor = "m2nex-profile-overview-select" >
253265 < select
254266 id = "m2nex-profile-overview-select"
255- aria-label = { t ( locale , "m2nex.select" ) }
267+ aria-label = { ` ${ status . name } profile` }
256268 value = { snapshot . activeM2NexProfile }
257269 disabled = { snapshot . settingInProgress || snapshot . pending . busy }
258270 onChange = { ( event ) => control . selectM2NexProfile ( Number ( event . currentTarget . value ) ) }
@@ -318,6 +330,7 @@ export function DeviceShowcaseSidebar({ snapshot }: { snapshot: ControlSnapshot
318330 const locale = snapshot . preferences . locale ;
319331 const image = snapshot . deviceArtwork ;
320332 const isNoirS1 = isNoirS1Status ( status ) ;
333+ const isNoirE1 = isNoirE1Status ( status ) ;
321334 const showButtonMarkers = snapshot . workspaceTab === "buttons"
322335 && isNoirKsnakeStatus ( status )
323336 && status . buttonMappings != null ;
@@ -337,22 +350,34 @@ export function DeviceShowcaseSidebar({ snapshot }: { snapshot: ControlSnapshot
337350 } }
338351 />
339352 < div className = "button-map-markers" aria-hidden = "true" >
340- { Object . keys ( status . buttonMappings ?? { } ) . map ( ( button , index ) => {
341- const position = isNoirS1
342- ? noirS1ButtonMarkerPosition ( button )
343- : m2NexButtonMarkerPosition ( button ) ;
344- if ( ! position ) return null ;
345- return (
346- < span
347- key = { button }
348- className = "button-map-marker"
349- style = { { left : `${ position . x * 100 } %` , top : `${ position . y * 100 } %` } }
350- >
351- { String ( index + 1 ) . padStart ( 2 , "0" ) }
352- </ span >
353- ) ;
354- } ) }
353+ { Object . keys ( status . buttonMappings ?? { } )
354+ . filter ( ( button ) => ! isNoirE1 || button . trim ( ) . toLowerCase ( ) !== "dpi" )
355+ . map ( ( button , index ) => {
356+ const position = isNoirS1
357+ ? noirS1ButtonMarkerPosition ( button )
358+ : isNoirE1
359+ ? noirE1ButtonMarkerPosition ( button )
360+ : m2NexButtonMarkerPosition ( button ) ;
361+ if ( ! position ) return null ;
362+ return (
363+ < span
364+ key = { button }
365+ className = "button-map-marker"
366+ style = { { left : `${ position . x * 100 } %` , top : `${ position . y * 100 } %` } }
367+ >
368+ { String ( index + 1 ) . padStart ( 2 , "0" ) }
369+ </ span >
370+ ) ;
371+ } ) }
355372 </ div >
373+ { isNoirE1 ? (
374+ < div className = "button-map-underside-callout" aria-hidden = "true" >
375+ < span className = "button-map-underside-badge" >
376+ < span className = "button-map-underside-number" > 06</ span >
377+ < span > DPI < small > UNDERSIDE</ small > </ span >
378+ </ span >
379+ </ div >
380+ ) : null }
356381 </ div >
357382 ) : (
358383 < img
@@ -719,7 +744,7 @@ function DeviceListView({ snapshot }: { snapshot: ControlSnapshot }): ReactNode
719744 const batteryText = liveStatus ?. batteryPercent != null ? `${ liveStatus . batteryPercent } %` : "–%" ;
720745 const imageSrc = deviceImage ( { vendorId : device . vendorId , productId : device . productId } as HIDDevice , device . name ) ;
721746 const imageFilename = deviceImageFilename ( device . name ) ;
722- const imageClass = imageFilename === "noir-m2-nex.png" || imageFilename === "noir-s1.png"
747+ const imageClass = imageFilename === "noir-m2-nex.png" || imageFilename === "noir-s1.png" || imageFilename === "noir-e1.png"
723748 ? " device-tile-image-is-portrait"
724749 : "" ;
725750 return (
0 commit comments