- {children}
+ return (
+
+ );
+}
diff --git a/src/components/tracker/TrackerStatus.tsx b/src/components/tracker/TrackerStatus.tsx
new file mode 100644
index 000000000..4d4706eca
--- /dev/null
+++ b/src/components/tracker/TrackerStatus.tsx
@@ -0,0 +1,36 @@
+import classNames from 'classnames';
+import { useMemo } from 'react';
+import { Typography } from '../commons/Typography';
+import { TrackerStatus as TrackerStatusEnum } from 'solarxr-protocol';
+
+const statusLabelMap: { [key: number]: string } = {
+ [TrackerStatusEnum.NONE]: 'No Status',
+ [TrackerStatusEnum.BUSY]: 'Busy',
+ [TrackerStatusEnum.ERROR]: 'Error',
+ [TrackerStatusEnum.DISCONNECTED]: 'Disconnected',
+ [TrackerStatusEnum.OCCLUDED]: 'Occluded',
+ [TrackerStatusEnum.OK]: 'Connected',
+};
+
+const statusClassMap: { [key: number]: string } = {
+ [TrackerStatusEnum.NONE]: 'bg-background-30',
+ [TrackerStatusEnum.BUSY]: 'bg-status-warning',
+ [TrackerStatusEnum.ERROR]: 'bg-status-critical',
+ [TrackerStatusEnum.DISCONNECTED]: 'bg-background-30',
+ [TrackerStatusEnum.OCCLUDED]: 'bg-status-warning',
+ [TrackerStatusEnum.OK]: 'bg-status-success',
+};
+
+export function TrackerStatus({ status }: { status: number }) {
+ const statusClass = useMemo(() => statusClassMap[status], [status]);
+ const statusLabel = useMemo(() => statusLabelMap[status], [status]);
+
+ return (
+
+ );
+}
diff --git a/src/components/tracker/TrackerWifi.tsx b/src/components/tracker/TrackerWifi.tsx
new file mode 100644
index 000000000..36efaff6e
--- /dev/null
+++ b/src/components/tracker/TrackerWifi.tsx
@@ -0,0 +1,30 @@
+import { WifiIcon } from '../commons/icon/WifiIcon';
+import { Typography } from '../commons/Typography';
+
+export function TrackerWifi({
+ rssi,
+ ping,
+ disabled,
+}: {
+ rssi: number;
+ ping: number;
+ disabled?: boolean;
+}) {
+ return (
+
+
+
+
+ {!disabled && (
+
+ {ping} ms
+
+ )}
+ {disabled && (
+
+ )}
+
+ );
+}
diff --git a/src/components/tracker/TrackersTable.tsx b/src/components/tracker/TrackersTable.tsx
new file mode 100644
index 000000000..52c3cc5ba
--- /dev/null
+++ b/src/components/tracker/TrackersTable.tsx
@@ -0,0 +1,223 @@
+import classNames from 'classnames';
+import { MouseEventHandler, ReactChild, useState } from 'react';
+import {
+ TrackerDataT,
+ TrackerIdT,
+ TrackerStatus as TrackerStatusEnum,
+} from 'solarxr-protocol';
+import { FlatDeviceTracker } from '../../hooks/app';
+import { useTracker } from '../../hooks/tracker';
+import { FootIcon } from '../commons/icon/FootIcon';
+import { Typography } from '../commons/Typography';
+import { TrackerBattery } from './TrackerBattery';
+import { TrackerStatus } from './TrackerStatus';
+import { TrackerWifi } from './TrackerWifi';
+
+export function TrackerNameCol({ tracker }: { tracker: TrackerDataT }) {
+ const { useName } = useTracker(tracker);
+
+ const name = useName();
+
+ return (
+
+ );
+}
+
+export function TrackerRotCol({ tracker }: { tracker: TrackerDataT }) {
+ const { useRotation } = useTracker(tracker);
+
+ const rot = useRotation();
+
+ return (
+
+
+ {`${rot.x.toFixed(0)} / ${rot.y.toFixed(0)} / ${rot.z.toFixed(0)}`}
+
+
+ );
+}
+
+export function RowContainer({
+ children,
+ rounded = 'none',
+ hover,
+ onClick,
+ onMouseOver,
+ onMouseOut,
+}: {
+ children: ReactChild;
+ rounded?: 'left' | 'right' | 'none';
+ hover: boolean;
+ onClick?: MouseEventHandler
;
+ onMouseOver?: MouseEventHandler;
+ onMouseOut?: MouseEventHandler;
+}) {
+ return (
+
+ {children}
+
+ );
+}
+
+export function TrackersTable({
+ flatTrackers,
+ clickedTracker,
+}: {
+ clickedTracker: (tracker: TrackerDataT) => void;
+ flatTrackers: FlatDeviceTracker[];
+}) {
+ const [hoverTracker, setHoverTracker] = useState(null);
+
+ const trackerEqual = (id: TrackerIdT | null) =>
+ id?.trackerNum == hoverTracker?.trackerNum &&
+ (!id?.deviceId || id.deviceId.id == hoverTracker?.deviceId?.id);
+
+ return (
+
+
+
Tracker
+ {flatTrackers.map(({ tracker }, index) => (
+
clickedTracker(tracker)}
+ hover={trackerEqual(tracker.trackerId)}
+ onMouseOver={() => setHoverTracker(tracker.trackerId)}
+ onMouseOut={() => setHoverTracker(null)}
+ >
+
+
+ ))}
+
+
+
Type
+ {flatTrackers.map(({ device, tracker }, index) => (
+
clickedTracker(tracker)}
+ hover={trackerEqual(tracker.trackerId)}
+ onMouseOver={() => setHoverTracker(tracker.trackerId)}
+ onMouseOut={() => setHoverTracker(null)}
+ >
+
+ {device?.hardwareInfo?.manufacturer || '--'}
+
+
+ ))}
+
+
+
Battery
+ {flatTrackers.map(({ device, tracker }, index) => (
+
clickedTracker(tracker)}
+ hover={trackerEqual(tracker.trackerId)}
+ onMouseOver={() => setHoverTracker(tracker.trackerId)}
+ onMouseOut={() => setHoverTracker(null)}
+ >
+ {(device &&
+ device.hardwareStatus &&
+ device.hardwareStatus.batteryPctEstimate && (
+
+ )) || <>>}
+
+ ))}
+
+
+
Ping
+ {flatTrackers.map(({ device, tracker }, index) => (
+
clickedTracker(tracker)}
+ hover={trackerEqual(tracker.trackerId)}
+ onMouseOver={() => setHoverTracker(tracker.trackerId)}
+ onMouseOut={() => setHoverTracker(null)}
+ >
+ {(device &&
+ device.hardwareStatus &&
+ device.hardwareStatus.rssi &&
+ device.hardwareStatus.ping && (
+
+ )) || <>>}
+
+ ))}
+
+
+
Rotation X/Y/Z
+ {flatTrackers.map(({ tracker }, index) => (
+
clickedTracker(tracker)}
+ hover={trackerEqual(tracker.trackerId)}
+ onMouseOver={() => setHoverTracker(tracker.trackerId)}
+ onMouseOut={() => setHoverTracker(null)}
+ >
+
+
+ ))}
+
+
+
Position X/Y/Z
+ {flatTrackers.map(({ tracker }, index) => (
+
clickedTracker(tracker)}
+ hover={trackerEqual(tracker.trackerId)}
+ onMouseOver={() => setHoverTracker(tracker.trackerId)}
+ onMouseOut={() => setHoverTracker(null)}
+ >
+
+
+ {`${tracker.position?.x.toFixed(
+ 0
+ )} / ${tracker.position?.y.toFixed(
+ 0
+ )} / ${tracker.position?.z.toFixed(0)}`}
+
+
+
+ ))}
+
+
+
URL
+ {flatTrackers.map(({ device, tracker }, index) => (
+
clickedTracker(tracker)}
+ hover={trackerEqual(tracker.trackerId)}
+ onMouseOver={() => setHoverTracker(tracker.trackerId)}
+ onMouseOut={() => setHoverTracker(null)}
+ >
+ {device?.customName}
+
+ ))}
+
+
+ );
+}
diff --git a/src/gobals.d.ts b/src/gobals.d.ts
index 6eb21d016..e69de29bb 100644
--- a/src/gobals.d.ts
+++ b/src/gobals.d.ts
@@ -1,5 +0,0 @@
-import * as react from 'react'
-
-declare module 'react' {
- function useId(): string;
-}
\ No newline at end of file
diff --git a/src/hooks/app.ts b/src/hooks/app.ts
index e18ecf210..8d8fd4e21 100644
--- a/src/hooks/app.ts
+++ b/src/hooks/app.ts
@@ -1,55 +1,127 @@
-import { createContext, Dispatch, useContext, useReducer } from "react";
-
-type AppStateAction =
- | { type: 'debug', value: boolean }
+import {
+ createContext,
+ Dispatch,
+ Reducer,
+ useContext,
+ useEffect,
+ useLayoutEffect,
+ useMemo,
+ useReducer,
+} from 'react';
+import { useNavigate } from 'react-router-dom';
+import {
+ DataFeedConfigT,
+ DataFeedMessage,
+ DataFeedUpdateT,
+ DeviceDataMaskT,
+ DeviceDataT,
+ StartDataFeedT,
+ TrackerDataMaskT,
+ TrackerDataT,
+} from 'solarxr-protocol';
+import { useConfig } from './config';
+import { useWebsocketAPI } from './websocket-api';
+export interface FlatDeviceTracker {
+ device?: DeviceDataT;
+ tracker: TrackerDataT;
+}
+type AppStateAction = { type: 'datafeed'; value: DataFeedUpdateT };
export interface AppState {
- debug: boolean;
+ datafeed?: DataFeedUpdateT;
}
-
export interface AppContext {
- state: AppState;
- dispatch: Dispatch;
- setDebug: (value: boolean) => void;
+ state: AppState;
+ trackers: FlatDeviceTracker[];
+ dispatch: Dispatch;
}
-
export function reducer(state: AppState, action: AppStateAction) {
- switch (action.type) {
- case 'debug':
- return { ...state, debug: action.value };
- default:
- throw new Error(`unhandled state action ${action.type}`);
- }
+ switch (action.type) {
+ case 'datafeed':
+ return { ...state, datafeed: action.value };
+ default:
+ throw new Error(`unhandled state action ${action.type}`);
+ }
}
-
-
export function useProvideAppContext(): AppContext {
-
- const [state, dispatch] = useReducer(reducer, { debug: false })
-
-
- return {
- state,
- dispatch,
- setDebug: (value: boolean) => dispatch({ type: 'debug', value }),
+ const { sendDataFeedPacket, useDataFeedPacket, isConnected } =
+ useWebsocketAPI();
+ const { config } = useConfig();
+ const navigate = useNavigate();
+ const [state, dispatch] = useReducer>(
+ reducer,
+ {
+ datafeed: new DataFeedUpdateT(),
}
+ );
+
+ useEffect(() => {
+ if (isConnected) {
+ const trackerData = new TrackerDataMaskT();
+ trackerData.position = true;
+ trackerData.rotation = true;
+ trackerData.info = true;
+ trackerData.status = true;
+ trackerData.temp = true;
+
+ const dataMask = new DeviceDataMaskT();
+ dataMask.deviceData = true;
+ dataMask.trackerData = trackerData;
+
+ const config = new DataFeedConfigT();
+ config.dataMask = dataMask;
+ config.minimumTimeSinceLast = 100;
+ config.syntheticTrackersMask = trackerData;
+
+ const startDataFeed = new StartDataFeedT();
+ startDataFeed.dataFeeds = [config];
+ sendDataFeedPacket(DataFeedMessage.StartDataFeed, startDataFeed);
+ }
+ }, [isConnected]);
+
+ useLayoutEffect(() => {
+ if (config && !config.doneOnboarding) {
+ navigate('/onboarding/home');
+ }
+ }, [config]);
+
+ const trackers = useMemo(
+ () =>
+ (state.datafeed?.devices || []).reduce(
+ (curr, device) => [
+ ...curr,
+ ...device.trackers.map((tracker) => ({ tracker, device })),
+ ],
+ []
+ ),
+ [state]
+ );
+
+ useDataFeedPacket(
+ DataFeedMessage.DataFeedUpdate,
+ (packet: DataFeedUpdateT) => {
+ dispatch({ type: 'datafeed', value: packet });
+ }
+ );
+
+ return {
+ state,
+ trackers,
+ dispatch,
+ };
}
-
-
-
export const AppContextC = createContext(undefined as any);
export function useAppContext() {
- const context = useContext(AppContextC);
- if (!context) {
- throw new Error('useWebsocketAPI must be within a WebSocketApi Provider')
- }
- return context;
-
-}
\ No newline at end of file
+ const context = useContext(AppContextC);
+ if (!context) {
+ throw new Error('useAppContext must be within a AppContext Provider');
+ }
+ return context;
+}
diff --git a/src/hooks/autobone.tsx b/src/hooks/autobone.tsx
new file mode 100644
index 000000000..5b0a7243e
--- /dev/null
+++ b/src/hooks/autobone.tsx
@@ -0,0 +1,164 @@
+import { createContext, useContext, useMemo, useState } from 'react';
+import {
+ AutoBoneEpochResponseT,
+ AutoBoneProcessRequestT,
+ AutoBoneProcessStatusResponseT,
+ AutoBoneProcessType,
+ RpcMessage,
+ SkeletonBone,
+ SkeletonConfigRequestT,
+ SkeletonPartT,
+} from 'solarxr-protocol';
+import { skeletonBoneLabels } from '../components/onboarding/pages/body-proportions/BodyProportions';
+import { useWebsocketAPI } from './websocket-api';
+
+export interface AutoboneContext {
+ hasRecording: boolean;
+ hasCalibration: boolean;
+ progress: number;
+ bodyParts: { bone: SkeletonBone; label: string; value: number }[] | null;
+ startRecording: () => void;
+ startProcessing: () => void;
+ applyProcessing: () => void;
+}
+
+export function useProvideAutobone(): AutoboneContext {
+ const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
+ const [hasRecording, setHasRecording] = useState(false);
+ const [hasCalibration, setHasCalibration] = useState(false);
+ const [progress, setProgress] = useState(0);
+ const [skeletonParts, setSkeletonParts] = useState(
+ null
+ );
+
+ const bodyParts = useMemo(() => {
+ return (
+ skeletonParts?.map(({ bone, value }) => ({
+ bone,
+ label: skeletonBoneLabels[bone],
+ value,
+ })) || []
+ );
+ }, [skeletonParts]);
+
+ const startProcess = (processType: AutoBoneProcessType) => {
+ // Don't allow multiple processes at once (for now atleast)
+ // if (isProcessRunning) {
+ // return;
+ // }
+
+ setProgress(0);
+
+ const processRequest = new AutoBoneProcessRequestT();
+ processRequest.processType = processType;
+
+ sendRPCPacket(RpcMessage.AutoBoneProcessRequest, processRequest);
+ };
+
+ const startRecording = () => {
+ setHasRecording(false);
+ startProcess(AutoBoneProcessType.RECORD);
+ };
+
+ const startProcessing = () => {
+ setHasCalibration(false);
+ startProcess(AutoBoneProcessType.PROCESS);
+ };
+
+ const applyProcessing = () => {
+ startProcess(AutoBoneProcessType.APPLY);
+ };
+
+ useRPCPacket(
+ RpcMessage.AutoBoneProcessStatusResponse,
+ (data: AutoBoneProcessStatusResponseT) => {
+ if (data.completed) {
+ setProgress(1);
+ }
+
+ if (data.processType) {
+ if (data.message) {
+ console.log(
+ AutoBoneProcessType[data.processType],
+ ': ',
+ data.message
+ );
+ }
+
+ if (data.total > 0 && data.current >= 0) {
+ setProgress(data.current / data.total);
+ }
+
+ if (data.completed) {
+ console.log(
+ 'Process ',
+ AutoBoneProcessType[data.processType],
+ ' has completed'
+ );
+
+ switch (data.processType) {
+ case AutoBoneProcessType.RECORD:
+ setHasRecording(data.success);
+ startProcessing();
+ break;
+
+ case AutoBoneProcessType.PROCESS:
+ setHasCalibration(data.success);
+
+ break;
+
+ case AutoBoneProcessType.APPLY:
+ // Update skeleton config when applied
+ sendRPCPacket(
+ RpcMessage.SkeletonConfigRequest,
+ new SkeletonConfigRequestT()
+ );
+ break;
+ }
+ }
+ }
+ }
+ );
+
+ useRPCPacket(
+ RpcMessage.AutoBoneEpochResponse,
+ (data: AutoBoneEpochResponseT) => {
+ setProgress(data.currentEpoch / data.totalEpochs);
+
+ // Probably not necessary to show to the user
+ console.log(
+ 'Epoch ',
+ data.currentEpoch,
+ '/',
+ data.totalEpochs,
+ ' (Error ',
+ data.epochError,
+ ')'
+ );
+
+ setSkeletonParts(data.adjustedSkeletonParts);
+ }
+ );
+
+ return {
+ hasCalibration,
+ hasRecording,
+ progress,
+ bodyParts,
+ startProcessing,
+ startRecording,
+ applyProcessing,
+ };
+}
+
+export const AutoboneContextC = createContext(
+ undefined as any
+);
+
+export function useAutobone() {
+ const context = useContext(AutoboneContextC);
+ if (!context) {
+ throw new Error('useAutobone must be within a AutoboneContext Provider');
+ }
+ return context;
+}
diff --git a/src/hooks/config.ts b/src/hooks/config.ts
new file mode 100644
index 000000000..45bb66a27
--- /dev/null
+++ b/src/hooks/config.ts
@@ -0,0 +1,79 @@
+import {
+ BaseDirectory,
+ readTextFile,
+ writeFile,
+ createDir,
+} from '@tauri-apps/api/fs';
+import { appDir } from '@tauri-apps/api/path';
+
+import { createContext, useContext, useState } from 'react';
+
+export interface Config {
+ debug: boolean;
+ doneOnboarding: boolean;
+}
+
+export interface ConfigContext {
+ config: Config | null;
+ loading: boolean;
+ setConfig: (config: Partial) => Promise;
+ loadConfig: () => Promise;
+}
+
+const initialConfig = { doneOnboarding: false };
+
+export function useConfigProvider(): ConfigContext {
+ const [currConfig, set] = useState(null);
+ const [loading, setLoading] = useState(false);
+
+ const setConfig = async (config: Partial) => {
+ const newConfig = config
+ ? {
+ ...currConfig,
+ ...config,
+ }
+ : null;
+ set(newConfig as Config);
+
+ await createDir('', { dir: BaseDirectory.App, recursive: true });
+ await writeFile(
+ { contents: JSON.stringify(newConfig), path: 'config.json' },
+ { dir: BaseDirectory.App }
+ );
+ };
+
+ return {
+ config: currConfig,
+ loading,
+ setConfig,
+ loadConfig: async () => {
+ setLoading(true);
+ try {
+ const appDirPath = await appDir();
+ console.log(appDirPath);
+ const json = await readTextFile('config.json', {
+ dir: BaseDirectory.App,
+ });
+ const loadedConfig = JSON.parse(json);
+ set(loadedConfig);
+ setLoading(false);
+ return loadedConfig;
+ } catch (e) {
+ console.log(e);
+ setConfig(initialConfig);
+ setLoading(false);
+ return null;
+ }
+ },
+ };
+}
+
+export const ConfigContextC = createContext(undefined as never);
+
+export function useConfig() {
+ const context = useContext(ConfigContextC);
+ if (!context) {
+ throw new Error('useConfig must be within a ConfigContext Provider');
+ }
+ return context;
+}
diff --git a/src/hooks/layout.ts b/src/hooks/layout.ts
index f7a67ba9b..bfba85735 100644
--- a/src/hooks/layout.ts
+++ b/src/hooks/layout.ts
@@ -1,35 +1,74 @@
-import { useEffect, useRef, useState } from "react";
-
-
+import {
+ MutableRefObject,
+ Ref,
+ useEffect,
+ useLayoutEffect,
+ useMemo,
+ useRef,
+ useState,
+} from 'react';
export function useLayout() {
- const [layoutHeight, setLayoutHeigt] = useState(window.innerHeight);
- const [layoutWidth, setLayoutWidth] = useState(window.innerWidth);
- const ref = useRef(null);
-
- const computeLayoutHeight = (windowHeight: number, windowWidth: number) => {
- if (ref.current) {
- setLayoutHeigt(windowHeight - ref.current.getBoundingClientRect().top)
- setLayoutWidth(windowWidth - ref.current.getBoundingClientRect().left)
- }
+ const [layoutHeight, setLayoutHeigt] = useState(window.innerHeight);
+ const [layoutWidth, setLayoutWidth] = useState(window.innerWidth);
+ const ref = useRef(null);
+
+ const computeLayoutHeight = (windowHeight: number, windowWidth: number) => {
+ if (ref.current) {
+ setLayoutHeigt(windowHeight - ref.current.getBoundingClientRect().top);
+ setLayoutWidth(windowWidth - ref.current.getBoundingClientRect().left);
}
-
- const onWindowResize = () => {
- computeLayoutHeight(window.innerHeight, window.innerWidth)
+ };
+
+ const onWindowResize = () => {
+ computeLayoutHeight(window.innerHeight, window.innerWidth);
+ };
+
+ useLayoutEffect(() => {
+ window.addEventListener('resize', onWindowResize);
+ computeLayoutHeight(window.innerHeight, window.innerWidth);
+ return () => {
+ window.removeEventListener('resize', onWindowResize);
+ };
+ });
+
+ return {
+ layoutHeight,
+ layoutWidth,
+ ref,
+ };
+}
+
+export function useElemSize(
+ forwardRef?: MutableRefObject
+) {
+ const innerRef = useRef(null);
+ const ref = forwardRef || innerRef;
+ const [height, setHeight] = useState(0);
+ const [width, setWidth] = useState(0);
+
+ const observer = useRef(
+ new ResizeObserver((entries) => {
+ const { width, height } = entries[0].contentRect;
+ setWidth(width);
+ setHeight(height);
+ })
+ );
+
+ useEffect(() => {
+ if (ref.current) {
+ observer.current.observe(ref.current);
}
- useEffect(() => {
- window.addEventListener('resize', onWindowResize);
- computeLayoutHeight(window.innerHeight, window.innerWidth)
- return () => {
- window.removeEventListener('resize', onWindowResize);
- }
- }, [])
+ return () => {
+ if (!ref.current) return;
+ observer.current.unobserve(ref.current);
+ };
+ }, [ref, observer]);
-
- return {
- layoutHeight,
- layoutWidth,
- ref
- }
-}
\ No newline at end of file
+ return {
+ ref,
+ height,
+ width,
+ };
+}
diff --git a/src/hooks/onboarding.tsx b/src/hooks/onboarding.tsx
new file mode 100644
index 000000000..eec33f318
--- /dev/null
+++ b/src/hooks/onboarding.tsx
@@ -0,0 +1,99 @@
+import {
+ createContext,
+ Reducer,
+ useContext,
+ useEffect,
+ useLayoutEffect,
+ useReducer,
+} from 'react';
+import { useLocation, useParams } from 'react-router-dom';
+import { useConfig } from './config';
+
+type OnboardingAction =
+ | { type: 'progress'; value: number }
+ | { type: 'alone-page'; value: boolean }
+ | { type: 'wifi-creds'; ssid: string; password: string };
+
+interface OnboardingState {
+ progress: number;
+ wifi?: { ssid: string; password: string };
+ alonePage: boolean;
+}
+
+export interface OnboardingContext {
+ state: OnboardingState;
+ applyProgress: (value: number) => void;
+ setWifiCredentials: (ssid: string, password: string) => void;
+ skipSetup: () => void;
+}
+
+export function reducer(state: OnboardingState, action: OnboardingAction) {
+ switch (action.type) {
+ case 'wifi-creds':
+ return {
+ ...state,
+ wifi: { ssid: action.ssid, password: action.password },
+ };
+ case 'progress':
+ return {
+ ...state,
+ progress: action.value,
+ };
+ case 'alone-page':
+ return {
+ ...state,
+ alonePage: action.value,
+ };
+ default:
+ throw new Error(`unhandled state action ${(action as any).type}`);
+ }
+}
+
+export function useProvideOnboarding(): OnboardingContext {
+ const { setConfig } = useConfig();
+ const [state, dispatch] = useReducer<
+ Reducer
+ >(reducer, {
+ progress: 0,
+ alonePage: false,
+ });
+
+ const { state: locatioState } = useLocation();
+
+ useLayoutEffect(() => {
+ const { alonePage = false }: { alonePage?: boolean } =
+ (locatioState as any) || {};
+
+ if (alonePage !== state.alonePage)
+ dispatch({ type: 'alone-page', value: alonePage });
+ }, [locatioState, state]);
+
+ return {
+ state,
+ applyProgress: (value: number) => {
+ useLayoutEffect(() => {
+ dispatch({ type: 'progress', value });
+ }, []);
+ },
+ setWifiCredentials: (ssid: string, password: string) => {
+ dispatch({ type: 'wifi-creds', ssid, password });
+ },
+ skipSetup: () => {
+ setConfig({ doneOnboarding: true });
+ },
+ };
+}
+
+export const OnboardingContextC = createContext(
+ undefined as any
+);
+
+export function useOnboarding() {
+ const context = useContext(OnboardingContextC);
+ if (!context) {
+ throw new Error(
+ 'useOnboarding must be within a OnboardingContext Provider'
+ );
+ }
+ return context;
+}
diff --git a/src/hooks/router.ts b/src/hooks/router.ts
deleted file mode 100644
index 3916ea694..000000000
--- a/src/hooks/router.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-export function usePageScrollTo() {
-
-}
\ No newline at end of file
diff --git a/src/hooks/timeout.ts b/src/hooks/timeout.ts
index ca160981d..311a470b5 100644
--- a/src/hooks/timeout.ts
+++ b/src/hooks/timeout.ts
@@ -1,15 +1,28 @@
-import { useEffect } from 'react';
+import { useEffect, useState } from 'react';
-export const useTimeout = (fn: () => void, delay: number) => {
+export function useTimeout(fn: () => void, delay: number) {
useEffect(() => {
const id = setTimeout(fn, delay);
return () => clearTimeout(id);
});
-};
+}
-export const useInterval = (fn: () => void, delay: number) => {
+export function useInterval(fn: () => void, delay: number) {
useEffect(() => {
const id = setInterval(fn, delay);
return () => clearInterval(id);
});
-};
\ No newline at end of file
+}
+
+export const useDebouncedEffect = (
+ effect: () => void,
+ deps: any[],
+ delay: number
+) => {
+ useEffect(() => {
+ const handler = setTimeout(() => effect(), delay);
+
+ return () => clearTimeout(handler);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [...(deps || []), delay]);
+};
diff --git a/src/hooks/tracker.tsx b/src/hooks/tracker.tsx
new file mode 100644
index 000000000..c4e36d873
--- /dev/null
+++ b/src/hooks/tracker.tsx
@@ -0,0 +1,116 @@
+import { useEffect, useMemo, useRef, useState } from 'react';
+import { BodyPart, TrackerDataT, TrackerStatus } from 'solarxr-protocol';
+import { QuaternionFromQuatT } from '../maths/quaternion';
+import { useAppContext } from './app';
+
+export function useTrackers() {
+ const { trackers } = useAppContext();
+
+ return {
+ trackers,
+ useAssignedTrackers: () =>
+ useMemo(
+ () =>
+ trackers.filter(
+ ({ tracker }) => tracker.info?.bodyPart !== BodyPart.NONE
+ ),
+ [trackers]
+ ),
+ useUnassignedTrackers: () =>
+ useMemo(
+ () =>
+ trackers.filter(
+ ({ tracker }) => tracker.info?.bodyPart === BodyPart.NONE
+ ),
+ [trackers]
+ ),
+ useConnectedTrackers: () =>
+ useMemo(
+ () =>
+ trackers.filter(
+ ({ tracker }) => tracker.status !== TrackerStatus.DISCONNECTED
+ ),
+ [trackers]
+ ),
+ };
+}
+
+export function useTracker(tracker: TrackerDataT) {
+ return {
+ useName: () =>
+ useMemo(() => {
+ if (tracker.info?.customName) return tracker.info?.customName;
+ if (tracker.info?.bodyPart) return BodyPart[tracker.info?.bodyPart];
+ return tracker.info?.displayName || 'NONE';
+ }, [tracker.info]),
+ useRotation: () =>
+ useMemo(
+ () =>
+ QuaternionFromQuatT({
+ x: tracker.rotation?.x || 0,
+ y: tracker.rotation?.y || 0,
+ z: tracker.rotation?.z || 0,
+ w: tracker.rotation?.w || 1,
+ }).eulerAngles,
+ [tracker.rotation]
+ ),
+ useVelocity: () => {
+ const previousRot = useRef<{
+ x: number;
+ y: number;
+ z: number;
+ w: number;
+ }>(tracker.rotation || { x: 0, y: 0, z: 0, w: 1 });
+ const [velocity, setVelocity] = useState(0);
+ const [rots, setRotation] = useState([]);
+
+ useEffect(() => {
+ if (tracker.rotation) {
+ const rot = QuaternionFromQuatT(tracker.rotation).mul(
+ QuaternionFromQuatT(previousRot.current).inverse()
+ );
+ const dif = Math.min(1, (rot.x ** 2 + rot.y ** 2 + rot.z ** 2) * 2.5);
+ // Use sum of rotation of last 3 frames (0.3sec) for smoother movement and better detection of slow movement.
+ if (rots.length === 3) {
+ rots.shift();
+ }
+ rots.push(dif);
+ setRotation(rots);
+ setVelocity(
+ Math.min(
+ 1,
+ Math.max(
+ 0,
+ rots.reduce((a, b) => a + b)
+ )
+ )
+ );
+ previousRot.current = tracker.rotation;
+ }
+ }, [tracker.rotation]);
+
+ return velocity;
+ },
+ };
+}
+
+export function useTrackerFromId(
+ trackerNum: string | number | undefined,
+ deviceId: string | number | undefined
+) {
+ const { trackers } = useAppContext();
+
+ const tracker = useMemo(
+ () =>
+ trackers.find(
+ ({ tracker }) =>
+ trackerNum &&
+ deviceId &&
+ tracker?.trackerId?.trackerNum == trackerNum &&
+ tracker?.trackerId?.deviceId?.id == deviceId
+ ),
+ [trackers, trackerNum, deviceId]
+ );
+
+ return tracker;
+}
diff --git a/src/hooks/websocket-api.ts b/src/hooks/websocket-api.ts
index 4b71d541a..8ff7f14c4 100644
--- a/src/hooks/websocket-api.ts
+++ b/src/hooks/websocket-api.ts
@@ -1,172 +1,190 @@
-import { createContext, useContext, useEffect, useRef, useState } from "react";
+import { createContext, useContext, useEffect, useRef, useState } from 'react';
-import { DataFeedMessage, DataFeedMessageHeaderT, MessageBundle, MessageBundleT, RpcMessage, RpcMessageHeaderT } from 'solarxr-protocol'
+import {
+ DataFeedMessage,
+ DataFeedMessageHeaderT,
+ MessageBundle,
+ MessageBundleT,
+ RpcMessage,
+ RpcMessageHeaderT,
+} from 'solarxr-protocol';
-import { Builder, ByteBuffer } from 'flatbuffers'
-import { useInterval } from "./timeout";
+import { Builder, ByteBuffer } from 'flatbuffers';
+import { useInterval } from './timeout';
export interface WebSocketApi {
- isConnected: boolean,
- useRPCPacket: (type: RpcMessage, callback: (packet: T) => void) => void
- useDataFeedPacket: (type: DataFeedMessage, callback: (packet: T) => void) => void
- sendRPCPacket: (type: RpcMessage, data: RPCPacketType) => void
- sendDataFeedPacket: (type: DataFeedMessage, data: DataFeedPacketType) => void
+ isConnected: boolean;
+ useRPCPacket: (type: RpcMessage, callback: (packet: T) => void) => void;
+ useDataFeedPacket: (
+ type: DataFeedMessage,
+ callback: (packet: T) => void
+ ) => void;
+ sendRPCPacket: (type: RpcMessage, data: RPCPacketType) => void;
+ sendDataFeedPacket: (type: DataFeedMessage, data: DataFeedPacketType) => void;
}
-
-export const WebSocketApiContext = createContext(undefined as any);
+export const WebSocketApiContext = createContext(
+ undefined as never
+);
export type RPCPacketType = RpcMessageHeaderT['message'];
export type DataFeedPacketType = DataFeedMessageHeaderT['message'];
// export type OutboundPacketType = OutboundPacketT['packet'];
export function useProvideWebsocketApi(): WebSocketApi {
- const rpcPacketCounterRef = useRef(0);
- const webSocketRef = useRef(null);
- const rpclistenerRef = useRef(new EventTarget());
- const datafeedlistenerRef = useRef(new EventTarget());
- const [isConnected, setConnected] = useState(false);
+ const rpcPacketCounterRef = useRef(0);
+ const webSocketRef = useRef(null);
+ const rpclistenerRef = useRef(new EventTarget());
+ const datafeedlistenerRef = useRef(new EventTarget());
+ const [isConnected, setConnected] = useState(false);
-
- useInterval(() => {
- if (webSocketRef.current && !isConnected) {
- disconnect();
- connect();
- console.log('Try reconnecting');
- }
- }, 3000);
-
- const onConnected = (event: Event) => {
- if (!webSocketRef.current) return ;
-
- setConnected(true);
+ useInterval(() => {
+ if (webSocketRef.current && !isConnected) {
+ disconnect();
+ connect();
+ console.log('Try reconnecting');
}
+ }, 3000);
- const onConnectionClose = (event: Event) => {
- setConnected(false);
- rpcPacketCounterRef.current = 0;
- }
+ const onConnected = () => {
+ if (!webSocketRef.current) return;
+ setConnected(true);
+ };
- const onMessage = async (event: { data: Blob }) => {
- if (!event.data.arrayBuffer)
- return ;
- const buffer = await event.data.arrayBuffer();
+ const onConnectionClose = () => {
+ setConnected(false);
+ rpcPacketCounterRef.current = 0;
+ };
- const fbb = new ByteBuffer(new Uint8Array(buffer));
+ const onMessage = async (event: { data: Blob }) => {
+ if (!event.data.arrayBuffer) return;
+ const buffer = await event.data.arrayBuffer();
- const message = MessageBundle.getRootAsMessageBundle(fbb).unpack();
+ const fbb = new ByteBuffer(new Uint8Array(buffer));
+ const message = MessageBundle.getRootAsMessageBundle(fbb).unpack();
- message.rpcMsgs.forEach((rpcHeader) => {
- rpclistenerRef.current?.dispatchEvent(new CustomEvent(RpcMessage[rpcHeader.messageType], { detail: rpcHeader.message }))
+ message.rpcMsgs.forEach((rpcHeader) => {
+ rpclistenerRef.current?.dispatchEvent(
+ new CustomEvent(RpcMessage[rpcHeader.messageType], {
+ detail: rpcHeader.message,
})
+ );
+ });
- message.dataFeedMsgs.forEach((datafeedHeader) => {
- datafeedlistenerRef.current?.dispatchEvent(new CustomEvent(DataFeedMessage[datafeedHeader.messageType], { detail: datafeedHeader.message }))
+ message.dataFeedMsgs.forEach((datafeedHeader) => {
+ datafeedlistenerRef.current?.dispatchEvent(
+ new CustomEvent(DataFeedMessage[datafeedHeader.messageType], {
+ detail: datafeedHeader.message,
})
- }
+ );
+ });
+ };
- const sendRPCPacket = (type: RpcMessage, data: RPCPacketType): void => {
- if (!webSocketRef.current)
- throw new Error('No connection');
+ const sendRPCPacket = (type: RpcMessage, data: RPCPacketType): void => {
+ if (!webSocketRef.current) throw new Error('No connection');
- const fbb = new Builder(1);
+ const fbb = new Builder(1);
- const message = new MessageBundleT();
+ const message = new MessageBundleT();
+ const rpcHeader = new RpcMessageHeaderT();
+ rpcHeader.messageType = type;
+ rpcHeader.message = data;
- const rpcHeader = new RpcMessageHeaderT();
- rpcHeader.messageType = type;
- rpcHeader.message = data;
+ message.rpcMsgs = [rpcHeader];
+ fbb.finish(message.pack(fbb));
- message.rpcMsgs = [rpcHeader]
- fbb.finish(message.pack(fbb));
+ webSocketRef.current.send(fbb.asUint8Array());
- webSocketRef.current.send(fbb.asUint8Array());
+ rpcPacketCounterRef.current++;
+ };
+ const sendDataFeedPacket = (
+ type: DataFeedMessage,
+ data: DataFeedPacketType
+ ): void => {
+ if (!webSocketRef.current) throw new Error('No connection');
+ const fbb = new Builder(1);
- rpcPacketCounterRef.current++;
- }
+ const message = new MessageBundleT();
- const sendDataFeedPacket = (type: DataFeedMessage, data: DataFeedPacketType): void => {
- if (!webSocketRef.current)
- throw new Error('No connection');
+ const datafeedHeader = new DataFeedMessageHeaderT();
+ datafeedHeader.messageType = type;
+ datafeedHeader.message = data;
- const fbb = new Builder(1);
+ message.dataFeedMsgs = [datafeedHeader];
+ fbb.finish(message.pack(fbb));
- const message = new MessageBundleT();
+ webSocketRef.current.send(fbb.asUint8Array());
+ };
- const datafeedHeader = new DataFeedMessageHeaderT();
- datafeedHeader.messageType = type;
- datafeedHeader.message = data;
+ const connect = () => {
+ webSocketRef.current = new WebSocket('ws://localhost:21110');
- message.dataFeedMsgs = [datafeedHeader]
- fbb.finish(message.pack(fbb));
+ // Connection opened
+ webSocketRef.current.addEventListener('open', onConnected);
+ webSocketRef.current.addEventListener('close', onConnectionClose);
+ webSocketRef.current.addEventListener('message', onMessage);
+ };
- webSocketRef.current.send(fbb.asUint8Array());
- }
+ const disconnect = () => {
+ if (!webSocketRef.current) return;
- const connect = () => {
- webSocketRef.current = new WebSocket('ws://localhost:21110');
-
+ webSocketRef.current.removeEventListener('open', onConnected);
+ webSocketRef.current.removeEventListener('close', onConnectionClose);
+ webSocketRef.current.removeEventListener('message', onMessage);
+ };
- // Connection opened
- webSocketRef.current.addEventListener('open', onConnected);
- webSocketRef.current.addEventListener('close', onConnectionClose);
- webSocketRef.current.addEventListener('message', onMessage);
- }
+ useEffect(() => {
+ connect();
+ return () => {
+ disconnect();
+ };
+ }, []);
- const disconnect = () => {
- if (!webSocketRef.current) return ;
-
- webSocketRef.current.removeEventListener('open', onConnected);
- webSocketRef.current.removeEventListener('close', onConnectionClose);
- webSocketRef.current.removeEventListener('message', onMessage);
- }
-
- useEffect(() => {
- connect();
+ return {
+ isConnected,
+ useDataFeedPacket: (
+ type: DataFeedMessage,
+ callback: (packet: T) => void
+ ) => {
+ useEffect(() => {
+ const onEvent = (event: CustomEventInit) => {
+ callback(event.detail);
+ };
+ datafeedlistenerRef.current.addEventListener(
+ DataFeedMessage[type],
+ onEvent
+ );
return () => {
- disconnect();
- }
- }, [])
-
- return {
- isConnected,
- useDataFeedPacket: (type: DataFeedMessage, callback: (packet: T) => void) => {
- const onEvent = (event: CustomEventInit) => {
- callback(event.detail)
- }
-
- useEffect(() => {
- datafeedlistenerRef.current.addEventListener(DataFeedMessage[type], onEvent)
- return () => {
- datafeedlistenerRef.current.removeEventListener(DataFeedMessage[type], onEvent)
- }
- }, [])
- },
- useRPCPacket: (type: RpcMessage, callback: (packet: T) => void) => {
- const onEvent = (event: CustomEventInit) => {
- callback(event.detail)
- }
-
- useEffect(() => {
- rpclistenerRef.current.addEventListener(RpcMessage[type], onEvent)
- return () => {
- rpclistenerRef.current.removeEventListener(RpcMessage[type], onEvent)
- }
- }, [])
- },
- sendRPCPacket,
- sendDataFeedPacket
- }
+ datafeedlistenerRef.current.removeEventListener(
+ DataFeedMessage[type],
+ onEvent
+ );
+ };
+ }, [callback, type]);
+ },
+ useRPCPacket: (type: RpcMessage, callback: (packet: T) => void) => {
+ useEffect(() => {
+ const onEvent = (event: CustomEventInit) => {
+ callback(event.detail);
+ };
+ rpclistenerRef.current.addEventListener(RpcMessage[type], onEvent);
+ return () => {
+ rpclistenerRef.current.removeEventListener(RpcMessage[type], onEvent);
+ };
+ }, [callback, type]);
+ },
+ sendRPCPacket,
+ sendDataFeedPacket,
+ };
}
-
export function useWebsocketAPI(): WebSocketApi {
- const context = useContext(WebSocketApiContext);
- if (!context) {
- throw new Error('useWebsocketAPI must be within a WebSocketApi Provider')
- }
- return context;
-}
\ No newline at end of file
+ const context = useContext(WebSocketApiContext);
+ if (!context) {
+ throw new Error('useWebsocketAPI must be within a WebSocketApi Provider');
+ }
+ return context;
+}
diff --git a/src/index.css b/src/index.css
index 3de263426..2aa04a45d 100644
--- a/src/index.css
+++ b/src/index.css
@@ -2,49 +2,48 @@
@tailwind components;
@tailwind utilities;
-
body {
font-variant-numeric: tabular-nums;
- font-family: "Work Sans", sans-serif;
+ font-family: 'poppins', sans-serif;
height: 100vh;
width: 100vw;
user-select: none;
- overflow: hidden;
- border-radius: 15px;
+ background: theme('colors.background.20');
}
html {
overflow: hidden;
- border-radius: 15px;
+ background: theme('colors.background.20');
}
#root {
height: 100%;
}
-
::-webkit-scrollbar {
- width: 10px;
- height: 10px;
+ width: 4px;
+ height: 4px;
}
::-webkit-scrollbar-corner {
- background-color: theme('colors.purple-gray.900');
+ background-color: transparent;
}
::-webkit-scrollbar-track {
- background: theme('colors.purple-gray.600');
+ background: transparent;
border-radius: 50px;
- border-radius: 50px;
-
}
::-webkit-scrollbar-thumb {
- background: theme('colors.purple-gray.400');
- border-radius: 50px;
+ background: theme('colors.background.50');
border-radius: 50px;
}
::-webkit-scrollbar-thumb:hover {
- background: theme('colors.purple-gray.300');
-}
\ No newline at end of file
+ background: theme('colors.background.60');
+}
+
+input::-ms-reveal,
+input::-ms-clear {
+ display: none;
+}
diff --git a/src/index.tsx b/src/index.tsx
index b40707a45..da70ffca2 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,8 +1,10 @@
import * as ReactDOMClient from 'react-dom/client';
-import "@fontsource/work-sans/variable.css";
+import '@fontsource/poppins/500.css';
+import '@fontsource/poppins/700.css';
import './index.css';
import App from './App';
import Modal from 'react-modal';
+import React from 'react';
Modal.setAppElement('#root');
@@ -11,6 +13,8 @@ const container = document.getElementById('root');
if (container) {
const root = ReactDOMClient.createRoot(container);
root.render(
-
+
+
+
);
}
diff --git a/src/maths/angle.ts b/src/maths/angle.ts
index d40a44de6..466a17589 100644
--- a/src/maths/angle.ts
+++ b/src/maths/angle.ts
@@ -1,5 +1,3 @@
-
-
export const DEG_TO_RAD = Math.PI / 180.0;
-export const RAD_TO_DEG = 180.0 / Math.PI;
\ No newline at end of file
+export const RAD_TO_DEG = 180.0 / Math.PI;
diff --git a/src/maths/quaternion.ts b/src/maths/quaternion.ts
index 37f646d89..1314ba3e5 100644
--- a/src/maths/quaternion.ts
+++ b/src/maths/quaternion.ts
@@ -1,25 +1,34 @@
import { Quaternion } from 'math3d';
import { QuatT } from 'solarxr-protocol';
-
-export function QuaternionFromQuatT(q: { x: number, y: number, z: number, w: number }) {
- return new Quaternion(q.x, q.y, q.z, q.w)
+export function QuaternionFromQuatT(q: {
+ x: number;
+ y: number;
+ z: number;
+ w: number;
+}) {
+ return new Quaternion(q.x, q.y, q.z, q.w);
}
-export function QuaternionToQuatT(q: { x: number, y: number, z: number, w: number }) {
- const quat = new QuatT();
+export function QuaternionToQuatT(q: {
+ x: number;
+ y: number;
+ z: number;
+ w: number;
+}) {
+ const quat = new QuatT();
- quat.x = q.x;
- quat.y = q.y;
- quat.z = q.z;
- quat.w = q.w;
- return quat;
+ quat.x = q.x;
+ quat.y = q.y;
+ quat.z = q.z;
+ quat.w = q.w;
+ return quat;
}
export function FixEuler(yaw: number) {
- if(yaw > 180) {
- yaw *= -1;
- yaw += 180;
- }
- return Math.round(yaw)
-}
\ No newline at end of file
+ if (yaw > 180) {
+ yaw *= -1;
+ yaw += 180;
+ }
+ return Math.round(yaw);
+}
diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts
index 624c875ec..7cb0ddde1 100644
--- a/src/react-app-env.d.ts
+++ b/src/react-app-env.d.ts
@@ -1,6 +1,6 @@
-///
-///
-///
+// /
+// /
+// /
declare namespace NodeJS {
interface ProcessEnv {
@@ -40,16 +40,16 @@ declare module '*.png' {
}
declare module '*.webp' {
- const src: string;
- export default src;
+ const src: string;
+ export default src;
}
declare module '*.svg' {
import * as React from 'react';
- export const ReactComponent: React.FunctionComponent & { title?: string }>;
+ export const ReactComponent: React.FunctionComponent<
+ React.SVGProps & { title?: string }
+ >;
const src: string;
export default src;
diff --git a/tailwind.config.js b/tailwind.config.js
index 020edaea5..1ceda9de0 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,60 +1,89 @@
-const plugin = require('tailwindcss/plugin')
+const plugin = require('tailwindcss/plugin');
-const rem = (px) => `${(px / 12).toFixed(4)}rem`
+const rem = (pt) => `${pt / 16}rem`;
+
+const colors = {
+ 'blue-gray': {
+ 100: '#ffffff',
+ 200: '#78A4C6',
+ 300: '#608AAB',
+ 400: '#3D6381',
+ 500: '#1A3D59',
+ 600: '#112D43',
+ 700: '#081E30',
+ 800: '#00101C',
+ 900: '#000509',
+ },
+ purple: {
+ 100: '#BB8AE5',
+ 200: '#9D5CD4',
+ 500: '#65459A',
+ 700: '#623B83',
+ 900: '#2E2145',
+ },
+};
module.exports = {
- content: ["./src/**/*.{js,jsx,ts,tsx}",],
+ content: ['./src/**/*.{js,jsx,ts,tsx}'],
theme: {
+ screens: {
+ xs: '800px',
+ sm: '900px',
+ md: '1100px',
+ lg: '1300px',
+ xl: '1600px',
+ },
extend: {
colors: {
- 'accent': {
- darker: '#831ECC',
- lighter: '#C06FFB'
- },
- 'status': {
- online: '#9AFF76',
- warning: '#FFB257',
- error: '#FF6464'
- },
- 'purple-gray': {
- 900: "#160B1D",
- 800: "#261730",
- 700: "#3F2A4F",
- 600: "#593E6C",
- 500: "#6E5084",
- 400: "#8E6BA7",
- 300: "#C0A1D8",
- 200: "#EFE2F9",
- 100: "#FFFFFF"
+ status: {
+ success: '#50E897',
+ warning: '#D8CD37',
+ critical: '#DF6D8C',
+ special: '#A44FED',
},
+ ...colors,
+ background: Object.keys(colors['blue-gray']).reduce(
+ (curr, colork, index) => ({
+ ...curr,
+ [(index + 1) * 10]: colors['blue-gray'][colork],
+ }),
+ {}
+ ),
+ 'accent-background': Object.keys(colors.purple).reduce(
+ (curr, colork, index) => ({
+ ...curr,
+ [(index + 1) * 10]: colors.purple[colork],
+ }),
+ {}
+ ),
},
fontSize: {
- DEFAULT: rem(12),
+ DEFAULT: rem(10),
},
fontWeight: {
- DEFAULT: 400,
- }
+ DEFAULT: 500,
+ },
+ color: {
+ DEFAULT: '#ffffff',
+ },
},
},
plugins: [
require('@tailwindcss/forms'),
- plugin(function({ addUtilities, theme }) {
-
- const textConfig = (fontSize, fontWeight, color) => ({
- color,
+ plugin(function ({ addUtilities, theme }) {
+ const textConfig = (fontSize, fontWeight) => ({
fontSize,
- fontWeight
- })
+ fontWeight,
+ });
addUtilities({
- '.text-heading': textConfig(rem(35), 700, theme('colors.purple-gray.100')),
- '.text-secondary-heading': textConfig(rem(25), 700, theme('colors.purple-gray.100')),
- '.text-field-title': textConfig(rem(12), 700, theme('colors.purple-gray.100')),
- '.text-extra-emphasised': textConfig(rem(12), 600, theme('colors.purple-gray.100')),
- '.text-emphasised': textConfig(rem(12), 400, theme('colors.purple-gray.100')),
- '.text-default': textConfig(rem(12), 400, theme('colors.purple-gray.300')),
- '.text-section-indicator': textConfig(rem(12), 700, theme('colors.purple-gray.400'))
- })
- })
+ '.text-main-title': textConfig(rem(25), 700),
+ '.text-section-title': textConfig(rem(12), 700),
+ '.text-standard': textConfig(rem(10), 500),
+ '.text-vr-accesible': textConfig(rem(14), 500),
+ '.text-vr-accesible-bold': textConfig(rem(14), 700),
+ '.text-standard-bold': textConfig(rem(10), 700),
+ });
+ }),
],
-}
\ No newline at end of file
+};