Working ship placement
This commit is contained in:
parent
0317a3343c
commit
c2af2dffa2
14 changed files with 1155 additions and 281 deletions
|
@ -1,9 +1,15 @@
|
||||||
import { EventBarModes } from "../../interfaces/frontend"
|
import { EventBarModes } from "../../interfaces/frontend"
|
||||||
import Item from "./Item"
|
import Item from "./Item"
|
||||||
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
|
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
|
||||||
|
import {
|
||||||
|
faSquare2,
|
||||||
|
faSquare3,
|
||||||
|
faSquare4,
|
||||||
|
} from "@fortawesome/pro-regular-svg-icons"
|
||||||
import {
|
import {
|
||||||
faArrowRightFromBracket,
|
faArrowRightFromBracket,
|
||||||
faBroomWide,
|
faBroomWide,
|
||||||
|
faCheck,
|
||||||
faComments,
|
faComments,
|
||||||
faEye,
|
faEye,
|
||||||
faEyeSlash,
|
faEyeSlash,
|
||||||
|
@ -11,6 +17,7 @@ import {
|
||||||
faPalette,
|
faPalette,
|
||||||
faReply,
|
faReply,
|
||||||
faScribble,
|
faScribble,
|
||||||
|
faShip,
|
||||||
faSparkles,
|
faSparkles,
|
||||||
faSwords,
|
faSwords,
|
||||||
} from "@fortawesome/pro-solid-svg-icons"
|
} from "@fortawesome/pro-solid-svg-icons"
|
||||||
|
@ -18,13 +25,13 @@ import { useDrawProps } from "@hooks/useDrawProps"
|
||||||
import { useGameProps } from "@hooks/useGameProps"
|
import { useGameProps } from "@hooks/useGameProps"
|
||||||
import { socket } from "@lib/socket"
|
import { socket } from "@lib/socket"
|
||||||
import { GamePropsSchema } from "@lib/zodSchemas"
|
import { GamePropsSchema } from "@lib/zodSchemas"
|
||||||
|
import { useSession } from "next-auth/react"
|
||||||
import {
|
import {
|
||||||
Dispatch,
|
Dispatch,
|
||||||
SetStateAction,
|
SetStateAction,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useState,
|
|
||||||
} from "react"
|
} from "react"
|
||||||
|
|
||||||
export function setGameSetting(
|
export function setGameSetting(
|
||||||
|
@ -42,23 +49,35 @@ export function setGameSetting(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function EventBar({
|
function EventBar({ clear }: { clear: () => void }) {
|
||||||
props: { setMode, clear },
|
|
||||||
}: {
|
|
||||||
props: {
|
|
||||||
setMode: Dispatch<SetStateAction<number>>
|
|
||||||
clear: () => void
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
const [menu, setMenu] = useState<keyof EventBarModes>("main")
|
|
||||||
const { shouldHide, color } = useDrawProps()
|
const { shouldHide, color } = useDrawProps()
|
||||||
const { payload, setSetting, full, setTarget } = useGameProps()
|
const { data: session } = useSession()
|
||||||
|
const {
|
||||||
|
ships,
|
||||||
|
payload,
|
||||||
|
menu,
|
||||||
|
mode,
|
||||||
|
setSetting,
|
||||||
|
full,
|
||||||
|
setTarget,
|
||||||
|
setIsReady,
|
||||||
|
} = useGameProps()
|
||||||
const gameSetting = useCallback(
|
const gameSetting = useCallback(
|
||||||
(payload: GameSettings) => setGameSetting(payload, setSetting, full),
|
(payload: GameSettings) => setGameSetting(payload, setSetting, full),
|
||||||
[full, setSetting]
|
[full, setSetting]
|
||||||
)
|
)
|
||||||
|
const setMenu = useCallback(
|
||||||
|
(menu: keyof EventBarModes) => useGameProps.setState({ menu }),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const setMode = useCallback(
|
||||||
|
(mode: number) => useGameProps.setState({ mode }),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const self = useMemo(
|
||||||
|
() => payload?.users.find((e) => e?.id === session?.user.id),
|
||||||
|
[payload?.users, session?.user.id]
|
||||||
|
)
|
||||||
const items = useMemo<EventBarModes>(
|
const items = useMemo<EventBarModes>(
|
||||||
() => ({
|
() => ({
|
||||||
main: [
|
main: [
|
||||||
|
@ -69,11 +88,19 @@ function EventBar({
|
||||||
setMenu("menu")
|
setMenu("menu")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
payload?.game?.state === "running"
|
||||||
|
? {
|
||||||
icon: faSwords,
|
icon: faSwords,
|
||||||
text: "Attack",
|
text: "Attack",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
setMenu("attack")
|
setMenu("actions")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
icon: faShip,
|
||||||
|
text: "Ships",
|
||||||
|
callback: () => {
|
||||||
|
setMenu("actions")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -92,14 +119,6 @@ function EventBar({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
menu: [
|
menu: [
|
||||||
{
|
|
||||||
icon: faReply,
|
|
||||||
text: "Return",
|
|
||||||
iconColor: "#555",
|
|
||||||
callback: () => {
|
|
||||||
setMenu("main")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: faArrowRightFromBracket,
|
icon: faArrowRightFromBracket,
|
||||||
text: "Leave",
|
text: "Leave",
|
||||||
|
@ -109,33 +128,9 @@ function EventBar({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
attack: [
|
actions:
|
||||||
{
|
payload?.game?.state === "running"
|
||||||
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",
|
icon: "scope",
|
||||||
text: "Fire missile",
|
text: "Fire missile",
|
||||||
|
@ -144,16 +139,73 @@ function EventBar({
|
||||||
setTarget((e) => ({ ...e, show: false }))
|
setTarget((e) => ({ ...e, show: false }))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: "torpedo",
|
||||||
|
text: "Fire torpedo",
|
||||||
|
amount:
|
||||||
|
2 -
|
||||||
|
(self?.moves.filter(
|
||||||
|
(e) => e.action === "htorpedo" || e.action === "vtorpedo"
|
||||||
|
).length ?? 0),
|
||||||
|
callback: () => {
|
||||||
|
setMode(1)
|
||||||
|
setTarget((e) => ({ ...e, show: false }))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: "radar",
|
||||||
|
text: "Radar scan",
|
||||||
|
amount:
|
||||||
|
1 -
|
||||||
|
(self?.moves.filter((e) => e.action === "radar").length ?? 0),
|
||||||
|
callback: () => {
|
||||||
|
setMode(0)
|
||||||
|
setTarget((e) => ({ ...e, show: false }))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
icon: faSquare2,
|
||||||
|
text: "Minensucher",
|
||||||
|
amount: 1 - ships.filter((e) => e.size === 2).length,
|
||||||
|
callback: () => {
|
||||||
|
if (1 - ships.filter((e) => e.size === 2).length === 0) return
|
||||||
|
setMode(0)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: faSquare3,
|
||||||
|
text: "Kreuzer",
|
||||||
|
amount: 3 - ships.filter((e) => e.size === 3).length,
|
||||||
|
callback: () => {
|
||||||
|
if (3 - ships.filter((e) => e.size === 3).length === 0) return
|
||||||
|
setMode(1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: faSquare4,
|
||||||
|
text: "Schlachtschiff",
|
||||||
|
amount: 2 - ships.filter((e) => e.size === 4).length,
|
||||||
|
callback: () => {
|
||||||
|
if (2 - ships.filter((e) => e.size === 4).length === 0) return
|
||||||
|
setMode(2)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: faCheck,
|
||||||
|
text: "Done",
|
||||||
|
disabled: mode >= 0,
|
||||||
|
callback: () => {
|
||||||
|
const i = payload?.users.findIndex(
|
||||||
|
(user) => session?.user?.id === user?.id
|
||||||
|
)
|
||||||
|
if (!i) return
|
||||||
|
setIsReady({ isReady: true, i })
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
draw: [
|
draw: [
|
||||||
{
|
|
||||||
icon: faReply,
|
|
||||||
text: "Return",
|
|
||||||
iconColor: "#555",
|
|
||||||
callback: () => {
|
|
||||||
setMenu("main")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ icon: faBroomWide, text: "Clear", callback: clear },
|
{ icon: faBroomWide, text: "Clear", callback: clear },
|
||||||
{ icon: faPalette, text: "Color", iconColor: color },
|
{ icon: faPalette, text: "Color", iconColor: color },
|
||||||
{
|
{
|
||||||
|
@ -165,14 +217,6 @@ function EventBar({
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
settings: [
|
settings: [
|
||||||
{
|
|
||||||
icon: faReply,
|
|
||||||
text: "Return",
|
|
||||||
iconColor: "#555",
|
|
||||||
callback: () => {
|
|
||||||
setMenu("main")
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: faGlasses,
|
icon: faGlasses,
|
||||||
text: "Spectators",
|
text: "Spectators",
|
||||||
|
@ -209,22 +253,54 @@ function EventBar({
|
||||||
clear,
|
clear,
|
||||||
color,
|
color,
|
||||||
gameSetting,
|
gameSetting,
|
||||||
|
mode,
|
||||||
payload?.game?.allowChat,
|
payload?.game?.allowChat,
|
||||||
payload?.game?.allowMarkDraw,
|
payload?.game?.allowMarkDraw,
|
||||||
payload?.game?.allowSpecials,
|
payload?.game?.allowSpecials,
|
||||||
payload?.game?.allowSpectators,
|
payload?.game?.allowSpectators,
|
||||||
|
payload?.game?.state,
|
||||||
|
payload?.users,
|
||||||
|
self?.moves,
|
||||||
|
session?.user?.id,
|
||||||
|
setIsReady,
|
||||||
|
setMenu,
|
||||||
setMode,
|
setMode,
|
||||||
setTarget,
|
setTarget,
|
||||||
|
ships,
|
||||||
shouldHide,
|
shouldHide,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (
|
||||||
|
menu !== "actions" ||
|
||||||
|
payload?.game?.state !== "starting" ||
|
||||||
|
mode < 0 ||
|
||||||
|
items.actions[mode].amount
|
||||||
|
)
|
||||||
|
return
|
||||||
|
const index = items.actions.findIndex((e) => e.amount)
|
||||||
|
useGameProps.setState({ mode: index })
|
||||||
|
}, [items.actions, menu, mode, payload?.game?.state])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
useDrawProps.setState({ enable: menu === "draw" })
|
useDrawProps.setState({ enable: menu === "draw" })
|
||||||
}, [menu])
|
}, [menu])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="event-bar">
|
<div className="event-bar">
|
||||||
|
{menu !== "main" && (
|
||||||
|
<Item
|
||||||
|
props={{
|
||||||
|
icon: faReply,
|
||||||
|
text: "Return",
|
||||||
|
iconColor: "#555",
|
||||||
|
callback: () => {
|
||||||
|
setMenu("main")
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
></Item>
|
||||||
|
)}
|
||||||
{items[menu].map((e, i) => (
|
{items[menu].map((e, i) => (
|
||||||
<Item key={i} props={e} />
|
<Item key={i} props={e} />
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -10,11 +10,14 @@ import Targets from "@components/Gamefield/Targets"
|
||||||
import { useDraw } from "@hooks/useDraw"
|
import { useDraw } from "@hooks/useDraw"
|
||||||
import { useDrawProps } from "@hooks/useDrawProps"
|
import { useDrawProps } from "@hooks/useDrawProps"
|
||||||
import { useGameProps } from "@hooks/useGameProps"
|
import { useGameProps } from "@hooks/useGameProps"
|
||||||
|
import useSocket from "@hooks/useSocket"
|
||||||
|
import { socket } from "@lib/socket"
|
||||||
import {
|
import {
|
||||||
initlialMouseCursor,
|
initlialMouseCursor,
|
||||||
overlapsWithAnyBorder,
|
overlapsWithAnyBorder,
|
||||||
isAlreadyHit,
|
isAlreadyHit,
|
||||||
targetList,
|
targetList,
|
||||||
|
shipProps,
|
||||||
} from "@lib/utils/helpers"
|
} from "@lib/utils/helpers"
|
||||||
import { CSSProperties, useCallback } from "react"
|
import { CSSProperties, useCallback } from "react"
|
||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
|
@ -25,17 +28,28 @@ function Gamefield() {
|
||||||
const [mouseCursor, setMouseCursor] =
|
const [mouseCursor, setMouseCursor] =
|
||||||
useState<MouseCursor>(initlialMouseCursor)
|
useState<MouseCursor>(initlialMouseCursor)
|
||||||
const {
|
const {
|
||||||
|
mode,
|
||||||
hits,
|
hits,
|
||||||
target,
|
target,
|
||||||
targetPreview,
|
targetPreview,
|
||||||
DispatchHits,
|
ships,
|
||||||
|
addShip,
|
||||||
|
payload,
|
||||||
|
DispatchAction,
|
||||||
setTarget,
|
setTarget,
|
||||||
setTargetPreview,
|
setTargetPreview,
|
||||||
|
full,
|
||||||
} = useGameProps()
|
} = useGameProps()
|
||||||
const [mode, setMode] = useState(0)
|
const { isConnected } = useSocket()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (payload?.game?.id || !isConnected) return
|
||||||
|
socket.emit("update", full)
|
||||||
|
}, [full, payload?.game?.id, isConnected])
|
||||||
|
|
||||||
const settingTarget = useCallback(
|
const settingTarget = useCallback(
|
||||||
(isGameTile: boolean, x: number, y: number) => {
|
(isGameTile: boolean, x: number, y: number) => {
|
||||||
|
if (payload?.game?.state === "running") {
|
||||||
const list = targetList(targetPreview, mode)
|
const list = targetList(targetPreview, mode)
|
||||||
if (
|
if (
|
||||||
!isGameTile ||
|
!isGameTile ||
|
||||||
|
@ -43,22 +57,32 @@ function Gamefield() {
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if (target.show && target.x == x && target.y == y) {
|
if (target.show && target.x == x && target.y == y) {
|
||||||
DispatchHits({
|
DispatchAction({
|
||||||
type: "fireMissile",
|
action: "missile",
|
||||||
payload: list.map(({ x, y }) => ({
|
...target,
|
||||||
hit: isAlreadyHit(x, y, hits),
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
})),
|
|
||||||
})
|
})
|
||||||
setTarget((t) => ({ ...t, show: false }))
|
setTarget((t) => ({ ...t, show: false }))
|
||||||
} else if (!overlapsWithAnyBorder(targetPreview, mode))
|
} else if (!overlapsWithAnyBorder(targetPreview, mode))
|
||||||
setTarget({ show: true, x, y })
|
setTarget({ show: true, x, y })
|
||||||
|
} else if (payload?.game?.state === "starting") {
|
||||||
|
addShip(shipProps(ships, mode, targetPreview))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[DispatchHits, hits, mode, setTarget, target, targetPreview]
|
[
|
||||||
|
DispatchAction,
|
||||||
|
addShip,
|
||||||
|
hits,
|
||||||
|
mode,
|
||||||
|
payload?.game?.state,
|
||||||
|
setTarget,
|
||||||
|
ships,
|
||||||
|
target,
|
||||||
|
targetPreview,
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (mode < 0) return
|
||||||
const { x, y, show } = target
|
const { x, y, show } = target
|
||||||
const { shouldShow, ...position } = mouseCursor
|
const { shouldShow, ...position } = mouseCursor
|
||||||
if (!shouldShow || overlapsWithAnyBorder(position, mode))
|
if (!shouldShow || overlapsWithAnyBorder(position, mode))
|
||||||
|
@ -97,8 +121,7 @@ function Gamefield() {
|
||||||
{/* Fog images */}
|
{/* Fog images */}
|
||||||
{/* <FogImages /> */}
|
{/* <FogImages /> */}
|
||||||
|
|
||||||
<Targets props={{ target, targetPreview, mode, hits }} />
|
<Targets />
|
||||||
{/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */}
|
|
||||||
|
|
||||||
<canvas
|
<canvas
|
||||||
style={
|
style={
|
||||||
|
@ -114,7 +137,7 @@ function Gamefield() {
|
||||||
height="648"
|
height="648"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<EventBar props={{ setMode, clear }} />
|
<EventBar clear={clear} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,11 +45,12 @@ function Item({
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
className={classNames("container", {
|
className={classNames("container", {
|
||||||
amount: amount,
|
amount: typeof amount !== "undefined",
|
||||||
disabled: disabled,
|
disabled: disabled || amount === 0,
|
||||||
|
enabled: disabled === false,
|
||||||
})}
|
})}
|
||||||
style={
|
style={
|
||||||
amount
|
typeof amount !== "undefined"
|
||||||
? ({
|
? ({
|
||||||
"--amount": JSON.stringify(amount.toString()),
|
"--amount": JSON.stringify(amount.toString()),
|
||||||
} as CSSProperties)
|
} as CSSProperties)
|
||||||
|
|
34
leaky-ships/components/Gamefield/Ship.tsx
Normal file
34
leaky-ships/components/Gamefield/Ship.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import { ShipProps } from "../../interfaces/frontend"
|
||||||
|
import { useGameProps } from "@hooks/useGameProps"
|
||||||
|
import classNames from "classnames"
|
||||||
|
import React, { CSSProperties } from "react"
|
||||||
|
|
||||||
|
function Ship({
|
||||||
|
props: { size, variant, x, y },
|
||||||
|
preview,
|
||||||
|
}: {
|
||||||
|
props: ShipProps
|
||||||
|
preview?: boolean
|
||||||
|
}) {
|
||||||
|
const { payload, removeShip } = useGameProps()
|
||||||
|
const filename = `ship_blue_${size}x_${variant}.gif`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames("ship", "s" + size, {
|
||||||
|
preview: preview,
|
||||||
|
interactive: payload?.game?.state === "starting",
|
||||||
|
})}
|
||||||
|
style={{ "--x": x, "--y": y } as CSSProperties}
|
||||||
|
onClick={() => {
|
||||||
|
if (payload?.game?.state !== "starting") return
|
||||||
|
removeShip({ size, variant, x, y })
|
||||||
|
useGameProps.setState({ mode: size - 2 })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img src={"/assets/" + filename} alt={filename} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Ship
|
|
@ -1,31 +1,23 @@
|
||||||
import { Ship } from "../../interfaces/frontend"
|
import Ship from "./Ship"
|
||||||
import classNames from "classnames"
|
import { useGameProps } from "@hooks/useGameProps"
|
||||||
import { CSSProperties } from "react"
|
|
||||||
|
|
||||||
function Ships() {
|
function Ships() {
|
||||||
const shipIndexes: Ship[] = [
|
// const shipIndexes: Ship[] = [
|
||||||
{ size: 2, variant: 1, x: 3, y: 3 },
|
// { size: 2, variant: 1, x: 3, y: 3 },
|
||||||
{ size: 3, variant: 1, x: 4, y: 3 },
|
// { size: 3, variant: 1, x: 4, y: 3 },
|
||||||
{ size: 3, variant: 2, x: 5, y: 3 },
|
// { size: 3, variant: 2, x: 5, y: 3 },
|
||||||
{ size: 3, variant: 3, x: 6, y: 3 },
|
// { size: 3, variant: 3, x: 6, y: 3 },
|
||||||
{ size: 4, variant: 1, x: 7, y: 3 },
|
// { size: 4, variant: 1, x: 7, y: 3 },
|
||||||
{ size: 4, variant: 2, x: 8, y: 3 },
|
// { size: 4, variant: 2, x: 8, y: 3 },
|
||||||
]
|
// ]
|
||||||
|
|
||||||
|
const { ships } = useGameProps()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{shipIndexes.map(({ size, variant, x, y }, i) => {
|
{ships.map((props, i) => (
|
||||||
const filename = `ship_blue_${size}x_${variant}.gif`
|
<Ship key={i} props={props} />
|
||||||
return (
|
))}
|
||||||
<div
|
|
||||||
key={i}
|
|
||||||
className={classNames("ship", "s" + size)}
|
|
||||||
style={{ "--x": x, "--y": y } as CSSProperties}
|
|
||||||
>
|
|
||||||
<img src={"/assets/" + filename} alt={filename} />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
import { Hit, Target } from "../../interfaces/frontend"
|
|
||||||
import GamefieldPointer from "./GamefieldPointer"
|
import GamefieldPointer from "./GamefieldPointer"
|
||||||
import { composeTargetTiles } from "@lib/utils/helpers"
|
import Ship from "./Ship"
|
||||||
import React from "react"
|
import { useGameProps } from "@hooks/useGameProps"
|
||||||
|
import { composeTargetTiles, shipProps } from "@lib/utils/helpers"
|
||||||
|
|
||||||
function Targets({
|
function Targets() {
|
||||||
props: { target, targetPreview, mode, hits },
|
const { payload, target, targetPreview, mode, hits, ships } = useGameProps()
|
||||||
}: {
|
|
||||||
props: {
|
if (payload?.game?.state === "running")
|
||||||
target: Target
|
|
||||||
targetPreview: Target
|
|
||||||
mode: number
|
|
||||||
hits: Hit[]
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{[
|
{[
|
||||||
|
@ -25,6 +19,9 @@ function Targets({
|
||||||
]}
|
]}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (payload?.game?.state === "starting" && mode >= 0 && targetPreview.show)
|
||||||
|
return <Ship preview props={shipProps(ships, mode, targetPreview)} />
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Targets
|
export default Targets
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import { Hit, HitDispatch, Target } from "../interfaces/frontend"
|
import {
|
||||||
|
ActionDispatchProps,
|
||||||
|
EventBarModes,
|
||||||
|
Hit,
|
||||||
|
ShipProps,
|
||||||
|
Target,
|
||||||
|
} from "../interfaces/frontend"
|
||||||
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
|
import { GameSettings } from "@components/Lobby/SettingsFrame/Setting"
|
||||||
import { getPayloadwithChecksum } from "@lib/getPayloadwithChecksum"
|
import { getPayloadwithChecksum } from "@lib/getPayloadwithChecksum"
|
||||||
import { socket } from "@lib/socket"
|
import { socket } from "@lib/socket"
|
||||||
|
@ -19,12 +25,18 @@ const initialState: optionalGamePropsSchema & {
|
||||||
isReady: boolean
|
isReady: boolean
|
||||||
isConnected: boolean
|
isConnected: boolean
|
||||||
}[]
|
}[]
|
||||||
|
menu: keyof EventBarModes
|
||||||
|
mode: number
|
||||||
|
ships: ShipProps[]
|
||||||
hits: Hit[]
|
hits: Hit[]
|
||||||
target: Target
|
target: Target
|
||||||
targetPreview: Target
|
targetPreview: Target
|
||||||
} = {
|
} = {
|
||||||
|
menu: "actions",
|
||||||
|
mode: 0,
|
||||||
payload: null,
|
payload: null,
|
||||||
hash: null,
|
hash: null,
|
||||||
|
ships: [],
|
||||||
hits: [],
|
hits: [],
|
||||||
target: initlialTarget,
|
target: initlialTarget,
|
||||||
targetPreview: initlialTargetPreview,
|
targetPreview: initlialTargetPreview,
|
||||||
|
@ -37,7 +49,7 @@ const initialState: optionalGamePropsSchema & {
|
||||||
export type State = typeof initialState
|
export type State = typeof initialState
|
||||||
|
|
||||||
export type Action = {
|
export type Action = {
|
||||||
DispatchHits: (action: HitDispatch) => void
|
DispatchAction: (props: ActionDispatchProps) => void
|
||||||
setTarget: (target: SetStateAction<Target>) => void
|
setTarget: (target: SetStateAction<Target>) => void
|
||||||
setTargetPreview: (targetPreview: SetStateAction<Target>) => void
|
setTargetPreview: (targetPreview: SetStateAction<Target>) => void
|
||||||
setPlayer: (payload: { users: PlayerSchema[] }) => string | null
|
setPlayer: (payload: { users: PlayerSchema[] }) => string | null
|
||||||
|
@ -46,6 +58,8 @@ export type Action = {
|
||||||
leave: (cb: () => void) => void
|
leave: (cb: () => void) => void
|
||||||
setIsReady: (payload: { i: number; isReady: boolean }) => void
|
setIsReady: (payload: { i: number; isReady: boolean }) => void
|
||||||
starting: () => void
|
starting: () => void
|
||||||
|
addShip: (props: ShipProps) => void
|
||||||
|
removeShip: (props: ShipProps) => void
|
||||||
setIsConnected: (payload: { i: number; isConnected: boolean }) => void
|
setIsConnected: (payload: { i: number; isConnected: boolean }) => void
|
||||||
reset: () => void
|
reset: () => void
|
||||||
}
|
}
|
||||||
|
@ -54,16 +68,16 @@ export const useGameProps = create<State & Action>()(
|
||||||
devtools(
|
devtools(
|
||||||
(set) => ({
|
(set) => ({
|
||||||
...initialState,
|
...initialState,
|
||||||
DispatchHits: (action) =>
|
DispatchAction: (action) =>
|
||||||
set(
|
set(
|
||||||
produce((state: State) => {
|
produce((state: State) => {
|
||||||
switch (action.type) {
|
// switch (action.type) {
|
||||||
case "fireMissile":
|
// case "fireMissile":
|
||||||
case "htorpedo":
|
// case "htorpedo":
|
||||||
case "vtorpedo": {
|
// case "vtorpedo": {
|
||||||
state.hits.push(...action.payload)
|
// state.hits.push(...action.payload)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
setTarget: (target) =>
|
setTarget: (target) =>
|
||||||
|
@ -82,6 +96,25 @@ export const useGameProps = create<State & Action>()(
|
||||||
else state.targetPreview = targetPreview
|
else state.targetPreview = targetPreview
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
addShip: (props) =>
|
||||||
|
set(
|
||||||
|
produce((state: State) => {
|
||||||
|
state.ships.push(props)
|
||||||
|
})
|
||||||
|
),
|
||||||
|
removeShip: ({ size, variant, x, y }) =>
|
||||||
|
set(
|
||||||
|
produce((state: State) => {
|
||||||
|
const indexToRemove = state.ships.findIndex(
|
||||||
|
(ship) =>
|
||||||
|
ship.size === size &&
|
||||||
|
ship.variant === variant &&
|
||||||
|
ship.x === x &&
|
||||||
|
ship.y === y
|
||||||
|
)
|
||||||
|
state.ships.splice(indexToRemove, 1)
|
||||||
|
})
|
||||||
|
),
|
||||||
setPlayer: (payload) => {
|
setPlayer: (payload) => {
|
||||||
let hash: string | null = null
|
let hash: string | null = null
|
||||||
set(
|
set(
|
||||||
|
@ -103,7 +136,6 @@ export const useGameProps = create<State & Action>()(
|
||||||
return hash
|
return hash
|
||||||
},
|
},
|
||||||
setSetting: (settings) => {
|
setSetting: (settings) => {
|
||||||
const payload = JSON.stringify(settings)
|
|
||||||
let hash: string | null = null
|
let hash: string | null = null
|
||||||
set(
|
set(
|
||||||
produce((state: State) => {
|
produce((state: State) => {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons"
|
import { IconDefinition } from "@fortawesome/pro-solid-svg-icons"
|
||||||
|
import { MoveType } from "@prisma/client"
|
||||||
|
|
||||||
export interface Position {
|
export interface Position {
|
||||||
x: number
|
x: number
|
||||||
|
@ -16,7 +17,7 @@ export interface TargetList extends Position {
|
||||||
}
|
}
|
||||||
export interface Mode {
|
export interface Mode {
|
||||||
pointerGrid: any[][]
|
pointerGrid: any[][]
|
||||||
type: string
|
type: MoveType
|
||||||
}
|
}
|
||||||
export interface ItemProps {
|
export interface ItemProps {
|
||||||
icon: string | IconDefinition
|
icon: string | IconDefinition
|
||||||
|
@ -29,7 +30,7 @@ export interface ItemProps {
|
||||||
export interface EventBarModes {
|
export interface EventBarModes {
|
||||||
main: ItemProps[]
|
main: ItemProps[]
|
||||||
menu: ItemProps[]
|
menu: ItemProps[]
|
||||||
attack: ItemProps[]
|
actions: ItemProps[]
|
||||||
draw: ItemProps[]
|
draw: ItemProps[]
|
||||||
settings: ItemProps[]
|
settings: ItemProps[]
|
||||||
}
|
}
|
||||||
|
@ -40,29 +41,7 @@ export interface Hit extends Position {
|
||||||
hit: boolean
|
hit: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface fireMissile {
|
export interface Point extends Position {}
|
||||||
type: "fireMissile" | "htorpedo" | "vtorpedo"
|
|
||||||
payload: {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
hit: boolean
|
|
||||||
}[]
|
|
||||||
}
|
|
||||||
interface removeMissile {
|
|
||||||
type: "removeMissile"
|
|
||||||
payload: {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
hit: boolean
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HitDispatch = fireMissile | removeMissile
|
|
||||||
|
|
||||||
export interface Point {
|
|
||||||
x: number
|
|
||||||
y: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DrawLineProps {
|
export interface DrawLineProps {
|
||||||
currentPoint: Point
|
currentPoint: Point
|
||||||
|
@ -74,7 +53,12 @@ export interface Draw extends DrawLineProps {
|
||||||
ctx: CanvasRenderingContext2D
|
ctx: CanvasRenderingContext2D
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Ship extends Position {
|
export interface ShipProps extends Position {
|
||||||
size: number
|
size: number
|
||||||
variant: number
|
variant: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ActionDispatchProps extends Position {
|
||||||
|
index?: number
|
||||||
|
action: MoveType
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import type {
|
import type {
|
||||||
Hit,
|
Hit,
|
||||||
HitDispatch,
|
|
||||||
Mode,
|
Mode,
|
||||||
Position,
|
Position,
|
||||||
|
ShipProps,
|
||||||
Target,
|
Target,
|
||||||
TargetList,
|
TargetList,
|
||||||
} from "../../interfaces/frontend"
|
} from "../../interfaces/frontend"
|
||||||
import { count } from "@components/Gamefield/Gamefield"
|
import { count } from "@components/Gamefield/Gamefield"
|
||||||
import { PointerProps } from "@components/Gamefield/GamefieldPointer"
|
import { PointerProps } from "@components/Gamefield/GamefieldPointer"
|
||||||
|
import { useGameProps } from "@hooks/useGameProps"
|
||||||
|
|
||||||
export function borderCN(count: number, x: number, y: number) {
|
export function borderCN(count: number, x: number, y: number) {
|
||||||
if (x === 0) return "left"
|
if (x === 0) return "left"
|
||||||
|
@ -54,13 +55,6 @@ export function isAlreadyHit(x: number, y: number, hits: Hit[]) {
|
||||||
return !!hits.filter((h) => h.x === x && h.y === y).length
|
return !!hits.filter((h) => h.x === x && h.y === y).length
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSet(x: number, y: number, targetList: TargetList[], show: boolean) {
|
|
||||||
return (
|
|
||||||
!!targetList.filter((field) => x === field.x && y === field.y).length &&
|
|
||||||
show
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function targetList(
|
export function targetList(
|
||||||
{ x: targetX, y: targetY }: Position,
|
{ x: targetX, y: targetY }: Position,
|
||||||
mode: number
|
mode: number
|
||||||
|
@ -129,3 +123,21 @@ export const initlialMouseCursor = {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const shipProps = (
|
||||||
|
ships: ShipProps[],
|
||||||
|
mode: number,
|
||||||
|
targetPreview: Target
|
||||||
|
) => ({
|
||||||
|
size: mode + 2,
|
||||||
|
variant:
|
||||||
|
ships
|
||||||
|
.filter((e) => e.size === mode + 2)
|
||||||
|
.sort((a, b) => a.variant - b.variant)
|
||||||
|
.reduce((prev, curr) => {
|
||||||
|
console.log(curr.variant - prev)
|
||||||
|
return curr.variant - prev < 2 ? curr.variant : prev
|
||||||
|
}, 0) + 1,
|
||||||
|
x: targetPreview.x - Math.floor((mode + 2) / 2),
|
||||||
|
y: targetPreview.y,
|
||||||
|
})
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ShipSchema } from "../prisma/generated/zod"
|
||||||
import { GameState } from "@prisma/client"
|
import { GameState } from "@prisma/client"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
|
@ -18,6 +19,9 @@ export const PlayerSchema = z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
index: z.number(),
|
index: z.number(),
|
||||||
|
action: z.string(),
|
||||||
|
x: z.number(),
|
||||||
|
y: z.number(),
|
||||||
})
|
})
|
||||||
.array(),
|
.array(),
|
||||||
})
|
})
|
||||||
|
@ -34,10 +38,20 @@ export const CreateSchema = z.object({
|
||||||
allowSpecials: z.boolean(),
|
allowSpecials: z.boolean(),
|
||||||
allowChat: z.boolean(),
|
allowChat: z.boolean(),
|
||||||
allowMarkDraw: z.boolean(),
|
allowMarkDraw: z.boolean(),
|
||||||
|
ships: z
|
||||||
|
.object({
|
||||||
|
id: z.string(),
|
||||||
|
size: z.number(),
|
||||||
|
variant: z.number(),
|
||||||
|
x: z.number(),
|
||||||
|
y: z.number(),
|
||||||
|
})
|
||||||
|
.array(),
|
||||||
})
|
})
|
||||||
.nullable(),
|
.nullable(),
|
||||||
gamePin: z.string().nullable(),
|
gamePin: z.string().nullable(),
|
||||||
users: PlayerSchema.array(),
|
users: PlayerSchema.array(),
|
||||||
|
activeIndex: z.number().optional(),
|
||||||
})
|
})
|
||||||
|
|
||||||
export const GamePropsSchema = z.object({
|
export const GamePropsSchema = z.object({
|
||||||
|
|
|
@ -15,6 +15,15 @@ export const gameSelects = {
|
||||||
allowSpecials: true,
|
allowSpecials: true,
|
||||||
allowSpectators: true,
|
allowSpectators: true,
|
||||||
state: true,
|
state: true,
|
||||||
|
ships: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
size: true,
|
||||||
|
variant: true,
|
||||||
|
x: true,
|
||||||
|
y: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
gamePin: {
|
gamePin: {
|
||||||
select: {
|
select: {
|
||||||
pin: true,
|
pin: true,
|
||||||
|
@ -36,6 +45,9 @@ export const gameSelects = {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
index: true,
|
index: true,
|
||||||
|
action: true,
|
||||||
|
x: true,
|
||||||
|
y: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -75,11 +75,22 @@ enum GameState {
|
||||||
ended
|
ended
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Ship {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
size Int
|
||||||
|
variant Int
|
||||||
|
x Int
|
||||||
|
y Int
|
||||||
|
gameId String
|
||||||
|
game Game @relation(fields: [gameId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
model Game {
|
model Game {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
state GameState @default(lobby)
|
state GameState @default(lobby)
|
||||||
|
ships Ship[]
|
||||||
allowSpectators Boolean @default(true)
|
allowSpectators Boolean @default(true)
|
||||||
allowSpecials Boolean @default(true)
|
allowSpecials Boolean @default(true)
|
||||||
allowChat Boolean @default(true)
|
allowChat Boolean @default(true)
|
||||||
|
@ -111,14 +122,25 @@ model User_Game {
|
||||||
@@unique([gameId, userId])
|
@@unique([gameId, userId])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum MoveType {
|
||||||
|
radar
|
||||||
|
htorpedo
|
||||||
|
vtorpedo
|
||||||
|
missile
|
||||||
|
}
|
||||||
|
|
||||||
model Move {
|
model Move {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
index Int
|
index Int
|
||||||
|
action MoveType
|
||||||
|
x Int
|
||||||
|
y Int
|
||||||
user_game_id String
|
user_game_id String
|
||||||
user_game User_Game @relation(fields: [user_game_id], references: [id], onDelete: Cascade)
|
user_game User_Game @relation(fields: [user_game_id], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
@@unique([user_game_id, index])
|
@@unique([user_game_id, index])
|
||||||
|
@@unique([action, x, y])
|
||||||
}
|
}
|
||||||
|
|
||||||
model Chat {
|
model Chat {
|
||||||
|
|
|
@ -108,7 +108,7 @@ body {
|
||||||
position: relative;
|
position: relative;
|
||||||
@include flex-col;
|
@include flex-col;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
grid-row: var(--x);
|
grid-row: var(--y);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
|
@ -119,16 +119,31 @@ body {
|
||||||
// object-fit: cover;
|
// object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.interactive:not(.preview) {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.preview {
|
||||||
|
border: 2px dashed orange;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
animation: blink 0.5s ease-in-out alternate infinite;
|
||||||
|
@keyframes blink {
|
||||||
|
from {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&.s2 {
|
&.s2 {
|
||||||
grid-column: var(--y) / 5;
|
grid-column: var(--x) / calc(var(--x) + 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.s3 {
|
&.s3 {
|
||||||
grid-column: var(--y) / 6;
|
grid-column: var(--x) / calc(var(--x) + 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.s4 {
|
&.s4 {
|
||||||
grid-column: var(--y) / 7;
|
grid-column: var(--x) / calc(var(--x) + 4);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,8 +300,8 @@ body {
|
||||||
|
|
||||||
&.left {
|
&.left {
|
||||||
transform: rotate(90deg);
|
transform: rotate(90deg);
|
||||||
animation: floatInl 3s ease-out;
|
animation: floatInLe 3s ease-out;
|
||||||
@keyframes floatInl {
|
@keyframes floatInLe {
|
||||||
from {
|
from {
|
||||||
transform: scale(2) rotate(90deg);
|
transform: scale(2) rotate(90deg);
|
||||||
margin-left: -324px;
|
margin-left: -324px;
|
||||||
|
@ -297,8 +312,8 @@ body {
|
||||||
|
|
||||||
&.right {
|
&.right {
|
||||||
transform: rotate(270deg);
|
transform: rotate(270deg);
|
||||||
animation: floatInr 3s ease-out;
|
animation: floatInRi 3s ease-out;
|
||||||
@keyframes floatInr {
|
@keyframes floatInRi {
|
||||||
from {
|
from {
|
||||||
transform: scale(2) rotate(270deg);
|
transform: scale(2) rotate(270deg);
|
||||||
margin-left: 324px;
|
margin-left: 324px;
|
||||||
|
@ -307,8 +322,8 @@ body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.top {
|
&.top {
|
||||||
animation: floatInt 3s ease-out;
|
animation: floatInTop 3s ease-out;
|
||||||
@keyframes floatInt {
|
@keyframes floatInTop {
|
||||||
from {
|
from {
|
||||||
transform: scale(2);
|
transform: scale(2);
|
||||||
margin-top: 648px;
|
margin-top: 648px;
|
||||||
|
@ -319,8 +334,8 @@ body {
|
||||||
|
|
||||||
&.bottom {
|
&.bottom {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
animation: floatInb 3s ease-out;
|
animation: floatInBot 3s ease-out;
|
||||||
@keyframes floatInb {
|
@keyframes floatInBot {
|
||||||
from {
|
from {
|
||||||
transform: scale(2) rotate(180deg);
|
transform: scale(2) rotate(180deg);
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
@ -331,12 +346,18 @@ body {
|
||||||
|
|
||||||
&.middle {
|
&.middle {
|
||||||
grid-area: 4 / 4 / -4 / -4;
|
grid-area: 4 / 4 / -4 / -4;
|
||||||
|
transform: scale(1.5);
|
||||||
|
animation: floatInMid 3s ease-in-out;
|
||||||
|
@keyframes floatInMid {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
grid-area: 1 / 1 / -1 / -1;
|
grid-area: 1 / 1 / -1 / -1;
|
||||||
border: 1px solid #000;
|
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
@ -379,6 +400,9 @@ body {
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
|
||||||
|
&.enabled {
|
||||||
|
box-shadow: 0 0 0.5rem 0.25rem royalblue;
|
||||||
|
}
|
||||||
&.disabled {
|
&.disabled {
|
||||||
box-shadow: inset 0 0 1rem 1rem #888;
|
box-shadow: inset 0 0 1rem 1rem #888;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue