add led layout and hooks

This commit is contained in:
Nomi Nonsense (Nonszy) 2024-03-07 02:32:08 +07:00
parent 81ae180f83
commit b46c8e40f0
13 changed files with 300 additions and 9 deletions

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Lunar Vein: Arduino</title>
</head>
<body>
<div id="root"></div>

View File

@ -1,8 +1,12 @@
import { BoardControllerProvider } from "./contexts/BoardController";
import MainPage from "./pages/MainPage";
import "bootstrap-icons/font/bootstrap-icons.min.css";
function App () {
return (
<MainPage />
<BoardControllerProvider>
<MainPage />
</BoardControllerProvider>
)
}

View File

@ -0,0 +1,43 @@
import { ChangeEventHandler, FormEventHandler, MouseEventHandler } from "react";
import Switch from "./Switch";
interface PinBoxProps {
value: number | string;
state: boolean;
onValueChange?: ChangeEventHandler<HTMLInputElement | null>
onStateChange?: FormEventHandler<HTMLButtonElement | null>
}
function Add ({ onClick }: { onClick?: MouseEventHandler<HTMLButtonElement> }) {
return (
<button className="bg-finn hover:bg-secondary transition border border-border rounded-lg flex items-center justify-center h-36" onClick={onClick}>
<i className="bi bi-plus text-border text-7xl"></i>
</button>
)
}
function PinBox ({ value, state, onValueChange, onStateChange }: PinBoxProps) {
return (
<div className="flex flex-col gap-3 animate-fade-in">
<div className="bg-secondary border border-border rounded-lg flex flex-col h-36 animate-size-in">
<div className="font-roboto-mono py-4 text-center">Pin</div>
<input
className="flex-grow bg-transparent border-none text-center text-3xl font-roboto-mono"
type="number"
name="pinbox"
id=""
value={value}
onChange={onValueChange}
/>
</div>
<Switch
state={state}
onChange={onStateChange}
/>
</div>
)
}
PinBox.Add = Add;
export default PinBox;

View File

@ -0,0 +1,13 @@
import { FormEventHandler } from "react";
export default function Switch ({ state, onChange }: { state: boolean, onChange?: FormEventHandler<HTMLButtonElement> }) {
return (
<button
className={`border switch ${state ? 'switch-on' : 'switch-off'}`}
onClick={onChange}
>
<div className="handler w-1/2 font-roboto-mono">OFF</div>
<input type="hidden" name="" className="hidden" />
</button>
)
}

View File

@ -0,0 +1,57 @@
import { useState } from "react";
import PinBox from "../forms/PinBox";
import { PinState } from "../../types/board";
import { useLed } from "../../hooks";
function ControlLED () {
const { addLed, getLed, leds, removeLed, setLed, setLedPin } = useLed();
const handleAdd = (): void => {
let anopin = 13;
for (let i = 0; i < leds.length; i++) {
// console.log(leds[i].pin != anopin, leds[i].pin, anopin);
if (leds[i].pin != anopin) break;
anopin--;
}
addLed(anopin, false);
}
return (
<div className="container py-16">
<div className="container-grid items-center relative">
<div className="col-span-6">
<h2 className="text-4xl font-poppins font-bold leading-normal mb-4">
LED
</h2>
<div className="grid grid-cols-6 gap-6">
{leds.map((led, i) => (
<PinBox
key={i}
value={led.pin}
state={led.state}
onValueChange={(e) => {
const pin = e.target.value;
if (leds.filter(led => led.pin == pin).length > 0) {
alert(`Pin ${pin} is already use`);
return;
}
setLedPin(led.pin, pin);
}}
onStateChange={() => {
const state = !led.state;
setLed(led.pin, state);
}}
/>
))}
{leds.length < 14 && <PinBox.Add
onClick={handleAdd}
/>}
</div>
</div>
</div>
</div>
)
}
export default ControlLED;

View File

@ -11,10 +11,10 @@ interface InHero {
shortNav: ShortNav[]
}
export default function Hero ({ img, shortNav }: InHero) {
function Hero ({ img, shortNav }: InHero) {
return (
<div className="h-screen container flex items-center">
<div className="grid grid-cols-8 gap-4 items-center relative">
<div className="container-grid items-center relative">
<div className="col-span-4">
<h2 className="font-poppins text-6xl font-bold leading-normal">
Lunar Vein: Arduino Client
@ -49,3 +49,5 @@ export default function Hero ({ img, shortNav }: InHero) {
</div>
)
}
export default Hero;

View File

@ -0,0 +1,55 @@
import { Dispatch, ReactNode, SetStateAction, createContext, useState } from "react";
import { ChannelPinState, DynamicPinState, PinMode, PinState } from "../types/board";
interface ControllerContextProps {
pinModes: PinMode[];
leds: PinState[];
rgbLed: ChannelPinState[];
piezo: DynamicPinState[];
motoServo: DynamicPinState[];
photoresistor: DynamicPinState[];
setPinModes?: Dispatch<SetStateAction<PinMode[]>>;
setLeds?: Dispatch<SetStateAction<PinState[]>>;
setRgbLed?: Dispatch<SetStateAction<ChannelPinState[]>>;
setPiezo?: Dispatch<SetStateAction<DynamicPinState[]>>;
setMotoServo?: Dispatch<SetStateAction<DynamicPinState[]>>;
setPhotoresistor?: Dispatch<SetStateAction<DynamicPinState[]>>;
}
const INIT_VALUES: ControllerContextProps = {
pinModes: [],
leds: [
{
pin: 13,
state: false
}
],
rgbLed: [],
piezo: [],
motoServo: [],
photoresistor: [],
}
export const BoardControllerContext = createContext<ControllerContextProps>(INIT_VALUES);
export function BoardControllerProvider ({ children }: { children: ReactNode }) {
const [pinModes, setPinModes] = useState<PinMode[]>(INIT_VALUES.pinModes);
const [leds, setLeds] = useState<PinState[]>(INIT_VALUES.leds);
const [rgbLed, setRgbLed] = useState<ChannelPinState[]>(INIT_VALUES.rgbLed);
const [piezo, setPiezo] = useState<DynamicPinState[]>(INIT_VALUES.piezo);
const [motoServo, setMotoServo] = useState<DynamicPinState[]>(INIT_VALUES.motoServo);
const [photoresistor, setPhotoresistor] = useState<DynamicPinState[]>(INIT_VALUES.photoresistor);
const contextValue: ControllerContextProps = {
pinModes, setPinModes,
leds, setLeds,
rgbLed, setRgbLed,
piezo, setPiezo,
motoServo, setMotoServo,
photoresistor, setPhotoresistor
};
return <BoardControllerContext.Provider value={contextValue}>
{children}
</BoardControllerContext.Provider>
}

View File

@ -4,7 +4,55 @@
@tailwind components;
@tailwind utilities;
@layer base {
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type=number] {
-moz-appearance: textfield;
}
}
@keyframes size-in {
from { transform: scale(0); }
to { transform: scale(1); }
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
.container {
@apply w-[1280px] mx-auto;
}
.container-grid {
@apply grid grid-cols-8 gap-6;
}
.switch {
@apply rounded-lg;
}
.switch > .handler {
@apply rounded-md p-[6px] transition-all;
}
.switch-on {
@apply border-primary bg-primary bg-opacity-40;
}
.switch-on > .handler {
@apply bg-primary ms-[50%];
}
.switch-off {
@apply border-disabled;
}
.switch-off > .handler {
@apply bg-disabled ms-0;
}

View File

@ -1,8 +1,4 @@
[
{
"name": "Serial Monitor",
"target": "#serial-monitor"
},
{
"name": "LED",
"target": "#led"

43
src/hooks/index.tsx Normal file
View File

@ -0,0 +1,43 @@
import { useContext } from "react";
import { BoardControllerContext } from "../contexts/BoardController";
export function usePin () {
return useContext(BoardControllerContext);
}
export function useLed () {
const { leds, setLeds } = useContext(BoardControllerContext);
const getLed = (pin: number | string) => {
const led = leds.find(val => val.pin == pin);
return led;
}
const addLed = (pin: number | string, state?: boolean) => {
const newLed = [...leds, { pin, state: state || false }];
setLeds!(newLed);
}
const removeLed = (pin: number | string) => {
const newLed = leds.filter(led => led.pin != pin);
setLeds!(newLed);
}
const setLed = (pin: number | string, state: boolean) => {
const newLed = leds.map(led => {
if (led.pin == pin) return { pin, state };
return led;
})
setLeds!(newLed);
}
const setLedPin = (pin: number | string, newPin: number | string) => {
const newLed = leds.map(led => {
if (led.pin == pin) return { pin: newPin, state: led.state };
return led;
})
setLeds!(newLed);
}
return { leds, getLed, setLed, setLedPin, addLed, removeLed };
}

View File

@ -1,5 +1,8 @@
import MainBody from "../components/MainBody";
// Section components
import Hero from "../components/landing/Hero";
import ControlLED from "../components/landing/ControlLED";
import LunarImg from "../assets/img/ocs/lunar-oc.png";
@ -12,6 +15,7 @@ function MainPage () {
img={LunarImg}
shortNav={ControlNav}
/>
<ControlLED />
</MainBody>
</>)
}

20
src/types/board.ts Normal file
View File

@ -0,0 +1,20 @@
export type PinMode = {
pin: string | number,
mode: 'INPUT' | 'OUTPUT' | 'SERVO'
}
export type PinState = {
pin: number | string,
state: boolean
}
export type DynamicPinState = {
pin: string | number,
state: number
}
export interface ChannelPinState {
red: PinState,
green: PinState,
blue: PinState
}

View File

@ -9,7 +9,9 @@ export default {
colors: {
'primary': '#3A3AFF',
'secondary': '#0B0B1D66',
'secondary-solid': '#0B0B1D',
'finn': '#00000066',
'finn-solid': '#000000',
'danger': '#FF016C',
'disabled': '#7373B0',
'border': '#FFFFFF66',
@ -18,6 +20,10 @@ export default {
fontFamily: {
'poppins': '"Poppins", sans-serif',
'roboto-mono': '"Roboto Mono", monospace',
},
animation: {
'size-in': 'size-in .3s ease-in-out',
'fade-in': 'fade-in .3s ease-in-out'
}
},
},