Working Eventbar

This commit is contained in:
aronmal 2023-06-07 11:51:33 +02:00
parent 787a85e425
commit 9895a286a3
Signed by: aronmal
GPG key ID: 816B7707426FC612
10 changed files with 403 additions and 77 deletions

View file

@ -1,35 +1,225 @@
import { Items, Target } from "../../interfaces/frontend"
import { EventBarModes, Target } from "../../interfaces/frontend"
import Item from "./Item"
import React, { Dispatch, SetStateAction } from "react"
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
import {
faArrowRightFromBracket,
faBroomWide,
faComments,
faEye,
faEyeSlash,
faGlasses,
faPalette,
faReply,
faScribble,
faSparkles,
faSwords,
} from "@fortawesome/pro-solid-svg-icons"
import { useDrawProps } from "@hooks/useDrawProps"
import { useGameProps } from "@hooks/useGameProps"
import { socket } from "@lib/socket"
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
} from "react"
function EventBar({
props: { setMode, setTarget },
props: { setMode, setTarget, clear },
}: {
props: {
setMode: Dispatch<SetStateAction<number>>
setTarget: Dispatch<SetStateAction<Target>>
clear: () => void
}
}) {
const items: Items[] = [
{ icon: "burger-menu", text: "Menu" },
{ icon: "radar", text: "Radar scan", mode: 0, amount: 1 },
{ icon: "torpedo", text: "Fire torpedo", mode: 1, amount: 1 },
{ icon: "scope", text: "Fire missile", mode: 3 },
{ icon: "gear", text: "Settings" },
]
const [menu, setMenu] = useState<keyof EventBarModes>("main")
const { shouldHide, color } = useDrawProps()
const { payload, setSetting, full } = useGameProps()
const gameSetting = useCallback(
(payload: GameSettings) => {
const hash = setSetting(payload)
socket.emit("gameSetting", payload, (newHash) => {
if (newHash === hash) return
console.log("hash", hash, newHash)
socket.emit("update", full)
})
},
[full, setSetting]
)
const items = useMemo<EventBarModes>(
() => ({
main: [
{
icon: "burger-menu",
text: "Menu",
callback: () => {
setMenu("menu")
},
},
{
icon: faSwords,
text: "Attack",
callback: () => {
setMenu("attack")
},
},
{
icon: "pen",
text: "Draw",
callback: () => {
setMenu("draw")
},
},
{
icon: "gear",
text: "Settings",
callback: () => {
setMenu("settings")
},
},
],
menu: [
{
icon: faReply,
text: "Return",
iconColor: "#555",
callback: () => {
setMenu("main")
},
},
{
icon: faArrowRightFromBracket,
text: "Leave",
iconColor: "darkred",
callback: () => {
// router.push()
},
},
],
attack: [
{
icon: faReply,
text: "Return",
iconColor: "#555",
callback: () => {
setMenu("main")
},
},
{
icon: "radar",
text: "Radar scan",
amount: 1,
callback: () => {
setMode(0)
setTarget((e) => ({ ...e, show: false }))
},
},
{
icon: "torpedo",
text: "Fire torpedo",
amount: 1,
callback: () => {
setMode(1)
setTarget((e) => ({ ...e, show: false }))
},
},
{
icon: "scope",
text: "Fire missile",
callback: () => {
setMode(3)
setTarget((e) => ({ ...e, show: false }))
},
},
],
draw: [
{
icon: faReply,
text: "Return",
iconColor: "#555",
callback: () => {
setMenu("main")
},
},
{ icon: faBroomWide, text: "Clear", callback: clear },
{ icon: faPalette, text: "Color", iconColor: color },
{
icon: shouldHide ? faEye : faEyeSlash,
text: shouldHide ? "Show" : "Hide",
callback: () => {
useDrawProps.setState({ shouldHide: !shouldHide })
},
},
],
settings: [
{
icon: faReply,
text: "Return",
iconColor: "#555",
callback: () => {
setMenu("main")
},
},
{
icon: faGlasses,
text: "Spectators",
disabled: !payload?.game?.allowSpectators,
callback: () => {
gameSetting({ allowSpectators: !payload?.game?.allowSpectators })
},
},
{
icon: faSparkles,
text: "Specials",
disabled: !payload?.game?.allowSpecials,
callback: () => {
gameSetting({ allowSpecials: !payload?.game?.allowSpecials })
},
},
{
icon: faComments,
text: "Chat",
disabled: !payload?.game?.allowChat,
callback: () => {
gameSetting({ allowChat: !payload?.game?.allowChat })
},
},
{
icon: faScribble,
text: "Mark/Draw",
disabled: !payload?.game?.allowMarkDraw,
callback: () => {
gameSetting({ allowMarkDraw: !payload?.game?.allowMarkDraw })
},
},
],
}),
[
clear,
color,
gameSetting,
payload?.game?.allowChat,
payload?.game?.allowMarkDraw,
payload?.game?.allowSpecials,
payload?.game?.allowSpectators,
setMode,
setTarget,
shouldHide,
]
)
useEffect(() => {
useDrawProps.setState({ enable: menu === "draw" })
}, [menu])
return (
<div className="event-bar">
{items.map((e, i) => (
<Item
key={i}
props={{
...e,
callback: () => {
if (e.mode !== undefined) setMode(e.mode)
setTarget((e) => ({ ...e, show: false }))
},
}}
/>
{items[menu].map((e, i) => (
<Item key={i} props={e} />
))}
</div>
)

View file

@ -1,4 +1,4 @@
import { Draw, Hit, MouseCursor, Target } from "../../interfaces/frontend"
import { Hit, MouseCursor, Target } from "../../interfaces/frontend"
// import Bluetooth from "./Bluetooth"
// import FogImages from "./FogImages"
import Labeling from "./Labeling"
@ -8,6 +8,7 @@ import EventBar from "@components/Gamefield/EventBar"
import HitElems from "@components/Gamefield/HitElems"
import Targets from "@components/Gamefield/Targets"
import { useDraw } from "@hooks/useDraw"
import { useDrawProps } from "@hooks/useDrawProps"
import {
hitReducer,
initlialTarget,
@ -69,28 +70,8 @@ function Gamefield() {
}
}, [mode, mouseCursor, target])
const [color, setColor] = useState<string>("#f00")
const [disable, setDisable] = useState(false)
const { canvasRef, onMouseDown, clear } = useDraw(drawLine)
function drawLine({ prevPoint, currentPoint, ctx }: Draw) {
const { x: currX, y: currY } = currentPoint
const lineColor = color
const lineWidth = 5
let startPoint = prevPoint ?? currentPoint
ctx.beginPath()
ctx.lineWidth = lineWidth
ctx.strokeStyle = lineColor
ctx.moveTo(startPoint.x, startPoint.y)
ctx.lineTo(currX, currY)
ctx.stroke()
ctx.fillStyle = lineColor
ctx.beginPath()
ctx.arc(startPoint.x, startPoint.y, 2, 0, 2 * Math.PI)
ctx.fill()
}
const { canvasRef, onMouseDown, clear } = useDraw()
const { enable, color, shouldHide } = useDrawProps()
return (
<div id="gamefield">
@ -117,10 +98,13 @@ function Gamefield() {
<Targets props={{ target, targetPreview, mode, hits }} />
{/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */}
<canvas
style={
{
pointerEvents: !disable ? "auto" : "none",
opacity: shouldHide ? 0 : 1,
boxShadow: enable ? "inset 0 0 0 2px " + color : "none",
pointerEvents: enable && !shouldHide ? "auto" : "none",
} as CSSProperties
}
ref={canvasRef}
@ -129,9 +113,7 @@ function Gamefield() {
height="648"
/>
</div>
<button onClick={() => setDisable((e) => !e)}>toggle disable</button>
<button onClick={clear}>Clear</button>
<EventBar props={{ setMode, setTarget }} />
<EventBar props={{ setMode, setTarget, clear }} />
</div>
)
}

View file

@ -1,20 +1,53 @@
import { ItemProps } from "../../interfaces/frontend"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { useDrawProps } from "@hooks/useDrawProps"
import classNames from "classnames"
import React, { CSSProperties } from "react"
import React, { CSSProperties, useEffect, useRef, useState } from "react"
import { HexColorPicker } from "react-colorful"
function Item({
props: { icon, text, amount, callback },
props: { icon, text, amount, iconColor, disabled, callback },
}: {
props: {
icon: string
text: string
amount?: number
callback: () => void
}
props: ItemProps
}) {
const isColor = text === "Color"
const { color, setColor } = useDrawProps()
const [active, setActive] = useState(false)
const cpRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const inActive = (e: MouseEvent) => {
if (cpRef.current && !cpRef.current.contains(e.target as Node))
setActive(false)
}
// Add event listeners
if (!isColor) return
setTimeout(() => window.addEventListener("click", inActive), 200)
// Remove event listeners
return () => {
window.removeEventListener("click", inActive)
}
}, [active, isColor])
return (
<div className="item" onClick={callback}>
<div className="item" onClick={isColor ? () => setActive(true) : callback}>
{isColor ? (
<div
ref={cpRef}
className={classNames("react-colorful-wrapper", { active: active })}
>
<HexColorPicker color={color} onChange={setColor} />
</div>
) : (
<></>
)}
<div
className={classNames("container", { amount: amount })}
className={classNames("container", {
amount: amount,
disabled: disabled,
})}
style={
amount
? ({
@ -23,7 +56,15 @@ function Item({
: {}
}
>
<img src={`/assets/${icon}.png`} alt={`${icon}.png`} />
{typeof icon === "string" ? (
<img
src={`/assets/${icon}.png`}
alt={`${icon}.png`}
className="pixelart"
/>
) : (
<FontAwesomeIcon icon={icon} color={iconColor ?? "#444"} />
)}
</div>
<span>{text}</span>
</div>

View file

@ -88,7 +88,6 @@ function Player({
isLatched={!!isReady}
onClick={() => {
if (!player) return
console.log(i, !isReady)
setIsReady({
i,
isReady: !isReady,

View file

@ -1,14 +1,34 @@
import { Draw, Point } from "../interfaces/frontend"
import { useDrawProps } from "./useDrawProps"
import { useEffect, useRef, useState } from "react"
export const useDraw = (
onDraw: ({ ctx, currentPoint, prevPoint }: Draw) => void
) => {
function onDraw({ prevPoint, currentPoint, ctx, color }: Draw) {
const { x: currX, y: currY } = currentPoint
const lineColor = color
const lineWidth = 5
let startPoint = prevPoint ?? currentPoint
ctx.beginPath()
ctx.lineWidth = lineWidth
ctx.strokeStyle = lineColor
ctx.moveTo(startPoint.x, startPoint.y)
ctx.lineTo(currX, currY)
ctx.stroke()
ctx.fillStyle = lineColor
ctx.beginPath()
ctx.arc(startPoint.x, startPoint.y, 2, 0, 2 * Math.PI)
ctx.fill()
}
export const useDraw = () => {
const [mouseDown, setMouseDown] = useState(false)
const canvasRef = useRef<HTMLCanvasElement>(null)
const prevPoint = useRef<null | Point>(null)
const { color } = useDrawProps()
const onMouseDown = () => setMouseDown(true)
const clear = () => {
@ -22,6 +42,9 @@ export const useDraw = (
}
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const handler = (e: MouseEvent) => {
if (!mouseDown) return
const currentPoint = computePointInCanvas(e)
@ -29,14 +52,11 @@ export const useDraw = (
const ctx = canvasRef.current?.getContext("2d")
if (!ctx || !currentPoint) return
onDraw({ ctx, currentPoint, prevPoint: prevPoint.current })
onDraw({ ctx, currentPoint, prevPoint: prevPoint.current, color })
prevPoint.current = currentPoint
}
const computePointInCanvas = (e: MouseEvent) => {
const canvas = canvasRef.current
if (!canvas) return
const rect = canvas.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
@ -50,15 +70,15 @@ export const useDraw = (
}
// Add event listeners
canvasRef.current?.addEventListener("mousemove", handler)
canvas.addEventListener("mousemove", handler)
window.addEventListener("mouseup", mouseUpHandler)
// Remove event listeners
return () => {
canvasRef.current?.removeEventListener("mousemove", handler)
canvas.removeEventListener("mousemove", handler)
window.removeEventListener("mouseup", mouseUpHandler)
}
}, [onDraw])
}, [color, mouseDown])
return { canvasRef, onMouseDown, clear }
}

View file

@ -0,0 +1,40 @@
import { produce } from "immer"
import { create } from "zustand"
import { devtools } from "zustand/middleware"
const initialState: {
enable: boolean
shouldHide: boolean
color: string
} = {
enable: false,
shouldHide: false,
color: "#b32aa9",
}
export type State = typeof initialState
export type Action = {
setColor: (color: string) => void
reset: () => void
}
export const useDrawProps = create<State & Action>()(
devtools(
(set) => ({
...initialState,
setColor: (color) =>
set(
produce((state) => {
state.color = color
})
),
reset: () => {
set(initialState)
},
}),
{
name: "gameState",
}
)
)

View file

@ -1,3 +1,5 @@
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons"
export interface Position {
x: number
y: number
@ -16,11 +18,20 @@ export interface Mode {
pointerGrid: any[][]
type: string
}
export interface Items {
icon: string
export interface ItemProps {
icon: string | IconDefinition
text: string
mode?: number
amount?: number
iconColor?: string
disabled?: boolean
callback?: () => void
}
export interface EventBarModes {
main: ItemProps[]
menu: ItemProps[]
attack: ItemProps[]
draw: ItemProps[]
settings: ItemProps[]
}
export interface Field extends Position {
field: string
@ -57,4 +68,5 @@ export interface Draw {
ctx: CanvasRenderingContext2D
currentPoint: Point
prevPoint: Point | null
color: string
}

View file

@ -31,6 +31,7 @@
"nodemailer": "^6.9.3",
"prisma": "^4.15.0",
"react": "18.2.0",
"react-colorful": "^5.6.1",
"react-dom": "18.2.0",
"react-otp-input": "^3.0.2",
"react-toastify": "^9.1.3",

View file

@ -71,6 +71,9 @@ dependencies:
react:
specifier: 18.2.0
version: 18.2.0
react-colorful:
specifier: ^5.6.1
version: 5.6.1(react-dom@18.2.0)(react@18.2.0)
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
@ -2877,6 +2880,16 @@ packages:
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
/react-colorful@5.6.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
dependencies:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: false
/react-dom@18.2.0(react@18.2.0):
resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
peerDependencies:

View file

@ -337,7 +337,8 @@ body {
canvas {
grid-area: 1 / 1 / -1 / -1;
border: 1px solid #000;
display: absolute;
border-radius: 0.5rem;
z-index: 1;
}
}
@ -352,10 +353,35 @@ body {
align-items: center;
gap: 0.5rem;
width: 128px;
position: relative;
.react-colorful-wrapper {
pointer-events: none;
opacity: 0;
transition: 0.2s;
position: absolute;
top: -225px;
left: 50%;
transform: translateX(-50%);
z-index: 1;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
&.active {
pointer-events: auto;
opacity: 1;
}
}
.container {
width: initial;
position: relative;
padding: 8px;
background-color: white;
border-radius: 1rem;
&.disabled {
box-shadow: inset 0 0 1rem 1rem #888;
}
&.amount::after {
content: var(--amount);
@ -372,10 +398,12 @@ body {
img {
width: 64px;
padding: 8px;
@include pixelart;
background-color: white;
border-radius: 1rem;
}
svg {
margin: 8px;
height: 48px;
display: inherit;
}
}