diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 50c0815..06dcf31 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,12 +1,23 @@ import { faBurst, faCrosshairs, faXmark } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { CSSProperties, useState } from 'react'; +import { CSSProperties, useEffect, useState } from 'react'; +import { TargetPreviewType } from './interfaces'; import './styles/App.scss'; function App() { const [target, setTarget] = useState({ - event: false, + show: false, + x: 0, + y: 0 + }) + const [targetPreview, setTargetPreview] = useState({ + newX: 0, + newY: 0, + shouldShow: false, + appearOK: false, + eventReady: true, + show: false, x: 0, y: 0 }) @@ -43,38 +54,101 @@ function App() {
); + const corner = (x: number, y: number, count: number) => {switch (true) { + case x === 0 && y === 0: + return 'left-top-corner'; + case x === count+1 && y === 0: + return 'right-top-corner'; + case x === 0 && y === count+1: + return 'left-bottom-corner'; + case x === count+1 && y === count+1: + return 'right-bottom-corner'; + + default: + return ''; + }}; + const border = (x: number, y: number, count: number) => {switch (true) { + case x === 0: + return 'left'; + case y === 0: + return 'top'; + case x === count+1: + return 'right'; + case y === count+1: + return 'bottom'; + + default: + return ''; + }}; for (let y = 0; y < count+2; y++) { for (let x = 0; x < count+2; x++) { - const corner = [ - x === 0 && y === 0 ? 'left-top-corner' : '', - x === count+1 && y === 0 ? 'right-top-corner' : '', - x === 0 && y === count+1 ? 'left-bottom-corner' : '', - x === count+1 && y === count+1 ? 'right-bottom-corner' : '' - ].filter(s => s); - const border = [ - x === 0 ? 'left' : '', - y === 0 ? 'top' : '', - x === count+1 ? 'right' : '', - y === count+1 ? 'bottom' : '' - ].filter(s => s); - const borderType = corner.length ? corner[0] : border[0]; - const action = x > 0 && x < count+1 && y > 0 && y < count+1; - const classNames = [ - 'border-tile', - borderType ? `edge ${borderType}` : '', - action ? 'action' : '' - ].join(' ') + const cornerReslt = corner(x, y, count); + const borderType = cornerReslt ? cornerReslt : border(x, y, count); + const isGameTile = x > 0 && x < count+1 && y > 0 && y < count+1; + const classNames = ['border-tile']; + if (borderType) + classNames.push('edge', borderType); + if (isGameTile) + classNames.push('game-tile'); borderTiles.push(
setTarget({ event: true, x, y }) : () => {}} + onClick={() => { + if (isGameTile) + setTarget({ show: true, x, y }); + }} + onMouseEnter={() => setTargetPreview(e => ({...e, newX: x, newY: y, shouldShow: isGameTile}))} >
) } } + // handle visibility and position change of targetPreview + useEffect(() => { + const {newX, newY, shouldShow, appearOK, eventReady, show, x, y} = targetPreview; + const positionChange = !(x === newX && y === newY); + // if not ready or no new position + if (!eventReady || !positionChange) + return; + if (show) { + // hide preview to change position when hidden + setTargetPreview(e => ({...e, appearOK: false, eventReady: false, show: false})); + } else if (shouldShow && appearOK) { + // BUT only appear again if it's supposed to (in case the mouse left over the edge) and () + setTargetPreview(e => ({...e, appearOK: false, eventReady: false, show: true, x: newX, y: newY})); + } + }, [targetPreview]) + + // enable targetPreview event again after 200 mil. sec. + useEffect(() => { + if (targetPreview.eventReady) + return; + const autoTimeout = setTimeout(() => { + setTargetPreview(e => ({...e, eventReady: true})); + }, 200); + + // or abort if state has changed early + return () => { + clearTimeout(autoTimeout); + } + }, [targetPreview.eventReady]); + + // approve targetPreview new position after 200 mil. sec. + useEffect(() => { + // early return to start cooldown only when about to show up + if (!targetPreview.shouldShow) + return; + const autoTimeout = setTimeout(() => { + setTargetPreview(e => ({...e, appearOK: true})); + }, 350); + + // or abort if movement is repeated early + return () => { + clearTimeout(autoTimeout); + } + }, [targetPreview.shouldShow, targetPreview.newX, targetPreview.newY]); for (let i = 1; i <= 6; i++) { shipElems.push( @@ -103,10 +177,10 @@ function App() { {/* {`fog1.png`} {`fog1.png`} {`fog4.png`} */} -
+
-
+
diff --git a/frontend/src/interfaces.ts b/frontend/src/interfaces.ts new file mode 100644 index 0000000..3b45114 --- /dev/null +++ b/frontend/src/interfaces.ts @@ -0,0 +1,10 @@ +export interface TargetPreviewType { + newX: number, + newY: number, + shouldShow: boolean, + appearOK: boolean, + eventReady: boolean, + show: boolean, + x: number, + y: number +} \ No newline at end of file diff --git a/frontend/src/styles/App.scss b/frontend/src/styles/App.scss index 1db4acb..d7b0d9c 100644 --- a/frontend/src/styles/App.scss +++ b/frontend/src/styles/App.scss @@ -36,7 +36,7 @@ #game-frame { // border: 1px solid orange; - position: relative; + // position: relative; $height: 945px; $width: $height; height: $height; @@ -63,73 +63,7 @@ &.edge { border: 1px solid gray; } - - &.left-top-corner { - -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))); - } - &.right-top-corner { - -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 { - -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 { - -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 { - -webkit-mask-image: -webkit-gradient(linear, right top, - left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0))); - } - &.right { - -webkit-mask-image: -webkit-gradient(linear, left top, - right top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0))); - } - &.top { - -webkit-mask-image: -webkit-gradient(linear, left bottom, - left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0))); - } - &.bottom { - -webkit-mask-image: -webkit-gradient(linear, left top, - left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0))); - } - - $point-size: 1; - $count: 12; - $grid-elem-width: $count + 2; - $warst: $count + 1.5; - $one: calc($height / $warst); - $elements-in-grid: $grid-elem-width * $grid-elem-width; - - // Inspired by https://stackoverflow.com/questions/35990505/get-position-of-mouse-pointer-in-css-without-javascript - @for $i from 1 through $elements-in-grid { - &:nth-child(#{$i}) { - &:hover { - &:not(.edge) ~ .target-preview { - opacity: 1; - } - & ~ .target-preview { - $row: ceil(calc($i / $grid-elem-width)); - top: ( - ($row - 2) - * $one - + ($one * 0.75) - ); - left: ( - ($i - - (($row - 1) * $grid-elem-width) - - 2) - * $one - + ($one * 0.75) - ); - } - } - } - } + @include gradient-edge; } > :not(.border) { box-sizing: border-box; @@ -212,24 +146,15 @@ &.target { color: red; opacity: 0; - - &.show { - opacity: 1; - } } &.target-preview { color: orange; opacity: 0; - position: absolute; @include transition(.2s); - - $count: 12; - height: calc($height / ($count + 1.5)); - width: calc($height / ($count + 1.5)); - &.show { - opacity: 1; - } + } + &.show { + opacity: 1; } } .r2 { diff --git a/frontend/src/styles/mixins/effects.scss b/frontend/src/styles/mixins/effects.scss index d4be8f2..287995a 100644 --- a/frontend/src/styles/mixins/effects.scss +++ b/frontend/src/styles/mixins/effects.scss @@ -15,4 +15,39 @@ To { opacity: 1; } +} +@mixin gradient-edge { + &.left-top-corner { + -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))); + } + &.right-top-corner { + -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 { + -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 { + -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 { + -webkit-mask-image: -webkit-gradient(linear, right top, + left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0))); + } + &.right { + -webkit-mask-image: -webkit-gradient(linear, left top, + right top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0))); + } + &.top { + -webkit-mask-image: -webkit-gradient(linear, left bottom, + left top, from(rgba(0,0,0,1)), to(rgba(0,0,0,0))); + } + &.bottom { + -webkit-mask-image: -webkit-gradient(linear, left top, + left bottom, from(rgba(0,0,0,1)), to(rgba(0,0,0,0))); + } } \ No newline at end of file