React Node InSim
A React renderer for InSim buttons, based on Node InSim.
Introduction
🚧 This project is still under development. Any API may change as needed.
React Node InSim is a React renderer for Live for Speed InSim 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:
Show source code
import type { InSim } from 'node-insim';
import { InSimFlags, IS_BTC, IS_MST, PacketType } from 'node-insim/packets';
import { StrictMode } from 'react';
import {
Button,
ConnectionsProvider,
createRoot,
PlayersProvider,
useConnections,
useOnConnect,
useOnPacket,
usePlayers,
VStack,
} from 'react-node-insim';
function App() {
const players = usePlayers();
const connections = useConnections();
useOnConnect((packet, inSim) => {
console.log(`Connected to LFS ${packet.Product} ${packet.Version}`);
inSim.send(new IS_MST({ Msg: `React Node InSim connected` }));
});
useOnPacket(PacketType.ISP_NCN, (packet) => {
console.log(`New connection: ${packet.UName}`);
});
const handlePlayerClick = (plid: number) => (_: IS_BTC, inSim: InSim) => {
inSim.send(new IS_MST({ Msg: `/echo PLID ${plid}` }));
};
const handleConnectionClick = (ucid: number) => (_: IS_BTC, inSim: InSim) => {
inSim.send(new IS_MST({ Msg: `/echo UCID ${ucid}` }));
};
return (
<>
<Button top={10} left={40} width={30} height={5} UCID={255} color="title">
Players
</Button>
<VStack
background="dark"
top={15}
left={40}
width={30}
height={5}
UCID={255}
>
{players.map((player) => (
<Button key={player.PLID} onClick={handlePlayerClick(player.PLID)}>
{player.PName}
</Button>
))}
</VStack>
<Button top={10} left={70} width={30} height={5} UCID={255} color="title">
Connections
</Button>
<VStack
background="dark"
top={15}
left={70}
width={30}
height={5}
UCID={255}
>
{connections.map((connection) => (
<Button
key={connection.UCID}
onClick={handleConnectionClick(connection.UCID)}
>
{connection.UName}
</Button>
))}
</VStack>
</>
);
}
const root = createRoot({
name: 'React InSim',
host: '127.0.0.1',
port: 29999,
flags: InSimFlags.ISF_LOCAL,
});
root.render(
<StrictMode>
<PlayersProvider>
<ConnectionsProvider>
<App />
</ConnectionsProvider>
</PlayersProvider>
</StrictMode>,
);
Table of contents
Requirements
Installation
NPM
npm install react@18 node-insim react-node-insim
Yarn
yarn add react@18 node-insim react-node-insim
pnpm
pnpm add react@18 node-insim react-node-insim
Basic usage
Displaying an InSim button on a local computer
import { InSimFlags } from 'node-insim/packets';
import { Button, createRoot } from 'react-node-insim';
const root = createRoot({
name: 'React InSim',
host: '127.0.0.1',
port: 29999,
flags: InSimFlags.ISF_LOCAL,
});
root.render(
<Button top={100} left={80} width={30} height={10}>
Hello InSim!
</Button>,
);
You can use React hooks as usual to display stateful data via InSim.
import { InSimFlags } from 'node-insim/packets';
import { useEffect, useState } from 'react';
import { Button, createRoot } from 'react-node-insim';
function App() {
const [time, setTime] = useState(new Date());
useEffect(() => {
const interval = setInterval(() => {
setTime(new Date());
}, 1000);
return () => {
clearInterval(interval);
};
});
return (
<Button top={100} left={80} width={40} height={10}>
Current time: {time.toLocaleTimeString()}
</Button>
);
}
const root = createRoot({
name: 'React InSim',
host: '127.0.0.1',
port: 29999,
flags: InSimFlags.ISF_LOCAL,
});
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 top={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 width={12} height={6} top={100} left={40}>
Button
</Button>
<Button width={12} height={6} top={100} left={53}>
Button
</Button>
<Button width={12} height={6} top={106} left={40}>
Button
</Button>
<Button width={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 variant="light" top={100} left={40} width={6} height={4}>
Button
</Button>
<Button variant="light" top={99} left={47} width={10} height={6}>
Button
</Button>
<Button variant="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 top={100} left={40} width={12} height={6} variant="light">
Button
</Button>
<Button top={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 top={73} left={40} width={12} height={6} color="default">
default
</Button>
<Button top={73} left={53} width={12} height={6} color="title">
title
</Button>
<Button top={73} left={66} width={12} height={6} color="unselected">
unselected
</Button>
<Button top={73} left={79} width={12} height={6} color="selected">
selected
</Button>
<Button top={80} left={40} width={12} height={6} color="ok">
ok
</Button>
<Button top={80} left={53} width={12} height={6} color="cancel">
cancel
</Button>
<Button top={80} left={66} width={12} height={6} color="textstring">
textstring
</Button>
<Button top={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 top={67} left={40} width={12} height={6} background="light">
light
</Button>
<Button top={67} left={53} width={12} height={6} background="dark">
dark
</Button>
<Button top={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
<HStack top={10} left={20} width={7} height={4} variant="dark">
<Button>Stacked button</Button>
<Button color="title">Custom color</Button>
<Button height={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
<VStack top={10} left={20} width={7} height={4} variant="dark">
<Button>Stacked button</Button>
<Button color="title">Custom color</Button>
<Button height={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
top={10}
left={20}
width={36}
height={16}
alignItems="center"
justifyContent="space-evenly"
background="dark"
backgroundColor="light"
>
<Button width={8} height={4}>
Left
</Button>
<Button width={10} height={6}>
Center
</Button>
<Button width={8} height={4}>
Right
</Button>
</Flex>
Grid
Grid layout displays buttons in a grid.
Import
import { Grid, GridButton } from 'react-node-insim';
Usage
<Grid
top={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>
<GridButton
gridColumnStart={2}
gridRowStart={1}
gridRowEnd={3}
color="title"
background="light"
>
2
</GridButton>
<GridButton
gridColumnStart={3}
gridColumnEnd={3}
gridRowStart={1}
gridRowEnd={3}
>
3
</GridButton>
<GridButton alignSelf="end" height={10}>
4
</GridButton>
<GridButton gridColumnStart={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
function App() {
const [isOn, setIsOn] = useState(false);
return (
<ToggleButton
top={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.
<>
<ToggleButton variant="light" top={100} left={40} width={12} height={6}>
Toggle
</ToggleButton>
<ToggleButton variant="dark" top={100} left={53} width={12} height={6}>
Toggle
</ToggleButton>
</>
Disabled state
Use the isDisabled
prop to prevent toggling the button on/off.
<>
<ToggleButton
isDisabled={false}
variant="light"
top={100}
left={40}
width={12}
height={6}
>
Enabled
</ToggleButton>
<ToggleButton
isDisabled
variant="light"
top={100}
left={53}
width={12}
height={6}
>
Disabled
</ToggleButton>
<ToggleButton
isDisabled={false}
variant="dark"
top={106}
left={40}
width={12}
height={6}
>
Enabled
</ToggleButton>
<ToggleButton
isDisabled
variant="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
const options = [
{ label: 'low', value: 1 },
{ label: 'medium', value: 2 },
{ label: 'high', value: 3 },
];
function App() {
const [selectedOption, setSelectedOption] = useState(options[0]);
return (
<ToggleButtonGroup
top={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
<TextBox
top={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';
function App() {
useOnConnect((packet, inSim) => {
console.log(`Connected to LFS ${packet.Product} ${packet.Version}`);
inSim.send(new IS_MST({ Msg: `React Node InSim connected` }));
});
return null;
}
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';
function App() {
useOnDisconnect(() => {
console.log('Disconnected from LFS');
});
return null;
}
useOnPacket
Execute code when an InSim packet is received
import { useOnPacket } from 'react-node-insim';
function App() {
useOnPacket(PacketType.ISP_NCN, (packet) => {
console.log(`New connection: ${packet.UName}`);
});
return null;
}
useConnections
Get a live list of all connected guests.
import { useConnections } from 'react-node-insim';
function App() {
const connections = useConnections();
return (
<VStack background="dark" top={10} left={10} width={20} height={4}>
{connections.map((connection) => (
<Button key={connection.UCID}>{connection.UName}</Button>
))}
</VStack>
);
}
usePlayers
Get a live list of all players on track.
import { usePlayers } from 'react-node-insim';
function App() {
const players = usePlayers();
return (
<VStack background="dark" top={10} left={10} width={20} height={4}>
{players.map((player) => (
<Button key={player.PLID}>{player.PName}</Button>
))}
</VStack>
);
}
useMessage
Send a message to a connection or a player.
import { useMessage } from 'react-node-insim';
function App() {
const { sendMessageToConnection, sendMessageToPlayer } = useMessage();
return (
<>
<Button
top={5}
left={10}
width={15}
height={5}
onClick={(packet) => {
sendMessageToConnection(
packet.UCID,
'Hello from React Node InSim',
MessageSound.SND_SYSMESSAGE,
);
}}
>
Send message to a connection
</Button>
<Button
top={10}
left={10}
width={15}
height={5}
onClick={(packet) => {
sendMessageToPlayer(
12, // PLID
'Hello from React Node InSim',
MessageSound.SND_SYSMESSAGE,
);
}}
>
Send message to a player
</Button>
</>
);
}
useRaceControlMessage
Send a race control message (RCM) to a connection or a player.
import { useRaceControlMessage } from 'react-node-insim';
function App() {
const { sendRaceControlMessageToConnection, sendRaceControlMessageToPlayer } =
useRaceControlMessage();
return (
<>
<Button
top={5}
left={10}
width={15}
height={5}
onClick={(packet) => {
sendRaceControlMessageToConnection(
packet.UCID,
'Hello from React Node InSim',
2000,
);
}}
>
Send message to a connection
</Button>
<Button
top={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';
function App() {
const inSim = useInSim();
useEffect(() => {
inSim.send(new IS_MST({ Msg: 'App mounted' }));
}, []);
return null;
}
Development
Requirements
Installation
yarn
Run example app
yarn start
Lint code
yarn lint
Format code
yarn format