This commit is contained in:
Maciej Caderek
2022-04-10 21:35:52 +02:00
parent 32c362b0d0
commit fc0746923c
25 changed files with 209 additions and 43 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@@ -83,6 +83,14 @@ class Board {
this.updateConfig(config, false); this.updateConfig(config, false);
} }
async setCanvas(canvas: HTMLCanvasElement) {
this.canvas = canvas;
this.ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
this.setSize(this.cfg.size);
await this.refresh();
}
async updateConfig(config: Partial<BoardConfig>, refresh: boolean = true) { async updateConfig(config: Partial<BoardConfig>, refresh: boolean = true) {
const cfg = { ...this.cfg, ...config }; const cfg = { ...this.cfg, ...config };

View File

@@ -1,6 +1,7 @@
import { Header, Style } from "../../types"; import { Header, Style } from "../../types";
import drawRectangle from "./drawRectangle"; import drawRectangle from "./drawRectangle";
import drawText from "./drawText"; import drawText from "./drawText";
import loadImage from "../loaders/loadImage";
const drawHeader = async ( const drawHeader = async (
ctx: CanvasRenderingContext2D, ctx: CanvasRenderingContext2D,
@@ -84,6 +85,25 @@ const drawHeader = async (
fromTop += line; fromTop += line;
}); });
const logo = await loadImage("/img/logo-full.svg");
const logoHeight = size * 0.114 * 0.5;
ctx.shadowColor = "rgba(0, 0, 0, 1)";
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = 10 * scale;
ctx.drawImage(
logo,
size / 4,
margin + size + margin - logoHeight * 2,
size / 2,
logoHeight
);
ctx.shadowColor = "rgba(0, 0, 0, 0)";
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}; };
export default drawHeader; export default drawHeader;

View File

@@ -24,7 +24,7 @@ const style: Style = {
}, },
moveIndicator: { moveIndicator: {
type: "color", type: "color",
data: "#3cff0055", data: "#ffaa0055",
}, },
border: { border: {
type: "gradient", type: "gradient",

View File

@@ -24,7 +24,7 @@ const style: Style = {
}, },
moveIndicator: { moveIndicator: {
type: "color", type: "color",
data: "#3cff0055", data: "#00ffff55",
}, },
border: { border: {
type: "gradient", type: "gradient",

View File

@@ -24,7 +24,7 @@ const style: Style = {
}, },
moveIndicator: { moveIndicator: {
type: "color", type: "color",
data: "#3cff0055", data: "#ffaa0055",
}, },
border: { border: {
type: "gradient", type: "gradient",

View File

@@ -24,7 +24,7 @@ const style: Style = {
}, },
moveIndicator: { moveIndicator: {
type: "color", type: "color",
data: "#00ffee55", data: "#ffff0055",
}, },
border: { border: {
type: "gradient", type: "gradient",

View File

@@ -24,7 +24,7 @@ const style: Style = {
}, },
moveIndicator: { moveIndicator: {
type: "color", type: "color",
data: "#3cff0055", data: "#ff00ff55",
}, },
border: { border: {
type: "gradient", type: "gradient",

View File

@@ -24,7 +24,7 @@ const style: Style = {
}, },
moveIndicator: { moveIndicator: {
type: "color", type: "color",
data: "#00ffee55", data: "#00ffff55",
}, },
border: { border: {
type: "gradient", type: "gradient",

View File

@@ -24,7 +24,7 @@ const style: Style = {
}, },
moveIndicator: { moveIndicator: {
type: "color", type: "color",
data: "#3cff0055", data: "#ffaa0055",
}, },
border: { border: {
type: "gradient", type: "gradient",

View File

@@ -23,7 +23,7 @@ const style: Style = {
}, },
moveIndicator: { moveIndicator: {
type: "color", type: "color",
data: "#ffff0055", data: "#ff000044",
}, },
border: { border: {
type: "solid", type: "solid",

View File

@@ -23,7 +23,7 @@ const style: Style = {
}, },
moveIndicator: { moveIndicator: {
type: "color", type: "color",
data: "#ffff0055", data: "#00ffff55",
}, },
border: { border: {
type: "solid", type: "solid",

View File

@@ -0,0 +1,28 @@
const importToLichess = async (pgn: string, site: string | null) => {
if (site && /^https:\/\/lichess\.org\/.{8}/.test(site)) {
return site;
}
const queryString = new URLSearchParams({ pgn }).toString();
try {
const res = await fetch("https://lichess.org/api/import", {
headers: {
"content-type": "application/x-www-form-urlencoded",
},
body: queryString,
method: "POST",
});
if (res.status === 200) {
const data = await res.json();
return data.url;
} else {
throw new Error("Cannot import to lichess");
}
} catch (e) {
throw new Error("Cannot import to lichess");
}
};
export default importToLichess;

View File

@@ -22,6 +22,7 @@ import isPGN from "./utils/isPGN";
import isSafeLink from "./utils/isSafeLink"; import isSafeLink from "./utils/isSafeLink";
import { PiecesStyle } from "./board/styles-pieces/piecesStyles"; import { PiecesStyle } from "./board/styles-pieces/piecesStyles";
import link from "./persistance/link"; import link from "./persistance/link";
import importToLichess from "./imports/importToLichess";
const main = async () => { const main = async () => {
const board = new Board(state.boardConfig); const board = new Board(state.boardConfig);
@@ -232,8 +233,27 @@ const main = async () => {
setState("boardConfig", "speech", !state.boardConfig.speech); setState("boardConfig", "speech", !state.boardConfig.speech);
saveConfig("board"); saveConfig("board");
}, },
async openOnLichess() {
if (state.pgn === "") {
window.open(
`https://lichess.org/analysis/${state.fen.replace(/\s+/g, "_")}`
);
return true;
}
try {
const url = await importToLichess(state.pgn, state.game.header.Site);
window.open(`${url}/${state.boardConfig.flipped ? "black" : ""}`);
return true;
} catch (e) {
return false;
}
},
}; };
// @ts-ignore
window.handlers = handlers;
/* Render the page */ /* Render the page */
render( render(
@@ -241,8 +261,10 @@ const main = async () => {
document.getElementById("root") as HTMLElement document.getElementById("root") as HTMLElement
); );
const $board = document.querySelector<HTMLImageElement>("#board"); const canvas = document.getElementById("canvas") as HTMLCanvasElement;
$board?.prepend(board.canvas);
await board.setCanvas(canvas);
await player.init();
/* Load game from the url */ /* Load game from the url */

View File

@@ -15,12 +15,18 @@ class Player {
private speech: Speech; private speech: Speech;
public playing: boolean = false; public playing: boolean = false;
private firstRender: Promise<void>; // private firstRender: Promise<void>;
constructor(private board: Board, private config: GameConfig) { constructor(private board: Board, private config: GameConfig) {
this.speech = new Speech(); this.speech = new Speech();
this.firstRender = this.board // this.firstRender = this.board
// .frame(this.game.getPosition(0), this.game.header)
// .then((_) => this.board.render());
}
async init() {
await this.board
.frame(this.game.getPosition(0), this.game.header) .frame(this.game.getPosition(0), this.game.header)
.then((_) => this.board.render()); .then((_) => this.board.render());
} }
@@ -43,7 +49,7 @@ class Player {
async load(game: Game) { async load(game: Game) {
this.pause(); this.pause();
await this.firstRender; // await this.firstRender;
this.game = game; this.game = game;
this.ply = 0; this.ply = 0;

View File

@@ -175,6 +175,7 @@ export type Handlers = {
downloadAnimation: () => Promise<void>; downloadAnimation: () => Promise<void>;
toggleSound(): void; toggleSound(): void;
toggleSpeech(): void; toggleSpeech(): void;
openOnLichess: () => Promise<boolean>;
}; };
export type Header = { export type Header = {

View File

@@ -94,6 +94,11 @@ button:hover,
cursor: pointer; cursor: pointer;
} }
.btn--error,
.btn--error:hover {
background-color: #fc4444;
}
input, input,
textarea { textarea {
width: 100%; width: 100%;
@@ -155,6 +160,10 @@ a:hover {
padding: var(--header-margin) 0 2rem 0; padding: var(--header-margin) 0 2rem 0;
} }
.board-box--left {
padding-left: 2rem;
}
.board { .board {
box-shadow: 0 0 2rem #00000099; box-shadow: 0 0 2rem #00000099;
border-radius: 5px; border-radius: 5px;

View File

@@ -23,7 +23,14 @@ const App: Component<{ handlers: Handlers; state: DeepReadonly<State> }> = (
<SetupTabs handlers={props.handlers}></SetupTabs> <SetupTabs handlers={props.handlers}></SetupTabs>
</div> </div>
</Show> </Show>
<div id="board" class="board-box"> <div
id="board"
classList={{
"board-box": true,
"board-box--left": state.layout === "double",
}}
>
<canvas class="board" id="canvas"></canvas>
<Show when={state.layout === "single"}> <Show when={state.layout === "single"}>
<Controls handlers={props.handlers} /> <Controls handlers={props.handlers} />
</Show> </Show>

View File

@@ -5,7 +5,7 @@
.controls { .controls {
background: var(--color-bg-block); background: var(--color-bg-block);
padding: 2rem; padding: 2rem 1rem;
border-bottom-left-radius: 5px; border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px; border-bottom-right-radius: 5px;
} }
@@ -85,3 +85,9 @@
max-height: 6rem; max-height: 6rem;
} }
} }
@media (orientation: landscape) and (max-height: 400px) {
.controls {
padding-top: 0;
}
}

View File

@@ -9,7 +9,7 @@
.game { .game {
height: 100%; height: 100%;
display: grid; display: grid;
grid-template-rows: 3.8rem 19.5rem 1fr 8.4rem; grid-template-rows: 3.8rem 22rem auto 8.4rem;
} }
.game-tabs { .game-tabs {
@@ -42,3 +42,9 @@
display: block; display: block;
} }
} }
@media (orientation: landscape) and (max-height: 400px) {
.game {
grid-template-rows: 3.8rem 1fr 6.4rem;
}
}

View File

@@ -5,9 +5,10 @@
.info { .info {
background: var(--color-bg-block); background: var(--color-bg-block);
padding: 3rem 2rem; padding: 2rem;
font-size: 1.4rem; font-size: 1.4rem;
text-align: left; text-align: left;
position: relative;
} }
.info__players { .info__players {
@@ -65,3 +66,10 @@
color: var(--color-text); color: var(--color-text);
padding: 0 1rem; padding: 0 1rem;
} }
.info__analyze {
position: absolute;
bottom: 2rem;
width: 100%;
padding-right: 4rem;
}

View File

@@ -1,11 +1,13 @@
import { Component, Show } from "solid-js"; import { Component, Show, createSignal } from "solid-js";
import { Handlers } from "../../types"; import { Handlers } from "../../types";
import { state } from "../../state"; import { state } from "../../state";
import "./Info.css"; import "./Info.css";
import isSafeLink from "../../utils/isSafeLink"; import isSafeLink from "../../utils/isSafeLink";
import isLink from "../../utils/isLink"; import isLink from "../../utils/isLink";
const Info: Component<{ handlers: Handlers }> = () => { const Info: Component<{ handlers: Handlers }> = (props) => {
const [error, setError] = createSignal(false);
return ( return (
<div class="info"> <div class="info">
<div className="info__players"> <div className="info__players">
@@ -83,22 +85,26 @@ const Info: Component<{ handlers: Handlers }> = () => {
</Show> </Show>
</p> </p>
</Show> </Show>
<Show when={state.pgn === ""}>
<p>
<a
href={`https://lichess.org/analysis/${state.fen.replace(
/\s+/g,
"_"
)}`}
>
Analyze on Lichess
</a>
</p>
</Show>
<Show when={state.game.header.DatePretty}> <Show when={state.game.header.DatePretty}>
<p>{state.game.header.DatePretty}</p> <p>{state.game.header.DatePretty}</p>
</Show> </Show>
</div> </div>
<div className="info__analyze">
<button
onClick={async () => {
const success = await props.handlers.openOnLichess();
if (!success) {
setError(true);
setTimeout(() => setError(false), 1000);
}
}}
classList={{ "btn--error": error() }}
>
<i className="las la-vial"></i>{" "}
{error() ? "Cannot import to lichess" : "Analyze on Lichess"}
</button>
</div>
</div> </div>
); );
}; };

View File

@@ -1,12 +1,11 @@
.load { .load {
background: var(--color-bg-block); background: var(--color-bg-block);
padding: 2rem;
border-bottom-left-radius: 5px; border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px; border-bottom-right-radius: 5px;
} }
.load__game-input { .load__game-input {
height: 50vh; height: 40vh;
margin-top: 2rem; margin-top: 2rem;
} }
@@ -44,3 +43,15 @@
.load__link-input { .load__link-input {
margin-top: 2rem; margin-top: 2rem;
} }
@media (orientation: portrait) {
.load__game-input {
height: 20vh;
}
}
@media (orientation: landscape) and (max-height: 400px) {
.load__game-input {
height: 30vh;
}
}

View File

@@ -1,16 +1,35 @@
import { Component, createSignal, Show } from "solid-js"; import { Component, createSignal, Show } from "solid-js";
import { Handlers } from "../../types"; import { Handlers } from "../../types";
import readFile from "../../utils/readFile"; import readFile from "../../utils/readFile";
import Scrollable from "./reusable/Scrollable";
import { setState, state } from "../../state"; import { setState, state } from "../../state";
import "./Load.css"; import "./Load.css";
const Load: Component<{ handlers: Handlers; class?: string }> = (props) => { const Load: Component<{ handlers: Handlers; class?: string }> = (props) => {
const [data, setData] = createSignal(""); const [data, setData] = createSignal("");
const [clipError, setClipError] = createSignal(false);
const [inputError, setInputError] = createSignal(false);
let filePicker: HTMLInputElement | undefined = undefined; let filePicker: HTMLInputElement | undefined = undefined;
return ( return (
<div class={"load" + (props.class ? ` ${props.class}` : "")}> <Scrollable class={"load" + (props.class ? ` ${props.class}` : "")}>
<button
classList={{ "load__game-btn": true, "btn--error": clipError() }}
onClick={async () => {
const clip = await navigator.clipboard.readText();
const success = await props.handlers.load(clip);
if (!success) {
setClipError(true);
setTimeout(() => setClipError(false), 1000);
}
}}
>
<i class="las la-paste"></i>{" "}
{clipError() ? "Incorrect data" : "Load from clipboard"}
</button>
<hr />
<textarea <textarea
class="load__game-input" class="load__game-input"
name="load-game" name="load-game"
@@ -20,15 +39,18 @@ const Load: Component<{ handlers: Handlers; class?: string }> = (props) => {
onInput={(e) => setData(e.currentTarget.value)} onInput={(e) => setData(e.currentTarget.value)}
></textarea> ></textarea>
<button <button
class="load__game-btn" classList={{ "load__game-btn": true, "btn--error": inputError() }}
onClick={() => { onClick={async () => {
if (data()) { const success = await props.handlers.load(data());
props.handlers.load(data());
setData(""); if (!success) {
setInputError(true);
setTimeout(() => setInputError(false), 1000);
} }
setData("");
}} }}
> >
LOAD {inputError() ? "Incorrect data" : "LOAD"}
</button> </button>
<hr /> <hr />
<input <input
@@ -53,7 +75,7 @@ const Load: Component<{ handlers: Handlers; class?: string }> = (props) => {
} }
}} }}
> >
UPLOAD PGN FILE Upload PGN file
</button> </button>
<Show when={!state.mobile}> <Show when={!state.mobile}>
@@ -62,7 +84,7 @@ const Load: Component<{ handlers: Handlers; class?: string }> = (props) => {
<p>drop the PGN file anywhere on the page</p> <p>drop the PGN file anywhere on the page</p>
</div> </div>
</Show> </Show>
</div> </Scrollable>
); );
}; };

View File

@@ -51,3 +51,9 @@
width: auto; width: auto;
} }
} }
@media (orientation: landscape) and (max-height: 400px) {
.moves {
display: none;
}
}