This commit is contained in:
Sacha Weatherstone
2021-08-11 23:09:45 +10:00
parent aff46494e9
commit 2940791b9e
36 changed files with 1096 additions and 623 deletions
+3
View File
@@ -0,0 +1,3 @@
import { IHTTPConnection } from '@meshtastic/meshtasticjs';
export const connection = new IHTTPConnection();
+8
View File
@@ -0,0 +1,8 @@
import { createRouter, defineRoute } from 'type-route';
export const { RouteProvider, useRoute, routes } = createRouter({
messages: defineRoute('/'),
nodes: defineRoute('/nodes'),
settings: defineRoute('/settings'),
about: defineRoute('/about'),
});
+45
View File
@@ -0,0 +1,45 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
export type currentPageName = 'messages' | 'settings';
interface AppState {
mobileNavOpen: boolean;
darkMode: boolean;
currentPage: currentPageName;
}
const initialState: AppState = {
mobileNavOpen: false,
darkMode: localStorage.getItem('darkMode') === 'true' ?? false,
currentPage: 'messages',
};
export const appSlice = createSlice({
name: 'app',
initialState,
reducers: {
openMobileNav(state) {
state.mobileNavOpen = true;
},
closeMobileNav(state) {
state.mobileNavOpen = false;
},
setDarkModeEnabled(state, action: PayloadAction<boolean>) {
localStorage.setItem('darkMode', String(action.payload));
state.darkMode = action.payload;
},
setCurrentPage(state, action: PayloadAction<currentPageName>) {
state.currentPage = action.payload;
},
},
});
export const {
openMobileNav,
closeMobileNav,
setDarkModeEnabled,
setCurrentPage,
} = appSlice.actions;
export default appSlice.reducer;
+137
View File
@@ -0,0 +1,137 @@
import { Protobuf, Types } from '@meshtastic/meshtasticjs';
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import { connection } from '../connection';
export interface MessageWithAck {
message: Types.TextPacket;
ack: boolean;
isSender: boolean;
received: Date;
}
interface AppState {
deviceStatus: Types.DeviceStatusEnum;
lastMeshInterraction: number;
ready: boolean;
myNodeInfo: Protobuf.MyNodeInfo;
positionPackets: Types.PositionPacket[];
nodes: Protobuf.NodeInfo[];
channels: Protobuf.Channel[];
preferences: Protobuf.RadioConfig_UserPreferences;
messages: MessageWithAck[];
hostOverrideEnabled: boolean;
hostOverride: string;
}
const initialState: AppState = {
deviceStatus: Types.DeviceStatusEnum.DEVICE_DISCONNECTED,
lastMeshInterraction: 0,
ready: false,
myNodeInfo: Protobuf.MyNodeInfo.create(),
positionPackets: [],
nodes: [],
channels: [],
preferences: Protobuf.RadioConfig_UserPreferences.create(),
messages: [],
hostOverrideEnabled:
localStorage.getItem('hostOverrideEnabled') === 'true' ?? false,
hostOverride: localStorage.getItem('hostOverride') ?? '',
};
export const meshtasticSlice = createSlice({
name: 'meshtastic',
initialState,
reducers: {
setDeviceStatus: (state, action: PayloadAction<Types.DeviceStatusEnum>) => {
state.deviceStatus = action.payload;
},
setLastMeshInterraction: (state, action: PayloadAction<number>) => {
state.lastMeshInterraction = action.payload;
},
setReady: (state, action: PayloadAction<boolean>) => {
state.ready = action.payload;
},
setMyNodeInfo: (state, action: PayloadAction<Protobuf.MyNodeInfo>) => {
state.myNodeInfo = action.payload;
},
addPositionPacket: (state, action: PayloadAction<Types.PositionPacket>) => {
state.positionPackets.push(action.payload);
},
addNode: (state, action: PayloadAction<Protobuf.NodeInfo>) => {
if (
state.nodes.findIndex((node) => node.num === action.payload.num) !== -1
) {
state.nodes = state.nodes.map((node) => {
return node.num === action.payload.num ? action.payload : node;
});
} else {
state.nodes.push(action.payload);
}
},
addChannel: (state, action: PayloadAction<Protobuf.Channel>) => {
if (
state.channels.findIndex(
(channel) => channel.index === action.payload.index,
) !== -1
) {
state.channels = state.channels.map((channel) => {
return channel.index === action.payload.index
? action.payload
: channel;
});
} else {
state.channels.push(action.payload);
}
},
setPreferences: (
state,
action: PayloadAction<Protobuf.RadioConfig_UserPreferences>,
) => {
state.preferences = action.payload;
},
addMessage: (state, action: PayloadAction<MessageWithAck>) => {
state.messages.push(action.payload);
},
ackMessage: (state, messageId: PayloadAction<number>) => {
state.messages.map((message) => {
if (message.message.packet.id === messageId.payload) {
message.ack = true;
}
});
},
setHostOverrideEnabled: (state, action: PayloadAction<boolean>) => {
state.hostOverrideEnabled = action.payload;
localStorage.setItem('hostOverrideEnabled', String(action.payload));
if (state.hostOverrideEnabled !== action.payload) {
connection.disconnect();
}
},
setHostOverride: (state, action: PayloadAction<string>) => {
state.hostOverride = action.payload;
localStorage.setItem('hostOverride', action.payload);
if (state.hostOverride !== action.payload) {
connection.disconnect();
}
},
},
});
export const {
setDeviceStatus,
setLastMeshInterraction,
setReady,
setMyNodeInfo,
addPositionPacket,
addNode,
addChannel,
setPreferences,
addMessage,
ackMessage,
setHostOverrideEnabled,
setHostOverride,
} = meshtasticSlice.actions;
export default meshtasticSlice.reducer;
+18
View File
@@ -0,0 +1,18 @@
import { configureStore } from '@reduxjs/toolkit';
import appReducer from './slices/appSlice';
import meshtasticReducer from './slices/meshtasticSlice';
export const store = configureStore({
reducer: {
app: appReducer,
meshtastic: meshtasticReducer,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: false,
}),
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
+28
View File
@@ -0,0 +1,28 @@
import type { Theme } from '@material-ui/core';
import { createTheme } from '@material-ui/core/styles';
export const theme = (darkMode: boolean): Theme => {
return createTheme(
darkMode
? {
palette: {
mode: 'dark',
primary: {
main: '#67ea94',
},
background: {
default: '#0F172A',
paper: '#0F172A',
},
},
}
: {
palette: {
mode: 'light',
primary: {
main: '#67ea94',
},
},
},
);
};
+21
View File
@@ -0,0 +1,21 @@
import i18n from 'i18next';
import detector from 'i18next-browser-languagedetector';
import { initReactI18next } from 'react-i18next';
import { en } from '../translations/en';
import { jp } from '../translations/jp';
import { pt } from '../translations/pt';
i18n
.use(detector)
.use(initReactI18next)
.init({
fallbackLng: 'en',
resources: {
en: { translation: en },
jp: { translation: jp },
pt: { translation: pt },
},
});
export default i18n;