Formatted all files with Prettier
This commit is contained in:
parent
0bc2196d9f
commit
ea80456a56
64 changed files with 2209 additions and 1839 deletions
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"semi": false
|
"semi": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
2
leaky-ships/global.d.ts
vendored
2
leaky-ships/global.d.ts
vendored
|
@ -1,3 +1,3 @@
|
||||||
declare module globalThis {
|
declare module globalThis {
|
||||||
var prismaClient: PrismaClient
|
var prismaClient: PrismaClient
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
}
|
||||||
}
|
})
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,4 +51,4 @@
|
||||||
"sass": "^1.57.1",
|
"sass": "^1.57.1",
|
||||||
"tailwindcss": "^3.2.4"
|
"tailwindcss": "^3.2.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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[],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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[],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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[],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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[],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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[],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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 (
|
||||||
|
|
|
@ -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 >
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import SocketIO from '../../components/SocketIO'
|
import SocketIO from "../../components/SocketIO"
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -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>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
$theme: #282c34;
|
$theme: #282c34;
|
||||||
$grayish: #B1B2B5CC;
|
$grayish: #b1b2b5cc;
|
||||||
$warn: #fabd04;
|
$warn: #fabd04;
|
||||||
|
|
|
@ -8,4 +8,4 @@ module.exports = {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue