Formatted all files with Prettier

This commit is contained in:
aronmal 2023-02-08 10:31:03 +01:00
parent 0bc2196d9f
commit ea80456a56
Signed by: aronmal
GPG key ID: 816B7707426FC612
64 changed files with 2209 additions and 1839 deletions

View file

@ -1,3 +1,3 @@
{ {
"semi": false "semi": false
} }

View file

@ -1,175 +1,189 @@
import { useState } from "react" import { useState } from "react"
function Bluetooth() { function Bluetooth() {
const [startDisabled, setStartDisabled] = useState(true) const [startDisabled, setStartDisabled] = useState(true)
const [stopDisabled, setStopDisabled] = useState(true) const [stopDisabled, setStopDisabled] = useState(true)
const deviceName = 'Chromecast Remote' const deviceName = "Chromecast Remote"
// ble UV Index // ble UV Index
const bleService = 'environmental_sensing' const bleService = "environmental_sensing"
const bleCharacteristic = 'uv_index' const bleCharacteristic = "uv_index"
// ble Battery percentage // ble Battery percentage
// const bleService = 'battery_service' // const bleService = 'battery_service'
// const bleCharacteristic = 'battery_level' // const bleCharacteristic = 'battery_level'
// ble Manufacturer Name // ble Manufacturer Name
// const bleService = 'device_information' // const bleService = 'device_information'
// const bleCharacteristic = 'manufacturer_name_string' // const bleCharacteristic = 'manufacturer_name_string'
let bluetoothDeviceDetected: BluetoothDevice let bluetoothDeviceDetected: BluetoothDevice
let gattCharacteristic: BluetoothRemoteGATTCharacteristic let gattCharacteristic: BluetoothRemoteGATTCharacteristic
function isWebBluetoothEnabled() { function isWebBluetoothEnabled() {
if (!navigator.bluetooth) { if (!navigator.bluetooth) {
alert('Web Bluetooth API is not available in this browser!') alert("Web Bluetooth API is not available in this browser!")
return false return false
}
return true
} }
function getDeviceInfo() { return true
const options = { }
// acceptAllDevices: true, function getDeviceInfo() {
filters: [ const options = {
{ name: deviceName } // acceptAllDevices: true,
], filters: [{ name: deviceName }],
// optionalServices: ['battery_service'], // optionalServices: ['battery_service'],
}
console.log('Requesting Bluetooth Device...')
return navigator.bluetooth
.requestDevice(options)
.then(device => {
bluetoothDeviceDetected = device
console.log('> Name: ' + device.name)
device.addEventListener('gattserverdisconnected', onDisconnected)
})
.catch(error => console.log('Argh! ' + error))
}
function read() {
if (!isWebBluetoothEnabled())
return
return getDeviceInfo()
.then(connectGatt)
.then(_ => {
console.log('Reading UV Index...')
return gattCharacteristic.readValue()
})
.catch(error => console.log('Waiting to start reading: ' + error))
}
function connectGatt() {
if (bluetoothDeviceDetected.gatt && bluetoothDeviceDetected.gatt.connected && gattCharacteristic)
return Promise.resolve()
if (!bluetoothDeviceDetected || !bluetoothDeviceDetected.gatt)
return Promise.reject()
return bluetoothDeviceDetected.gatt.connect()
.then(server => {
console.log('Getting GATT Service...')
return server.getPrimaryService(bleService)
})
.then(service => {
console.log('Getting GATT Characteristic...')
return service.getCharacteristic(bleCharacteristic)
})
.then(characteristic => {
gattCharacteristic = characteristic
characteristic.addEventListener('characteristicvaluechanged', handleChangedValue)
setStartDisabled(false)
setStopDisabled(true)
})
}
function handleChangedValue(event: Event) {
const characteristic = event.target as BluetoothRemoteGATTCharacteristic
if (!characteristic.value) {
console.log('Characteristic undefined!')
return
}
const value = characteristic.value.getUint8(0)
const now = new Date()
// Output the UV Index
console.log(`> ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} UV Index is ${value}`)
// Output the Battery percentage
// console.log(`> ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} Battery percentage is ${value}`)
// Output the Manufacturer Name
// let decoder = new TextDecoder('utf-8')
// console.log(`> ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} Manufacturer Name is ${decoder.decode(characteristic.value)}`)
}
function start() {
if (!isWebBluetoothEnabled())
return
gattCharacteristic.startNotifications()
.then(_ => {
console.log('Start reading...')
setStartDisabled(true)
setStopDisabled(false)
})
.catch(error => console.log('[ERROR] Start: ' + error))
}
function stop() {
if (!isWebBluetoothEnabled())
return
gattCharacteristic.stopNotifications()
.then(_ => {
console.log('Stop reading...')
setStartDisabled(false)
setStopDisabled(true)
})
.catch(error => console.log('[ERROR] Stop: ' + error))
}
function onDisconnected(event: Event) {
alert("Device Disconnected")
// console.log(event)
const device = event.target as BluetoothDevice
console.log(`Device "${device.name}" is disconnected.`)
} }
return ( console.log("Requesting Bluetooth Device...")
<div>
<button
id="read"
className="bluetooth"
onClick={read}
>Connect with BLE device</button>
<button
id="start"
className="bluetooth"
disabled={startDisabled}
onClick={start}
>Start</button>
<button
id="stop"
className="bluetooth"
disabled={stopDisabled}
onClick={stop}
>Stop</button>
<p>
<span
className="App-link"
onClick={() => { navigator.clipboard.writeText("chrome://flags/#enable-experimental-web-platform-features") }}
// target="_blank"
style={{ "cursor": "pointer" }}
// rel="noopener noreferrer"
>
Step 1
</span>
{" "}
<span
className="App-link"
onClick={() => { navigator.clipboard.writeText("chrome://flags/#enable-web-bluetooth-new-permissions-backend") }}
// target="_blank"
style={{ "cursor": "pointer" }}
// rel="noopener noreferrer"
> return navigator.bluetooth
Step 2 .requestDevice(options)
</span> .then((device) => {
</p> bluetoothDeviceDetected = device
</div> console.log("> Name: " + device.name)
device.addEventListener("gattserverdisconnected", onDisconnected)
})
.catch((error) => console.log("Argh! " + error))
}
function read() {
if (!isWebBluetoothEnabled()) return
return getDeviceInfo()
.then(connectGatt)
.then((_) => {
console.log("Reading UV Index...")
return gattCharacteristic.readValue()
})
.catch((error) => console.log("Waiting to start reading: " + error))
}
function connectGatt() {
if (
bluetoothDeviceDetected.gatt &&
bluetoothDeviceDetected.gatt.connected &&
gattCharacteristic
)
return Promise.resolve()
if (!bluetoothDeviceDetected || !bluetoothDeviceDetected.gatt)
return Promise.reject()
return bluetoothDeviceDetected.gatt
.connect()
.then((server) => {
console.log("Getting GATT Service...")
return server.getPrimaryService(bleService)
})
.then((service) => {
console.log("Getting GATT Characteristic...")
return service.getCharacteristic(bleCharacteristic)
})
.then((characteristic) => {
gattCharacteristic = characteristic
characteristic.addEventListener(
"characteristicvaluechanged",
handleChangedValue
)
setStartDisabled(false)
setStopDisabled(true)
})
}
function handleChangedValue(event: Event) {
const characteristic = event.target as BluetoothRemoteGATTCharacteristic
if (!characteristic.value) {
console.log("Characteristic undefined!")
return
}
const value = characteristic.value.getUint8(0)
const now = new Date()
// Output the UV Index
console.log(
`> ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} UV Index is ${value}`
) )
// Output the Battery percentage
// console.log(`> ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} Battery percentage is ${value}`)
// Output the Manufacturer Name
// let decoder = new TextDecoder('utf-8')
// console.log(`> ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} Manufacturer Name is ${decoder.decode(characteristic.value)}`)
}
function start() {
if (!isWebBluetoothEnabled()) return
gattCharacteristic
.startNotifications()
.then((_) => {
console.log("Start reading...")
setStartDisabled(true)
setStopDisabled(false)
})
.catch((error) => console.log("[ERROR] Start: " + error))
}
function stop() {
if (!isWebBluetoothEnabled()) return
gattCharacteristic
.stopNotifications()
.then((_) => {
console.log("Stop reading...")
setStartDisabled(false)
setStopDisabled(true)
})
.catch((error) => console.log("[ERROR] Stop: " + error))
}
function onDisconnected(event: Event) {
alert("Device Disconnected")
// console.log(event)
const device = event.target as BluetoothDevice
console.log(`Device "${device.name}" is disconnected.`)
}
return (
<div>
<button id="read" className="bluetooth" onClick={read}>
Connect with BLE device
</button>
<button
id="start"
className="bluetooth"
disabled={startDisabled}
onClick={start}
>
Start
</button>
<button
id="stop"
className="bluetooth"
disabled={stopDisabled}
onClick={stop}
>
Stop
</button>
<p>
<span
className="App-link"
onClick={() => {
navigator.clipboard.writeText(
"chrome://flags/#enable-experimental-web-platform-features"
)
}}
// target="_blank"
style={{ cursor: "pointer" }}
// rel="noopener noreferrer"
>
Step 1
</span>{" "}
<span
className="App-link"
onClick={() => {
navigator.clipboard.writeText(
"chrome://flags/#enable-web-bluetooth-new-permissions-backend"
)
}}
// target="_blank"
style={{ cursor: "pointer" }}
// rel="noopener noreferrer"
>
Step 2
</span>
</p>
</div>
)
} }
export default Bluetooth export default Bluetooth

View file

@ -1,53 +1,65 @@
import { CSSProperties, Dispatch, SetStateAction } from 'react' import { CSSProperties, Dispatch, SetStateAction } from "react"
import { borderCN, cornerCN, fieldIndex } from '../lib/utils/helpers' import { borderCN, cornerCN, fieldIndex } from "../lib/utils/helpers"
import { Position, MouseCursor } from '../interfaces/frontend' import { Position, MouseCursor } from "../interfaces/frontend"
type TilesType = { type TilesType = {
key: number, key: number
isGameTile: boolean, isGameTile: boolean
classNameString: string, classNameString: string
x: number, x: number
y: number y: number
} }
function BorderTiles({ props: { count, settingTarget, setMouseCursor, setLastLeftTile } }: { function BorderTiles({
props: { props: { count, settingTarget, setMouseCursor, setLastLeftTile },
count: number, }: {
settingTarget: (isGameTile: boolean, x: number, y: number) => void, props: {
setMouseCursor: Dispatch<SetStateAction<MouseCursor>>, count: number
setLastLeftTile: Dispatch<SetStateAction<Position>> settingTarget: (isGameTile: boolean, x: number, y: number) => void
} setMouseCursor: Dispatch<SetStateAction<MouseCursor>>
setLastLeftTile: Dispatch<SetStateAction<Position>>
}
}) { }) {
let tilesProperties: TilesType[] = [] let tilesProperties: TilesType[] = []
for (let y = 0; y < count + 2; y++) { for (let y = 0; y < count + 2; y++) {
for (let x = 0; x < count + 2; x++) { for (let x = 0; x < count + 2; x++) {
const key = fieldIndex(count, x, y) const key = fieldIndex(count, x, y)
const cornerReslt = cornerCN(count, x, y) const cornerReslt = cornerCN(count, x, y)
const borderType = cornerReslt ? cornerReslt : borderCN(count, x, y) const borderType = cornerReslt ? cornerReslt : borderCN(count, x, y)
const isGameTile = x > 0 && x < count + 1 && y > 0 && y < count + 1 const isGameTile = x > 0 && x < count + 1 && y > 0 && y < count + 1
const classNames = ['border-tile'] const classNames = ["border-tile"]
if (borderType) if (borderType) classNames.push("edge", borderType)
classNames.push('edge', borderType) if (isGameTile) classNames.push("game-tile")
if (isGameTile) const classNameString = classNames.join(" ")
classNames.push('game-tile') tilesProperties.push({
const classNameString = classNames.join(' ') key,
tilesProperties.push({ key, classNameString, isGameTile, x: x + 1, y: y + 1 }) classNameString,
} isGameTile,
x: x + 1,
y: y + 1,
})
} }
}
return (<> return (
{tilesProperties.map(({ key, classNameString, isGameTile, x, y }) => { <>
return <div {tilesProperties.map(({ key, classNameString, isGameTile, x, y }) => {
key={key} return (
className={classNameString} <div
style={{ '--x': x, '--y': y } as CSSProperties} key={key}
onClick={() => settingTarget(isGameTile, x, y)} className={classNameString}
onMouseEnter={() => setMouseCursor({ x, y, shouldShow: isGameTile })} style={{ "--x": x, "--y": y } as CSSProperties}
onMouseLeave={() => setLastLeftTile({ x, y })} onClick={() => settingTarget(isGameTile, x, y)}
></div> onMouseEnter={() =>
})} setMouseCursor({ x, y, shouldShow: isGameTile })
</>) }
onMouseLeave={() => setLastLeftTile({ x, y })}
></div>
)
})}
</>
)
} }
export default BorderTiles export default BorderTiles

View file

@ -1,33 +1,38 @@
import React, { Dispatch, SetStateAction } from 'react' import React, { Dispatch, SetStateAction } from "react"
import { Items, Target } from '../interfaces/frontend' import { Items, Target } from "../interfaces/frontend"
import Item from './Item' import Item from "./Item"
function EventBar({ props: { setMode, setTarget } }: { function EventBar({
props: { props: { setMode, setTarget },
setMode: Dispatch<SetStateAction<number>>, }: {
setTarget: Dispatch<SetStateAction<Target>> props: {
} setMode: Dispatch<SetStateAction<number>>
setTarget: Dispatch<SetStateAction<Target>>
}
}) { }) {
const items: Items[] = [ const items: Items[] = [
{ icon: 'burger-menu', text: 'Menu' }, { icon: "burger-menu", text: "Menu" },
{ icon: 'radar', text: 'Radar scan', mode: 0, amount: 1 }, { icon: "radar", text: "Radar scan", mode: 0, amount: 1 },
{ icon: 'torpedo', text: 'Fire torpedo', mode: 1, amount: 1 }, { icon: "torpedo", text: "Fire torpedo", mode: 1, amount: 1 },
{ icon: 'scope', text: 'Fire missile', mode: 2 }, { icon: "scope", text: "Fire missile", mode: 2 },
{ icon: 'gear', text: 'Settings' } { icon: "gear", text: "Settings" },
] ]
return ( return (
<div className='event-bar'> <div className="event-bar">
{items.map((e, i) => ( {items.map((e, i) => (
<Item key={i} props={{ <Item
...e, callback: () => { key={i}
if (e.mode !== undefined) props={{
setMode(e.mode) ...e,
setTarget(e => ({ ...e, show: false })) callback: () => {
} if (e.mode !== undefined) setMode(e.mode)
}} /> setTarget((e) => ({ ...e, show: false }))
))} },
</div> }}
) />
))}
</div>
)
} }
export default EventBar export default EventBar

View file

@ -1,11 +1,13 @@
import Image from "next/image" import Image from "next/image"
function FogImages() { function FogImages() {
return <> return (
<Image className='fog left' src={`/fog/fog2.png`} alt={`fog1.png`} /> <>
<Image className='fog right' src={`/fog/fog2.png`} alt={`fog1.png`} /> <Image className="fog left" src={`/fog/fog2.png`} alt={`fog1.png`} />
<Image className='fog middle' src={`/fog/fog4.png`} alt={`fog4.png`} /> <Image className="fog right" src={`/fog/fog2.png`} alt={`fog1.png`} />
<Image className="fog middle" src={`/fog/fog4.png`} alt={`fog4.png`} />
</> </>
)
} }
export default FogImages export default FogImages

View file

@ -1,47 +1,41 @@
import { CSSProperties } from 'react' import { CSSProperties } from "react"
// import Bluetooth from './Bluetooth' // import Bluetooth from './Bluetooth'
import BorderTiles from './BorderTiles' import BorderTiles from "./BorderTiles"
import EventBar from './EventBar' import EventBar from "./EventBar"
// import FogImages from './FogImages' // import FogImages from './FogImages'
import HitElems from './HitElems' import HitElems from "./HitElems"
import Labeling from './Labeling' import Labeling from "./Labeling"
import Ships from './Ships' import Ships from "./Ships"
import useGameEvent from '../lib/hooks/useGameEvent' import useGameEvent from "../lib/hooks/useGameEvent"
import Targets from './Targets' import Targets from "./Targets"
function Gamefield() { function Gamefield() {
const count = 12
const { pointersProps, targetsProps, tilesProps, hits } = useGameEvent(count)
const count = 12 return (
const { <div id="gamefield">
pointersProps, {/* <Bluetooth /> */}
targetsProps, <div id="game-frame" style={{ "--i": count } as CSSProperties}>
tilesProps, {/* Bordes */}
hits <BorderTiles props={tilesProps} />
} = useGameEvent(count)
return ( {/* Collumn lettes and row numbers */}
<div id='gamefield'> <Labeling count={count} />
{/* <Bluetooth /> */}
<div id="game-frame" style={{ '--i': count } as CSSProperties}>
{/* Bordes */}
<BorderTiles props={tilesProps} />
{/* Collumn lettes and row numbers */} {/* Ships */}
<Labeling count={count} /> <Ships />
{/* Ships */} <HitElems hits={hits} />
<Ships />
<HitElems hits={hits} /> {/* Fog images */}
{/* <FogImages /> */}
{/* Fog images */} <Targets props={pointersProps} />
{/* <FogImages /> */} {/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */}
<Targets props={pointersProps} /> </div>
{/* <span id='dev-debug' style={{gridArea: '1 / 12 / 1 / 15', backgroundColor: 'red', zIndex: 3} as CSSProperties}>Debug</span> */} <EventBar props={targetsProps} />
</div> </div>
<EventBar props={targetsProps} /> )
</div>
)
} }
export default Gamefield export default Gamefield

View file

@ -1,30 +1,39 @@
import { faCrosshairs } from '@fortawesome/pro-solid-svg-icons' import { faCrosshairs } from "@fortawesome/pro-solid-svg-icons"
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { CSSProperties } from 'react' import { CSSProperties } from "react"
import classNames from 'classnames' import classNames from "classnames"
import { faRadar } from '@fortawesome/pro-thin-svg-icons' import { faRadar } from "@fortawesome/pro-thin-svg-icons"
import { Target, TargetList } from '../interfaces/frontend' import { Target, TargetList } from "../interfaces/frontend"
export interface PointerProps extends Target, TargetList { export interface PointerProps extends Target, TargetList {
imply: boolean; imply: boolean
} }
function GamefieldPointer({ props: { function GamefieldPointer({
preview, props: { preview, x, y, show, type, edges, imply },
x, }: {
y, props: PointerProps
show, }) {
type, const isRadar = type === "radar"
edges, const style = !(isRadar && !edges.filter((s) => s).length)
imply ? { "--x": x, "--y": y }
} }: { props: PointerProps }) { : { "--x1": x - 1, "--x2": x + 2, "--y1": y - 1, "--y2": y + 2 }
const isRadar = type === 'radar' return (
const style = !(isRadar && !edges.filter(s => s).length) ? { '--x': x, '--y': y } : { '--x1': x - 1, '--x2': x + 2, '--y1': y - 1, '--y2': y + 2 } <div
return ( className={classNames(
<div className={classNames('hit-svg', { preview: preview }, 'target', type, { show: show }, ...edges, { imply: imply })} style={style as CSSProperties}> "hit-svg",
<FontAwesomeIcon icon={!isRadar ? faCrosshairs : faRadar} /> { preview: preview },
</div> "target",
) type,
{ show: show },
...edges,
{ imply: imply }
)}
style={style as CSSProperties}
>
<FontAwesomeIcon icon={!isRadar ? faCrosshairs : faRadar} />
</div>
)
} }
export default GamefieldPointer export default GamefieldPointer

View file

@ -1,95 +1,112 @@
import classNames from 'classnames' import classNames from "classnames"
import { CSSProperties, useEffect, useMemo, useState } from 'react' import { CSSProperties, useEffect, useMemo, useState } from "react"
function Grid() { function Grid() {
function floorClient(number: number) {
return Math.floor(number / 50)
}
function floorClient(number: number) { const [columns, setColumns] = useState(0)
return Math.floor(number / 50) const [rows, setRows] = useState(0)
const [params, setParams] = useState({
columns,
rows,
quantity: columns * rows,
})
const [position, setPosition] = useState([0, 0])
const [active, setActve] = useState(false)
const [count, setCount] = useState(0)
useEffect(() => {
function handleResize() {
setColumns(floorClient(document.body.clientWidth))
setRows(floorClient(document.body.clientHeight))
} }
handleResize()
window.addEventListener("resize", handleResize)
}, [])
const [columns, setColumns] = useState(0) useEffect(() => {
const [rows, setRows] = useState(0) const timeout = setTimeout(() => {
const [params, setParams] = useState({ columns, rows, quantity: columns * rows }) setParams({ columns, rows, quantity: columns * rows })
const [position, setPosition] = useState([0, 0]) }, 500)
const [active, setActve] = useState(false) return () => clearTimeout(timeout)
const [count, setCount] = useState(0) }, [columns, rows])
useEffect(() => { const createTiles = useMemo(() => {
function handleResize() { const colors = [
setColumns(floorClient(document.body.clientWidth)) "rgb(229, 57, 53)",
setRows(floorClient(document.body.clientHeight)) "rgb(253, 216, 53)",
"rgb(244, 81, 30)",
"rgb(76, 175, 80)",
"rgb(33, 150, 243)",
"rgb(156, 39, 176)",
]
function createTile(index: number) {
const x = index % params.columns
const y = Math.floor(index / params.columns)
const xDiff = (x - position[0]) / 20
const yDiff = (y - position[1]) / 20
const pos = Math.sqrt(xDiff * xDiff + yDiff * yDiff).toFixed(2)
function doEffect(posX: number, posY: number) {
if (active) return
setPosition([posX, posY])
setActve(true)
function xDiff(x: number) {
return (x - posX) / 20
} }
handleResize() function yDiff(y: number) {
window.addEventListener('resize', handleResize) return (y - posY) / 20
}, []) }
function pos(x: number, y: number) {
useEffect(() => { return Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
const timeout = setTimeout(() => { }
setParams({ columns, rows, quantity: columns * rows }) const diagonals = [
}, 500) pos(0, 0),
return () => clearTimeout(timeout) pos(params.columns, 0),
}, [columns, rows]) pos(0, params.rows),
pos(params.columns, params.rows),
const createTiles = useMemo(() => {
const colors = [
'rgb(229, 57, 53)',
'rgb(253, 216, 53)',
'rgb(244, 81, 30)',
'rgb(76, 175, 80)',
'rgb(33, 150, 243)',
'rgb(156, 39, 176)'
] ]
function createTile(index: number) { setTimeout(() => {
setActve(false)
setCount((e) => e + 1)
}, Math.max(...diagonals) * 1000 + 300)
}
const x = index % params.columns return (
const y = Math.floor(index / params.columns) <div
const xDiff = (x - position[0]) / 20 key={index}
const yDiff = (y - position[1]) / 20 className={classNames("tile", { active: active })}
const pos = (Math.sqrt(xDiff * xDiff + yDiff * yDiff)).toFixed(2) style={{ "--delay": pos + "s" } as CSSProperties}
onClick={() => doEffect(x, y)}
></div>
)
}
function doEffect(posX: number, posY: number) { return (
if (active) <div
return id="tiles"
setPosition([posX, posY]) style={
setActve(true) {
"--columns": params.columns,
function xDiff(x: number) { "--rows": params.rows,
return (x - posX) / 20 "--bg-color-1": colors[count % colors.length],
} "--bg-color-2": colors[(count + 1) % colors.length],
function yDiff(y: number) { } as CSSProperties
return (y - posY) / 20
}
function pos(x: number, y: number) {
return Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
}
const diagonals = [pos(0, 0), pos(params.columns, 0), pos(0, params.rows), pos(params.columns, params.rows)]
setTimeout(() => {
setActve(false)
setCount(e => e + 1)
}, Math.max(...diagonals) * 1000 + 300)
}
return (
<div
key={index}
className={classNames('tile', { active: active })}
style={{ '--delay': pos + 's' } as CSSProperties}
onClick={() => doEffect(x, y)}
></div>
)
} }
>
{Array.from(Array(params.quantity), (_tile, index) =>
createTile(index)
)}
</div>
)
}, [params, position, active, count])
return ( return createTiles
<div id='tiles' style={{ '--columns': params.columns, '--rows': params.rows, '--bg-color-1': colors[count % colors.length], '--bg-color-2': colors[(count + 1) % colors.length] } as CSSProperties}>
{Array.from(Array(params.quantity), (_tile, index) => createTile(index))}
</div>
)
}, [params, position, active, count])
return createTiles
} }
export default Grid export default Grid

View file

@ -1,100 +1,118 @@
import classNames from 'classnames' import classNames from "classnames"
import { CSSProperties, useEffect, useMemo, useState } from 'react' import { CSSProperties, useEffect, useMemo, useState } from "react"
function Grid2() { function Grid2() {
function floorClient(number: number) {
return Math.floor(number / 50)
}
function floorClient(number: number) { const [columns, setColumns] = useState(0)
return Math.floor(number / 50) const [rows, setRows] = useState(0)
const [params, setParams] = useState({
columns,
rows,
quantity: columns * rows,
})
const [position, setPosition] = useState([0, 0])
const [active, setActve] = useState(false)
const [action, setAction] = useState(false)
const [count, setCount] = useState(0)
useEffect(() => {
function handleResize() {
setColumns(floorClient(document.body.clientWidth))
setRows(floorClient(document.body.clientHeight))
} }
handleResize()
window.addEventListener("resize", handleResize)
}, [])
const [columns, setColumns] = useState(0) useEffect(() => {
const [rows, setRows] = useState(0) const timeout = setTimeout(() => {
const [params, setParams] = useState({ columns, rows, quantity: columns * rows }) setParams({ columns, rows, quantity: columns * rows })
const [position, setPosition] = useState([0, 0]) }, 500)
const [active, setActve] = useState(false) return () => clearTimeout(timeout)
const [action, setAction] = useState(false) }, [columns, rows])
const [count, setCount] = useState(0)
useEffect(() => { const createTiles = useMemo(() => {
function handleResize() { const sentences = [
setColumns(floorClient(document.body.clientWidth)) "Ethem ...",
setRows(floorClient(document.body.clientHeight)) "hat ...",
"lange ...",
"Hörner 🐂",
"Grüße von Mallorca 🌊 🦦 ☀️",
]
function createTile(index: number) {
const x = index % params.columns
const y = Math.floor(index / params.columns)
const xDiff = (x - position[0]) / 20
const yDiff = (y - position[1]) / 20
const pos = Math.sqrt(xDiff * xDiff + yDiff * yDiff).toFixed(2)
function doEffect(posX: number, posY: number) {
if (action) return
setPosition([posX, posY])
setActve((e) => !e)
setAction(true)
function xDiff(x: number) {
return (x - posX) / 20
} }
handleResize() function yDiff(y: number) {
window.addEventListener('resize', handleResize) return (y - posY) / 20
}, []) }
function pos(x: number, y: number) {
useEffect(() => { return Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
const timeout = setTimeout(() => { }
setParams({ columns, rows, quantity: columns * rows }) const diagonals = [
}, 500) pos(0, 0),
return () => clearTimeout(timeout) pos(params.columns, 0),
}, [columns, rows]) pos(0, params.rows),
pos(params.columns, params.rows),
const createTiles = useMemo(() => {
const sentences = [
'Ethem ...',
'hat ...',
'lange ...',
'Hörner 🐂',
'Grüße von Mallorca 🌊 🦦 ☀️'
] ]
function createTile(index: number) { setTimeout(() => {
setAction(false)
if (active) setCount((e) => e + 1)
}, Math.max(...diagonals) * 1000 + 1000)
}
const x = index % params.columns return (
const y = Math.floor(index / params.columns) <div
const xDiff = (x - position[0]) / 20 key={index}
const yDiff = (y - position[1]) / 20 className={classNames("tile", active ? "active" : "inactive")}
const pos = (Math.sqrt(xDiff * xDiff + yDiff * yDiff)).toFixed(2) style={{ "--delay": pos + "s" } as CSSProperties}
onClick={() => doEffect(x, y)}
></div>
)
}
function doEffect(posX: number, posY: number) { return (
if (action) <div
return id="tiles"
setPosition([posX, posY]) style={
setActve(e => !e) {
setAction(true) "--columns": params.columns,
"--rows": params.rows,
function xDiff(x: number) { } as CSSProperties
return (x - posX) / 20
}
function yDiff(y: number) {
return (y - posY) / 20
}
function pos(x: number, y: number) {
return Math.sqrt(xDiff(x) * xDiff(x) + yDiff(y) * yDiff(y))
}
const diagonals = [pos(0, 0), pos(params.columns, 0), pos(0, params.rows), pos(params.columns, params.rows)]
setTimeout(() => {
setAction(false)
if (active)
setCount(e => e + 1)
}, Math.max(...diagonals) * 1000 + 1000)
}
return (
<div
key={index}
className={classNames('tile', (active ? 'active' : 'inactive'))}
style={{ '--delay': pos + 's' } as CSSProperties}
onClick={() => doEffect(x, y)}
></div>
)
} }
>
<div className="center-div">
<h1
className={classNames("headline", !active ? "active" : "inactive")}
>
{sentences[count % sentences.length]}
</h1>
</div>
{Array.from(Array(params.quantity), (_tile, index) =>
createTile(index)
)}
</div>
)
}, [params, position, active, action, count])
return ( return createTiles
<div id='tiles' style={{ '--columns': params.columns, '--rows': params.rows } as CSSProperties}>
<div className="center-div">
<h1 className={classNames('headline', (!active ? 'active' : 'inactive'))}>{sentences[count % sentences.length]}</h1>
</div>
{Array.from(Array(params.quantity), (_tile, index) => createTile(index))}
</div >
)
}, [params, position, active, action, count])
return createTiles
} }
export default Grid2 export default Grid2

View file

@ -1,17 +1,22 @@
import { faBurst, faXmark } from '@fortawesome/pro-solid-svg-icons' import { faBurst, faXmark } from "@fortawesome/pro-solid-svg-icons"
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { CSSProperties } from 'react' import { CSSProperties } from "react"
import { Hit } from '../interfaces/frontend' import { Hit } from "../interfaces/frontend"
function HitElems({hits}: {hits: Hit[]}) { function HitElems({ hits }: { hits: Hit[] }) {
return (
return <> <>
{hits.map(({hit, x, y}, i) => {hits.map(({ hit, x, y }, i) => (
<div key={i} className='hit-svg' style={{'--x': x, '--y': y} as CSSProperties}> <div
<FontAwesomeIcon icon={hit ? faBurst : faXmark} /> key={i}
</div> className="hit-svg"
)} style={{ "--x": x, "--y": y } as CSSProperties}
>
<FontAwesomeIcon icon={hit ? faBurst : faXmark} />
</div>
))}
</> </>
)
} }
export default HitElems export default HitElems

View file

@ -1,22 +1,33 @@
import classNames from 'classnames' import classNames from "classnames"
import React, { CSSProperties } from 'react' import React, { CSSProperties } from "react"
function Item({ props: { icon, text, amount, callback } }: { function Item({
props: { props: { icon, text, amount, callback },
icon: string, }: {
text: string, props: {
amount?: number, icon: string
callback: () => void text: string
} amount?: number
callback: () => void
}
}) { }) {
return ( return (
<div className='item' onClick={callback}> <div className="item" onClick={callback}>
<div className={classNames('container', { amount: amount })} style={amount ? { '--amount': JSON.stringify(amount.toString()) } as CSSProperties : {}}> <div
<img src={`/assets/${icon}.png`} alt={`${icon}.png`} /> className={classNames("container", { amount: amount })}
</div> style={
<span>{text}</span> amount
</div> ? ({
) "--amount": JSON.stringify(amount.toString()),
} as CSSProperties)
: {}
}
>
<img src={`/assets/${icon}.png`} alt={`${icon}.png`} />
</div>
<span>{text}</span>
</div>
)
} }
export default Item export default Item

View file

@ -1,30 +1,50 @@
import classNames from 'classnames' import classNames from "classnames"
import { CSSProperties } from 'react' import { CSSProperties } from "react"
import { fieldIndex } from '../lib/utils/helpers' import { fieldIndex } from "../lib/utils/helpers"
import { Field } from '../interfaces/frontend' import { Field } from "../interfaces/frontend"
function Labeling({count}: {count: number}) { function Labeling({ count }: { count: number }) {
let elems: (Field & { let elems: (Field & {
orientation: string orientation: string
})[] = [] })[] = []
for (let x = 0; x < count; x++) { for (let x = 0; x < count; x++) {
elems.push( elems.push(
// Up // Up
{field: String.fromCharCode(65+x), x: x+2, y: 1, orientation: 'up'}, { field: String.fromCharCode(65 + x), x: x + 2, y: 1, orientation: "up" },
// Left // Left
{field: (x+1).toString(), x: 1, y: x+2, orientation: 'left'}, { field: (x + 1).toString(), x: 1, y: x + 2, orientation: "left" },
// Bottom // Bottom
{field: String.fromCharCode(65+x), x: x+2, y: count+2, orientation: 'bottom'}, {
// Right field: String.fromCharCode(65 + x),
{field: (x+1).toString(), x: count+2, y: x+2, orientation: 'right'} x: x + 2,
) y: count + 2,
} orientation: "bottom",
elems = elems.sort((a, b) => fieldIndex(count, a.x, a.y)-fieldIndex(count, b.x, b.y)) },
return <> // Right
{elems.map(({field, x, y, orientation}, i) => {
<span key={i} className={classNames('label', orientation, field)} style={{'--x': x, '--y': y} as CSSProperties}>{field}</span> field: (x + 1).toString(),
)} x: count + 2,
y: x + 2,
orientation: "right",
}
)
}
elems = elems.sort(
(a, b) => fieldIndex(count, a.x, a.y) - fieldIndex(count, b.x, b.y)
)
return (
<>
{elems.map(({ field, x, y, orientation }, i) => (
<span
key={i}
className={classNames("label", orientation, field)}
style={{ "--x": x, "--y": y } as CSSProperties}
>
{field}
</span>
))}
</> </>
)
} }
export default Labeling export default Labeling

View file

@ -1,26 +1,34 @@
import classNames from 'classnames' import classNames from "classnames"
import { CSSProperties } from 'react' import { CSSProperties } from "react"
function Ships() { function Ships() {
let shipIndexes = [ let shipIndexes = [
{ size: 2, index: null }, { size: 2, index: null },
{ size: 3, index: 1 }, { size: 3, index: 1 },
{ size: 3, index: 2 }, { size: 3, index: 2 },
{ size: 3, index: 3 }, { size: 3, index: 3 },
{ size: 4, index: 1 }, { size: 4, index: 1 },
{ size: 4, index: 2 } { size: 4, index: 2 },
] ]
return <> return (
{shipIndexes.map(({ size, index }, i) => { <>
const filename = `/assets/ship_blue_${size}x${index ? '_' + index : ''}.gif` {shipIndexes.map(({ size, index }, i) => {
return ( const filename = `/assets/ship_blue_${size}x${
<div key={i} className={classNames('ship', 's' + size)} style={{ '--x': i + 3 } as CSSProperties}> index ? "_" + index : ""
<img src={filename} alt={filename} /> }.gif`
</div> return (
) <div
})} key={i}
className={classNames("ship", "s" + size)}
style={{ "--x": i + 3 } as CSSProperties}
>
<img src={filename} alt={filename} />
</div>
)
})}
</> </>
)
} }
export default Ships export default Ships

View file

@ -1,40 +1,38 @@
import { useEffect } from 'react' import { useEffect } from "react"
import { io } from 'socket.io-client' import { io } from "socket.io-client"
function SocketIO() { function SocketIO() {
useEffect(() => { useEffect(() => {
socketInitializer() socketInitializer()
}, []) }, [])
const socketInitializer = async () => { const socketInitializer = async () => {
await fetch('/api/ws') await fetch("/api/ws")
const socket = io() const socket = io()
socket.on('test2', (warst) => { socket.on("test2", (warst) => {
console.log('Test2:', warst, socket.id) console.log("Test2:", warst, socket.id)
}) })
socket.on("connect", () => { socket.on("connect", () => {
console.log(socket.connected) // true console.log(socket.connected) // true
setTimeout(() => { setTimeout(() => {
socket.emit('test', "warst") socket.emit("test", "warst")
socket.emit('test', "tsra") socket.emit("test", "tsra")
socket.emit('test', "1234") socket.emit("test", "1234")
// socket.disconnect() // socket.disconnect()
}, 1000) }, 1000)
}) })
socket.on("test", () => { socket.on("test", () => {
console.log("Got test1234") // false console.log("Got test1234") // false
}) })
socket.on("disconnect", () => { socket.on("disconnect", () => {
console.log(socket.connected) // false console.log(socket.connected) // false
}) })
} }
return ( return <div>SocketIO</div>
<div>SocketIO</div>
)
} }
export default SocketIO export default SocketIO

View file

@ -1,20 +1,28 @@
import React from 'react' import React from "react"
import { Target } from '../interfaces/frontend' import { Target } from "../interfaces/frontend"
import GamefieldPointer, { PointerProps } from './GamefieldPointer' import GamefieldPointer, { PointerProps } from "./GamefieldPointer"
function Targets({ props: { composeTargetTiles, target, targetPreview } }: { function Targets({
props: { props: { composeTargetTiles, target, targetPreview },
composeTargetTiles: (target: Target) => PointerProps[], }: {
target: Target, props: {
targetPreview: Target composeTargetTiles: (target: Target) => PointerProps[]
} target: Target
targetPreview: Target
}
}) { }) {
return (<> return (
{[ <>
...composeTargetTiles(target).map((props, i) => <GamefieldPointer key={'t' + i} props={props} />), {[
...composeTargetTiles(targetPreview).map((props, i) => <GamefieldPointer key={'p' + i} props={props} />) ...composeTargetTiles(target).map((props, i) => (
]} <GamefieldPointer key={"t" + i} props={props} />
</>) )),
...composeTargetTiles(targetPreview).map((props, i) => (
<GamefieldPointer key={"p" + i} props={props} />
)),
]}
</>
)
} }
export default Targets export default Targets

View file

@ -1,3 +1,3 @@
declare module globalThis { declare module globalThis {
var prismaClient: PrismaClient var prismaClient: PrismaClient
} }

View file

@ -1,7 +1,7 @@
import type { Server as HTTPServer } from 'http' import type { Server as HTTPServer } from "http"
import type { NextApiResponse } from 'next' import type { NextApiResponse } from "next"
import type { Socket as NetSocket } from 'net' import type { Socket as NetSocket } from "net"
import type { Server as IOServer } from 'socket.io' import type { Server as IOServer } from "socket.io"
interface SocketServer extends HTTPServer { interface SocketServer extends HTTPServer {
io?: IOServer | undefined io?: IOServer | undefined
@ -13,4 +13,4 @@ interface SocketWithIO extends NetSocket {
export interface NextApiResponseWithSocket extends NextApiResponse { export interface NextApiResponseWithSocket extends NextApiResponse {
socket: SocketWithIO socket: SocketWithIO
} }

View file

@ -1,50 +1,50 @@
export interface Position { export interface Position {
x: number, x: number
y: number y: number
} }
export interface Target extends Position { export interface Target extends Position {
preview: boolean, preview: boolean
show: boolean show: boolean
} }
export interface MouseCursor extends Position { export interface MouseCursor extends Position {
shouldShow: boolean shouldShow: boolean
} }
export interface TargetList extends Position { export interface TargetList extends Position {
type: string, type: string
edges: string[] edges: string[]
} }
export interface Mode { export interface Mode {
pointerGrid: any[][], pointerGrid: any[][]
type: string type: string
} }
export interface Items { export interface Items {
icon: string, icon: string
text: string, text: string
mode?: number, mode?: number
amount?: number, amount?: number
} }
export interface Field extends Position { export interface Field extends Position {
field: string field: string
} }
export interface Hit extends Position { export interface Hit extends Position {
hit: boolean hit: boolean
} }
interface fireMissile { interface fireMissile {
type: 'fireMissile', type: "fireMissile"
payload: { payload: {
x: number, x: number
y: number, y: number
hit: boolean hit: boolean
} }
} }
interface removeMissile { interface removeMissile {
type: 'removeMissile', type: "removeMissile"
payload: { payload: {
x: number, x: number
y: number, y: number
hit: boolean hit: boolean
} }
} }
export type HitDispatch = fireMissile | removeMissile export type HitDispatch = fireMissile | removeMissile

View file

@ -1,22 +1,23 @@
import { Player } from "@prisma/client" import { Player } from "@prisma/client"
import bcrypt from "bcrypt" import bcrypt from "bcrypt"
export default async function checkPasswordIsValid<T>(payload: T & { player: Player, password: string }) { export default async function checkPasswordIsValid<T>(
const { player, password } = payload payload: T & { player: Player; password: string }
) {
const { player, password } = payload
// Validate for correct password // Validate for correct password
return bcrypt.compare(password, player.passwordHash) return bcrypt.compare(password, player.passwordHash).then(async (result) => {
.then(async result => { if (!result) {
if (!result) { return Promise.reject({
return Promise.reject({ message: "Passwords do not match!",
message: 'Passwords do not match!', statusCode: 401,
statusCode: 401, solved: true,
solved: true, })
}) }
} return {
return { ...payload,
...payload, passwordIsValid: true,
passwordIsValid: true }
} })
}) }
}

View file

@ -2,27 +2,29 @@ import { Token } from "@prisma/client"
import jwt from "jsonwebtoken" import jwt from "jsonwebtoken"
import jwtVerifyCatch from "../jwtVerifyCatch" import jwtVerifyCatch from "../jwtVerifyCatch"
async function checkTokenIsValid<T>(payload: T & { token: string, tokenType: Token['type'] }) { async function checkTokenIsValid<T>(
const { token, tokenType } = payload payload: T & { token: string; tokenType: Token["type"] }
) {
const { token, tokenType } = payload
// Verify the token and get the payload // Verify the token and get the payload
let tokenData: string | jwt.JwtPayload let tokenData: string | jwt.JwtPayload
try { try {
tokenData = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET as string) tokenData = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET as string)
} catch (err: any) { } catch (err: any) {
// Deal with the problem in more detail // Deal with the problem in more detail
return Promise.reject(jwtVerifyCatch(tokenType, err)) return Promise.reject(jwtVerifyCatch(tokenType, err))
} }
// Making sure the token data is not a string (because it should be an object) // Making sure the token data is not a string (because it should be an object)
if (typeof tokenData === 'string') { if (typeof tokenData === "string") {
return Promise.reject({ return Promise.reject({
message: tokenType + '-Token data was a string. Token: ' + token, message: tokenType + "-Token data was a string. Token: " + token,
statusCode: 401, statusCode: 401,
solved: false solved: false,
}) })
} }
return { ...payload, tokenBody: token, tokenIsValid: true } return { ...payload, tokenBody: token, tokenIsValid: true }
} }
export default checkTokenIsValid export default checkTokenIsValid

View file

@ -1,28 +1,28 @@
import prisma from "../../prisma" import prisma from "../../prisma"
async function createAnonymousDB<T>(payload: T) { async function createAnonymousDB<T>(payload: T) {
const player = await prisma.player.create({ const player = await prisma.player.create({
data: { data: {
anonymous: true anonymous: true,
} },
}) })
// .catch((err: any) => { // .catch((err: any) => {
// if (err.code === 11000) { // if (err.code === 11000) {
// return Promise.reject({ // return Promise.reject({
// message: `Duplicate key error while creating Player in DB!`, // message: `Duplicate key error while creating Player in DB!`,
// statusCode: 409, // statusCode: 409,
// solved: true, // solved: true,
// type: 'warn' // type: 'warn'
// }) // })
// } else { // } else {
// console.log(err) // console.log(err)
// return Promise.reject({ // return Promise.reject({
// message: `Unknown error while creating Player in DB.`, // message: `Unknown error while creating Player in DB.`,
// solved: false // solved: false
// }) // })
// } // }
// }) // })
return { ...payload, player } return { ...payload, player }
} }
export default createAnonymousDB export default createAnonymousDB

View file

@ -1,34 +1,38 @@
import bcrypt from "bcrypt" import bcrypt from "bcrypt"
import prisma from "../../prisma" import prisma from "../../prisma"
async function createPlayerDB<T>(payload: T & { username: string, password: string }) { async function createPlayerDB<T>(
const { username, password } = payload payload: T & { username: string; password: string }
) {
const { username, password } = payload
return await prisma.player.create({ return await prisma.player
data: { .create({
username, data: {
passwordHash: await bcrypt.hash(password, 10), username,
anonymous: false passwordHash: await bcrypt.hash(password, 10),
} anonymous: false,
},
}) })
.then(player => { .then((player) => {
return { ...payload, player } return { ...payload, player }
}).catch((err: any) => { })
if (err.code === 11000) { .catch((err: any) => {
return Promise.reject({ if (err.code === 11000) {
message: `Duplicate key error while creating Player in DB!`, return Promise.reject({
statusCode: 409, message: `Duplicate key error while creating Player in DB!`,
solved: true, statusCode: 409,
type: 'warn' solved: true,
}) type: "warn",
} else {
console.log(err)
return Promise.reject({
message: `Unknown error while creating Player in DB.`,
solved: false
})
}
}) })
} else {
console.log(err)
return Promise.reject({
message: `Unknown error while creating Player in DB.`,
solved: false,
})
}
})
} }
export default createPlayerDB export default createPlayerDB

View file

@ -4,33 +4,39 @@ import { v4 as uuidv4 } from "uuid"
import prisma from "../../prisma" import prisma from "../../prisma"
const tokenLifetime = { const tokenLifetime = {
REFRESH: 172800, REFRESH: 172800,
ACCESS: 15 ACCESS: 15,
}; }
export default async function createTokenDB<T>(payload: T & { player: Player, newTokenType: Token['type'] }) { export default async function createTokenDB<T>(
const { player, newTokenType } = payload payload: T & { player: Player; newTokenType: Token["type"] }
) {
const { player, newTokenType } = payload
// Sign a new access token // Sign a new access token
const newToken = jwt.sign({ uuid: uuidv4(), user: player.id }, process.env.ACCESS_TOKEN_SECRET as string, { expiresIn: tokenLifetime[newTokenType] }) const newToken = jwt.sign(
{ uuid: uuidv4(), user: player.id },
process.env.ACCESS_TOKEN_SECRET as string,
{ expiresIn: tokenLifetime[newTokenType] }
)
// Save token to DB // Save token to DB
const newTokenDB = await prisma.token.create({ const newTokenDB = await prisma.token.create({
data: { data: {
token: newToken, token: newToken,
type: newTokenType, type: newTokenType,
expires: new Date(Date.now() + tokenLifetime[newTokenType] + '000'), expires: new Date(Date.now() + tokenLifetime[newTokenType] + "000"),
owner: { owner: {
connect: { connect: {
id: player.id id: player.id,
} },
} },
} },
}) })
return { return {
...payload, ...payload,
newToken, newToken,
newTokenDB newTokenDB,
} }
} }

View file

@ -1,24 +1,26 @@
import { Token } from "@prisma/client" import { Token } from "@prisma/client"
import prisma from "../../prisma" import prisma from "../../prisma"
export default async function getPlayerByIdDB<T>(payload: T & { tokenDB: Token }) { export default async function getPlayerByIdDB<T>(
const { tokenDB } = payload payload: T & { tokenDB: Token }
// Find Host in DB if it still exists (just to make sure) ) {
const player = await prisma.player.findUnique({ const { tokenDB } = payload
where: { // Find Host in DB if it still exists (just to make sure)
id: tokenDB.ownerId const player = await prisma.player.findUnique({
} where: {
id: tokenDB.ownerId,
},
})
if (!player) {
return Promise.reject({
message: "Player not found in DB!",
statusCode: 401,
solved: false,
}) })
if (!player) { }
return Promise.reject({
message: 'Player not found in DB!',
statusCode: 401,
solved: false
})
}
return { return {
...payload, ...payload,
player player,
} }
} }

View file

@ -1,29 +1,32 @@
import prisma from "../../prisma" import prisma from "../../prisma"
export default async function getPlayerByNameDB<T>(payload: T & { username: string }) { export default async function getPlayerByNameDB<T>(
const { username } = payload payload: T & { username: string }
// Find Player in DB if it still exists (just to make sure) ) {
const player = await Promise.any([ const { username } = payload
prisma.player.findUnique({ // Find Player in DB if it still exists (just to make sure)
where: { const player = await Promise.any([
username: username prisma.player.findUnique({
} where: {
}), prisma.player.findUnique({ username: username,
where: { },
email: username }),
} prisma.player.findUnique({
}) where: {
]) email: username,
if (!player) { },
return Promise.reject({ }),
message: 'Player not found in DB!', ])
statusCode: 401, if (!player) {
solved: false return Promise.reject({
}) message: "Player not found in DB!",
} statusCode: 401,
solved: false,
})
}
return { return {
...payload, ...payload,
player player,
} }
} }

View file

@ -1,52 +1,54 @@
import { NextApiRequest, NextApiResponse } from "next" import { NextApiRequest, NextApiResponse } from "next"
import prisma from "../../prisma" import prisma from "../../prisma"
async function getTokenDB<T>(payload: T & { async function getTokenDB<T>(
payload: T & {
tokenBody: string tokenBody: string
tokenIsValid: boolean, tokenIsValid: boolean
req: NextApiRequest, req: NextApiRequest
res: NextApiResponse<any>, res: NextApiResponse<any>
}) { }
const { tokenBody } = payload ) {
const { tokenBody } = payload
// Find refresh token in DB // Find refresh token in DB
const tokenDB = await prisma.token.findUnique({ const tokenDB = await prisma.token.findUnique({
where: { where: {
token: tokenBody token: tokenBody,
} },
})
if (!tokenDB) {
return Promise.reject({
message: "Access-Token not found in DB!",
statusCode: 401,
solved: true,
type: "warn",
}) })
if (!tokenDB) { }
return Promise.reject({
message: 'Access-Token not found in DB!',
statusCode: 401,
solved: true,
type: 'warn'
})
}
if (tokenDB.used) { if (tokenDB.used) {
return Promise.reject({ return Promise.reject({
message: 'DBToken was already used!', message: "DBToken was already used!",
statusCode: 401, statusCode: 401,
solved: true solved: true,
})
}
await prisma.token.update({
where: {
token: tokenBody
},
data: {
used: true
}
}) })
}
// await logging('Old token has been invalidated.', ['debug'], req) await prisma.token.update({
where: {
token: tokenBody,
},
data: {
used: true,
},
})
return { // await logging('Old token has been invalidated.', ['debug'], req)
...payload,
tokenDB return {
} ...payload,
tokenDB,
}
} }
export default getTokenDB export default getTokenDB

View file

@ -1,19 +1,19 @@
import { NextApiRequest } from "next" import { NextApiRequest } from "next"
async function getTokenFromBody<T>(payload: T & { req: NextApiRequest }) { async function getTokenFromBody<T>(payload: T & { req: NextApiRequest }) {
const { req } = payload const { req } = payload
const token: string = req.body.token const token: string = req.body.token
// Checking for cookie presens, because it is necessary // Checking for cookie presens, because it is necessary
if (!token) { if (!token) {
return Promise.reject({ return Promise.reject({
message: 'Unauthorized. No Access-Token.', message: "Unauthorized. No Access-Token.",
statusCode: 401, statusCode: 401,
solved: true, solved: true,
}) })
} }
return { ...payload, token } return { ...payload, token }
} }
export default getTokenFromBody export default getTokenFromBody

View file

@ -2,19 +2,19 @@ import { Token } from "@prisma/client"
import { NextApiRequest } from "next" import { NextApiRequest } from "next"
async function getTokenFromCookie<T>(payload: T & { req: NextApiRequest }) { async function getTokenFromCookie<T>(payload: T & { req: NextApiRequest }) {
const { req } = payload const { req } = payload
const token = req.cookies.token const token = req.cookies.token
// Checking for cookie presens, because it is necessary // Checking for cookie presens, because it is necessary
if (!token) { if (!token) {
return Promise.reject({ return Promise.reject({
message: 'Unauthorized. No cookie.', message: "Unauthorized. No cookie.",
statusCode: 401, statusCode: 401,
solved: true, solved: true,
}) })
} }
return { ...payload, token, tokenType: 'REFRESH' as Token['type'] } return { ...payload, token, tokenType: "REFRESH" as Token["type"] }
} }
export default getTokenFromCookie export default getTokenFromCookie

View file

@ -1,15 +1,17 @@
import { Token } from "@prisma/client" import { Token } from "@prisma/client"
import { Logging } from "../logging" import { Logging } from "../logging"
export default async function loginCheck<T>(payload: T & { loginCheck: boolean, tokenDB: Token, tokenType: 'REFRESH' }) { export default async function loginCheck<T>(
const { loginCheck, tokenDB } = payload payload: T & { loginCheck: boolean; tokenDB: Token; tokenType: "REFRESH" }
// True login check response ) {
if (loginCheck) { const { loginCheck, tokenDB } = payload
return Promise.resolve({ // True login check response
message: 'loginCheck ' + loginCheck + ' of ' + tokenDB.id, if (loginCheck) {
body: { loggedIn: true }, return Promise.resolve({
type: ['debug', 'info.cyan'] as Logging[] message: "loginCheck " + loginCheck + " of " + tokenDB.id,
}) body: { loggedIn: true },
} type: ["debug", "info.cyan"] as Logging[],
return payload })
} }
return payload
}

View file

@ -2,11 +2,11 @@ import { NextApiRequest, NextApiResponse } from "next"
import logging from "../logging" import logging from "../logging"
export default function sendError<T>( export default function sendError<T>(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<T>, res: NextApiResponse<T>,
err: any err: any
) { ) {
// If something went wrong, let the client know with status 500 // If something went wrong, let the client know with status 500
res.status(err.statusCode ?? 500).end() res.status(err.statusCode ?? 500).end()
logging(err.message, [err.type ?? (err.solved ? 'debug' : 'error')], req) logging(err.message, [err.type ?? (err.solved ? "debug" : "error")], req)
} }

View file

@ -2,21 +2,19 @@ import { NextApiRequest, NextApiResponse } from "next"
import logging, { Logging } from "../logging" import logging, { Logging } from "../logging"
export interface Result<T> { export interface Result<T> {
message: string, message: string
statusCode?: number, statusCode?: number
body?: T, body?: T
type?: Logging[], type?: Logging[]
} }
export default function sendResponse<T>(payload: { export default function sendResponse<T>(payload: {
req: NextApiRequest, req: NextApiRequest
res: NextApiResponse<T>, res: NextApiResponse<T>
result: Result<T> result: Result<T>
}) { }) {
const { req, res, result } = payload const { req, res, result } = payload
res.status(result.statusCode ?? 200) res.status(result.statusCode ?? 200)
result.body ? result.body ? res.json(result.body) : res.end()
res.json(result.body) : logging(result.message, result.type ?? ["debug"], req)
res.end()
logging(result.message, result.type ?? ['debug'], req)
} }

View file

@ -1,21 +1,36 @@
import { Token } from "@prisma/client" import { Token } from "@prisma/client"
export default async function jwtVerifyCatch( export default async function jwtVerifyCatch(
tokenType: Token['type'], tokenType: Token["type"],
err: Error err: Error
) { ) {
switch (err.message) { switch (err.message) {
case 'jwt expired': case "jwt expired":
return { message: `JWT (${tokenType}) expired!`, statusCode: 403, solved: true, type: 'warn' } return {
message: `JWT (${tokenType}) expired!`,
statusCode: 403,
solved: true,
type: "warn",
}
case 'invalid signature': case "invalid signature":
return { message: `Invalid JWT (${tokenType}) signature! Token: `, statusCode: 401, solved: true, type: 'error' } return {
message: `Invalid JWT (${tokenType}) signature! Token: `,
statusCode: 401,
solved: true,
type: "error",
}
case 'jwt must be provided': case "jwt must be provided":
return { message: `No JWT (${tokenType}) given.`, statusCode: 401, solved: true, type: 'warn' } return {
message: `No JWT (${tokenType}) given.`,
statusCode: 401,
solved: true,
type: "warn",
}
default: default:
console.log(err) console.log(err)
return { message: `Unknown error on 'JWT.verify()'.`, solved: false } return { message: `Unknown error on 'JWT.verify()'.`, solved: false }
} }
} }

View file

@ -1,17 +1,17 @@
import fs from 'fs' import fs from "fs"
import colors, { Color } from 'colors' import colors, { Color } from "colors"
import { NextApiRequest } from 'next' import { NextApiRequest } from "next"
import { IncomingMessage } from 'http' import { IncomingMessage } from "http"
colors.enable() colors.enable()
const loggingTemplates: { [key: string]: LoggingType } = { const loggingTemplates: { [key: string]: LoggingType } = {
'system': ['SYSTEM', 'green'], system: ["SYSTEM", "green"],
'info.green': ['INFO', 'green'], "info.green": ["INFO", "green"],
'info.cyan': ['INFO', 'cyan'], "info.cyan": ["INFO", "cyan"],
'debug': ['Debug', 'grey'], debug: ["Debug", "grey"],
'post': ['Post', 'white'], post: ["Post", "white"],
'warn': ['WARN', 'yellow'], warn: ["WARN", "yellow"],
'error': ['ERROR', 'red'] error: ["ERROR", "red"],
} }
type LoggingType = [string, keyof Color] type LoggingType = [string, keyof Color]
@ -20,32 +20,40 @@ export type Logging = keyof typeof loggingTemplates | LoggingType
let started: boolean = false let started: boolean = false
async function logStartup() { async function logStartup() {
await fs.promises.stat('log').catch(async () => { await fs.promises.stat("log").catch(async () => {
await fs.promises.mkdir('log') await fs.promises.mkdir("log")
await logging(`Created 'log' Folder.`, ['info.cyan', 'system']) await logging(`Created 'log' Folder.`, ["info.cyan", "system"])
}) })
started = true started = true
} }
async function logging(message: string, types: Logging[], req?: NextApiRequest | IncomingMessage) { async function logging(
if (!started) message: string,
await logStartup() types: Logging[],
const messages = { console: message, file: message } req?: NextApiRequest | IncomingMessage
types.slice().reverse().forEach(async (type) => { ) {
const [name, color] = typeof type === 'object' ? type : loggingTemplates[type] if (!started) await logStartup()
messages.console = `[${name}] `[color] + messages.console const messages = { console: message, file: message }
messages.file = `[${name}] ` + messages.file types
.slice()
.reverse()
.forEach(async (type) => {
const [name, color] =
typeof type === "object" ? type : loggingTemplates[type]
messages.console = `[${name}] `[color] + messages.console
messages.file = `[${name}] ` + messages.file
}) })
messages.console = `[${new Date().toString().slice(0, 33)}] ` + messages.console messages.console =
messages.file = `[${new Date().toString().slice(0, 33)}] ` + messages.file `[${new Date().toString().slice(0, 33)}] ` + messages.console
if (req) { messages.file = `[${new Date().toString().slice(0, 33)}] ` + messages.file
const forwardedFor: any = req.headers['x-forwarded-for'] if (req) {
const ip = (forwardedFor || '127.0.0.1, 192.168.178.1').split(',') const forwardedFor: any = req.headers["x-forwarded-for"]
messages.console = ip[0].yellow + ' - ' + messages.console const ip = (forwardedFor || "127.0.0.1, 192.168.178.1").split(",")
messages.file = ip[0] + ' - ' + messages.file messages.console = ip[0].yellow + " - " + messages.console
} messages.file = ip[0] + " - " + messages.file
await fs.promises.appendFile('log/log.txt', messages.file + '\n') }
console.log(messages.console) await fs.promises.appendFile("log/log.txt", messages.file + "\n")
console.log(messages.console)
} }
export default logging export default logging

View file

@ -6,18 +6,24 @@ import getTokenDB from "../backend/components/getTokenDB"
import getPlayerByIdDB from "../backend/components/getPlayerByIdDB" import getPlayerByIdDB from "../backend/components/getPlayerByIdDB"
import logging from "../backend/logging" import logging from "../backend/logging"
export default async function checkIsLoggedIn(context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>) { export default async function checkIsLoggedIn(
const req: any = context.req context: GetServerSidePropsContext<ParsedUrlQuery, PreviewData>
const res: any = context.res ) {
const req: any = context.req
const res: any = context.res
const isLoggedIn = await getTokenFromCookie({ req, res }) const isLoggedIn = await getTokenFromCookie({ req, res })
.then(checkTokenIsValid) .then(checkTokenIsValid)
.then(getTokenDB) .then(getTokenDB)
.then(getPlayerByIdDB) .then(getPlayerByIdDB)
.then(({ player }) => !!player) .then(({ player }) => !!player)
.catch(() => false) .catch(() => false)
logging('loginCheck ' + (isLoggedIn ? true : '-> loggedIn: ' + false), ['debug', 'info.cyan'], req) logging(
"loginCheck " + (isLoggedIn ? true : "-> loggedIn: " + false),
["debug", "info.cyan"],
req
)
return isLoggedIn return isLoggedIn
} }

View file

@ -1,7 +1,7 @@
export default function getAccessToken(): Promise<string> { export default function getAccessToken(): Promise<string> {
return fetch('/api/auth', { return fetch("/api/auth", {
method: 'GET', method: "GET",
}) })
.then(res => res.json()) .then((res) => res.json())
.then(res => res.newAccessToken) .then((res) => res.newAccessToken)
} }

View file

@ -1,150 +1,222 @@
import { useCallback, useEffect, useMemo, useReducer, useState } from 'react' import { useCallback, useEffect, useMemo, useReducer, useState } from "react"
import { hitReducer, initlialLastLeftTile, initlialTarget, initlialTargetPreview, initlialMouseCursor } from '../utils/helpers' import {
import { Hit, Mode, MouseCursor, Target, Position } from '../../interfaces/frontend' hitReducer,
import { PointerProps } from '../../components/GamefieldPointer' initlialLastLeftTile,
initlialTarget,
initlialTargetPreview,
initlialMouseCursor,
} from "../utils/helpers"
import {
Hit,
Mode,
MouseCursor,
Target,
Position,
} from "../../interfaces/frontend"
import { PointerProps } from "../../components/GamefieldPointer"
const modes: Mode[] = [ const modes: Mode[] = [
{ pointerGrid: Array.from(Array(3), () => Array.from(Array(3))), type: 'radar' }, {
{ pointerGrid: Array.from(Array(3), () => Array.from(Array(1))), type: 'htorpedo' }, pointerGrid: Array.from(Array(3), () => Array.from(Array(3))),
{ pointerGrid: Array.from(Array(1), () => Array.from(Array(3))), type: 'vtorpedo' }, type: "radar",
{ pointerGrid: [[{ x: 0, y: 0 }]], type: 'missile' } },
{
pointerGrid: Array.from(Array(3), () => Array.from(Array(1))),
type: "htorpedo",
},
{
pointerGrid: Array.from(Array(1), () => Array.from(Array(3))),
type: "vtorpedo",
},
{ pointerGrid: [[{ x: 0, y: 0 }]], type: "missile" },
] ]
function useGameEvent(count: number) { function useGameEvent(count: number) {
const [lastLeftTile, setLastLeftTile] = useState<Position>(initlialLastLeftTile) const [lastLeftTile, setLastLeftTile] =
const [target, setTarget] = useState<Target>(initlialTarget) useState<Position>(initlialLastLeftTile)
const [eventReady, setEventReady] = useState(false) const [target, setTarget] = useState<Target>(initlialTarget)
const [appearOK, setAppearOK] = useState(false) const [eventReady, setEventReady] = useState(false)
const [targetPreview, setTargetPreview] = useState<Target>(initlialTargetPreview) const [appearOK, setAppearOK] = useState(false)
const [mouseCursor, setMouseCursor] = useState<MouseCursor>(initlialMouseCursor) const [targetPreview, setTargetPreview] = useState<Target>(
const [hits, DispatchHits] = useReducer(hitReducer, [] as Hit[]) initlialTargetPreview
const [mode, setMode] = useState(0) )
const [mouseCursor, setMouseCursor] =
useState<MouseCursor>(initlialMouseCursor)
const [hits, DispatchHits] = useReducer(hitReducer, [] as Hit[])
const [mode, setMode] = useState(0)
const targetList = useCallback((target: Position) => { const targetList = useCallback(
const { pointerGrid, type } = modes[mode] (target: Position) => {
const xLength = pointerGrid.length const { pointerGrid, type } = modes[mode]
const yLength = pointerGrid[0].length const xLength = pointerGrid.length
const { x: targetX, y: targetY } = target const yLength = pointerGrid[0].length
return pointerGrid.map((arr, i) => { const { x: targetX, y: targetY } = target
return arr.map((_, i2) => { return pointerGrid
const relativeX = -Math.floor(xLength / 2) + i .map((arr, i) => {
const relativeY = -Math.floor(yLength / 2) + i2 return arr.map((_, i2) => {
const x = targetX + (relativeX ?? 0) const relativeX = -Math.floor(xLength / 2) + i
const y = targetY + (relativeY ?? 0) const relativeY = -Math.floor(yLength / 2) + i2
return { const x = targetX + (relativeX ?? 0)
x, const y = targetY + (relativeY ?? 0)
y,
type,
edges: [
i === 0 ? 'left' : '',
i === xLength - 1 ? 'right' : '',
i2 === 0 ? 'top' : '',
i2 === yLength - 1 ? 'bottom' : '',
]
}
})
})
.reduce((prev, curr) => [...prev, ...curr], [])
}, [mode])
const isHit = useCallback((x: number, y: number) => {
return hits.filter(h => h.x === x && h.y === y)
}, [hits])
const settingTarget = useCallback((isGameTile: boolean, x: number, y: number) => {
if (!isGameTile || isHit(x, y).length)
return
setMouseCursor(e => ({ ...e, shouldShow: false }))
setTarget(t => {
if (t.x === x && t.y === y && t.show) {
DispatchHits({ type: 'fireMissile', payload: { hit: (x + y) % 2 !== 0, x, y } })
return { preview: false, show: false, x, y }
} else {
const target = { preview: false, show: true, x, y }
const hasAnyBorder = targetList(target).filter(({ x, y }) => isBorder(x, y, count)).length
if (hasAnyBorder)
return t
return target
}
})
}, [count, isHit, targetList])
const isSet = useCallback((x: number, y: number) => {
return !!targetList(target).filter(field => x === field.x && y === field.y).length && target.show
}, [target, targetList])
const composeTargetTiles = useCallback((target: Target): PointerProps[] => {
const { preview, show } = target
const result = targetList(target).map(({ x, y, type, edges }) => {
return { return {
preview, x,
x, y,
y, type,
show, edges: [
type, i === 0 ? "left" : "",
edges, i === xLength - 1 ? "right" : "",
imply: !!isHit(x, y).length || (!!isSet(x, y) && preview) i2 === 0 ? "top" : "",
i2 === yLength - 1 ? "bottom" : "",
],
} }
})
}) })
return result .reduce((prev, curr) => [...prev, ...curr], [])
}, [isHit, isSet, targetList]) },
[mode]
)
// handle visibility and position change of targetPreview const isHit = useCallback(
useEffect(() => { (x: number, y: number) => {
const { show, x, y } = targetPreview return hits.filter((h) => h.x === x && h.y === y)
// if mouse has moved too quickly and last event was entering and leaving the same field, it must have gone outside the grid },
const hasLeft = x === lastLeftTile.x && y === lastLeftTile.y [hits]
const isSet = x === target.x && y === target.y && target.show )
if (show && !appearOK) const settingTarget = useCallback(
setTargetPreview(e => ({ ...e, show: false })) (isGameTile: boolean, x: number, y: number) => {
if (!show && mouseCursor.shouldShow && eventReady && appearOK && !isHit(x, y).length && !isSet && !hasLeft) if (!isGameTile || isHit(x, y).length) return
setTargetPreview(e => ({ ...e, show: true })) setMouseCursor((e) => ({ ...e, shouldShow: false }))
}, [targetPreview, mouseCursor.shouldShow, isHit, eventReady, appearOK, lastLeftTile, target]) setTarget((t) => {
if (t.x === x && t.y === y && t.show) {
// enable targetPreview event again after 200 ms. DispatchHits({
useEffect(() => { type: "fireMissile",
setEventReady(false) payload: { hit: (x + y) % 2 !== 0, x, y },
const previewTarget = { x: mouseCursor.x, y: mouseCursor.y } })
const hasAnyBorder = targetList(previewTarget).filter(({ x, y }) => isBorder(x, y, count)).length return { preview: false, show: false, x, y }
if (targetPreview.show || !appearOK || hasAnyBorder) } else {
return const target = { preview: false, show: true, x, y }
const autoTimeout = setTimeout(() => { const hasAnyBorder = targetList(target).filter(({ x, y }) =>
setTargetPreview(e => ({ ...e, ...previewTarget })) isBorder(x, y, count)
setEventReady(true) ).length
setAppearOK(true) if (hasAnyBorder) return t
}, 300) return target
// or abort if state has changed early
return () => {
clearTimeout(autoTimeout)
} }
}, [appearOK, count, mouseCursor.x, mouseCursor.y, targetList, targetPreview.show]) })
},
[count, isHit, targetList]
)
// approve targetPreview new position after 200 mil. sec. const isSet = useCallback(
useEffect(() => { (x: number, y: number) => {
// early return to start cooldown only when about to show up return (
const autoTimeout = setTimeout(() => { !!targetList(target).filter((field) => x === field.x && y === field.y)
setAppearOK(!targetPreview.show) .length && target.show
}, targetPreview.show ? 500 : 300) )
},
[target, targetList]
)
// or abort if movement is repeated early const composeTargetTiles = useCallback(
return () => { (target: Target): PointerProps[] => {
clearTimeout(autoTimeout) const { preview, show } = target
const result = targetList(target).map(({ x, y, type, edges }) => {
return {
preview,
x,
y,
show,
type,
edges,
imply: !!isHit(x, y).length || (!!isSet(x, y) && preview),
} }
}, [targetPreview.show]) })
return result
},
[isHit, isSet, targetList]
)
return { // handle visibility and position change of targetPreview
tilesProps: { count, settingTarget, setMouseCursor, setLastLeftTile }, useEffect(() => {
pointersProps: { composeTargetTiles, target, targetPreview }, const { show, x, y } = targetPreview
targetsProps: { setMode, setTarget }, // if mouse has moved too quickly and last event was entering and leaving the same field, it must have gone outside the grid
hits const hasLeft = x === lastLeftTile.x && y === lastLeftTile.y
const isSet = x === target.x && y === target.y && target.show
if (show && !appearOK) setTargetPreview((e) => ({ ...e, show: false }))
if (
!show &&
mouseCursor.shouldShow &&
eventReady &&
appearOK &&
!isHit(x, y).length &&
!isSet &&
!hasLeft
)
setTargetPreview((e) => ({ ...e, show: true }))
}, [
targetPreview,
mouseCursor.shouldShow,
isHit,
eventReady,
appearOK,
lastLeftTile,
target,
])
// enable targetPreview event again after 200 ms.
useEffect(() => {
setEventReady(false)
const previewTarget = { x: mouseCursor.x, y: mouseCursor.y }
const hasAnyBorder = targetList(previewTarget).filter(({ x, y }) =>
isBorder(x, y, count)
).length
if (targetPreview.show || !appearOK || hasAnyBorder) return
const autoTimeout = setTimeout(() => {
setTargetPreview((e) => ({ ...e, ...previewTarget }))
setEventReady(true)
setAppearOK(true)
}, 300)
// or abort if state has changed early
return () => {
clearTimeout(autoTimeout)
} }
}, [
appearOK,
count,
mouseCursor.x,
mouseCursor.y,
targetList,
targetPreview.show,
])
// approve targetPreview new position after 200 mil. sec.
useEffect(() => {
// early return to start cooldown only when about to show up
const autoTimeout = setTimeout(
() => {
setAppearOK(!targetPreview.show)
},
targetPreview.show ? 500 : 300
)
// or abort if movement is repeated early
return () => {
clearTimeout(autoTimeout)
}
}, [targetPreview.show])
return {
tilesProps: { count, settingTarget, setMouseCursor, setLastLeftTile },
pointersProps: { composeTargetTiles, target, targetPreview },
targetsProps: { setMode, setTarget },
hits,
}
} }
function isBorder(x: number, y: number, count: number) { function isBorder(x: number, y: number, count: number) {
return x < 2 || x > count + 1 || y < 2 || y > count + 1 return x < 2 || x > count + 1 || y < 2 || y > count + 1
} }
export default useGameEvent export default useGameEvent

View file

@ -1,15 +1,15 @@
// lib/prisma.ts // lib/prisma.ts
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from "@prisma/client"
let prisma: PrismaClient; let prisma: PrismaClient
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient(); prisma = new PrismaClient()
} else { } else {
if (!global.prismaClient) { if (!global.prismaClient) {
global.prismaClient = new PrismaClient(); global.prismaClient = new PrismaClient()
} }
prisma = global.prismaClient; prisma = global.prismaClient
} }
export default prisma; export default prisma

View file

@ -1,60 +1,51 @@
import { Hit, HitDispatch } from "../../interfaces/frontend" import { Hit, HitDispatch } from "../../interfaces/frontend"
export function borderCN(count: number, x: number, y: number) { export function borderCN(count: number, x: number, y: number) {
if (x === 0) if (x === 0) return "left"
return 'left' if (y === 0) return "top"
if (y === 0) if (x === count + 1) return "right"
return 'top' if (y === count + 1) return "bottom"
if (x === count + 1) return ""
return 'right'
if (y === count + 1)
return 'bottom'
return ''
} }
export function cornerCN(count: number, x: number, y: number) { export function cornerCN(count: number, x: number, y: number) {
if (x === 0 && y === 0) if (x === 0 && y === 0) return "left-top-corner"
return 'left-top-corner' if (x === count + 1 && y === 0) return "right-top-corner"
if (x === count + 1 && y === 0) if (x === 0 && y === count + 1) return "left-bottom-corner"
return 'right-top-corner' if (x === count + 1 && y === count + 1) return "right-bottom-corner"
if (x === 0 && y === count + 1) return ""
return 'left-bottom-corner'
if (x === count + 1 && y === count + 1)
return 'right-bottom-corner'
return ''
} }
export function fieldIndex(count: number, x: number, y: number) { export function fieldIndex(count: number, x: number, y: number) {
return y * (count + 2) + x return y * (count + 2) + x
} }
export function hitReducer(formObject: Hit[], action: HitDispatch) { export function hitReducer(formObject: Hit[], action: HitDispatch) {
switch (action.type) { switch (action.type) {
case "fireMissile": {
case 'fireMissile': { const result = [...formObject, action.payload]
const result = [...formObject, action.payload] return result
return result
}
default:
return formObject
} }
default:
return formObject
}
} }
export const initlialLastLeftTile = { export const initlialLastLeftTile = {
x: 0, x: 0,
y: 0 y: 0,
} }
export const initlialTarget = { export const initlialTarget = {
preview: false, preview: false,
show: false, show: false,
x: 2, x: 2,
y: 2 y: 2,
} }
export const initlialTargetPreview = { export const initlialTargetPreview = {
preview: true, preview: true,
show: false, show: false,
x: 2, x: 2,
y: 2 y: 2,
} }
export const initlialMouseCursor = { export const initlialMouseCursor = {
shouldShow: false, shouldShow: false,
x: 0, x: 0,
y: 0 y: 0,
} }

View file

@ -51,4 +51,4 @@
"sass": "^1.57.1", "sass": "^1.57.1",
"tailwindcss": "^3.2.4" "tailwindcss": "^3.2.4"
} }
} }

View file

@ -1,9 +1,9 @@
import '../styles/App.scss' import "../styles/App.scss"
import '../styles/grid.scss' import "../styles/grid.scss"
import '../styles/grid2.scss' import "../styles/grid2.scss"
import '../styles/homepage.scss' import "../styles/homepage.scss"
import '../styles/globals.css' import "../styles/globals.css"
import type { AppProps } from 'next/app' import type { AppProps } from "next/app"
export default function App({ Component, pageProps }: AppProps) { export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} /> return <Component {...pageProps} />

View file

@ -1,4 +1,4 @@
import { Html, Head, Main, NextScript } from 'next/document' import { Html, Head, Main, NextScript } from "next/document"
export default function Document() { export default function Document() {
return ( return (

View file

@ -10,40 +10,48 @@ import { Logging } from "../../lib/backend/logging"
import { Token } from "@prisma/client" import { Token } from "@prisma/client"
interface Data { interface Data {
token: string token: string
} }
export default async function auth( export default async function auth(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<Data> res: NextApiResponse<Data>
) { ) {
return getTokenFromCookie({ req, res, newTokenType: 'ACCESS' as Token['type'] }) return getTokenFromCookie({
.then(checkTokenIsValid) req,
.then(getTokenDB) res,
.then(getPlayerByIdDB) newTokenType: "ACCESS" as Token["type"],
.then(createTokenDB) })
.then(authResponse<Data>) .then(checkTokenIsValid)
.then(sendResponse<Data>) .then(getTokenDB)
.catch(err => sendError(req, res, err)) .then(getPlayerByIdDB)
.then(createTokenDB)
.then(authResponse<Data>)
.then(sendResponse<Data>)
.catch((err) => sendError(req, res, err))
} }
async function authResponse<T>(payload: { async function authResponse<T>(payload: {
newToken: string, newToken: string
newTokenDB: Token, newTokenDB: Token
tokenDB: Token, tokenDB: Token
req: NextApiRequest, req: NextApiRequest
res: NextApiResponse<T> res: NextApiResponse<T>
}) { }) {
const { newToken, newTokenDB, tokenDB, req, res } = payload const { newToken, newTokenDB, tokenDB, req, res } = payload
// Successfull response // Successfull response
return { return {
req, req,
res, res,
result: { result: {
message: 'Access-Token generated: ' + newTokenDB.id + ' with Refreshtoken-Token: ' + tokenDB.id, message:
body: { token: newToken }, "Access-Token generated: " +
type: ['debug', 'info.cyan'] as Logging[] newTokenDB.id +
} " with Refreshtoken-Token: " +
} tokenDB.id,
} body: { token: newToken },
type: ["debug", "info.cyan"] as Logging[],
},
}
}

View file

@ -9,40 +9,44 @@ import { Logging } from "../../lib/backend/logging"
import { Game, Player, Token } from "@prisma/client" import { Game, Player, Token } from "@prisma/client"
interface Data { interface Data {
games: Game[] games: Game[]
} }
export default async function data( export default async function data(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<Data> res: NextApiResponse<Data>
) { ) {
return getTokenFromBody({ req, res, tokenType: 'ACCESS' as Token['type'] }) return getTokenFromBody({ req, res, tokenType: "ACCESS" as Token["type"] })
.then(checkTokenIsValid) .then(checkTokenIsValid)
.then(getTokenDB) .then(getTokenDB)
.then(getPlayerByIdDB) .then(getPlayerByIdDB)
.then(dataResponse<Data>) .then(dataResponse<Data>)
.then(sendResponse<Data>) .then(sendResponse<Data>)
.catch(err => sendError(req, res, err)) .catch((err) => sendError(req, res, err))
} }
async function dataResponse<T>(payload: { async function dataResponse<T>(payload: {
player: Player, player: Player
tokenDB: Token, tokenDB: Token
// games: Game[], // games: Game[],
req: NextApiRequest, req: NextApiRequest
res: NextApiResponse<T> res: NextApiResponse<T>
}) { }) {
const { player, tokenDB, req, res } = payload const { player, tokenDB, req, res } = payload
const games: any = {} const games: any = {}
// Successfull response // Successfull response
return { return {
req, req,
res, res,
result: { result: {
message: 'Requested data of user: ' + player.id + ' with Access-Token: ' + tokenDB.id, message:
body: { games }, "Requested data of user: " +
type: ['debug', 'info.cyan'] as Logging[] player.id +
} " with Access-Token: " +
} tokenDB.id,
} body: { games },
type: ["debug", "info.cyan"] as Logging[],
},
}
}

View file

@ -10,77 +10,89 @@ import { Player, Token } from "@prisma/client"
import prisma from "../../lib/prisma" import prisma from "../../lib/prisma"
interface Data { interface Data {
loggedIn: boolean loggedIn: boolean
} }
export default async function login( export default async function login(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<any> res: NextApiResponse<any>
) { ) {
const { username, password } = req.body const { username, password } = req.body
return preCheck({ req, res, username, password, newTokenType: 'REFRESH' as Token['type'] }) return preCheck({
.then(getPlayerByNameDB) req,
.then(checkPasswordIsValid) res,
.then(createTokenDB) username,
.then(loginResponse<Data>) password,
.then(sendResponse<Data>) newTokenType: "REFRESH" as Token["type"],
.catch(err => sendError(req, res, err)) })
.then(getPlayerByNameDB)
.then(checkPasswordIsValid)
.then(createTokenDB)
.then(loginResponse<Data>)
.then(sendResponse<Data>)
.catch((err) => sendError(req, res, err))
} }
async function preCheck<T>(payload: T & { async function preCheck<T>(
req: NextApiRequest, payload: T & {
req: NextApiRequest
res: NextApiResponse<T> res: NextApiResponse<T>
}) { }
const { req } = payload ) {
const oldRefreshToken = req.cookies.token const { req } = payload
// Check for old cookie, if unused invalidate it const oldRefreshToken = req.cookies.token
const oldDBToken = await prisma.token.findUnique({ // Check for old cookie, if unused invalidate it
where: { const oldDBToken = await prisma.token.findUnique({
token: oldRefreshToken where: {
} token: oldRefreshToken,
},
})
if (oldDBToken?.used) {
await prisma.token.update({
where: {
token: oldRefreshToken,
},
data: {
used: true,
},
}) })
if (oldDBToken?.used) { await logging("Old token has been invalidated.", ["debug"], req)
await prisma.token.update({ }
where: { return { ...payload, noCookiePresent: true }
token: oldRefreshToken
},
data: {
used: true
}
})
await logging('Old token has been invalidated.', ['debug'], req)
}
return { ...payload, noCookiePresent: true }
} }
async function loginResponse<T>(payload: { async function loginResponse<T>(payload: {
player: Player, player: Player
passwordIsValid: boolean, passwordIsValid: boolean
refreshToken: string, refreshToken: string
refreshTokenDB: Token, refreshTokenDB: Token
req: NextApiRequest, req: NextApiRequest
res: NextApiResponse<T> res: NextApiResponse<T>
}) { }) {
const { player, refreshToken, refreshTokenDB, req, res } = payload const { player, refreshToken, refreshTokenDB, req, res } = payload
// Set login cookie // Set login cookie
setCookie('token', refreshToken, { setCookie("token", refreshToken, {
req, req,
res, res,
maxAge: 172800000, maxAge: 172800000,
httpOnly: true, httpOnly: true,
sameSite: true, sameSite: true,
secure: true, secure: true,
}) })
// Successfull response // Successfull response
return { return {
req, req,
res, res,
result: { result: {
message: 'User ' + player.id + ' logged in and generated Refresh-Token: ' + refreshTokenDB.id, message:
body: { loggedIn: true }, "User " +
type: ['debug', 'info.cyan'] as Logging[] player.id +
} " logged in and generated Refresh-Token: " +
} refreshTokenDB.id,
} body: { loggedIn: true },
type: ["debug", "info.cyan"] as Logging[],
},
}
}

View file

@ -9,39 +9,39 @@ import getTokenFromCookie from "../../lib/backend/components/getTokenFromCookie"
import logging, { Logging } from "../../lib/backend/logging" import logging, { Logging } from "../../lib/backend/logging"
interface Data { interface Data {
loggedOut: boolean loggedOut: boolean
} }
export default async function logout( export default async function logout(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<any> res: NextApiResponse<any>
) { ) {
return getTokenFromCookie({ req, res }) return getTokenFromCookie({ req, res })
.then(checkTokenIsValid) .then(checkTokenIsValid)
.then(getTokenDB) .then(getTokenDB)
.then(logoutResponse<Data>) .then(logoutResponse<Data>)
.then(sendResponse<Data>) .then(sendResponse<Data>)
.catch(err => sendError(req, res, err)) .catch((err) => sendError(req, res, err))
} }
async function logoutResponse<T>(payload: { async function logoutResponse<T>(payload: {
tokenDB: Token, tokenDB: Token
req: NextApiRequest, req: NextApiRequest
res: NextApiResponse<T> res: NextApiResponse<T>
}) { }) {
const { tokenDB, req, res } = payload const { tokenDB, req, res } = payload
// Set login cookie // Set login cookie
deleteCookie('token', { req, res }) deleteCookie("token", { req, res })
// Successfull response // Successfull response
return { return {
req, req,
res, res,
result: { result: {
message: 'User of Token ' + tokenDB.id + ' logged out.', message: "User of Token " + tokenDB.id + " logged out.",
body: { loggedOut: true }, body: { loggedOut: true },
type: ['debug', 'info.cyan'] as Logging[] type: ["debug", "info.cyan"] as Logging[],
} },
} }
} }

View file

@ -6,36 +6,36 @@ import { Logging } from "../../lib/backend/logging"
import { Player } from "@prisma/client" import { Player } from "@prisma/client"
interface Data { interface Data {
registered: boolean registered: boolean
} }
export default async function register( export default async function register(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse<any> res: NextApiResponse<any>
) { ) {
const { username, password } = req.body const { username, password } = req.body
return createPlayerDB({ req, res, username, password }) return createPlayerDB({ req, res, username, password })
.then(registerResponse<Data>) .then(registerResponse<Data>)
.then(sendResponse<Data>) .then(sendResponse<Data>)
.catch(err => sendError(req, res, err)) .catch((err) => sendError(req, res, err))
} }
async function registerResponse<T>(payload: { async function registerResponse<T>(payload: {
player: Player, player: Player
req: NextApiRequest, req: NextApiRequest
res: NextApiResponse<T> res: NextApiResponse<T>
}) { }) {
const { player, req, res } = payload const { player, req, res } = payload
// Successfull response // Successfull response
return { return {
req, req,
res, res,
result: { result: {
message: 'Player created : ' + player.id, message: "Player created : " + player.id,
statusCode: 201, statusCode: 201,
body: { registered: true }, body: { registered: true },
type: ['debug', 'info.cyan'] as Logging[] type: ["debug", "info.cyan"] as Logging[],
} },
} }
} }

View file

@ -1,18 +1,18 @@
import type { NextApiRequest } from 'next' import type { NextApiRequest } from "next"
import { Server } from 'socket.io' import { Server } from "socket.io"
import { NextApiResponseWithSocket } from '../../interfaces/NextApiSocket' import { NextApiResponseWithSocket } from "../../interfaces/NextApiSocket"
const SocketHandler = (req: NextApiRequest, res: NextApiResponseWithSocket) => { const SocketHandler = (req: NextApiRequest, res: NextApiResponseWithSocket) => {
if (res.socket.server.io) { if (res.socket.server.io) {
console.log('Socket is already running ' + req.url) console.log("Socket is already running " + req.url)
} else { } else {
console.log('Socket is initializing ' + req.url) console.log("Socket is initializing " + req.url)
const io = new Server(res.socket.server) const io = new Server(res.socket.server)
res.socket.server.io = io res.socket.server.io = io
io.on('connection', socket => { io.on("connection", (socket) => {
socket.on('input-change', msg => { socket.on("input-change", (msg) => {
socket.broadcast.emit('update-input', msg) socket.broadcast.emit("update-input", msg)
}) })
// console.log(socket.id) // console.log(socket.id)
// console.log(socket) // console.log(socket)
@ -23,10 +23,10 @@ const SocketHandler = (req: NextApiRequest, res: NextApiResponseWithSocket) => {
// ... // ...
}) })
socket.emit('test2', 'lol') socket.emit("test2", "lol")
}) })
} }
res.end() res.end()
} }
export default SocketHandler export default SocketHandler

View file

@ -1,5 +1,5 @@
import Head from 'next/head' import Head from "next/head"
import Gamefield from '../../components/Gamefield' import Gamefield from "../../components/Gamefield"
export default function Home() { export default function Home() {
return ( return (

View file

@ -1,5 +1,5 @@
import Head from 'next/head' import Head from "next/head"
import Grid from '../../components/Grid' import Grid from "../../components/Grid"
export default function Home() { export default function Home() {
return ( return (

View file

@ -1,5 +1,5 @@
import Head from 'next/head' import Head from "next/head"
import Grid2 from '../../components/Grid2' import Grid2 from "../../components/Grid2"
export default function Home() { export default function Home() {
return ( return (

View file

@ -1,48 +1,52 @@
import { faPlus, faUserPlus } from '@fortawesome/pro-solid-svg-icons' import { faPlus, faUserPlus } from "@fortawesome/pro-solid-svg-icons"
import { faEye, faLeftLong } from '@fortawesome/pro-regular-svg-icons' import { faEye, faLeftLong } from "@fortawesome/pro-regular-svg-icons"
import { faCirclePlay } from '@fortawesome/pro-thin-svg-icons' import { faCirclePlay } from "@fortawesome/pro-thin-svg-icons"
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { useState } from 'react' import { useState } from "react"
export default function Home() { export default function Home() {
const [heWantsToPlay, setHeWantsToPlay] = useState(false) const [heWantsToPlay, setHeWantsToPlay] = useState(false)
return ( return (
<div id='box'> <div id="box">
<button id='navExpand'> <button id="navExpand">
<img id='burgerMenu' src='/assets/burger-menu.png' alt='Burger Menu' /> <img id="burgerMenu" src="/assets/burger-menu.png" alt="Burger Menu" />
</button>
<div id="shield">
<div id="width">
<h1>Leaky</h1>
<h1>Ships</h1>
</div>
</div>
{!heWantsToPlay ? (
<>
<div id="videoWrapper">
<FontAwesomeIcon icon={faCirclePlay} />
</div>
<button id="startButton" onClick={() => setHeWantsToPlay(true)}>
START
</button>
</>
) : (
<div id="startBox">
<button id="back" onClick={() => setHeWantsToPlay(false)}>
<FontAwesomeIcon icon={faLeftLong} />
</button>
<div id="sameWidth">
<button className="optionButton">
<span>Raum erstellen</span>
<FontAwesomeIcon icon={faPlus} />
</button> </button>
<div id='shield'> <button className="optionButton">
<div id='width'> <span>Raum beitreten</span>
<h1>Leaky</h1> <FontAwesomeIcon icon={faUserPlus} />
<h1>Ships</h1> </button>
</div> <button className="optionButton">
</div> <span>Zuschauen</span>
{!heWantsToPlay ? <FontAwesomeIcon icon={faEye} />
<> </button>
<div id='videoWrapper' > </div>
<FontAwesomeIcon icon={faCirclePlay} /> </div>
</div> )}
<button id='startButton' onClick={() => setHeWantsToPlay(true)}>START</button> </div>
</> : )
<div id='startBox'>
<button id='back' onClick={() => setHeWantsToPlay(false)}>
<FontAwesomeIcon icon={faLeftLong} />
</button>
<div id='sameWidth'>
<button className='optionButton'>
<span>Raum erstellen</span>
<FontAwesomeIcon icon={faPlus} />
</button>
<button className='optionButton'>
<span>Raum beitreten</span>
<FontAwesomeIcon icon={faUserPlus} />
</button>
<button className='optionButton'>
<span>Zuschauen</span>
<FontAwesomeIcon icon={faEye} />
</button>
</div>
</div>}
</div >
)
} }

View file

@ -1,30 +1,30 @@
import { ChangeEventHandler, useEffect, useState } from 'react' import { ChangeEventHandler, useEffect, useState } from "react"
import { io } from 'socket.io-client' import { io } from "socket.io-client"
let socket: ReturnType<typeof io> let socket: ReturnType<typeof io>
const Home = () => { const Home = () => {
const [input, setInput] = useState('') const [input, setInput] = useState("")
useEffect(() => { useEffect(() => {
socketInitializer() socketInitializer()
}, []) }, [])
const socketInitializer = async () => { const socketInitializer = async () => {
await fetch('/api/ws') await fetch("/api/ws")
socket = io() socket = io()
socket.on('connect', () => { socket.on("connect", () => {
console.log('connected') console.log("connected")
}) })
socket.on('update-input', msg => { socket.on("update-input", (msg) => {
setInput(msg) setInput(msg)
}) })
} }
const onChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => { const onChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
setInput(e.target.value) setInput(e.target.value)
socket.emit('input-change', e.target.value) socket.emit("input-change", e.target.value)
} }
return ( return (
@ -36,4 +36,4 @@ const Home = () => {
) )
} }
export default Home export default Home

View file

@ -1,4 +1,4 @@
import SocketIO from '../../components/SocketIO' import SocketIO from "../../components/SocketIO"
export default function Home() { export default function Home() {
return ( return (

View file

@ -1,5 +1,5 @@
import Head from 'next/head' import Head from "next/head"
import Link from 'next/link' import Link from "next/link"
export default function Home() { export default function Home() {
return ( return (
@ -11,12 +11,36 @@ export default function Home() {
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<main> <main>
<p><Link href='/dev/gamefield' target='_blank'>Gamefield</Link></p> <p>
<p><Link href='/dev' target='_blank'>Homepage</Link></p> <Link href="/dev/gamefield" target="_blank">
<p><Link href='/dev/grid' target='_blank'>Grid Effect</Link></p> Gamefield
<p><Link href='/dev/grid2' target='_blank'>Grid Effect with Content</Link></p> </Link>
<p><Link href='/dev/socket' target='_blank'>Socket</Link></p> </p>
<p><Link href='/dev/socketio' target='_blank'>SocketIO</Link></p> <p>
<Link href="/dev" target="_blank">
Homepage
</Link>
</p>
<p>
<Link href="/dev/grid" target="_blank">
Grid Effect
</Link>
</p>
<p>
<Link href="/dev/grid2" target="_blank">
Grid Effect with Content
</Link>
</p>
<p>
<Link href="/dev/socket" target="_blank">
Socket
</Link>
</p>
<p>
<Link href="/dev/socketio" target="_blank">
SocketIO
</Link>
</p>
</main> </main>
</> </>
) )

View file

@ -1,6 +1,6 @@
@use './mixins/display' as *; @use "./mixins/display" as *;
@use './mixins/effects' as *; @use "./mixins/effects" as *;
@import './mixins/variables'; @import "./mixins/variables";
html, html,
body, body,
@ -10,8 +10,8 @@ body,
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif; sans-serif;
} }
@ -69,15 +69,15 @@ body {
display: grid; display: grid;
align-items: center; align-items: center;
justify-items: center; justify-items: center;
grid-template-rows: .75fr repeat(12, 1fr) .75fr; grid-template-rows: 0.75fr repeat(12, 1fr) 0.75fr;
grid-template-columns: .75fr repeat(12, 1fr) .75fr; grid-template-columns: 0.75fr repeat(12, 1fr) 0.75fr;
>.label { > .label {
grid-column: var(--x); grid-column: var(--x);
grid-row: var(--y); grid-row: var(--y);
} }
>.border-tile { > .border-tile {
box-sizing: border-box; box-sizing: border-box;
border: 1px solid blue; border: 1px solid blue;
height: 100%; height: 100%;
@ -96,7 +96,7 @@ body {
box-sizing: border-box; box-sizing: border-box;
} }
>span { > span {
vertical-align: center; vertical-align: center;
user-select: none; user-select: none;
} }
@ -172,7 +172,7 @@ body {
&.preview { &.preview {
opacity: 0; opacity: 0;
@include transition(.5s); @include transition(0.5s);
} }
&.radar { &.radar {
@ -184,7 +184,7 @@ body {
&:not(.left):not(.right):not(.top):not(.bottom) { &:not(.left):not(.right):not(.top):not(.bottom) {
// border: 5px solid var(--color); // border: 5px solid var(--color);
grid-area: var(--y1) /var(--x1) /var(--y2) /var(--x2); grid-area: var(--y1) / var(--x1) / var(--y2) / var(--x2);
svg { svg {
opacity: 1; opacity: 1;
@ -207,7 +207,7 @@ body {
opacity: 1; opacity: 1;
} }
&.imply>svg { &.imply > svg {
opacity: 0; opacity: 0;
} }
@ -300,7 +300,7 @@ body {
.item { .item {
@include flex-col; @include flex-col;
align-items: center; align-items: center;
gap: .5rem; gap: 0.5rem;
width: 128px; width: 128px;
.container { .container {
@ -332,8 +332,8 @@ body {
span { span {
color: black; color: black;
font-size: .75em; font-size: 0.75em;
font-weight: bold; font-weight: bold;
} }
} }
} }

View file

@ -1,3 +1,3 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;

View file

@ -1,36 +1,34 @@
@use './mixins/effects' as *; @use "./mixins/effects" as *;
#tiles { #tiles {
height: 100vh; height: 100vh;
width: 100vw; width: 100vw;
display: grid; display: grid;
grid-template-columns: repeat(var(--columns), 1fr); grid-template-columns: repeat(var(--columns), 1fr);
grid-template-rows: repeat(var(--rows), 1fr); grid-template-rows: repeat(var(--rows), 1fr);
.tile { .tile {
background-color: var(--bg-color-1); background-color: var(--bg-color-1);
&.active { &.active {
animation: bright .3s forwards; animation: bright 0.3s forwards;
animation-delay: var(--delay); animation-delay: var(--delay);
}
} }
}
} }
@keyframes bright { @keyframes bright {
0% { 0% {
background-color: var(--bg-color-1); background-color: var(--bg-color-1);
} }
50% { 50% {
background-color: var(--bg-color-2);
filter: brightness(130%);
outline: 1px solid white;
}
background-color: var(--bg-color-2); 100% {
filter: brightness(130%); background-color: var(--bg-color-2);
outline: 1px solid white; }
} }
100% {
background-color: var(--bg-color-2);
}
}

View file

@ -1,94 +1,94 @@
@use './mixins/effects' as *; @use "./mixins/effects" as *;
$g1: rgb(98, 0, 234); $g1: rgb(98, 0, 234);
$g2: rgb(236, 64, 122); $g2: rgb(236, 64, 122);
#tiles { #tiles {
height: 100vh;
width: 100vw;
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
grid-template-rows: repeat(var(--rows), 1fr);
background: linear-gradient(to right, $g1, $g2, $g1);
background-size: 200%;
animation: background-pan 10s linear infinite;
.tile {
position: relative;
&::before {
position: absolute;
content: "";
background-color: rgb(20, 20, 20);
inset: 1px;
}
&.active {
opacity: 1;
animation: hide 0.2s forwards;
animation-delay: var(--delay);
}
&.inactive {
opacity: 0;
animation: show 0.2s forwards;
animation-delay: var(--delay);
}
}
.center-div {
position: absolute;
height: 100vh; height: 100vh;
width: 100vw; width: 100vw;
display: grid; display: inherit;
grid-template-columns: repeat(var(--columns), 1fr);
grid-template-rows: repeat(var(--rows), 1fr);
background: linear-gradient(to right, $g1, $g2, $g1);
background-size: 200%;
animation: background-pan 10s linear infinite;
.tile { .headline {
position: relative; margin: auto;
font-size: 5em;
background-color: rgba(20, 20, 20, 0.2);
padding: 0.25em 0.5em;
border-radius: 0.25em;
&::before { &.active {
position: absolute; opacity: 1;
content: ''; animation: hide 2s forwards;
background-color: rgb(20, 20, 20); // animation-delay: 1s;
inset: 1px; }
}
&.active { &.inactive {
opacity: 1; opacity: 0;
animation: hide .2s forwards; animation: show 2s forwards;
animation-delay: var(--delay); // animation-delay: 1s;
} }
&.inactive {
opacity: 0;
animation: show .2s forwards;
animation-delay: var(--delay);
}
}
.center-div {
position: absolute;
height: 100vh;
width: 100vw;
display: inherit;
.headline {
margin: auto;
font-size: 5em;
background-color: rgba(20, 20, 20, 0.2);
padding: .25em .5em;
border-radius: .25em;
&.active {
opacity: 1;
animation: hide 2s forwards;
// animation-delay: 1s;
}
&.inactive {
opacity: 0;
animation: show 2s forwards;
// animation-delay: 1s;
}
}
} }
}
} }
@keyframes background-pan { @keyframes background-pan {
from { from {
background-position: 0% center; background-position: 0% center;
} }
to { to {
background-position: -200% center; background-position: -200% center;
} }
} }
@keyframes hide { @keyframes hide {
0% { 0% {
opacity: 1; opacity: 1;
} }
100% { 100% {
opacity: 0; opacity: 0;
} }
} }
@keyframes show { @keyframes show {
0% { 0% {
opacity: 0; opacity: 0;
} }
100% { 100% {
opacity: 1; opacity: 1;
} }
} }

View file

@ -1,338 +1,330 @@
@use './mixins/display' as *; @use "./mixins/display" as *;
@use './mixins/effects' as *; @use "./mixins/effects" as *;
@use './mixins/CP_Font' as *; @use "./mixins/CP_Font" as *;
@import './mixins/variables'; @import "./mixins/variables";
@import url("https://fonts.googleapis.com/css2?family=Farro:wght@300;400;500;700&display=swap");
@import url('https://fonts.googleapis.com/css2?family=Farro:wght@300;400;500;700&display=swap');
#box { #box {
min-height: 100%; min-height: 100%;
background-color: #282c34; background-color: #282c34;
@include flex-col; @include flex-col;
align-items: center;
justify-content: space-around;
#navExpand {
@include flex;
align-items: center; align-items: center;
justify-content: space-around; justify-content: center;
position: absolute;
top: 64px;
left: 64px;
width: 96px;
height: 96px;
background-color: $grayish;
border-radius: 8px;
box-shadow: 0 0 2px 2px #0008 inset;
#navExpand { #burgerMenu {
@include flex; @include pixelart;
align-items: center; height: 84px;
justify-content: center; width: 84px;
position: absolute; }
top: 64px; }
left: 64px;
width: 96px;
height: 96px;
background-color: $grayish;
border-radius: 8px;
box-shadow: 0 0 2px 2px #0008 inset;
#burgerMenu { #shield {
@include pixelart; @include flex-row;
height: 84px; justify-content: center;
width: 84px; height: 250px;
width: 700px;
background-image: url("/assets/shield.png");
background-repeat: no-repeat;
background-size: contain;
#width {
@include flex-col;
justify-content: space-between;
h1 {
@include CP_Font;
margin: 3%;
width: 100%;
text-align: center;
font-size: 5.8em;
letter-spacing: 6px;
&:first-child {
border-top: 5px solid black;
} }
&:last-child {
border-bottom: 5px solid black;
}
}
}
}
#videoWrapper {
position: relative;
display: flex;
align-items: center;
justify-content: center;
height: 350px;
width: 700px;
background-color: #2227;
border: 4px solid black;
border-radius: 8px;
svg {
font-size: 6em;
color: #231f20;
path {
stroke: black;
stroke-width: 2px;
stroke-linejoin: round;
}
}
}
#startButton {
font-family: "Farro", sans-serif;
font-weight: bold;
font-size: 3em;
color: black;
background-color: $warn;
border: none;
border-radius: 8px;
padding: calc(2rem + 8px) 6rem 2rem 6rem;
}
#startBox {
display: flex;
flex-direction: column;
align-items: center;
background-color: $grayish;
box-shadow: 0 0 2px 2px #fffb inset, 0 0 2px 2px #fff2;
border: 4px solid black;
padding: 3rem;
border-radius: 8px;
min-width: 60vw;
gap: 2rem;
#back {
font-size: 3em;
color: $grayish;
align-self: flex-start;
background-color: #000c;
border: none;
border-radius: 8px;
padding: 0 0.5rem;
margin-top: -1.5rem;
width: 10rem;
box-shadow: 0 0 2px 2px #fff6 inset;
border: 2px solid #000c;
} }
#shield { #sameWidth {
display: flex;
flex-direction: column;
align-items: center;
gap: 3rem;
.optionButton {
@include flex-row; @include flex-row;
justify-content: center; justify-content: space-between;
height: 250px;
width: 700px;
background-image: url("/assets/shield.png");
background-repeat: no-repeat;
background-size: contain;
#width {
@include flex-col;
justify-content: space-between;
h1 {
@include CP_Font;
margin: 3%;
width: 100%;
text-align: center;
font-size: 5.8em;
letter-spacing: 6px;
&:first-child {
border-top: 5px solid black;
}
&:last-child {
border-bottom: 5px solid black;
}
}
}
}
#videoWrapper {
position: relative;
display: flex;
align-items: center; align-items: center;
justify-content: center; font-size: 2.5em;
height: 350px; color: $grayish;
width: 700px; background-color: #000c;
background-color: #2227;
border: 4px solid black;
border-radius: 8px;
svg {
font-size: 6em;
color: #231f20;
path {
stroke: black;
stroke-width: 2px;
stroke-linejoin: round;
}
}
}
#startButton {
font-family: 'Farro', sans-serif;
font-weight: bold;
font-size: 3em;
color: black;
background-color: $warn;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
padding: calc(2rem + 8px) 6rem 2rem 6rem; padding: 1rem 2rem 1rem 4rem;
} width: 100%;
box-shadow: 0 0 2px 2px #fff6 inset;
border: 2px solid #000c;
#startBox { &:last-child {
display: flex; margin-top: 2rem;
flex-direction: column;
align-items: center;
background-color: $grayish;
box-shadow: 0 0 2px 2px #fffb inset, 0 0 2px 2px #fff2;
border: 4px solid black;
padding: 3rem;
border-radius: 8px;
min-width: 60vw;
gap: 2rem;
#back {
font-size: 3em;
color: $grayish;
align-self: flex-start;
background-color: #000C;
border: none;
border-radius: 8px;
padding: 0 .5rem;
margin-top: -1.5rem;
width: 10rem;
box-shadow: 0 0 2px 2px #fff6 inset;
border: 2px solid #000C;
} }
#sameWidth { span {
display: flex; margin: 0 auto;
flex-direction: column;
align-items: center;
gap: 3rem;
.optionButton {
@include flex-row;
justify-content: space-between;
align-items: center;
font-size: 2.5em;
color: $grayish;
background-color: #000C;
border: none;
border-radius: 8px;
padding: 1rem 2rem 1rem 4rem;
width: 100%;
box-shadow: 0 0 2px 2px #fff6 inset;
border: 2px solid #000C;
&:last-child {
margin-top: 2rem;
}
span {
margin: 0 auto;
}
svg {
margin-left: 3rem;
font-size: 2em;
width: 2.5rem;
height: inherit;
}
}
} }
svg {
margin-left: 3rem;
font-size: 2em;
width: 2.5rem;
height: inherit;
}
}
} }
}
} }
@media (max-width: 2000px) { @media (max-width: 2000px) {
#box { #box {
#shield { #shield {
height: 200px; height: 200px;
width: 560px; width: 560px;
#width { #width {
h1 {
font-size: 4.5em;
letter-spacing: 4px;
h1 { &:first-child {
font-size: 4.5em; border-top: 4px solid black;
letter-spacing: 4px; }
&:first-child { &:last-child {
border-top: 4px solid black; border-bottom: 4px solid black;
} }
&:last-child {
border-bottom: 4px solid black;
}
}
}
} }
}
} }
}
} }
@media (max-width: 1000px) { @media (max-width: 1000px) {
#box { #box {
#navExpand { #navExpand {
top: 32px; top: 32px;
left: 32px; left: 32px;
width: 80px; width: 80px;
height: 80px; height: 80px;
#burgerMenu { #burgerMenu {
height: 64px; height: 64px;
width: 64px; width: 64px;
} }
}
#shield {
height: 160px;
width: 450px;
#width {
h1 {
font-size: 3.6em;
letter-spacing: 3px;
&:first-child {
border-top: 3px solid black;
}
&:last-child {
border-bottom: 3px solid black;
}
}
}
}
#videoWrapper {
height: 300px;
width: 600px;
}
} }
#shield {
height: 160px;
width: 450px;
#width {
h1 {
font-size: 3.6em;
letter-spacing: 3px;
&:first-child {
border-top: 3px solid black;
}
&:last-child {
border-bottom: 3px solid black;
}
}
}
}
#videoWrapper {
height: 300px;
width: 600px;
}
}
} }
@media (max-width: 700px) { @media (max-width: 700px) {
#box { #box {
justify-content: space-evenly; justify-content: space-evenly;
#navExpand { #navExpand {
top: 16px; top: 16px;
left: 16px; left: 16px;
width: 64px; width: 64px;
height: 64px; height: 64px;
#burgerMenu { #burgerMenu {
height: 44px; height: 44px;
width: 44px; width: 44px;
} }
}
#shield {
height: 100px;
width: 280px;
#width {
h1 {
font-size: 2.2em;
letter-spacing: 2px;
&:first-child {
border-top: 2px solid black;
}
&:last-child {
border-bottom: 2px solid black;
}
}
}
}
#videoWrapper {
height: 250px;
width: 450px;
svg {
font-size: 4em;
}
}
#startButton {
font-size: 2em;
padding: calc(1rem + 8px) 3rem 1rem 3rem;
}
#startBox {
max-width: 90vw;
padding: 2rem;
#back {
margin-top: -1rem;
font-size: 2em;
width: 7rem;
padding: .125rem;
}
#sameWidth {
max-width: 100%;
.optionButton {
font-size: 2em;
padding: 1rem 2rem;
svg {
margin-left: 2rem;
font-size: 1.5em;
width: 2rem;
}
}
}
}
} }
#shield {
height: 100px;
width: 280px;
#width {
h1 {
font-size: 2.2em;
letter-spacing: 2px;
&:first-child {
border-top: 2px solid black;
}
&:last-child {
border-bottom: 2px solid black;
}
}
}
}
#videoWrapper {
height: 250px;
width: 450px;
svg {
font-size: 4em;
}
}
#startButton {
font-size: 2em;
padding: calc(1rem + 8px) 3rem 1rem 3rem;
}
#startBox {
max-width: 90vw;
padding: 2rem;
#back {
margin-top: -1rem;
font-size: 2em;
width: 7rem;
padding: 0.125rem;
}
#sameWidth {
max-width: 100%;
.optionButton {
font-size: 2em;
padding: 1rem 2rem;
svg {
margin-left: 2rem;
font-size: 1.5em;
width: 2rem;
}
}
}
}
}
} }
@media (max-width: 500px) { @media (max-width: 500px) {
#box { #box {
#videoWrapper { #videoWrapper {
height: 150px; height: 150px;
width: 300px; width: 300px;
}
#startBox {
#sameWidth {
.optionButton {
font-size: 1.5em;
svg {
margin-left: 1rem;
font-size: 1em;
}
}
}
}
} }
}
#startBox {
#sameWidth {
.optionButton {
font-size: 1.5em;
svg {
margin-left: 1rem;
font-size: 1em;
}
}
}
}
}
}

View file

@ -1,9 +1,8 @@
@font-face { @font-face {
font-family: "CP_Font"; font-family: "CP_Font";
src: url("/fonts/cpfont_ote/CP Font.otf") format("opentype"); src: url("/fonts/cpfont_ote/CP Font.otf") format("opentype");
} }
@mixin CP_Font { @mixin CP_Font {
font-family: "CP_Font", sans-serif; font-family: "CP_Font", sans-serif;
} }

View file

@ -1,11 +1,11 @@
@mixin flex { @mixin flex {
display: flex; display: flex;
} }
@mixin flex-col { @mixin flex-col {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@mixin flex-row { @mixin flex-row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
} }

View file

@ -1,61 +1,157 @@
@mixin transition($timing) { @mixin transition($timing) {
-moz-transition: $timing; -moz-transition: $timing;
-webkit-transition: $timing; -webkit-transition: $timing;
-o-transition: $timing; -o-transition: $timing;
-ms-transition: $timing; -ms-transition: $timing;
transition: $timing; transition: $timing;
} }
@mixin appear($timing) { @mixin appear($timing) {
animation: appear $timing forwards; animation: appear $timing forwards;
} }
@keyframes appear { @keyframes appear {
From { from {
opacity: 0; opacity: 0;
} }
To { to {
opacity: 1; opacity: 1;
} }
} }
@mixin gradient-edge { @mixin gradient-edge {
&.left-top-corner { &.left-top-corner {
mask-image: -webkit-gradient(linear, right bottom, left top, color-stop(0, rgba(0, 0, 0, 1)), color-stop(0.5, rgba(0, 0, 0, 0))); mask-image: -webkit-gradient(
-webkit-mask-image: -webkit-gradient(linear, right bottom, left top, color-stop(0, rgba(0, 0, 0, 1)), color-stop(0.5, rgba(0, 0, 0, 0))); linear,
} right bottom,
&.right-top-corner { left top,
mask-image: -webkit-gradient(linear, left bottom, right top, color-stop(0, rgba(0, 0, 0, 1)), color-stop(0.5, rgba(0, 0, 0, 0))); color-stop(0, rgba(0, 0, 0, 1)),
-webkit-mask-image: -webkit-gradient(linear, left bottom, right top, color-stop(0, rgba(0, 0, 0, 1)), color-stop(0.5, rgba(0, 0, 0, 0))); color-stop(0.5, rgba(0, 0, 0, 0))
} );
&.left-bottom-corner { -webkit-mask-image: -webkit-gradient(
mask-image: -webkit-gradient(linear, right top, left bottom, color-stop(0, rgba(0, 0, 0, 1)), color-stop(0.5, rgba(0, 0, 0, 0))); linear,
-webkit-mask-image: -webkit-gradient(linear, right top, left bottom, color-stop(0, rgba(0, 0, 0, 1)), color-stop(0.5, rgba(0, 0, 0, 0))); right bottom,
} left top,
&.right-bottom-corner { color-stop(0, rgba(0, 0, 0, 1)),
mask-image: -webkit-gradient(linear, left top, right bottom, color-stop(0, rgba(0, 0, 0, 1)), color-stop(0.5, rgba(0, 0, 0, 0))); color-stop(0.5, rgba(0, 0, 0, 0))
-webkit-mask-image: -webkit-gradient(linear, left top, right bottom, color-stop(0, rgba(0, 0, 0, 1)), color-stop(0.5, rgba(0, 0, 0, 0))); );
} }
&.right-top-corner {
mask-image: -webkit-gradient(
linear,
left bottom,
right top,
color-stop(0, rgba(0, 0, 0, 1)),
color-stop(0.5, rgba(0, 0, 0, 0))
);
-webkit-mask-image: -webkit-gradient(
linear,
left bottom,
right top,
color-stop(0, rgba(0, 0, 0, 1)),
color-stop(0.5, rgba(0, 0, 0, 0))
);
}
&.left-bottom-corner {
mask-image: -webkit-gradient(
linear,
right top,
left bottom,
color-stop(0, rgba(0, 0, 0, 1)),
color-stop(0.5, rgba(0, 0, 0, 0))
);
-webkit-mask-image: -webkit-gradient(
linear,
right top,
left bottom,
color-stop(0, rgba(0, 0, 0, 1)),
color-stop(0.5, rgba(0, 0, 0, 0))
);
}
&.right-bottom-corner {
mask-image: -webkit-gradient(
linear,
left top,
right bottom,
color-stop(0, rgba(0, 0, 0, 1)),
color-stop(0.5, rgba(0, 0, 0, 0))
);
-webkit-mask-image: -webkit-gradient(
linear,
left top,
right bottom,
color-stop(0, rgba(0, 0, 0, 1)),
color-stop(0.5, rgba(0, 0, 0, 0))
);
}
&.left { &.left {
mask-image: -webkit-gradient(linear, right top, left top, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0))); mask-image: -webkit-gradient(
-webkit-mask-image: -webkit-gradient(linear, right top, left top, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0))); linear,
} right top,
&.right { left top,
mask-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0))); from(rgba(0, 0, 0, 1)),
-webkit-mask-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0))); to(rgba(0, 0, 0, 0))
} );
&.top { -webkit-mask-image: -webkit-gradient(
mask-image: -webkit-gradient(linear, left bottom, left top, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0))); linear,
-webkit-mask-image: -webkit-gradient(linear, left bottom, left top, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0))); right top,
} left top,
&.bottom { from(rgba(0, 0, 0, 1)),
mask-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0))); to(rgba(0, 0, 0, 0))
-webkit-mask-image: -webkit-gradient(linear, left top, left bottom, from(rgba(0, 0, 0, 1)), to(rgba(0, 0, 0, 0))); );
} }
&.right {
mask-image: -webkit-gradient(
linear,
left top,
right top,
from(rgba(0, 0, 0, 1)),
to(rgba(0, 0, 0, 0))
);
-webkit-mask-image: -webkit-gradient(
linear,
left top,
right top,
from(rgba(0, 0, 0, 1)),
to(rgba(0, 0, 0, 0))
);
}
&.top {
mask-image: -webkit-gradient(
linear,
left bottom,
left top,
from(rgba(0, 0, 0, 1)),
to(rgba(0, 0, 0, 0))
);
-webkit-mask-image: -webkit-gradient(
linear,
left bottom,
left top,
from(rgba(0, 0, 0, 1)),
to(rgba(0, 0, 0, 0))
);
}
&.bottom {
mask-image: -webkit-gradient(
linear,
left top,
left bottom,
from(rgba(0, 0, 0, 1)),
to(rgba(0, 0, 0, 0))
);
-webkit-mask-image: -webkit-gradient(
linear,
left top,
left bottom,
from(rgba(0, 0, 0, 1)),
to(rgba(0, 0, 0, 0))
);
}
} }
// Pixelart sharpness (no interpolation) // Pixelart sharpness (no interpolation)
// https://css-tricks.com/keep-pixelated-images-pixelated-as-they-scale/ // https://css-tricks.com/keep-pixelated-images-pixelated-as-they-scale/
@mixin pixelart { @mixin pixelart {
image-rendering: pixelated; image-rendering: pixelated;
image-rendering: -moz-crisp-edges; image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges; image-rendering: crisp-edges;
} }

View file

@ -1,3 +1,3 @@
$theme: #282c34; $theme: #282c34;
$grayish: #B1B2B5CC; $grayish: #b1b2b5cc;
$warn: #fabd04; $warn: #fabd04;

View file

@ -8,4 +8,4 @@ module.exports = {
extend: {}, extend: {},
}, },
plugins: [], plugins: [],
} }