add led layout and hooks
This commit is contained in:
parent
81ae180f83
commit
b46c8e40f0
@ -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>
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
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[]
|
||||
}
|
||||
|
||||
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;
|
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 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;
|
||||
}
|
@ -1,8 +1,4 @@
|
||||
[
|
||||
{
|
||||
"name": "Serial Monitor",
|
||||
"target": "#serial-monitor"
|
||||
},
|
||||
{
|
||||
"name": "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";
|
||||
|
||||
// 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
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: {
|
||||
'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'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user