Repository files navigation

React Node InSim

NPM VersionNode.js CI

A React renderer for InSim buttons, based on Node InSim.

Introduction

Warning

This project is still under development. Any API may change as needed.

React Node InSim is a React renderer for Live for SpeedInSim buttons. It also provides layout components for easier button positioning, hooks for handling incoming InSim packets and tracking server connections & players.

It is based on Node InSim, a Node.js library, written in TypeScript, for InSim communication.

It allows you to create things like this:

Live list of connections and players

Show source code
import{InSim}from'node-insim';importtype{InSimPacketInstance}from'node-insim/packets';import{InSimFlags,IS_MST,PacketType}from'node-insim/packets';import{StrictMode}from'react';import{Button,ConnectionsPlayersProvider,createRoot,useConnections,useOnConnect,useOnPacket,usePlayers,VStack,}from'react-node-insim';functionApp(){// Get the list of current players and connectionsconstplayers=usePlayers();constconnections=useConnections();// Do something after the InSim app has been connected to LFSuseOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});// Handle incoming packetsuseOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});// Clickable buttonsconsthandlePlayerClick=(plid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo PLID ${plid}`}));};consthandleConnectionClick=(ucid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo UCID ${ucid}`}));};return(<><Buttontop={10}left={40}width={30}height={5}UCID={255}color="title">
Players
</Button><VStackbackground="dark"top={15}left={40}width={30}height={5}UCID={255}>{players.map((player)=>(<Buttonkey={player.PLID}onClick={handlePlayerClick(player.PLID)}>{player.PName}</Button>))}</VStack><Buttontop={10}left={70}width={30}height={5}UCID={255}color="title">
Connections
</Button><VStackbackground="dark"top={15}left={70}width={30}height={5}UCID={255}>{connections.map((connection)=>(<Buttonkey={connection.UCID}onClick={handleConnectionClick(connection.UCID)}>{connection.UName}</Button>))}</VStack></>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<StrictMode><ConnectionsPlayersProvider><App/></ConnectionsPlayersProvider></StrictMode>,);

Table of contents

Requirements

Installation

NPM

npm install react@19 node-insim react-node-insim

Yarn

yarn add react@19 node-insim react-node-insim

pnpm

pnpm add react@19 node-insim react-node-insim

Basic usage

Displaying an InSim button on a local computer

import{InSim}from'node-insim';import{InSimFlags}from'node-insim/packets';import{Button,createRoot}from'react-node-insim';constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<Buttontop={100}left={80}width={30}height={10}>
Hello InSim!
</Button>,);

You can use React hooks as usual to display stateful data via InSim.

Button showing current time

import{InSimFlags}from'node-insim/packets';import{useEffect,useState}from'react';import{Button,createRoot}from'react-node-insim';functionApp(){const[time,setTime]=useState(newDate());useEffect(()=>{constinterval=setInterval(()=>{setTime(newDate());},1000);return()=>{clearInterval(interval);};});return(<Buttontop={100}left={80}width={40}height={10}>
Current time: {time.toLocaleTimeString()}</Button>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<App/>);

Button

The Button component is used to display a button in LFS.

  • Buttons are drawn on a 200 by 200 canvas using absolute positioning
  • The maximum number of rendered buttons on a screen is 240

Import

import{Button}from'react-node-insim';

Usage

Button

<Buttontop={100}left={80}width={30}height={10}>
Button
</Button>

Placement

Buttons use XY coordinates to position themselves on the screen. The top and left props control the button's X and Y position on the screen. The allowed range of values is 0 to 200.

Button placement

<><Buttonwidth={12}height={6}top={100}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={100}left={53}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={53}>
Button
</Button></>

Sizes

Use the width and height props to change the dimensions of the button. The allowed range of values is 0 to 200.

Button sizes

<><Buttonvariant="light"top={100}left={40}width={6}height={4}>
Button
</Button><Buttonvariant="light"top={99}left={47}width={10}height={6}>
Button
</Button><Buttonvariant="light"top={97}left={58}width={14}height={10}>
Button
</Button></>

Variants

Use the variant prop to change the button's visual style. You can use light or dark. If you don't specify a variant, the button will have transparent background and a light gray text color.

Button variants

<><Buttontop={100}left={40}width={12}height={6}variant="light">
Button
</Button><Buttontop={100}left={53}width={12}height={6}variant="dark">
Button
</Button></>

Text colors

Use the color prop to customize the button's text color. If you don't specify a color, the button text will be default.

Button text colors

<><Buttontop={73}left={40}width={12}height={6}color="default">
default
</Button><Buttontop={73}left={53}width={12}height={6}color="title">
title
</Button><Buttontop={73}left={66}width={12}height={6}color="unselected">
unselected
</Button><Buttontop={73}left={79}width={12}height={6}color="selected">
selected
</Button><Buttontop={80}left={40}width={12}height={6}color="ok">
ok
</Button><Buttontop={80}left={53}width={12}height={6}color="cancel">
cancel
</Button><Buttontop={80}left={66}width={12}height={6}color="textstring">
textstring
</Button><Buttontop={80}left={79}width={12}height={6}color="unavailable">
unavailable
</Button></>

You can choose from a set of semantic colors or use one of the colors from the LFS color palette.

Semantic colors

  • default
  • title
  • unselected
  • selected
  • ok
  • cancel
  • textstring
  • unavailable

Note: The semantic color values can be customized in LFS Options -> Display -> Interface.

LFS color palette

  • black
  • red
  • green
  • yellow
  • blue
  • magenta
  • cyan
  • white

Background colors

Use the background prop to customize the button's background color. If you don't specify a color, the background will be transparent.

Button background colors

<><Buttontop={67}left={40}width={12}height={6}background="light">
light
</Button><Buttontop={67}left={53}width={12}height={6}background="dark">
dark
</Button><Buttontop={67}left={66}width={12}height={6}background="transparent">
transparent
</Button></>

Horizontal stack

HStack displays buttons in a column without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{HStack}from'react-node-insim';

Usage

Horizontal stack

<HStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></HStack>

Vertical stack

VStack displays buttons in a row without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{VStack}from'react-node-insim';

Usage

Vertical stack

<VStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></VStack>

Flex

Flex layout displays buttons in a row or column with flexbox options.

Import

import{Flex}from'react-node-insim';

Usage

Flex

<Flextop={10}left={20}width={36}height={16}alignItems="center"justifyContent="space-evenly"background="dark"backgroundColor="light"><Buttonwidth={8}height={4}>
Left
</Button><Buttonwidth={10}height={6}>
Center
</Button><Buttonwidth={8}height={4}>
Right
</Button></Flex>

Grid

Grid layout displays buttons in a grid.

Import

import{Grid,GridButton}from'react-node-insim';

Usage

Grid

<Gridtop={30}left={40}width={30}height={30}background="dark"backgroundColor="light"gridTemplateColumns="1fr 2fr 1fr"gridTemplateRows="1fr 3fr 2fr"gridColumnGap={1}gridRowGap={1}><GridButton>1</GridButton><GridButtongridColumnStart={2}gridRowStart={1}gridRowEnd={3}color="title"background="light">
2
</GridButton><GridButtongridColumnStart={3}gridColumnEnd={3}gridRowStart={1}gridRowEnd={3}>
3
</GridButton><GridButtonalignSelf="end"height={10}>
4
</GridButton><GridButtongridColumnStart={1}gridColumnEnd={4}>
5
</GridButton></Grid>

Toggle button

A button that can be toggled on and off by clicking it.

Import

import{ToggleButton}from'react-node-insim';

Usage

Toggle button being toggled on and off

functionApp(){const[isOn,setIsOn]=useState(false);return(<ToggleButtontop={100}left={80}width={12}height={6}isOn={isOn}onToggle={setIsOn}>
Toggle
</ToggleButton>);}

Variants

Use the variant prop to change the button's background style. You can use light or dark. If you don't specify a variant, light will be used.

Toggle button variants

<><ToggleButtonvariant="light"top={100}left={40}width={12}height={6}>
Toggle
</ToggleButton><ToggleButtonvariant="dark"top={100}left={53}width={12}height={6}>
Toggle
</ToggleButton></>

Disabled state

Use the isDisabled prop to prevent toggling the button on/off.

Enabled and disabled toggle buttons

<><ToggleButtonisDisabled={false}variant="light"top={100}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="light"top={100}left={53}width={12}height={6}>
Disabled
</ToggleButton><ToggleButtonisDisabled={false}variant="dark"top={106}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="dark"top={106}left={53}width={12}height={6}>
Disabled
</ToggleButton></>

Toggle button group

A group of buttons that can be toggled on and off by clicking them.

Import

import{ToggleButtonGroup}from'react-node-insim';

Usage

Toggle button group

constoptions=[{label: 'low',value: 1},{label: 'medium',value: 2},{label: 'high',value: 3},];functionApp(){const[selectedOption,setSelectedOption]=useState(options[0]);return(<ToggleButtonGrouptop={30}left={50}width={36}height={6}options={options}selectedOption={selectedOption}onChange={setSelectedOption}/>);}

Text box

A text box whose content can span multiple rows. If the content is too long, the text box will show a scrollbar.

Import

import{TextBox}from'react-node-insim';

Usage

Text box

<TextBoxtop={40}left={50}cols={20}rows={4}width={20}rowHeight={4}variant="light">
Hello world this is a text box lorem ipsum dolor sit amet consectetur
adipisicing elitrea lorem ipsum dolor sit amet consectetur adipisicing elit
</TextBox>

Hooks

useOnConnect

Execute code after the InSim app has been connected.

The first parameter is an IS_VER packet callback executed when IS_VER is received upon successful InSim connection to LFS.

import{useOnConnect}from'react-node-insim';functionApp(){useOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});returnnull;}

useOnDisconnect

Execute code after the InSim app has been disconnected.

The first parameter is the "disconnect" event callback from Node InSim.

import{useOnDisconnect}from'react-node-insim';functionApp(){useOnDisconnect(()=>{console.log('Disconnected from LFS');});returnnull;}

useOnPacket

Execute code when an InSim packet is received

import{useOnPacket}from'react-node-insim';functionApp(){useOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});returnnull;}

useConnections

Get a live list of all connected guests.

import{useConnections}from'react-node-insim';functionApp(){constconnections=useConnections();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{connections.map((connection)=>(<Buttonkey={connection.UCID}>{connection.UName}</Button>))}</VStack>);}

usePlayers

Get a live list of all players on track.

import{usePlayers}from'react-node-insim';functionApp(){constplayers=usePlayers();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{players.map((player)=>(<Buttonkey={player.PLID}>{player.PName}</Button>))}</VStack>);}

useRaceControlMessage

Send a race control message (RCM) to a connection or a player.

import{useRaceControlMessage}from'react-node-insim';functionApp(){const{ sendRaceControlMessageToConnection, sendRaceControlMessageToPlayer }=useRaceControlMessage();return(<><Buttontop={5}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToConnection(packet.UCID,'Hello from React Node InSim',2000,);}}>
Send message to a connection
</Button><Buttontop={10}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToPlayer(12,// PLID'Hello from React Node InSim',2000,);}}>
Send message to a player
</Button></>);}

useInSim

Access to Node InSim API of the current InSim client instance.

import{useInSim}from'react-node-insim';functionApp(){constinSim=useInSim();useEffect(()=>{inSim.send(newIS_MST({Msg: 'App mounted'}));},[]);returnnull;}

Scopes

If you needed to show personalised buttons for each connection or each human player on track, you would need to map over the list of connections/players and pass the correct UCIDs to each button manually. Scopes help in such use cases.

Connection scope

You can show different buttons to each connection by wrapping a sub-tree in a ConnectionScopeProvider, then using the useConnectionScope hook anywhere within that sub-tree to access the connection object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,ConnectionScopeProvider,useConnectionScope,}from'react-node-insim';functionApp(){return(<ConnectionScopeProvider><UserNameButton/></ConnectionScopeProvider>);}functionUserNameButton(){const{ UName }=useConnectionScope();return(<Buttontop={0}left={80}height={5}width={25}>{UName}</Button>);}

Human player scope

You can show different buttons to each human player on track by wrapping a sub-tree in a HumanPlayerScopeProvider, then using the useHumanPlayerScope hook anywhere within that sub-tree to access the player object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,HumanPlayerScopeProvider,useHumanPlayerScope,}from'react-node-insim';functionApp(){return(<HumanPlayerScopeProvider><PlayerNameButton/></HumanPlayerScopeProvider>);}functionUserNameButton(){const{ PName }=useHumanPlayerScope();return(<Buttontop={0}left={80}height={5}width={25}>{PName}</Button>);}

Global scope

You can show the same set of buttons to all connections wrapping a sub-tree in a GlobalScopeProvider.

You don't need to specify the button's UCID in the scope - the correct UCID value of 255 will be injected automatically.

import{Button,GlobalScopeProvider}from'react-node-insim';functionApp(){return(<GlobalScopeProvider><Buttontop={0}left={80}height={5}width={40}>
React Node InSim
</Button></GlobalScopeProvider>);}

Using React Devtools

React Node InSim supports React Devtools out of the box. To enable integration with React Devtools in your application, first ensure you have installed the optional react-devtools-core dependency, and then run your app with the DEV=true environment variable:

DEV=true npm start

Then, start React Devtools itself:

npx react-devtools

After it starts, you should see the component tree of your InSim app. You can even inspect and change the props of components, and see the results immediately in LFS, without restarting it.

Development

Requirements

Installation

yarn

Run example app

yarn start

Lint code

yarn lint

Format code

yarn format

React Node Insim - An open source project by Sim Broadcasts

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

React Node InSim

NPM VersionNode.js CI

A React renderer for InSim buttons, based on Node InSim.

Introduction

Warning

This project is still under development. Any API may change as needed.

React Node InSim is a React renderer for Live for SpeedInSim buttons. It also provides layout components for easier button positioning, hooks for handling incoming InSim packets and tracking server connections & players.

It is based on Node InSim, a Node.js library, written in TypeScript, for InSim communication.

It allows you to create things like this:

Live list of connections and players

Show source code
import{InSim}from'node-insim';importtype{InSimPacketInstance}from'node-insim/packets';import{InSimFlags,IS_MST,PacketType}from'node-insim/packets';import{StrictMode}from'react';import{Button,ConnectionsPlayersProvider,createRoot,useConnections,useOnConnect,useOnPacket,usePlayers,VStack,}from'react-node-insim';functionApp(){// Get the list of current players and connectionsconstplayers=usePlayers();constconnections=useConnections();// Do something after the InSim app has been connected to LFSuseOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});// Handle incoming packetsuseOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});// Clickable buttonsconsthandlePlayerClick=(plid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo PLID ${plid}`}));};consthandleConnectionClick=(ucid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo UCID ${ucid}`}));};return(<><Buttontop={10}left={40}width={30}height={5}UCID={255}color="title">
Players
</Button><VStackbackground="dark"top={15}left={40}width={30}height={5}UCID={255}>{players.map((player)=>(<Buttonkey={player.PLID}onClick={handlePlayerClick(player.PLID)}>{player.PName}</Button>))}</VStack><Buttontop={10}left={70}width={30}height={5}UCID={255}color="title">
Connections
</Button><VStackbackground="dark"top={15}left={70}width={30}height={5}UCID={255}>{connections.map((connection)=>(<Buttonkey={connection.UCID}onClick={handleConnectionClick(connection.UCID)}>{connection.UName}</Button>))}</VStack></>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<StrictMode><ConnectionsPlayersProvider><App/></ConnectionsPlayersProvider></StrictMode>,);

Table of contents

Requirements

Installation

NPM

npm install react@19 node-insim react-node-insim

Yarn

yarn add react@19 node-insim react-node-insim

pnpm

pnpm add react@19 node-insim react-node-insim

Basic usage

Displaying an InSim button on a local computer

import{InSim}from'node-insim';import{InSimFlags}from'node-insim/packets';import{Button,createRoot}from'react-node-insim';constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<Buttontop={100}left={80}width={30}height={10}>
Hello InSim!
</Button>,);

You can use React hooks as usual to display stateful data via InSim.

Button showing current time

import{InSimFlags}from'node-insim/packets';import{useEffect,useState}from'react';import{Button,createRoot}from'react-node-insim';functionApp(){const[time,setTime]=useState(newDate());useEffect(()=>{constinterval=setInterval(()=>{setTime(newDate());},1000);return()=>{clearInterval(interval);};});return(<Buttontop={100}left={80}width={40}height={10}>
Current time: {time.toLocaleTimeString()}</Button>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<App/>);

Button

The Button component is used to display a button in LFS.

  • Buttons are drawn on a 200 by 200 canvas using absolute positioning
  • The maximum number of rendered buttons on a screen is 240

Import

import{Button}from'react-node-insim';

Usage

Button

<Buttontop={100}left={80}width={30}height={10}>
Button
</Button>

Placement

Buttons use XY coordinates to position themselves on the screen. The top and left props control the button's X and Y position on the screen. The allowed range of values is 0 to 200.

Button placement

<><Buttonwidth={12}height={6}top={100}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={100}left={53}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={53}>
Button
</Button></>

Sizes

Use the width and height props to change the dimensions of the button. The allowed range of values is 0 to 200.

Button sizes

<><Buttonvariant="light"top={100}left={40}width={6}height={4}>
Button
</Button><Buttonvariant="light"top={99}left={47}width={10}height={6}>
Button
</Button><Buttonvariant="light"top={97}left={58}width={14}height={10}>
Button
</Button></>

Variants

Use the variant prop to change the button's visual style. You can use light or dark. If you don't specify a variant, the button will have transparent background and a light gray text color.

Button variants

<><Buttontop={100}left={40}width={12}height={6}variant="light">
Button
</Button><Buttontop={100}left={53}width={12}height={6}variant="dark">
Button
</Button></>

Text colors

Use the color prop to customize the button's text color. If you don't specify a color, the button text will be default.

Button text colors

<><Buttontop={73}left={40}width={12}height={6}color="default">
default
</Button><Buttontop={73}left={53}width={12}height={6}color="title">
title
</Button><Buttontop={73}left={66}width={12}height={6}color="unselected">
unselected
</Button><Buttontop={73}left={79}width={12}height={6}color="selected">
selected
</Button><Buttontop={80}left={40}width={12}height={6}color="ok">
ok
</Button><Buttontop={80}left={53}width={12}height={6}color="cancel">
cancel
</Button><Buttontop={80}left={66}width={12}height={6}color="textstring">
textstring
</Button><Buttontop={80}left={79}width={12}height={6}color="unavailable">
unavailable
</Button></>

You can choose from a set of semantic colors or use one of the colors from the LFS color palette.

Semantic colors

  • default
  • title
  • unselected
  • selected
  • ok
  • cancel
  • textstring
  • unavailable

Note: The semantic color values can be customized in LFS Options -> Display -> Interface.

LFS color palette

  • black
  • red
  • green
  • yellow
  • blue
  • magenta
  • cyan
  • white

Background colors

Use the background prop to customize the button's background color. If you don't specify a color, the background will be transparent.

Button background colors

<><Buttontop={67}left={40}width={12}height={6}background="light">
light
</Button><Buttontop={67}left={53}width={12}height={6}background="dark">
dark
</Button><Buttontop={67}left={66}width={12}height={6}background="transparent">
transparent
</Button></>

Horizontal stack

HStack displays buttons in a column without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{HStack}from'react-node-insim';

Usage

Horizontal stack

<HStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></HStack>

Vertical stack

VStack displays buttons in a row without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{VStack}from'react-node-insim';

Usage

Vertical stack

<VStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></VStack>

Flex

Flex layout displays buttons in a row or column with flexbox options.

Import

import{Flex}from'react-node-insim';

Usage

Flex

<Flextop={10}left={20}width={36}height={16}alignItems="center"justifyContent="space-evenly"background="dark"backgroundColor="light"><Buttonwidth={8}height={4}>
Left
</Button><Buttonwidth={10}height={6}>
Center
</Button><Buttonwidth={8}height={4}>
Right
</Button></Flex>

Grid

Grid layout displays buttons in a grid.

Import

import{Grid,GridButton}from'react-node-insim';

Usage

Grid

<Gridtop={30}left={40}width={30}height={30}background="dark"backgroundColor="light"gridTemplateColumns="1fr 2fr 1fr"gridTemplateRows="1fr 3fr 2fr"gridColumnGap={1}gridRowGap={1}><GridButton>1</GridButton><GridButtongridColumnStart={2}gridRowStart={1}gridRowEnd={3}color="title"background="light">
2
</GridButton><GridButtongridColumnStart={3}gridColumnEnd={3}gridRowStart={1}gridRowEnd={3}>
3
</GridButton><GridButtonalignSelf="end"height={10}>
4
</GridButton><GridButtongridColumnStart={1}gridColumnEnd={4}>
5
</GridButton></Grid>

Toggle button

A button that can be toggled on and off by clicking it.

Import

import{ToggleButton}from'react-node-insim';

Usage

Toggle button being toggled on and off

functionApp(){const[isOn,setIsOn]=useState(false);return(<ToggleButtontop={100}left={80}width={12}height={6}isOn={isOn}onToggle={setIsOn}>
Toggle
</ToggleButton>);}

Variants

Use the variant prop to change the button's background style. You can use light or dark. If you don't specify a variant, light will be used.

Toggle button variants

<><ToggleButtonvariant="light"top={100}left={40}width={12}height={6}>
Toggle
</ToggleButton><ToggleButtonvariant="dark"top={100}left={53}width={12}height={6}>
Toggle
</ToggleButton></>

Disabled state

Use the isDisabled prop to prevent toggling the button on/off.

Enabled and disabled toggle buttons

<><ToggleButtonisDisabled={false}variant="light"top={100}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="light"top={100}left={53}width={12}height={6}>
Disabled
</ToggleButton><ToggleButtonisDisabled={false}variant="dark"top={106}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="dark"top={106}left={53}width={12}height={6}>
Disabled
</ToggleButton></>

Toggle button group

A group of buttons that can be toggled on and off by clicking them.

Import

import{ToggleButtonGroup}from'react-node-insim';

Usage

Toggle button group

constoptions=[{label: 'low',value: 1},{label: 'medium',value: 2},{label: 'high',value: 3},];functionApp(){const[selectedOption,setSelectedOption]=useState(options[0]);return(<ToggleButtonGrouptop={30}left={50}width={36}height={6}options={options}selectedOption={selectedOption}onChange={setSelectedOption}/>);}

Text box

A text box whose content can span multiple rows. If the content is too long, the text box will show a scrollbar.

Import

import{TextBox}from'react-node-insim';

Usage

Text box

<TextBoxtop={40}left={50}cols={20}rows={4}width={20}rowHeight={4}variant="light">
Hello world this is a text box lorem ipsum dolor sit amet consectetur
adipisicing elitrea lorem ipsum dolor sit amet consectetur adipisicing elit
</TextBox>

Hooks

useOnConnect

Execute code after the InSim app has been connected.

The first parameter is an IS_VER packet callback executed when IS_VER is received upon successful InSim connection to LFS.

import{useOnConnect}from'react-node-insim';functionApp(){useOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});returnnull;}

useOnDisconnect

Execute code after the InSim app has been disconnected.

The first parameter is the "disconnect" event callback from Node InSim.

import{useOnDisconnect}from'react-node-insim';functionApp(){useOnDisconnect(()=>{console.log('Disconnected from LFS');});returnnull;}

useOnPacket

Execute code when an InSim packet is received

import{useOnPacket}from'react-node-insim';functionApp(){useOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});returnnull;}

useConnections

Get a live list of all connected guests.

import{useConnections}from'react-node-insim';functionApp(){constconnections=useConnections();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{connections.map((connection)=>(<Buttonkey={connection.UCID}>{connection.UName}</Button>))}</VStack>);}

usePlayers

Get a live list of all players on track.

import{usePlayers}from'react-node-insim';functionApp(){constplayers=usePlayers();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{players.map((player)=>(<Buttonkey={player.PLID}>{player.PName}</Button>))}</VStack>);}

useRaceControlMessage

Send a race control message (RCM) to a connection or a player.

import{useRaceControlMessage}from'react-node-insim';functionApp(){const{ sendRaceControlMessageToConnection, sendRaceControlMessageToPlayer }=useRaceControlMessage();return(<><Buttontop={5}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToConnection(packet.UCID,'Hello from React Node InSim',2000,);}}>
Send message to a connection
</Button><Buttontop={10}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToPlayer(12,// PLID'Hello from React Node InSim',2000,);}}>
Send message to a player
</Button></>);}

useInSim

Access to Node InSim API of the current InSim client instance.

import{useInSim}from'react-node-insim';functionApp(){constinSim=useInSim();useEffect(()=>{inSim.send(newIS_MST({Msg: 'App mounted'}));},[]);returnnull;}

Scopes

If you needed to show personalised buttons for each connection or each human player on track, you would need to map over the list of connections/players and pass the correct UCIDs to each button manually. Scopes help in such use cases.

Connection scope

You can show different buttons to each connection by wrapping a sub-tree in a ConnectionScopeProvider, then using the useConnectionScope hook anywhere within that sub-tree to access the connection object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,ConnectionScopeProvider,useConnectionScope,}from'react-node-insim';functionApp(){return(<ConnectionScopeProvider><UserNameButton/></ConnectionScopeProvider>);}functionUserNameButton(){const{ UName }=useConnectionScope();return(<Buttontop={0}left={80}height={5}width={25}>{UName}</Button>);}

Human player scope

You can show different buttons to each human player on track by wrapping a sub-tree in a HumanPlayerScopeProvider, then using the useHumanPlayerScope hook anywhere within that sub-tree to access the player object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,HumanPlayerScopeProvider,useHumanPlayerScope,}from'react-node-insim';functionApp(){return(<HumanPlayerScopeProvider><PlayerNameButton/></HumanPlayerScopeProvider>);}functionUserNameButton(){const{ PName }=useHumanPlayerScope();return(<Buttontop={0}left={80}height={5}width={25}>{PName}</Button>);}

Global scope

You can show the same set of buttons to all connections wrapping a sub-tree in a GlobalScopeProvider.

You don't need to specify the button's UCID in the scope - the correct UCID value of 255 will be injected automatically.

import{Button,GlobalScopeProvider}from'react-node-insim';functionApp(){return(<GlobalScopeProvider><Buttontop={0}left={80}height={5}width={40}>
React Node InSim
</Button></GlobalScopeProvider>);}

Using React Devtools

React Node InSim supports React Devtools out of the box. To enable integration with React Devtools in your application, first ensure you have installed the optional react-devtools-core dependency, and then run your app with the DEV=true environment variable:

DEV=true npm start

Then, start React Devtools itself:

npx react-devtools

After it starts, you should see the component tree of your InSim app. You can even inspect and change the props of components, and see the results immediately in LFS, without restarting it.

Development

Requirements

Installation

yarn

Run example app

yarn start

Lint code

yarn lint

Format code

yarn format

React Node Insim - An open source project by Sim Broadcasts

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

React Node InSim

NPM VersionNode.js CI

A React renderer for InSim buttons, based on Node InSim.

Introduction

Warning

This project is still under development. Any API may change as needed.

React Node InSim is a React renderer for Live for SpeedInSim buttons. It also provides layout components for easier button positioning, hooks for handling incoming InSim packets and tracking server connections & players.

It is based on Node InSim, a Node.js library, written in TypeScript, for InSim communication.

It allows you to create things like this:

Live list of connections and players

Show source code
import{InSim}from'node-insim';importtype{InSimPacketInstance}from'node-insim/packets';import{InSimFlags,IS_MST,PacketType}from'node-insim/packets';import{StrictMode}from'react';import{Button,ConnectionsPlayersProvider,createRoot,useConnections,useOnConnect,useOnPacket,usePlayers,VStack,}from'react-node-insim';functionApp(){// Get the list of current players and connectionsconstplayers=usePlayers();constconnections=useConnections();// Do something after the InSim app has been connected to LFSuseOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});// Handle incoming packetsuseOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});// Clickable buttonsconsthandlePlayerClick=(plid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo PLID ${plid}`}));};consthandleConnectionClick=(ucid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo UCID ${ucid}`}));};return(<><Buttontop={10}left={40}width={30}height={5}UCID={255}color="title">
Players
</Button><VStackbackground="dark"top={15}left={40}width={30}height={5}UCID={255}>{players.map((player)=>(<Buttonkey={player.PLID}onClick={handlePlayerClick(player.PLID)}>{player.PName}</Button>))}</VStack><Buttontop={10}left={70}width={30}height={5}UCID={255}color="title">
Connections
</Button><VStackbackground="dark"top={15}left={70}width={30}height={5}UCID={255}>{connections.map((connection)=>(<Buttonkey={connection.UCID}onClick={handleConnectionClick(connection.UCID)}>{connection.UName}</Button>))}</VStack></>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<StrictMode><ConnectionsPlayersProvider><App/></ConnectionsPlayersProvider></StrictMode>,);

Table of contents

Requirements

Installation

NPM

npm install react@19 node-insim react-node-insim

Yarn

yarn add react@19 node-insim react-node-insim

pnpm

pnpm add react@19 node-insim react-node-insim

Basic usage

Displaying an InSim button on a local computer

import{InSim}from'node-insim';import{InSimFlags}from'node-insim/packets';import{Button,createRoot}from'react-node-insim';constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<Buttontop={100}left={80}width={30}height={10}>
Hello InSim!
</Button>,);

You can use React hooks as usual to display stateful data via InSim.

Button showing current time

import{InSimFlags}from'node-insim/packets';import{useEffect,useState}from'react';import{Button,createRoot}from'react-node-insim';functionApp(){const[time,setTime]=useState(newDate());useEffect(()=>{constinterval=setInterval(()=>{setTime(newDate());},1000);return()=>{clearInterval(interval);};});return(<Buttontop={100}left={80}width={40}height={10}>
Current time: {time.toLocaleTimeString()}</Button>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<App/>);

Button

The Button component is used to display a button in LFS.

  • Buttons are drawn on a 200 by 200 canvas using absolute positioning
  • The maximum number of rendered buttons on a screen is 240

Import

import{Button}from'react-node-insim';

Usage

Button

<Buttontop={100}left={80}width={30}height={10}>
Button
</Button>

Placement

Buttons use XY coordinates to position themselves on the screen. The top and left props control the button's X and Y position on the screen. The allowed range of values is 0 to 200.

Button placement

<><Buttonwidth={12}height={6}top={100}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={100}left={53}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={53}>
Button
</Button></>

Sizes

Use the width and height props to change the dimensions of the button. The allowed range of values is 0 to 200.

Button sizes

<><Buttonvariant="light"top={100}left={40}width={6}height={4}>
Button
</Button><Buttonvariant="light"top={99}left={47}width={10}height={6}>
Button
</Button><Buttonvariant="light"top={97}left={58}width={14}height={10}>
Button
</Button></>

Variants

Use the variant prop to change the button's visual style. You can use light or dark. If you don't specify a variant, the button will have transparent background and a light gray text color.

Button variants

<><Buttontop={100}left={40}width={12}height={6}variant="light">
Button
</Button><Buttontop={100}left={53}width={12}height={6}variant="dark">
Button
</Button></>

Text colors

Use the color prop to customize the button's text color. If you don't specify a color, the button text will be default.

Button text colors

<><Buttontop={73}left={40}width={12}height={6}color="default">
default
</Button><Buttontop={73}left={53}width={12}height={6}color="title">
title
</Button><Buttontop={73}left={66}width={12}height={6}color="unselected">
unselected
</Button><Buttontop={73}left={79}width={12}height={6}color="selected">
selected
</Button><Buttontop={80}left={40}width={12}height={6}color="ok">
ok
</Button><Buttontop={80}left={53}width={12}height={6}color="cancel">
cancel
</Button><Buttontop={80}left={66}width={12}height={6}color="textstring">
textstring
</Button><Buttontop={80}left={79}width={12}height={6}color="unavailable">
unavailable
</Button></>

You can choose from a set of semantic colors or use one of the colors from the LFS color palette.

Semantic colors

  • default
  • title
  • unselected
  • selected
  • ok
  • cancel
  • textstring
  • unavailable

Note: The semantic color values can be customized in LFS Options -> Display -> Interface.

LFS color palette

  • black
  • red
  • green
  • yellow
  • blue
  • magenta
  • cyan
  • white

Background colors

Use the background prop to customize the button's background color. If you don't specify a color, the background will be transparent.

Button background colors

<><Buttontop={67}left={40}width={12}height={6}background="light">
light
</Button><Buttontop={67}left={53}width={12}height={6}background="dark">
dark
</Button><Buttontop={67}left={66}width={12}height={6}background="transparent">
transparent
</Button></>

Horizontal stack

HStack displays buttons in a column without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{HStack}from'react-node-insim';

Usage

Horizontal stack

<HStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></HStack>

Vertical stack

VStack displays buttons in a row without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{VStack}from'react-node-insim';

Usage

Vertical stack

<VStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></VStack>

Flex

Flex layout displays buttons in a row or column with flexbox options.

Import

import{Flex}from'react-node-insim';

Usage

Flex

<Flextop={10}left={20}width={36}height={16}alignItems="center"justifyContent="space-evenly"background="dark"backgroundColor="light"><Buttonwidth={8}height={4}>
Left
</Button><Buttonwidth={10}height={6}>
Center
</Button><Buttonwidth={8}height={4}>
Right
</Button></Flex>

Grid

Grid layout displays buttons in a grid.

Import

import{Grid,GridButton}from'react-node-insim';

Usage

Grid

<Gridtop={30}left={40}width={30}height={30}background="dark"backgroundColor="light"gridTemplateColumns="1fr 2fr 1fr"gridTemplateRows="1fr 3fr 2fr"gridColumnGap={1}gridRowGap={1}><GridButton>1</GridButton><GridButtongridColumnStart={2}gridRowStart={1}gridRowEnd={3}color="title"background="light">
2
</GridButton><GridButtongridColumnStart={3}gridColumnEnd={3}gridRowStart={1}gridRowEnd={3}>
3
</GridButton><GridButtonalignSelf="end"height={10}>
4
</GridButton><GridButtongridColumnStart={1}gridColumnEnd={4}>
5
</GridButton></Grid>

Toggle button

A button that can be toggled on and off by clicking it.

Import

import{ToggleButton}from'react-node-insim';

Usage

Toggle button being toggled on and off

functionApp(){const[isOn,setIsOn]=useState(false);return(<ToggleButtontop={100}left={80}width={12}height={6}isOn={isOn}onToggle={setIsOn}>
Toggle
</ToggleButton>);}

Variants

Use the variant prop to change the button's background style. You can use light or dark. If you don't specify a variant, light will be used.

Toggle button variants

<><ToggleButtonvariant="light"top={100}left={40}width={12}height={6}>
Toggle
</ToggleButton><ToggleButtonvariant="dark"top={100}left={53}width={12}height={6}>
Toggle
</ToggleButton></>

Disabled state

Use the isDisabled prop to prevent toggling the button on/off.

Enabled and disabled toggle buttons

<><ToggleButtonisDisabled={false}variant="light"top={100}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="light"top={100}left={53}width={12}height={6}>
Disabled
</ToggleButton><ToggleButtonisDisabled={false}variant="dark"top={106}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="dark"top={106}left={53}width={12}height={6}>
Disabled
</ToggleButton></>

Toggle button group

A group of buttons that can be toggled on and off by clicking them.

Import

import{ToggleButtonGroup}from'react-node-insim';

Usage

Toggle button group

constoptions=[{label: 'low',value: 1},{label: 'medium',value: 2},{label: 'high',value: 3},];functionApp(){const[selectedOption,setSelectedOption]=useState(options[0]);return(<ToggleButtonGrouptop={30}left={50}width={36}height={6}options={options}selectedOption={selectedOption}onChange={setSelectedOption}/>);}

Text box

A text box whose content can span multiple rows. If the content is too long, the text box will show a scrollbar.

Import

import{TextBox}from'react-node-insim';

Usage

Text box

<TextBoxtop={40}left={50}cols={20}rows={4}width={20}rowHeight={4}variant="light">
Hello world this is a text box lorem ipsum dolor sit amet consectetur
adipisicing elitrea lorem ipsum dolor sit amet consectetur adipisicing elit
</TextBox>

Hooks

useOnConnect

Execute code after the InSim app has been connected.

The first parameter is an IS_VER packet callback executed when IS_VER is received upon successful InSim connection to LFS.

import{useOnConnect}from'react-node-insim';functionApp(){useOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});returnnull;}

useOnDisconnect

Execute code after the InSim app has been disconnected.

The first parameter is the "disconnect" event callback from Node InSim.

import{useOnDisconnect}from'react-node-insim';functionApp(){useOnDisconnect(()=>{console.log('Disconnected from LFS');});returnnull;}

useOnPacket

Execute code when an InSim packet is received

import{useOnPacket}from'react-node-insim';functionApp(){useOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});returnnull;}

useConnections

Get a live list of all connected guests.

import{useConnections}from'react-node-insim';functionApp(){constconnections=useConnections();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{connections.map((connection)=>(<Buttonkey={connection.UCID}>{connection.UName}</Button>))}</VStack>);}

usePlayers

Get a live list of all players on track.

import{usePlayers}from'react-node-insim';functionApp(){constplayers=usePlayers();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{players.map((player)=>(<Buttonkey={player.PLID}>{player.PName}</Button>))}</VStack>);}

useRaceControlMessage

Send a race control message (RCM) to a connection or a player.

import{useRaceControlMessage}from'react-node-insim';functionApp(){const{ sendRaceControlMessageToConnection, sendRaceControlMessageToPlayer }=useRaceControlMessage();return(<><Buttontop={5}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToConnection(packet.UCID,'Hello from React Node InSim',2000,);}}>
Send message to a connection
</Button><Buttontop={10}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToPlayer(12,// PLID'Hello from React Node InSim',2000,);}}>
Send message to a player
</Button></>);}

useInSim

Access to Node InSim API of the current InSim client instance.

import{useInSim}from'react-node-insim';functionApp(){constinSim=useInSim();useEffect(()=>{inSim.send(newIS_MST({Msg: 'App mounted'}));},[]);returnnull;}

Scopes

If you needed to show personalised buttons for each connection or each human player on track, you would need to map over the list of connections/players and pass the correct UCIDs to each button manually. Scopes help in such use cases.

Connection scope

You can show different buttons to each connection by wrapping a sub-tree in a ConnectionScopeProvider, then using the useConnectionScope hook anywhere within that sub-tree to access the connection object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,ConnectionScopeProvider,useConnectionScope,}from'react-node-insim';functionApp(){return(<ConnectionScopeProvider><UserNameButton/></ConnectionScopeProvider>);}functionUserNameButton(){const{ UName }=useConnectionScope();return(<Buttontop={0}left={80}height={5}width={25}>{UName}</Button>);}

Human player scope

You can show different buttons to each human player on track by wrapping a sub-tree in a HumanPlayerScopeProvider, then using the useHumanPlayerScope hook anywhere within that sub-tree to access the player object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,HumanPlayerScopeProvider,useHumanPlayerScope,}from'react-node-insim';functionApp(){return(<HumanPlayerScopeProvider><PlayerNameButton/></HumanPlayerScopeProvider>);}functionUserNameButton(){const{ PName }=useHumanPlayerScope();return(<Buttontop={0}left={80}height={5}width={25}>{PName}</Button>);}

Global scope

You can show the same set of buttons to all connections wrapping a sub-tree in a GlobalScopeProvider.

You don't need to specify the button's UCID in the scope - the correct UCID value of 255 will be injected automatically.

import{Button,GlobalScopeProvider}from'react-node-insim';functionApp(){return(<GlobalScopeProvider><Buttontop={0}left={80}height={5}width={40}>
React Node InSim
</Button></GlobalScopeProvider>);}

Using React Devtools

React Node InSim supports React Devtools out of the box. To enable integration with React Devtools in your application, first ensure you have installed the optional react-devtools-core dependency, and then run your app with the DEV=true environment variable:

DEV=true npm start

Then, start React Devtools itself:

npx react-devtools

After it starts, you should see the component tree of your InSim app. You can even inspect and change the props of components, and see the results immediately in LFS, without restarting it.

Development

Requirements

Installation

yarn

Run example app

yarn start

Lint code

yarn lint

Format code

yarn format

React Node Insim - An open source project by Sim Broadcasts

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

React Node InSim

NPM VersionNode.js CI

A React renderer for InSim buttons, based on Node InSim.

Introduction

Warning

This project is still under development. Any API may change as needed.

React Node InSim is a React renderer for Live for SpeedInSim buttons. It also provides layout components for easier button positioning, hooks for handling incoming InSim packets and tracking server connections & players.

It is based on Node InSim, a Node.js library, written in TypeScript, for InSim communication.

It allows you to create things like this:

Live list of connections and players

Show source code
import{InSim}from'node-insim';importtype{InSimPacketInstance}from'node-insim/packets';import{InSimFlags,IS_MST,PacketType}from'node-insim/packets';import{StrictMode}from'react';import{Button,ConnectionsPlayersProvider,createRoot,useConnections,useOnConnect,useOnPacket,usePlayers,VStack,}from'react-node-insim';functionApp(){// Get the list of current players and connectionsconstplayers=usePlayers();constconnections=useConnections();// Do something after the InSim app has been connected to LFSuseOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});// Handle incoming packetsuseOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});// Clickable buttonsconsthandlePlayerClick=(plid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo PLID ${plid}`}));};consthandleConnectionClick=(ucid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo UCID ${ucid}`}));};return(<><Buttontop={10}left={40}width={30}height={5}UCID={255}color="title">
Players
</Button><VStackbackground="dark"top={15}left={40}width={30}height={5}UCID={255}>{players.map((player)=>(<Buttonkey={player.PLID}onClick={handlePlayerClick(player.PLID)}>{player.PName}</Button>))}</VStack><Buttontop={10}left={70}width={30}height={5}UCID={255}color="title">
Connections
</Button><VStackbackground="dark"top={15}left={70}width={30}height={5}UCID={255}>{connections.map((connection)=>(<Buttonkey={connection.UCID}onClick={handleConnectionClick(connection.UCID)}>{connection.UName}</Button>))}</VStack></>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<StrictMode><ConnectionsPlayersProvider><App/></ConnectionsPlayersProvider></StrictMode>,);

Table of contents

Requirements

Installation

NPM

npm install react@19 node-insim react-node-insim

Yarn

yarn add react@19 node-insim react-node-insim

pnpm

pnpm add react@19 node-insim react-node-insim

Basic usage

Displaying an InSim button on a local computer

import{InSim}from'node-insim';import{InSimFlags}from'node-insim/packets';import{Button,createRoot}from'react-node-insim';constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<Buttontop={100}left={80}width={30}height={10}>
Hello InSim!
</Button>,);

You can use React hooks as usual to display stateful data via InSim.

Button showing current time

import{InSimFlags}from'node-insim/packets';import{useEffect,useState}from'react';import{Button,createRoot}from'react-node-insim';functionApp(){const[time,setTime]=useState(newDate());useEffect(()=>{constinterval=setInterval(()=>{setTime(newDate());},1000);return()=>{clearInterval(interval);};});return(<Buttontop={100}left={80}width={40}height={10}>
Current time: {time.toLocaleTimeString()}</Button>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<App/>);

Button

The Button component is used to display a button in LFS.

  • Buttons are drawn on a 200 by 200 canvas using absolute positioning
  • The maximum number of rendered buttons on a screen is 240

Import

import{Button}from'react-node-insim';

Usage

Button

<Buttontop={100}left={80}width={30}height={10}>
Button
</Button>

Placement

Buttons use XY coordinates to position themselves on the screen. The top and left props control the button's X and Y position on the screen. The allowed range of values is 0 to 200.

Button placement

<><Buttonwidth={12}height={6}top={100}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={100}left={53}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={53}>
Button
</Button></>

Sizes

Use the width and height props to change the dimensions of the button. The allowed range of values is 0 to 200.

Button sizes

<><Buttonvariant="light"top={100}left={40}width={6}height={4}>
Button
</Button><Buttonvariant="light"top={99}left={47}width={10}height={6}>
Button
</Button><Buttonvariant="light"top={97}left={58}width={14}height={10}>
Button
</Button></>

Variants

Use the variant prop to change the button's visual style. You can use light or dark. If you don't specify a variant, the button will have transparent background and a light gray text color.

Button variants

<><Buttontop={100}left={40}width={12}height={6}variant="light">
Button
</Button><Buttontop={100}left={53}width={12}height={6}variant="dark">
Button
</Button></>

Text colors

Use the color prop to customize the button's text color. If you don't specify a color, the button text will be default.

Button text colors

<><Buttontop={73}left={40}width={12}height={6}color="default">
default
</Button><Buttontop={73}left={53}width={12}height={6}color="title">
title
</Button><Buttontop={73}left={66}width={12}height={6}color="unselected">
unselected
</Button><Buttontop={73}left={79}width={12}height={6}color="selected">
selected
</Button><Buttontop={80}left={40}width={12}height={6}color="ok">
ok
</Button><Buttontop={80}left={53}width={12}height={6}color="cancel">
cancel
</Button><Buttontop={80}left={66}width={12}height={6}color="textstring">
textstring
</Button><Buttontop={80}left={79}width={12}height={6}color="unavailable">
unavailable
</Button></>

You can choose from a set of semantic colors or use one of the colors from the LFS color palette.

Semantic colors

  • default
  • title
  • unselected
  • selected
  • ok
  • cancel
  • textstring
  • unavailable

Note: The semantic color values can be customized in LFS Options -> Display -> Interface.

LFS color palette

  • black
  • red
  • green
  • yellow
  • blue
  • magenta
  • cyan
  • white

Background colors

Use the background prop to customize the button's background color. If you don't specify a color, the background will be transparent.

Button background colors

<><Buttontop={67}left={40}width={12}height={6}background="light">
light
</Button><Buttontop={67}left={53}width={12}height={6}background="dark">
dark
</Button><Buttontop={67}left={66}width={12}height={6}background="transparent">
transparent
</Button></>

Horizontal stack

HStack displays buttons in a column without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{HStack}from'react-node-insim';

Usage

Horizontal stack

<HStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></HStack>

Vertical stack

VStack displays buttons in a row without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{VStack}from'react-node-insim';

Usage

Vertical stack

<VStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></VStack>

Flex

Flex layout displays buttons in a row or column with flexbox options.

Import

import{Flex}from'react-node-insim';

Usage

Flex

<Flextop={10}left={20}width={36}height={16}alignItems="center"justifyContent="space-evenly"background="dark"backgroundColor="light"><Buttonwidth={8}height={4}>
Left
</Button><Buttonwidth={10}height={6}>
Center
</Button><Buttonwidth={8}height={4}>
Right
</Button></Flex>

Grid

Grid layout displays buttons in a grid.

Import

import{Grid,GridButton}from'react-node-insim';

Usage

Grid

<Gridtop={30}left={40}width={30}height={30}background="dark"backgroundColor="light"gridTemplateColumns="1fr 2fr 1fr"gridTemplateRows="1fr 3fr 2fr"gridColumnGap={1}gridRowGap={1}><GridButton>1</GridButton><GridButtongridColumnStart={2}gridRowStart={1}gridRowEnd={3}color="title"background="light">
2
</GridButton><GridButtongridColumnStart={3}gridColumnEnd={3}gridRowStart={1}gridRowEnd={3}>
3
</GridButton><GridButtonalignSelf="end"height={10}>
4
</GridButton><GridButtongridColumnStart={1}gridColumnEnd={4}>
5
</GridButton></Grid>

Toggle button

A button that can be toggled on and off by clicking it.

Import

import{ToggleButton}from'react-node-insim';

Usage

Toggle button being toggled on and off

functionApp(){const[isOn,setIsOn]=useState(false);return(<ToggleButtontop={100}left={80}width={12}height={6}isOn={isOn}onToggle={setIsOn}>
Toggle
</ToggleButton>);}

Variants

Use the variant prop to change the button's background style. You can use light or dark. If you don't specify a variant, light will be used.

Toggle button variants

<><ToggleButtonvariant="light"top={100}left={40}width={12}height={6}>
Toggle
</ToggleButton><ToggleButtonvariant="dark"top={100}left={53}width={12}height={6}>
Toggle
</ToggleButton></>

Disabled state

Use the isDisabled prop to prevent toggling the button on/off.

Enabled and disabled toggle buttons

<><ToggleButtonisDisabled={false}variant="light"top={100}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="light"top={100}left={53}width={12}height={6}>
Disabled
</ToggleButton><ToggleButtonisDisabled={false}variant="dark"top={106}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="dark"top={106}left={53}width={12}height={6}>
Disabled
</ToggleButton></>

Toggle button group

A group of buttons that can be toggled on and off by clicking them.

Import

import{ToggleButtonGroup}from'react-node-insim';

Usage

Toggle button group

constoptions=[{label: 'low',value: 1},{label: 'medium',value: 2},{label: 'high',value: 3},];functionApp(){const[selectedOption,setSelectedOption]=useState(options[0]);return(<ToggleButtonGrouptop={30}left={50}width={36}height={6}options={options}selectedOption={selectedOption}onChange={setSelectedOption}/>);}

Text box

A text box whose content can span multiple rows. If the content is too long, the text box will show a scrollbar.

Import

import{TextBox}from'react-node-insim';

Usage

Text box

<TextBoxtop={40}left={50}cols={20}rows={4}width={20}rowHeight={4}variant="light">
Hello world this is a text box lorem ipsum dolor sit amet consectetur
adipisicing elitrea lorem ipsum dolor sit amet consectetur adipisicing elit
</TextBox>

Hooks

useOnConnect

Execute code after the InSim app has been connected.

The first parameter is an IS_VER packet callback executed when IS_VER is received upon successful InSim connection to LFS.

import{useOnConnect}from'react-node-insim';functionApp(){useOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});returnnull;}

useOnDisconnect

Execute code after the InSim app has been disconnected.

The first parameter is the "disconnect" event callback from Node InSim.

import{useOnDisconnect}from'react-node-insim';functionApp(){useOnDisconnect(()=>{console.log('Disconnected from LFS');});returnnull;}

useOnPacket

Execute code when an InSim packet is received

import{useOnPacket}from'react-node-insim';functionApp(){useOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});returnnull;}

useConnections

Get a live list of all connected guests.

import{useConnections}from'react-node-insim';functionApp(){constconnections=useConnections();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{connections.map((connection)=>(<Buttonkey={connection.UCID}>{connection.UName}</Button>))}</VStack>);}

usePlayers

Get a live list of all players on track.

import{usePlayers}from'react-node-insim';functionApp(){constplayers=usePlayers();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{players.map((player)=>(<Buttonkey={player.PLID}>{player.PName}</Button>))}</VStack>);}

useRaceControlMessage

Send a race control message (RCM) to a connection or a player.

import{useRaceControlMessage}from'react-node-insim';functionApp(){const{ sendRaceControlMessageToConnection, sendRaceControlMessageToPlayer }=useRaceControlMessage();return(<><Buttontop={5}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToConnection(packet.UCID,'Hello from React Node InSim',2000,);}}>
Send message to a connection
</Button><Buttontop={10}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToPlayer(12,// PLID'Hello from React Node InSim',2000,);}}>
Send message to a player
</Button></>);}

useInSim

Access to Node InSim API of the current InSim client instance.

import{useInSim}from'react-node-insim';functionApp(){constinSim=useInSim();useEffect(()=>{inSim.send(newIS_MST({Msg: 'App mounted'}));},[]);returnnull;}

Scopes

If you needed to show personalised buttons for each connection or each human player on track, you would need to map over the list of connections/players and pass the correct UCIDs to each button manually. Scopes help in such use cases.

Connection scope

You can show different buttons to each connection by wrapping a sub-tree in a ConnectionScopeProvider, then using the useConnectionScope hook anywhere within that sub-tree to access the connection object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,ConnectionScopeProvider,useConnectionScope,}from'react-node-insim';functionApp(){return(<ConnectionScopeProvider><UserNameButton/></ConnectionScopeProvider>);}functionUserNameButton(){const{ UName }=useConnectionScope();return(<Buttontop={0}left={80}height={5}width={25}>{UName}</Button>);}

Human player scope

You can show different buttons to each human player on track by wrapping a sub-tree in a HumanPlayerScopeProvider, then using the useHumanPlayerScope hook anywhere within that sub-tree to access the player object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,HumanPlayerScopeProvider,useHumanPlayerScope,}from'react-node-insim';functionApp(){return(<HumanPlayerScopeProvider><PlayerNameButton/></HumanPlayerScopeProvider>);}functionUserNameButton(){const{ PName }=useHumanPlayerScope();return(<Buttontop={0}left={80}height={5}width={25}>{PName}</Button>);}

Global scope

You can show the same set of buttons to all connections wrapping a sub-tree in a GlobalScopeProvider.

You don't need to specify the button's UCID in the scope - the correct UCID value of 255 will be injected automatically.

import{Button,GlobalScopeProvider}from'react-node-insim';functionApp(){return(<GlobalScopeProvider><Buttontop={0}left={80}height={5}width={40}>
React Node InSim
</Button></GlobalScopeProvider>);}

Using React Devtools

React Node InSim supports React Devtools out of the box. To enable integration with React Devtools in your application, first ensure you have installed the optional react-devtools-core dependency, and then run your app with the DEV=true environment variable:

DEV=true npm start

Then, start React Devtools itself:

npx react-devtools

After it starts, you should see the component tree of your InSim app. You can even inspect and change the props of components, and see the results immediately in LFS, without restarting it.

Development

Requirements

Installation

yarn

Run example app

yarn start

Lint code

yarn lint

Format code

yarn format

React Node Insim - An open source project by Sim Broadcasts

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

React Node InSim

NPM VersionNode.js CI

A React renderer for InSim buttons, based on Node InSim.

Introduction

Warning

This project is still under development. Any API may change as needed.

React Node InSim is a React renderer for Live for SpeedInSim buttons. It also provides layout components for easier button positioning, hooks for handling incoming InSim packets and tracking server connections & players.

It is based on Node InSim, a Node.js library, written in TypeScript, for InSim communication.

It allows you to create things like this:

Live list of connections and players

Show source code
import{InSim}from'node-insim';importtype{InSimPacketInstance}from'node-insim/packets';import{InSimFlags,IS_MST,PacketType}from'node-insim/packets';import{StrictMode}from'react';import{Button,ConnectionsPlayersProvider,createRoot,useConnections,useOnConnect,useOnPacket,usePlayers,VStack,}from'react-node-insim';functionApp(){// Get the list of current players and connectionsconstplayers=usePlayers();constconnections=useConnections();// Do something after the InSim app has been connected to LFSuseOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});// Handle incoming packetsuseOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});// Clickable buttonsconsthandlePlayerClick=(plid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo PLID ${plid}`}));};consthandleConnectionClick=(ucid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo UCID ${ucid}`}));};return(<><Buttontop={10}left={40}width={30}height={5}UCID={255}color="title">
Players
</Button><VStackbackground="dark"top={15}left={40}width={30}height={5}UCID={255}>{players.map((player)=>(<Buttonkey={player.PLID}onClick={handlePlayerClick(player.PLID)}>{player.PName}</Button>))}</VStack><Buttontop={10}left={70}width={30}height={5}UCID={255}color="title">
Connections
</Button><VStackbackground="dark"top={15}left={70}width={30}height={5}UCID={255}>{connections.map((connection)=>(<Buttonkey={connection.UCID}onClick={handleConnectionClick(connection.UCID)}>{connection.UName}</Button>))}</VStack></>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<StrictMode><ConnectionsPlayersProvider><App/></ConnectionsPlayersProvider></StrictMode>,);

Table of contents

Requirements

Installation

NPM

npm install react@19 node-insim react-node-insim

Yarn

yarn add react@19 node-insim react-node-insim

pnpm

pnpm add react@19 node-insim react-node-insim

Basic usage

Displaying an InSim button on a local computer

import{InSim}from'node-insim';import{InSimFlags}from'node-insim/packets';import{Button,createRoot}from'react-node-insim';constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<Buttontop={100}left={80}width={30}height={10}>
Hello InSim!
</Button>,);

You can use React hooks as usual to display stateful data via InSim.

Button showing current time

import{InSimFlags}from'node-insim/packets';import{useEffect,useState}from'react';import{Button,createRoot}from'react-node-insim';functionApp(){const[time,setTime]=useState(newDate());useEffect(()=>{constinterval=setInterval(()=>{setTime(newDate());},1000);return()=>{clearInterval(interval);};});return(<Buttontop={100}left={80}width={40}height={10}>
Current time: {time.toLocaleTimeString()}</Button>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<App/>);

Button

The Button component is used to display a button in LFS.

  • Buttons are drawn on a 200 by 200 canvas using absolute positioning
  • The maximum number of rendered buttons on a screen is 240

Import

import{Button}from'react-node-insim';

Usage

Button

<Buttontop={100}left={80}width={30}height={10}>
Button
</Button>

Placement

Buttons use XY coordinates to position themselves on the screen. The top and left props control the button's X and Y position on the screen. The allowed range of values is 0 to 200.

Button placement

<><Buttonwidth={12}height={6}top={100}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={100}left={53}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={53}>
Button
</Button></>

Sizes

Use the width and height props to change the dimensions of the button. The allowed range of values is 0 to 200.

Button sizes

<><Buttonvariant="light"top={100}left={40}width={6}height={4}>
Button
</Button><Buttonvariant="light"top={99}left={47}width={10}height={6}>
Button
</Button><Buttonvariant="light"top={97}left={58}width={14}height={10}>
Button
</Button></>

Variants

Use the variant prop to change the button's visual style. You can use light or dark. If you don't specify a variant, the button will have transparent background and a light gray text color.

Button variants

<><Buttontop={100}left={40}width={12}height={6}variant="light">
Button
</Button><Buttontop={100}left={53}width={12}height={6}variant="dark">
Button
</Button></>

Text colors

Use the color prop to customize the button's text color. If you don't specify a color, the button text will be default.

Button text colors

<><Buttontop={73}left={40}width={12}height={6}color="default">
default
</Button><Buttontop={73}left={53}width={12}height={6}color="title">
title
</Button><Buttontop={73}left={66}width={12}height={6}color="unselected">
unselected
</Button><Buttontop={73}left={79}width={12}height={6}color="selected">
selected
</Button><Buttontop={80}left={40}width={12}height={6}color="ok">
ok
</Button><Buttontop={80}left={53}width={12}height={6}color="cancel">
cancel
</Button><Buttontop={80}left={66}width={12}height={6}color="textstring">
textstring
</Button><Buttontop={80}left={79}width={12}height={6}color="unavailable">
unavailable
</Button></>

You can choose from a set of semantic colors or use one of the colors from the LFS color palette.

Semantic colors

  • default
  • title
  • unselected
  • selected
  • ok
  • cancel
  • textstring
  • unavailable

Note: The semantic color values can be customized in LFS Options -> Display -> Interface.

LFS color palette

  • black
  • red
  • green
  • yellow
  • blue
  • magenta
  • cyan
  • white

Background colors

Use the background prop to customize the button's background color. If you don't specify a color, the background will be transparent.

Button background colors

<><Buttontop={67}left={40}width={12}height={6}background="light">
light
</Button><Buttontop={67}left={53}width={12}height={6}background="dark">
dark
</Button><Buttontop={67}left={66}width={12}height={6}background="transparent">
transparent
</Button></>

Horizontal stack

HStack displays buttons in a column without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{HStack}from'react-node-insim';

Usage

Horizontal stack

<HStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></HStack>

Vertical stack

VStack displays buttons in a row without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{VStack}from'react-node-insim';

Usage

Vertical stack

<VStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></VStack>

Flex

Flex layout displays buttons in a row or column with flexbox options.

Import

import{Flex}from'react-node-insim';

Usage

Flex

<Flextop={10}left={20}width={36}height={16}alignItems="center"justifyContent="space-evenly"background="dark"backgroundColor="light"><Buttonwidth={8}height={4}>
Left
</Button><Buttonwidth={10}height={6}>
Center
</Button><Buttonwidth={8}height={4}>
Right
</Button></Flex>

Grid

Grid layout displays buttons in a grid.

Import

import{Grid,GridButton}from'react-node-insim';

Usage

Grid

<Gridtop={30}left={40}width={30}height={30}background="dark"backgroundColor="light"gridTemplateColumns="1fr 2fr 1fr"gridTemplateRows="1fr 3fr 2fr"gridColumnGap={1}gridRowGap={1}><GridButton>1</GridButton><GridButtongridColumnStart={2}gridRowStart={1}gridRowEnd={3}color="title"background="light">
2
</GridButton><GridButtongridColumnStart={3}gridColumnEnd={3}gridRowStart={1}gridRowEnd={3}>
3
</GridButton><GridButtonalignSelf="end"height={10}>
4
</GridButton><GridButtongridColumnStart={1}gridColumnEnd={4}>
5
</GridButton></Grid>

Toggle button

A button that can be toggled on and off by clicking it.

Import

import{ToggleButton}from'react-node-insim';

Usage

Toggle button being toggled on and off

functionApp(){const[isOn,setIsOn]=useState(false);return(<ToggleButtontop={100}left={80}width={12}height={6}isOn={isOn}onToggle={setIsOn}>
Toggle
</ToggleButton>);}

Variants

Use the variant prop to change the button's background style. You can use light or dark. If you don't specify a variant, light will be used.

Toggle button variants

<><ToggleButtonvariant="light"top={100}left={40}width={12}height={6}>
Toggle
</ToggleButton><ToggleButtonvariant="dark"top={100}left={53}width={12}height={6}>
Toggle
</ToggleButton></>

Disabled state

Use the isDisabled prop to prevent toggling the button on/off.

Enabled and disabled toggle buttons

<><ToggleButtonisDisabled={false}variant="light"top={100}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="light"top={100}left={53}width={12}height={6}>
Disabled
</ToggleButton><ToggleButtonisDisabled={false}variant="dark"top={106}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="dark"top={106}left={53}width={12}height={6}>
Disabled
</ToggleButton></>

Toggle button group

A group of buttons that can be toggled on and off by clicking them.

Import

import{ToggleButtonGroup}from'react-node-insim';

Usage

Toggle button group

constoptions=[{label: 'low',value: 1},{label: 'medium',value: 2},{label: 'high',value: 3},];functionApp(){const[selectedOption,setSelectedOption]=useState(options[0]);return(<ToggleButtonGrouptop={30}left={50}width={36}height={6}options={options}selectedOption={selectedOption}onChange={setSelectedOption}/>);}

Text box

A text box whose content can span multiple rows. If the content is too long, the text box will show a scrollbar.

Import

import{TextBox}from'react-node-insim';

Usage

Text box

<TextBoxtop={40}left={50}cols={20}rows={4}width={20}rowHeight={4}variant="light">
Hello world this is a text box lorem ipsum dolor sit amet consectetur
adipisicing elitrea lorem ipsum dolor sit amet consectetur adipisicing elit
</TextBox>

Hooks

useOnConnect

Execute code after the InSim app has been connected.

The first parameter is an IS_VER packet callback executed when IS_VER is received upon successful InSim connection to LFS.

import{useOnConnect}from'react-node-insim';functionApp(){useOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});returnnull;}

useOnDisconnect

Execute code after the InSim app has been disconnected.

The first parameter is the "disconnect" event callback from Node InSim.

import{useOnDisconnect}from'react-node-insim';functionApp(){useOnDisconnect(()=>{console.log('Disconnected from LFS');});returnnull;}

useOnPacket

Execute code when an InSim packet is received

import{useOnPacket}from'react-node-insim';functionApp(){useOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});returnnull;}

useConnections

Get a live list of all connected guests.

import{useConnections}from'react-node-insim';functionApp(){constconnections=useConnections();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{connections.map((connection)=>(<Buttonkey={connection.UCID}>{connection.UName}</Button>))}</VStack>);}

usePlayers

Get a live list of all players on track.

import{usePlayers}from'react-node-insim';functionApp(){constplayers=usePlayers();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{players.map((player)=>(<Buttonkey={player.PLID}>{player.PName}</Button>))}</VStack>);}

useRaceControlMessage

Send a race control message (RCM) to a connection or a player.

import{useRaceControlMessage}from'react-node-insim';functionApp(){const{ sendRaceControlMessageToConnection, sendRaceControlMessageToPlayer }=useRaceControlMessage();return(<><Buttontop={5}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToConnection(packet.UCID,'Hello from React Node InSim',2000,);}}>
Send message to a connection
</Button><Buttontop={10}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToPlayer(12,// PLID'Hello from React Node InSim',2000,);}}>
Send message to a player
</Button></>);}

useInSim

Access to Node InSim API of the current InSim client instance.

import{useInSim}from'react-node-insim';functionApp(){constinSim=useInSim();useEffect(()=>{inSim.send(newIS_MST({Msg: 'App mounted'}));},[]);returnnull;}

Scopes

If you needed to show personalised buttons for each connection or each human player on track, you would need to map over the list of connections/players and pass the correct UCIDs to each button manually. Scopes help in such use cases.

Connection scope

You can show different buttons to each connection by wrapping a sub-tree in a ConnectionScopeProvider, then using the useConnectionScope hook anywhere within that sub-tree to access the connection object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,ConnectionScopeProvider,useConnectionScope,}from'react-node-insim';functionApp(){return(<ConnectionScopeProvider><UserNameButton/></ConnectionScopeProvider>);}functionUserNameButton(){const{ UName }=useConnectionScope();return(<Buttontop={0}left={80}height={5}width={25}>{UName}</Button>);}

Human player scope

You can show different buttons to each human player on track by wrapping a sub-tree in a HumanPlayerScopeProvider, then using the useHumanPlayerScope hook anywhere within that sub-tree to access the player object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,HumanPlayerScopeProvider,useHumanPlayerScope,}from'react-node-insim';functionApp(){return(<HumanPlayerScopeProvider><PlayerNameButton/></HumanPlayerScopeProvider>);}functionUserNameButton(){const{ PName }=useHumanPlayerScope();return(<Buttontop={0}left={80}height={5}width={25}>{PName}</Button>);}

Global scope

You can show the same set of buttons to all connections wrapping a sub-tree in a GlobalScopeProvider.

You don't need to specify the button's UCID in the scope - the correct UCID value of 255 will be injected automatically.

import{Button,GlobalScopeProvider}from'react-node-insim';functionApp(){return(<GlobalScopeProvider><Buttontop={0}left={80}height={5}width={40}>
React Node InSim
</Button></GlobalScopeProvider>);}

Using React Devtools

React Node InSim supports React Devtools out of the box. To enable integration with React Devtools in your application, first ensure you have installed the optional react-devtools-core dependency, and then run your app with the DEV=true environment variable:

DEV=true npm start

Then, start React Devtools itself:

npx react-devtools

After it starts, you should see the component tree of your InSim app. You can even inspect and change the props of components, and see the results immediately in LFS, without restarting it.

Development

Requirements

Installation

yarn

Run example app

yarn start

Lint code

yarn lint

Format code

yarn format

React Node Insim - An open source project by Sim Broadcasts

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

React Node InSim

NPM VersionNode.js CI

A React renderer for InSim buttons, based on Node InSim.

Introduction

Warning

This project is still under development. Any API may change as needed.

React Node InSim is a React renderer for Live for SpeedInSim buttons. It also provides layout components for easier button positioning, hooks for handling incoming InSim packets and tracking server connections & players.

It is based on Node InSim, a Node.js library, written in TypeScript, for InSim communication.

It allows you to create things like this:

Live list of connections and players

Show source code
import{InSim}from'node-insim';importtype{InSimPacketInstance}from'node-insim/packets';import{InSimFlags,IS_MST,PacketType}from'node-insim/packets';import{StrictMode}from'react';import{Button,ConnectionsPlayersProvider,createRoot,useConnections,useOnConnect,useOnPacket,usePlayers,VStack,}from'react-node-insim';functionApp(){// Get the list of current players and connectionsconstplayers=usePlayers();constconnections=useConnections();// Do something after the InSim app has been connected to LFSuseOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});// Handle incoming packetsuseOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});// Clickable buttonsconsthandlePlayerClick=(plid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo PLID ${plid}`}));};consthandleConnectionClick=(ucid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo UCID ${ucid}`}));};return(<><Buttontop={10}left={40}width={30}height={5}UCID={255}color="title">
Players
</Button><VStackbackground="dark"top={15}left={40}width={30}height={5}UCID={255}>{players.map((player)=>(<Buttonkey={player.PLID}onClick={handlePlayerClick(player.PLID)}>{player.PName}</Button>))}</VStack><Buttontop={10}left={70}width={30}height={5}UCID={255}color="title">
Connections
</Button><VStackbackground="dark"top={15}left={70}width={30}height={5}UCID={255}>{connections.map((connection)=>(<Buttonkey={connection.UCID}onClick={handleConnectionClick(connection.UCID)}>{connection.UName}</Button>))}</VStack></>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<StrictMode><ConnectionsPlayersProvider><App/></ConnectionsPlayersProvider></StrictMode>,);

Table of contents

Requirements

Installation

NPM

npm install react@19 node-insim react-node-insim

Yarn

yarn add react@19 node-insim react-node-insim

pnpm

pnpm add react@19 node-insim react-node-insim

Basic usage

Displaying an InSim button on a local computer

import{InSim}from'node-insim';import{InSimFlags}from'node-insim/packets';import{Button,createRoot}from'react-node-insim';constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<Buttontop={100}left={80}width={30}height={10}>
Hello InSim!
</Button>,);

You can use React hooks as usual to display stateful data via InSim.

Button showing current time

import{InSimFlags}from'node-insim/packets';import{useEffect,useState}from'react';import{Button,createRoot}from'react-node-insim';functionApp(){const[time,setTime]=useState(newDate());useEffect(()=>{constinterval=setInterval(()=>{setTime(newDate());},1000);return()=>{clearInterval(interval);};});return(<Buttontop={100}left={80}width={40}height={10}>
Current time: {time.toLocaleTimeString()}</Button>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<App/>);

Button

The Button component is used to display a button in LFS.

  • Buttons are drawn on a 200 by 200 canvas using absolute positioning
  • The maximum number of rendered buttons on a screen is 240

Import

import{Button}from'react-node-insim';

Usage

Button

<Buttontop={100}left={80}width={30}height={10}>
Button
</Button>

Placement

Buttons use XY coordinates to position themselves on the screen. The top and left props control the button's X and Y position on the screen. The allowed range of values is 0 to 200.

Button placement

<><Buttonwidth={12}height={6}top={100}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={100}left={53}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={53}>
Button
</Button></>

Sizes

Use the width and height props to change the dimensions of the button. The allowed range of values is 0 to 200.

Button sizes

<><Buttonvariant="light"top={100}left={40}width={6}height={4}>
Button
</Button><Buttonvariant="light"top={99}left={47}width={10}height={6}>
Button
</Button><Buttonvariant="light"top={97}left={58}width={14}height={10}>
Button
</Button></>

Variants

Use the variant prop to change the button's visual style. You can use light or dark. If you don't specify a variant, the button will have transparent background and a light gray text color.

Button variants

<><Buttontop={100}left={40}width={12}height={6}variant="light">
Button
</Button><Buttontop={100}left={53}width={12}height={6}variant="dark">
Button
</Button></>

Text colors

Use the color prop to customize the button's text color. If you don't specify a color, the button text will be default.

Button text colors

<><Buttontop={73}left={40}width={12}height={6}color="default">
default
</Button><Buttontop={73}left={53}width={12}height={6}color="title">
title
</Button><Buttontop={73}left={66}width={12}height={6}color="unselected">
unselected
</Button><Buttontop={73}left={79}width={12}height={6}color="selected">
selected
</Button><Buttontop={80}left={40}width={12}height={6}color="ok">
ok
</Button><Buttontop={80}left={53}width={12}height={6}color="cancel">
cancel
</Button><Buttontop={80}left={66}width={12}height={6}color="textstring">
textstring
</Button><Buttontop={80}left={79}width={12}height={6}color="unavailable">
unavailable
</Button></>

You can choose from a set of semantic colors or use one of the colors from the LFS color palette.

Semantic colors

  • default
  • title
  • unselected
  • selected
  • ok
  • cancel
  • textstring
  • unavailable

Note: The semantic color values can be customized in LFS Options -> Display -> Interface.

LFS color palette

  • black
  • red
  • green
  • yellow
  • blue
  • magenta
  • cyan
  • white

Background colors

Use the background prop to customize the button's background color. If you don't specify a color, the background will be transparent.

Button background colors

<><Buttontop={67}left={40}width={12}height={6}background="light">
light
</Button><Buttontop={67}left={53}width={12}height={6}background="dark">
dark
</Button><Buttontop={67}left={66}width={12}height={6}background="transparent">
transparent
</Button></>

Horizontal stack

HStack displays buttons in a column without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{HStack}from'react-node-insim';

Usage

Horizontal stack

<HStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></HStack>

Vertical stack

VStack displays buttons in a row without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{VStack}from'react-node-insim';

Usage

Vertical stack

<VStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></VStack>

Flex

Flex layout displays buttons in a row or column with flexbox options.

Import

import{Flex}from'react-node-insim';

Usage

Flex

<Flextop={10}left={20}width={36}height={16}alignItems="center"justifyContent="space-evenly"background="dark"backgroundColor="light"><Buttonwidth={8}height={4}>
Left
</Button><Buttonwidth={10}height={6}>
Center
</Button><Buttonwidth={8}height={4}>
Right
</Button></Flex>

Grid

Grid layout displays buttons in a grid.

Import

import{Grid,GridButton}from'react-node-insim';

Usage

Grid

<Gridtop={30}left={40}width={30}height={30}background="dark"backgroundColor="light"gridTemplateColumns="1fr 2fr 1fr"gridTemplateRows="1fr 3fr 2fr"gridColumnGap={1}gridRowGap={1}><GridButton>1</GridButton><GridButtongridColumnStart={2}gridRowStart={1}gridRowEnd={3}color="title"background="light">
2
</GridButton><GridButtongridColumnStart={3}gridColumnEnd={3}gridRowStart={1}gridRowEnd={3}>
3
</GridButton><GridButtonalignSelf="end"height={10}>
4
</GridButton><GridButtongridColumnStart={1}gridColumnEnd={4}>
5
</GridButton></Grid>

Toggle button

A button that can be toggled on and off by clicking it.

Import

import{ToggleButton}from'react-node-insim';

Usage

Toggle button being toggled on and off

functionApp(){const[isOn,setIsOn]=useState(false);return(<ToggleButtontop={100}left={80}width={12}height={6}isOn={isOn}onToggle={setIsOn}>
Toggle
</ToggleButton>);}

Variants

Use the variant prop to change the button's background style. You can use light or dark. If you don't specify a variant, light will be used.

Toggle button variants

<><ToggleButtonvariant="light"top={100}left={40}width={12}height={6}>
Toggle
</ToggleButton><ToggleButtonvariant="dark"top={100}left={53}width={12}height={6}>
Toggle
</ToggleButton></>

Disabled state

Use the isDisabled prop to prevent toggling the button on/off.

Enabled and disabled toggle buttons

<><ToggleButtonisDisabled={false}variant="light"top={100}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="light"top={100}left={53}width={12}height={6}>
Disabled
</ToggleButton><ToggleButtonisDisabled={false}variant="dark"top={106}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="dark"top={106}left={53}width={12}height={6}>
Disabled
</ToggleButton></>

Toggle button group

A group of buttons that can be toggled on and off by clicking them.

Import

import{ToggleButtonGroup}from'react-node-insim';

Usage

Toggle button group

constoptions=[{label: 'low',value: 1},{label: 'medium',value: 2},{label: 'high',value: 3},];functionApp(){const[selectedOption,setSelectedOption]=useState(options[0]);return(<ToggleButtonGrouptop={30}left={50}width={36}height={6}options={options}selectedOption={selectedOption}onChange={setSelectedOption}/>);}

Text box

A text box whose content can span multiple rows. If the content is too long, the text box will show a scrollbar.

Import

import{TextBox}from'react-node-insim';

Usage

Text box

<TextBoxtop={40}left={50}cols={20}rows={4}width={20}rowHeight={4}variant="light">
Hello world this is a text box lorem ipsum dolor sit amet consectetur
adipisicing elitrea lorem ipsum dolor sit amet consectetur adipisicing elit
</TextBox>

Hooks

useOnConnect

Execute code after the InSim app has been connected.

The first parameter is an IS_VER packet callback executed when IS_VER is received upon successful InSim connection to LFS.

import{useOnConnect}from'react-node-insim';functionApp(){useOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});returnnull;}

useOnDisconnect

Execute code after the InSim app has been disconnected.

The first parameter is the "disconnect" event callback from Node InSim.

import{useOnDisconnect}from'react-node-insim';functionApp(){useOnDisconnect(()=>{console.log('Disconnected from LFS');});returnnull;}

useOnPacket

Execute code when an InSim packet is received

import{useOnPacket}from'react-node-insim';functionApp(){useOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});returnnull;}

useConnections

Get a live list of all connected guests.

import{useConnections}from'react-node-insim';functionApp(){constconnections=useConnections();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{connections.map((connection)=>(<Buttonkey={connection.UCID}>{connection.UName}</Button>))}</VStack>);}

usePlayers

Get a live list of all players on track.

import{usePlayers}from'react-node-insim';functionApp(){constplayers=usePlayers();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{players.map((player)=>(<Buttonkey={player.PLID}>{player.PName}</Button>))}</VStack>);}

useRaceControlMessage

Send a race control message (RCM) to a connection or a player.

import{useRaceControlMessage}from'react-node-insim';functionApp(){const{ sendRaceControlMessageToConnection, sendRaceControlMessageToPlayer }=useRaceControlMessage();return(<><Buttontop={5}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToConnection(packet.UCID,'Hello from React Node InSim',2000,);}}>
Send message to a connection
</Button><Buttontop={10}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToPlayer(12,// PLID'Hello from React Node InSim',2000,);}}>
Send message to a player
</Button></>);}

useInSim

Access to Node InSim API of the current InSim client instance.

import{useInSim}from'react-node-insim';functionApp(){constinSim=useInSim();useEffect(()=>{inSim.send(newIS_MST({Msg: 'App mounted'}));},[]);returnnull;}

Scopes

If you needed to show personalised buttons for each connection or each human player on track, you would need to map over the list of connections/players and pass the correct UCIDs to each button manually. Scopes help in such use cases.

Connection scope

You can show different buttons to each connection by wrapping a sub-tree in a ConnectionScopeProvider, then using the useConnectionScope hook anywhere within that sub-tree to access the connection object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,ConnectionScopeProvider,useConnectionScope,}from'react-node-insim';functionApp(){return(<ConnectionScopeProvider><UserNameButton/></ConnectionScopeProvider>);}functionUserNameButton(){const{ UName }=useConnectionScope();return(<Buttontop={0}left={80}height={5}width={25}>{UName}</Button>);}

Human player scope

You can show different buttons to each human player on track by wrapping a sub-tree in a HumanPlayerScopeProvider, then using the useHumanPlayerScope hook anywhere within that sub-tree to access the player object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,HumanPlayerScopeProvider,useHumanPlayerScope,}from'react-node-insim';functionApp(){return(<HumanPlayerScopeProvider><PlayerNameButton/></HumanPlayerScopeProvider>);}functionUserNameButton(){const{ PName }=useHumanPlayerScope();return(<Buttontop={0}left={80}height={5}width={25}>{PName}</Button>);}

Global scope

You can show the same set of buttons to all connections wrapping a sub-tree in a GlobalScopeProvider.

You don't need to specify the button's UCID in the scope - the correct UCID value of 255 will be injected automatically.

import{Button,GlobalScopeProvider}from'react-node-insim';functionApp(){return(<GlobalScopeProvider><Buttontop={0}left={80}height={5}width={40}>
React Node InSim
</Button></GlobalScopeProvider>);}

Using React Devtools

React Node InSim supports React Devtools out of the box. To enable integration with React Devtools in your application, first ensure you have installed the optional react-devtools-core dependency, and then run your app with the DEV=true environment variable:

DEV=true npm start

Then, start React Devtools itself:

npx react-devtools

After it starts, you should see the component tree of your InSim app. You can even inspect and change the props of components, and see the results immediately in LFS, without restarting it.

Development

Requirements

Installation

yarn

Run example app

yarn start

Lint code

yarn lint

Format code

yarn format

React Node Insim - An open source project by Sim Broadcasts

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

React Node InSim

NPM VersionNode.js CI

A React renderer for InSim buttons, based on Node InSim.

Introduction

Warning

This project is still under development. Any API may change as needed.

React Node InSim is a React renderer for Live for SpeedInSim buttons. It also provides layout components for easier button positioning, hooks for handling incoming InSim packets and tracking server connections & players.

It is based on Node InSim, a Node.js library, written in TypeScript, for InSim communication.

It allows you to create things like this:

Live list of connections and players

Show source code
import{InSim}from'node-insim';importtype{InSimPacketInstance}from'node-insim/packets';import{InSimFlags,IS_MST,PacketType}from'node-insim/packets';import{StrictMode}from'react';import{Button,ConnectionsPlayersProvider,createRoot,useConnections,useOnConnect,useOnPacket,usePlayers,VStack,}from'react-node-insim';functionApp(){// Get the list of current players and connectionsconstplayers=usePlayers();constconnections=useConnections();// Do something after the InSim app has been connected to LFSuseOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});// Handle incoming packetsuseOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});// Clickable buttonsconsthandlePlayerClick=(plid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo PLID ${plid}`}));};consthandleConnectionClick=(ucid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo UCID ${ucid}`}));};return(<><Buttontop={10}left={40}width={30}height={5}UCID={255}color="title">
Players
</Button><VStackbackground="dark"top={15}left={40}width={30}height={5}UCID={255}>{players.map((player)=>(<Buttonkey={player.PLID}onClick={handlePlayerClick(player.PLID)}>{player.PName}</Button>))}</VStack><Buttontop={10}left={70}width={30}height={5}UCID={255}color="title">
Connections
</Button><VStackbackground="dark"top={15}left={70}width={30}height={5}UCID={255}>{connections.map((connection)=>(<Buttonkey={connection.UCID}onClick={handleConnectionClick(connection.UCID)}>{connection.UName}</Button>))}</VStack></>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<StrictMode><ConnectionsPlayersProvider><App/></ConnectionsPlayersProvider></StrictMode>,);

Table of contents

Requirements

Installation

NPM

npm install react@19 node-insim react-node-insim

Yarn

yarn add react@19 node-insim react-node-insim

pnpm

pnpm add react@19 node-insim react-node-insim

Basic usage

Displaying an InSim button on a local computer

import{InSim}from'node-insim';import{InSimFlags}from'node-insim/packets';import{Button,createRoot}from'react-node-insim';constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<Buttontop={100}left={80}width={30}height={10}>
Hello InSim!
</Button>,);

You can use React hooks as usual to display stateful data via InSim.

Button showing current time

import{InSimFlags}from'node-insim/packets';import{useEffect,useState}from'react';import{Button,createRoot}from'react-node-insim';functionApp(){const[time,setTime]=useState(newDate());useEffect(()=>{constinterval=setInterval(()=>{setTime(newDate());},1000);return()=>{clearInterval(interval);};});return(<Buttontop={100}left={80}width={40}height={10}>
Current time: {time.toLocaleTimeString()}</Button>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<App/>);

Button

The Button component is used to display a button in LFS.

  • Buttons are drawn on a 200 by 200 canvas using absolute positioning
  • The maximum number of rendered buttons on a screen is 240

Import

import{Button}from'react-node-insim';

Usage

Button

<Buttontop={100}left={80}width={30}height={10}>
Button
</Button>

Placement

Buttons use XY coordinates to position themselves on the screen. The top and left props control the button's X and Y position on the screen. The allowed range of values is 0 to 200.

Button placement

<><Buttonwidth={12}height={6}top={100}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={100}left={53}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={53}>
Button
</Button></>

Sizes

Use the width and height props to change the dimensions of the button. The allowed range of values is 0 to 200.

Button sizes

<><Buttonvariant="light"top={100}left={40}width={6}height={4}>
Button
</Button><Buttonvariant="light"top={99}left={47}width={10}height={6}>
Button
</Button><Buttonvariant="light"top={97}left={58}width={14}height={10}>
Button
</Button></>

Variants

Use the variant prop to change the button's visual style. You can use light or dark. If you don't specify a variant, the button will have transparent background and a light gray text color.

Button variants

<><Buttontop={100}left={40}width={12}height={6}variant="light">
Button
</Button><Buttontop={100}left={53}width={12}height={6}variant="dark">
Button
</Button></>

Text colors

Use the color prop to customize the button's text color. If you don't specify a color, the button text will be default.

Button text colors

<><Buttontop={73}left={40}width={12}height={6}color="default">
default
</Button><Buttontop={73}left={53}width={12}height={6}color="title">
title
</Button><Buttontop={73}left={66}width={12}height={6}color="unselected">
unselected
</Button><Buttontop={73}left={79}width={12}height={6}color="selected">
selected
</Button><Buttontop={80}left={40}width={12}height={6}color="ok">
ok
</Button><Buttontop={80}left={53}width={12}height={6}color="cancel">
cancel
</Button><Buttontop={80}left={66}width={12}height={6}color="textstring">
textstring
</Button><Buttontop={80}left={79}width={12}height={6}color="unavailable">
unavailable
</Button></>

You can choose from a set of semantic colors or use one of the colors from the LFS color palette.

Semantic colors

  • default
  • title
  • unselected
  • selected
  • ok
  • cancel
  • textstring
  • unavailable

Note: The semantic color values can be customized in LFS Options -> Display -> Interface.

LFS color palette

  • black
  • red
  • green
  • yellow
  • blue
  • magenta
  • cyan
  • white

Background colors

Use the background prop to customize the button's background color. If you don't specify a color, the background will be transparent.

Button background colors

<><Buttontop={67}left={40}width={12}height={6}background="light">
light
</Button><Buttontop={67}left={53}width={12}height={6}background="dark">
dark
</Button><Buttontop={67}left={66}width={12}height={6}background="transparent">
transparent
</Button></>

Horizontal stack

HStack displays buttons in a column without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{HStack}from'react-node-insim';

Usage

Horizontal stack

<HStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></HStack>

Vertical stack

VStack displays buttons in a row without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{VStack}from'react-node-insim';

Usage

Vertical stack

<VStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></VStack>

Flex

Flex layout displays buttons in a row or column with flexbox options.

Import

import{Flex}from'react-node-insim';

Usage

Flex

<Flextop={10}left={20}width={36}height={16}alignItems="center"justifyContent="space-evenly"background="dark"backgroundColor="light"><Buttonwidth={8}height={4}>
Left
</Button><Buttonwidth={10}height={6}>
Center
</Button><Buttonwidth={8}height={4}>
Right
</Button></Flex>

Grid

Grid layout displays buttons in a grid.

Import

import{Grid,GridButton}from'react-node-insim';

Usage

Grid

<Gridtop={30}left={40}width={30}height={30}background="dark"backgroundColor="light"gridTemplateColumns="1fr 2fr 1fr"gridTemplateRows="1fr 3fr 2fr"gridColumnGap={1}gridRowGap={1}><GridButton>1</GridButton><GridButtongridColumnStart={2}gridRowStart={1}gridRowEnd={3}color="title"background="light">
2
</GridButton><GridButtongridColumnStart={3}gridColumnEnd={3}gridRowStart={1}gridRowEnd={3}>
3
</GridButton><GridButtonalignSelf="end"height={10}>
4
</GridButton><GridButtongridColumnStart={1}gridColumnEnd={4}>
5
</GridButton></Grid>

Toggle button

A button that can be toggled on and off by clicking it.

Import

import{ToggleButton}from'react-node-insim';

Usage

Toggle button being toggled on and off

functionApp(){const[isOn,setIsOn]=useState(false);return(<ToggleButtontop={100}left={80}width={12}height={6}isOn={isOn}onToggle={setIsOn}>
Toggle
</ToggleButton>);}

Variants

Use the variant prop to change the button's background style. You can use light or dark. If you don't specify a variant, light will be used.

Toggle button variants

<><ToggleButtonvariant="light"top={100}left={40}width={12}height={6}>
Toggle
</ToggleButton><ToggleButtonvariant="dark"top={100}left={53}width={12}height={6}>
Toggle
</ToggleButton></>

Disabled state

Use the isDisabled prop to prevent toggling the button on/off.

Enabled and disabled toggle buttons

<><ToggleButtonisDisabled={false}variant="light"top={100}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="light"top={100}left={53}width={12}height={6}>
Disabled
</ToggleButton><ToggleButtonisDisabled={false}variant="dark"top={106}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="dark"top={106}left={53}width={12}height={6}>
Disabled
</ToggleButton></>

Toggle button group

A group of buttons that can be toggled on and off by clicking them.

Import

import{ToggleButtonGroup}from'react-node-insim';

Usage

Toggle button group

constoptions=[{label: 'low',value: 1},{label: 'medium',value: 2},{label: 'high',value: 3},];functionApp(){const[selectedOption,setSelectedOption]=useState(options[0]);return(<ToggleButtonGrouptop={30}left={50}width={36}height={6}options={options}selectedOption={selectedOption}onChange={setSelectedOption}/>);}

Text box

A text box whose content can span multiple rows. If the content is too long, the text box will show a scrollbar.

Import

import{TextBox}from'react-node-insim';

Usage

Text box

<TextBoxtop={40}left={50}cols={20}rows={4}width={20}rowHeight={4}variant="light">
Hello world this is a text box lorem ipsum dolor sit amet consectetur
adipisicing elitrea lorem ipsum dolor sit amet consectetur adipisicing elit
</TextBox>

Hooks

useOnConnect

Execute code after the InSim app has been connected.

The first parameter is an IS_VER packet callback executed when IS_VER is received upon successful InSim connection to LFS.

import{useOnConnect}from'react-node-insim';functionApp(){useOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});returnnull;}

useOnDisconnect

Execute code after the InSim app has been disconnected.

The first parameter is the "disconnect" event callback from Node InSim.

import{useOnDisconnect}from'react-node-insim';functionApp(){useOnDisconnect(()=>{console.log('Disconnected from LFS');});returnnull;}

useOnPacket

Execute code when an InSim packet is received

import{useOnPacket}from'react-node-insim';functionApp(){useOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});returnnull;}

useConnections

Get a live list of all connected guests.

import{useConnections}from'react-node-insim';functionApp(){constconnections=useConnections();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{connections.map((connection)=>(<Buttonkey={connection.UCID}>{connection.UName}</Button>))}</VStack>);}

usePlayers

Get a live list of all players on track.

import{usePlayers}from'react-node-insim';functionApp(){constplayers=usePlayers();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{players.map((player)=>(<Buttonkey={player.PLID}>{player.PName}</Button>))}</VStack>);}

useRaceControlMessage

Send a race control message (RCM) to a connection or a player.

import{useRaceControlMessage}from'react-node-insim';functionApp(){const{ sendRaceControlMessageToConnection, sendRaceControlMessageToPlayer }=useRaceControlMessage();return(<><Buttontop={5}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToConnection(packet.UCID,'Hello from React Node InSim',2000,);}}>
Send message to a connection
</Button><Buttontop={10}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToPlayer(12,// PLID'Hello from React Node InSim',2000,);}}>
Send message to a player
</Button></>);}

useInSim

Access to Node InSim API of the current InSim client instance.

import{useInSim}from'react-node-insim';functionApp(){constinSim=useInSim();useEffect(()=>{inSim.send(newIS_MST({Msg: 'App mounted'}));},[]);returnnull;}

Scopes

If you needed to show personalised buttons for each connection or each human player on track, you would need to map over the list of connections/players and pass the correct UCIDs to each button manually. Scopes help in such use cases.

Connection scope

You can show different buttons to each connection by wrapping a sub-tree in a ConnectionScopeProvider, then using the useConnectionScope hook anywhere within that sub-tree to access the connection object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,ConnectionScopeProvider,useConnectionScope,}from'react-node-insim';functionApp(){return(<ConnectionScopeProvider><UserNameButton/></ConnectionScopeProvider>);}functionUserNameButton(){const{ UName }=useConnectionScope();return(<Buttontop={0}left={80}height={5}width={25}>{UName}</Button>);}

Human player scope

You can show different buttons to each human player on track by wrapping a sub-tree in a HumanPlayerScopeProvider, then using the useHumanPlayerScope hook anywhere within that sub-tree to access the player object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,HumanPlayerScopeProvider,useHumanPlayerScope,}from'react-node-insim';functionApp(){return(<HumanPlayerScopeProvider><PlayerNameButton/></HumanPlayerScopeProvider>);}functionUserNameButton(){const{ PName }=useHumanPlayerScope();return(<Buttontop={0}left={80}height={5}width={25}>{PName}</Button>);}

Global scope

You can show the same set of buttons to all connections wrapping a sub-tree in a GlobalScopeProvider.

You don't need to specify the button's UCID in the scope - the correct UCID value of 255 will be injected automatically.

import{Button,GlobalScopeProvider}from'react-node-insim';functionApp(){return(<GlobalScopeProvider><Buttontop={0}left={80}height={5}width={40}>
React Node InSim
</Button></GlobalScopeProvider>);}

Using React Devtools

React Node InSim supports React Devtools out of the box. To enable integration with React Devtools in your application, first ensure you have installed the optional react-devtools-core dependency, and then run your app with the DEV=true environment variable:

DEV=true npm start

Then, start React Devtools itself:

npx react-devtools

After it starts, you should see the component tree of your InSim app. You can even inspect and change the props of components, and see the results immediately in LFS, without restarting it.

Development

Requirements

Installation

yarn

Run example app

yarn start

Lint code

yarn lint

Format code

yarn format

React Node Insim - An open source project by Sim Broadcasts

Releases

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

React Node InSim

NPM VersionNode.js CI

A React renderer for InSim buttons, based on Node InSim.

Introduction

Warning

This project is still under development. Any API may change as needed.

React Node InSim is a React renderer for Live for SpeedInSim buttons. It also provides layout components for easier button positioning, hooks for handling incoming InSim packets and tracking server connections & players.

It is based on Node InSim, a Node.js library, written in TypeScript, for InSim communication.

It allows you to create things like this:

Live list of connections and players

Show source code
import{InSim}from'node-insim';importtype{InSimPacketInstance}from'node-insim/packets';import{InSimFlags,IS_MST,PacketType}from'node-insim/packets';import{StrictMode}from'react';import{Button,ConnectionsPlayersProvider,createRoot,useConnections,useOnConnect,useOnPacket,usePlayers,VStack,}from'react-node-insim';functionApp(){// Get the list of current players and connectionsconstplayers=usePlayers();constconnections=useConnections();// Do something after the InSim app has been connected to LFSuseOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});// Handle incoming packetsuseOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});// Clickable buttonsconsthandlePlayerClick=(plid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo PLID ${plid}`}));};consthandleConnectionClick=(ucid: number)=>(_: InSimPacketInstance<PacketType.ISP_BTC>,inSim: InSim)=>{inSim.send(newIS_MST({Msg: `/echo UCID ${ucid}`}));};return(<><Buttontop={10}left={40}width={30}height={5}UCID={255}color="title">
Players
</Button><VStackbackground="dark"top={15}left={40}width={30}height={5}UCID={255}>{players.map((player)=>(<Buttonkey={player.PLID}onClick={handlePlayerClick(player.PLID)}>{player.PName}</Button>))}</VStack><Buttontop={10}left={70}width={30}height={5}UCID={255}color="title">
Connections
</Button><VStackbackground="dark"top={15}left={70}width={30}height={5}UCID={255}>{connections.map((connection)=>(<Buttonkey={connection.UCID}onClick={handleConnectionClick(connection.UCID)}>{connection.UName}</Button>))}</VStack></>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<StrictMode><ConnectionsPlayersProvider><App/></ConnectionsPlayersProvider></StrictMode>,);

Table of contents

Requirements

Installation

NPM

npm install react@19 node-insim react-node-insim

Yarn

yarn add react@19 node-insim react-node-insim

pnpm

pnpm add react@19 node-insim react-node-insim

Basic usage

Displaying an InSim button on a local computer

import{InSim}from'node-insim';import{InSimFlags}from'node-insim/packets';import{Button,createRoot}from'react-node-insim';constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<Buttontop={100}left={80}width={30}height={10}>
Hello InSim!
</Button>,);

You can use React hooks as usual to display stateful data via InSim.

Button showing current time

import{InSimFlags}from'node-insim/packets';import{useEffect,useState}from'react';import{Button,createRoot}from'react-node-insim';functionApp(){const[time,setTime]=useState(newDate());useEffect(()=>{constinterval=setInterval(()=>{setTime(newDate());},1000);return()=>{clearInterval(interval);};});return(<Buttontop={100}left={80}width={40}height={10}>
Current time: {time.toLocaleTimeString()}</Button>);}constinSim=newInSim();inSim.connect({IName: 'React InSim',ReqI: 1,Host: '127.0.0.1',Port: 29999,Flags: InSimFlags.ISF_LOCAL,});constroot=createRoot(inSim);root.render(<App/>);

Button

The Button component is used to display a button in LFS.

  • Buttons are drawn on a 200 by 200 canvas using absolute positioning
  • The maximum number of rendered buttons on a screen is 240

Import

import{Button}from'react-node-insim';

Usage

Button

<Buttontop={100}left={80}width={30}height={10}>
Button
</Button>

Placement

Buttons use XY coordinates to position themselves on the screen. The top and left props control the button's X and Y position on the screen. The allowed range of values is 0 to 200.

Button placement

<><Buttonwidth={12}height={6}top={100}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={100}left={53}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={40}>
Button
</Button><Buttonwidth={12}height={6}top={106}left={53}>
Button
</Button></>

Sizes

Use the width and height props to change the dimensions of the button. The allowed range of values is 0 to 200.

Button sizes

<><Buttonvariant="light"top={100}left={40}width={6}height={4}>
Button
</Button><Buttonvariant="light"top={99}left={47}width={10}height={6}>
Button
</Button><Buttonvariant="light"top={97}left={58}width={14}height={10}>
Button
</Button></>

Variants

Use the variant prop to change the button's visual style. You can use light or dark. If you don't specify a variant, the button will have transparent background and a light gray text color.

Button variants

<><Buttontop={100}left={40}width={12}height={6}variant="light">
Button
</Button><Buttontop={100}left={53}width={12}height={6}variant="dark">
Button
</Button></>

Text colors

Use the color prop to customize the button's text color. If you don't specify a color, the button text will be default.

Button text colors

<><Buttontop={73}left={40}width={12}height={6}color="default">
default
</Button><Buttontop={73}left={53}width={12}height={6}color="title">
title
</Button><Buttontop={73}left={66}width={12}height={6}color="unselected">
unselected
</Button><Buttontop={73}left={79}width={12}height={6}color="selected">
selected
</Button><Buttontop={80}left={40}width={12}height={6}color="ok">
ok
</Button><Buttontop={80}left={53}width={12}height={6}color="cancel">
cancel
</Button><Buttontop={80}left={66}width={12}height={6}color="textstring">
textstring
</Button><Buttontop={80}left={79}width={12}height={6}color="unavailable">
unavailable
</Button></>

You can choose from a set of semantic colors or use one of the colors from the LFS color palette.

Semantic colors

  • default
  • title
  • unselected
  • selected
  • ok
  • cancel
  • textstring
  • unavailable

Note: The semantic color values can be customized in LFS Options -> Display -> Interface.

LFS color palette

  • black
  • red
  • green
  • yellow
  • blue
  • magenta
  • cyan
  • white

Background colors

Use the background prop to customize the button's background color. If you don't specify a color, the background will be transparent.

Button background colors

<><Buttontop={67}left={40}width={12}height={6}background="light">
light
</Button><Buttontop={67}left={53}width={12}height={6}background="dark">
dark
</Button><Buttontop={67}left={66}width={12}height={6}background="transparent">
transparent
</Button></>

Horizontal stack

HStack displays buttons in a column without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{HStack}from'react-node-insim';

Usage

Horizontal stack

<HStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></HStack>

Vertical stack

VStack displays buttons in a row without having to specify each button's position manually. You can also override button colors and sizes.

Import

import{VStack}from'react-node-insim';

Usage

Vertical stack

<VStacktop={10}left={20}width={7}height={4}variant="dark"><Button>Stacked button</Button><Buttoncolor="title">Custom color</Button><Buttonheight={6}>Custom height</Button></VStack>

Flex

Flex layout displays buttons in a row or column with flexbox options.

Import

import{Flex}from'react-node-insim';

Usage

Flex

<Flextop={10}left={20}width={36}height={16}alignItems="center"justifyContent="space-evenly"background="dark"backgroundColor="light"><Buttonwidth={8}height={4}>
Left
</Button><Buttonwidth={10}height={6}>
Center
</Button><Buttonwidth={8}height={4}>
Right
</Button></Flex>

Grid

Grid layout displays buttons in a grid.

Import

import{Grid,GridButton}from'react-node-insim';

Usage

Grid

<Gridtop={30}left={40}width={30}height={30}background="dark"backgroundColor="light"gridTemplateColumns="1fr 2fr 1fr"gridTemplateRows="1fr 3fr 2fr"gridColumnGap={1}gridRowGap={1}><GridButton>1</GridButton><GridButtongridColumnStart={2}gridRowStart={1}gridRowEnd={3}color="title"background="light">
2
</GridButton><GridButtongridColumnStart={3}gridColumnEnd={3}gridRowStart={1}gridRowEnd={3}>
3
</GridButton><GridButtonalignSelf="end"height={10}>
4
</GridButton><GridButtongridColumnStart={1}gridColumnEnd={4}>
5
</GridButton></Grid>

Toggle button

A button that can be toggled on and off by clicking it.

Import

import{ToggleButton}from'react-node-insim';

Usage

Toggle button being toggled on and off

functionApp(){const[isOn,setIsOn]=useState(false);return(<ToggleButtontop={100}left={80}width={12}height={6}isOn={isOn}onToggle={setIsOn}>
Toggle
</ToggleButton>);}

Variants

Use the variant prop to change the button's background style. You can use light or dark. If you don't specify a variant, light will be used.

Toggle button variants

<><ToggleButtonvariant="light"top={100}left={40}width={12}height={6}>
Toggle
</ToggleButton><ToggleButtonvariant="dark"top={100}left={53}width={12}height={6}>
Toggle
</ToggleButton></>

Disabled state

Use the isDisabled prop to prevent toggling the button on/off.

Enabled and disabled toggle buttons

<><ToggleButtonisDisabled={false}variant="light"top={100}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="light"top={100}left={53}width={12}height={6}>
Disabled
</ToggleButton><ToggleButtonisDisabled={false}variant="dark"top={106}left={40}width={12}height={6}>
Enabled
</ToggleButton><ToggleButtonisDisabledvariant="dark"top={106}left={53}width={12}height={6}>
Disabled
</ToggleButton></>

Toggle button group

A group of buttons that can be toggled on and off by clicking them.

Import

import{ToggleButtonGroup}from'react-node-insim';

Usage

Toggle button group

constoptions=[{label: 'low',value: 1},{label: 'medium',value: 2},{label: 'high',value: 3},];functionApp(){const[selectedOption,setSelectedOption]=useState(options[0]);return(<ToggleButtonGrouptop={30}left={50}width={36}height={6}options={options}selectedOption={selectedOption}onChange={setSelectedOption}/>);}

Text box

A text box whose content can span multiple rows. If the content is too long, the text box will show a scrollbar.

Import

import{TextBox}from'react-node-insim';

Usage

Text box

<TextBoxtop={40}left={50}cols={20}rows={4}width={20}rowHeight={4}variant="light">
Hello world this is a text box lorem ipsum dolor sit amet consectetur
adipisicing elitrea lorem ipsum dolor sit amet consectetur adipisicing elit
</TextBox>

Hooks

useOnConnect

Execute code after the InSim app has been connected.

The first parameter is an IS_VER packet callback executed when IS_VER is received upon successful InSim connection to LFS.

import{useOnConnect}from'react-node-insim';functionApp(){useOnConnect((packet,inSim)=>{console.log(`Connected to LFS ${packet.Product}${packet.Version}`);inSim.send(newIS_MST({Msg: `React Node InSim connected`}));});returnnull;}

useOnDisconnect

Execute code after the InSim app has been disconnected.

The first parameter is the "disconnect" event callback from Node InSim.

import{useOnDisconnect}from'react-node-insim';functionApp(){useOnDisconnect(()=>{console.log('Disconnected from LFS');});returnnull;}

useOnPacket

Execute code when an InSim packet is received

import{useOnPacket}from'react-node-insim';functionApp(){useOnPacket(PacketType.ISP_NCN,(packet)=>{console.log(`New connection: ${packet.UName}`);});returnnull;}

useConnections

Get a live list of all connected guests.

import{useConnections}from'react-node-insim';functionApp(){constconnections=useConnections();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{connections.map((connection)=>(<Buttonkey={connection.UCID}>{connection.UName}</Button>))}</VStack>);}

usePlayers

Get a live list of all players on track.

import{usePlayers}from'react-node-insim';functionApp(){constplayers=usePlayers();return(<VStackbackground="dark"top={10}left={10}width={20}height={4}>{players.map((player)=>(<Buttonkey={player.PLID}>{player.PName}</Button>))}</VStack>);}

useRaceControlMessage

Send a race control message (RCM) to a connection or a player.

import{useRaceControlMessage}from'react-node-insim';functionApp(){const{ sendRaceControlMessageToConnection, sendRaceControlMessageToPlayer }=useRaceControlMessage();return(<><Buttontop={5}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToConnection(packet.UCID,'Hello from React Node InSim',2000,);}}>
Send message to a connection
</Button><Buttontop={10}left={10}width={15}height={5}onClick={(packet)=>{sendRaceControlMessageToPlayer(12,// PLID'Hello from React Node InSim',2000,);}}>
Send message to a player
</Button></>);}

useInSim

Access to Node InSim API of the current InSim client instance.

import{useInSim}from'react-node-insim';functionApp(){constinSim=useInSim();useEffect(()=>{inSim.send(newIS_MST({Msg: 'App mounted'}));},[]);returnnull;}

Scopes

If you needed to show personalised buttons for each connection or each human player on track, you would need to map over the list of connections/players and pass the correct UCIDs to each button manually. Scopes help in such use cases.

Connection scope

You can show different buttons to each connection by wrapping a sub-tree in a ConnectionScopeProvider, then using the useConnectionScope hook anywhere within that sub-tree to access the connection object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,ConnectionScopeProvider,useConnectionScope,}from'react-node-insim';functionApp(){return(<ConnectionScopeProvider><UserNameButton/></ConnectionScopeProvider>);}functionUserNameButton(){const{ UName }=useConnectionScope();return(<Buttontop={0}left={80}height={5}width={25}>{UName}</Button>);}

Human player scope

You can show different buttons to each human player on track by wrapping a sub-tree in a HumanPlayerScopeProvider, then using the useHumanPlayerScope hook anywhere within that sub-tree to access the player object.

You don't need to specify the button's UCID in the scope - the correct UCID will be injected automatically.

import{Button,HumanPlayerScopeProvider,useHumanPlayerScope,}from'react-node-insim';functionApp(){return(<HumanPlayerScopeProvider><PlayerNameButton/></HumanPlayerScopeProvider>);}functionUserNameButton(){const{ PName }=useHumanPlayerScope();return(<Buttontop={0}left={80}height={5}width={25}>{PName}</Button>);}

Global scope

You can show the same set of buttons to all connections wrapping a sub-tree in a GlobalScopeProvider.

You don't need to specify the button's UCID in the scope - the correct UCID value of 255 will be injected automatically.

import{Button,GlobalScopeProvider}from'react-node-insim';functionApp(){return(<GlobalScopeProvider><Buttontop={0}left={80}height={5}width={40}>
React Node InSim
</Button></GlobalScopeProvider>);}

Using React Devtools

React Node InSim supports React Devtools out of the box. To enable integration with React Devtools in your application, first ensure you have installed the optional react-devtools-core dependency, and then run your app with the DEV=true environment variable:

DEV=true npm start

Then, start React Devtools itself:

npx react-devtools

After it starts, you should see the component tree of your InSim app. You can even inspect and change the props of components, and see the results immediately in LFS, without restarting it.

Development

Requirements

Installation

yarn

Run example app

yarn start

Lint code

yarn lint

Format code

yarn format

React Node Insim - An open source project by Sim Broadcasts

Releases

Used by

Contributors

Languages