add led layout and hooks
This commit is contained in:
parent
81ae180f83
commit
b46c8e40f0
@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Vite + React + TS</title>
|
<title>Lunar Vein: Arduino</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
|
import { BoardControllerProvider } from "./contexts/BoardController";
|
||||||
import MainPage from "./pages/MainPage";
|
import MainPage from "./pages/MainPage";
|
||||||
|
import "bootstrap-icons/font/bootstrap-icons.min.css";
|
||||||
|
|
||||||
function App () {
|
function App () {
|
||||||
return (
|
return (
|
||||||
<MainPage />
|
<BoardControllerProvider>
|
||||||
|
<MainPage />
|
||||||
|
</BoardControllerProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
43
src/components/forms/PinBox.tsx
Normal file
43
src/components/forms/PinBox.tsx
Normal 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;
|
13
src/components/forms/Switch.tsx
Normal file
13
src/components/forms/Switch.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
57
src/components/landing/ControlLED.tsx
Normal file
57
src/components/landing/ControlLED.tsx
Normal 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;
|
@ -11,10 +11,10 @@ interface InHero {
|
|||||||
shortNav: ShortNav[]
|
shortNav: ShortNav[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Hero ({ img, shortNav }: InHero) {
|
function Hero ({ img, shortNav }: InHero) {
|
||||||
return (
|
return (
|
||||||
<div className="h-screen container flex items-center">
|
<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">
|
<div className="col-span-4">
|
||||||
<h2 className="font-poppins text-6xl font-bold leading-normal">
|
<h2 className="font-poppins text-6xl font-bold leading-normal">
|
||||||
Lunar Vein: Arduino Client
|
Lunar Vein: Arduino Client
|
||||||
@ -48,4 +48,6 @@ export default function Hero ({ img, shortNav }: InHero) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default Hero;
|
55
src/contexts/BoardController.tsx
Normal file
55
src/contexts/BoardController.tsx
Normal 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>
|
||||||
|
}
|
@ -4,7 +4,55 @@
|
|||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@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 {
|
.container {
|
||||||
@apply w-[1280px] mx-auto;
|
@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;
|
||||||
}
|
}
|
@ -1,8 +1,4 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"name": "Serial Monitor",
|
|
||||||
"target": "#serial-monitor"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "LED",
|
"name": "LED",
|
||||||
"target": "#led"
|
"target": "#led"
|
||||||
|
43
src/hooks/index.tsx
Normal file
43
src/hooks/index.tsx
Normal 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 };
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
import MainBody from "../components/MainBody";
|
import MainBody from "../components/MainBody";
|
||||||
|
|
||||||
|
// Section components
|
||||||
import Hero from "../components/landing/Hero";
|
import Hero from "../components/landing/Hero";
|
||||||
|
import ControlLED from "../components/landing/ControlLED";
|
||||||
|
|
||||||
import LunarImg from "../assets/img/ocs/lunar-oc.png";
|
import LunarImg from "../assets/img/ocs/lunar-oc.png";
|
||||||
|
|
||||||
@ -12,6 +15,7 @@ function MainPage () {
|
|||||||
img={LunarImg}
|
img={LunarImg}
|
||||||
shortNav={ControlNav}
|
shortNav={ControlNav}
|
||||||
/>
|
/>
|
||||||
|
<ControlLED />
|
||||||
</MainBody>
|
</MainBody>
|
||||||
</>)
|
</>)
|
||||||
}
|
}
|
||||||
|
20
src/types/board.ts
Normal file
20
src/types/board.ts
Normal 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
|
||||||
|
}
|
@ -9,7 +9,9 @@ export default {
|
|||||||
colors: {
|
colors: {
|
||||||
'primary': '#3A3AFF',
|
'primary': '#3A3AFF',
|
||||||
'secondary': '#0B0B1D66',
|
'secondary': '#0B0B1D66',
|
||||||
|
'secondary-solid': '#0B0B1D',
|
||||||
'finn': '#00000066',
|
'finn': '#00000066',
|
||||||
|
'finn-solid': '#000000',
|
||||||
'danger': '#FF016C',
|
'danger': '#FF016C',
|
||||||
'disabled': '#7373B0',
|
'disabled': '#7373B0',
|
||||||
'border': '#FFFFFF66',
|
'border': '#FFFFFF66',
|
||||||
@ -18,6 +20,10 @@ export default {
|
|||||||
fontFamily: {
|
fontFamily: {
|
||||||
'poppins': '"Poppins", sans-serif',
|
'poppins': '"Poppins", sans-serif',
|
||||||
'roboto-mono': '"Roboto Mono", monospace',
|
'roboto-mono': '"Roboto Mono", monospace',
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
'size-in': 'size-in .3s ease-in-out',
|
||||||
|
'fade-in': 'fade-in .3s ease-in-out'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user