WIP
This commit is contained in:
@@ -83,6 +83,14 @@ class Board {
|
||||
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) {
|
||||
const cfg = { ...this.cfg, ...config };
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Header, Style } from "../../types";
|
||||
import drawRectangle from "./drawRectangle";
|
||||
import drawText from "./drawText";
|
||||
import loadImage from "../loaders/loadImage";
|
||||
|
||||
const drawHeader = async (
|
||||
ctx: CanvasRenderingContext2D,
|
||||
@@ -84,6 +85,25 @@ const drawHeader = async (
|
||||
|
||||
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;
|
||||
|
||||
@@ -24,7 +24,7 @@ const style: Style = {
|
||||
},
|
||||
moveIndicator: {
|
||||
type: "color",
|
||||
data: "#3cff0055",
|
||||
data: "#ffaa0055",
|
||||
},
|
||||
border: {
|
||||
type: "gradient",
|
||||
|
||||
@@ -24,7 +24,7 @@ const style: Style = {
|
||||
},
|
||||
moveIndicator: {
|
||||
type: "color",
|
||||
data: "#3cff0055",
|
||||
data: "#00ffff55",
|
||||
},
|
||||
border: {
|
||||
type: "gradient",
|
||||
|
||||
@@ -24,7 +24,7 @@ const style: Style = {
|
||||
},
|
||||
moveIndicator: {
|
||||
type: "color",
|
||||
data: "#3cff0055",
|
||||
data: "#ffaa0055",
|
||||
},
|
||||
border: {
|
||||
type: "gradient",
|
||||
|
||||
@@ -24,7 +24,7 @@ const style: Style = {
|
||||
},
|
||||
moveIndicator: {
|
||||
type: "color",
|
||||
data: "#00ffee55",
|
||||
data: "#ffff0055",
|
||||
},
|
||||
border: {
|
||||
type: "gradient",
|
||||
|
||||
@@ -24,7 +24,7 @@ const style: Style = {
|
||||
},
|
||||
moveIndicator: {
|
||||
type: "color",
|
||||
data: "#3cff0055",
|
||||
data: "#ff00ff55",
|
||||
},
|
||||
border: {
|
||||
type: "gradient",
|
||||
|
||||
@@ -24,7 +24,7 @@ const style: Style = {
|
||||
},
|
||||
moveIndicator: {
|
||||
type: "color",
|
||||
data: "#00ffee55",
|
||||
data: "#00ffff55",
|
||||
},
|
||||
border: {
|
||||
type: "gradient",
|
||||
|
||||
@@ -24,7 +24,7 @@ const style: Style = {
|
||||
},
|
||||
moveIndicator: {
|
||||
type: "color",
|
||||
data: "#3cff0055",
|
||||
data: "#ffaa0055",
|
||||
},
|
||||
border: {
|
||||
type: "gradient",
|
||||
|
||||
@@ -23,7 +23,7 @@ const style: Style = {
|
||||
},
|
||||
moveIndicator: {
|
||||
type: "color",
|
||||
data: "#ffff0055",
|
||||
data: "#ff000044",
|
||||
},
|
||||
border: {
|
||||
type: "solid",
|
||||
|
||||
@@ -23,7 +23,7 @@ const style: Style = {
|
||||
},
|
||||
moveIndicator: {
|
||||
type: "color",
|
||||
data: "#ffff0055",
|
||||
data: "#00ffff55",
|
||||
},
|
||||
border: {
|
||||
type: "solid",
|
||||
|
||||
28
src/imports/importToLichess.ts
Normal file
28
src/imports/importToLichess.ts
Normal 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;
|
||||
26
src/main.tsx
26
src/main.tsx
@@ -22,6 +22,7 @@ import isPGN from "./utils/isPGN";
|
||||
import isSafeLink from "./utils/isSafeLink";
|
||||
import { PiecesStyle } from "./board/styles-pieces/piecesStyles";
|
||||
import link from "./persistance/link";
|
||||
import importToLichess from "./imports/importToLichess";
|
||||
|
||||
const main = async () => {
|
||||
const board = new Board(state.boardConfig);
|
||||
@@ -232,8 +233,27 @@ const main = async () => {
|
||||
setState("boardConfig", "speech", !state.boardConfig.speech);
|
||||
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(
|
||||
@@ -241,8 +261,10 @@ const main = async () => {
|
||||
document.getElementById("root") as HTMLElement
|
||||
);
|
||||
|
||||
const $board = document.querySelector<HTMLImageElement>("#board");
|
||||
$board?.prepend(board.canvas);
|
||||
const canvas = document.getElementById("canvas") as HTMLCanvasElement;
|
||||
|
||||
await board.setCanvas(canvas);
|
||||
await player.init();
|
||||
|
||||
/* Load game from the url */
|
||||
|
||||
|
||||
@@ -15,12 +15,18 @@ class Player {
|
||||
private speech: Speech;
|
||||
public playing: boolean = false;
|
||||
|
||||
private firstRender: Promise<void>;
|
||||
// private firstRender: Promise<void>;
|
||||
|
||||
constructor(private board: Board, private config: GameConfig) {
|
||||
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)
|
||||
.then((_) => this.board.render());
|
||||
}
|
||||
@@ -43,7 +49,7 @@ class Player {
|
||||
|
||||
async load(game: Game) {
|
||||
this.pause();
|
||||
await this.firstRender;
|
||||
// await this.firstRender;
|
||||
|
||||
this.game = game;
|
||||
this.ply = 0;
|
||||
|
||||
@@ -175,6 +175,7 @@ export type Handlers = {
|
||||
downloadAnimation: () => Promise<void>;
|
||||
toggleSound(): void;
|
||||
toggleSpeech(): void;
|
||||
openOnLichess: () => Promise<boolean>;
|
||||
};
|
||||
|
||||
export type Header = {
|
||||
|
||||
@@ -94,6 +94,11 @@ button:hover,
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn--error,
|
||||
.btn--error:hover {
|
||||
background-color: #fc4444;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
width: 100%;
|
||||
@@ -155,6 +160,10 @@ a:hover {
|
||||
padding: var(--header-margin) 0 2rem 0;
|
||||
}
|
||||
|
||||
.board-box--left {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.board {
|
||||
box-shadow: 0 0 2rem #00000099;
|
||||
border-radius: 5px;
|
||||
|
||||
@@ -23,7 +23,14 @@ const App: Component<{ handlers: Handlers; state: DeepReadonly<State> }> = (
|
||||
<SetupTabs handlers={props.handlers}></SetupTabs>
|
||||
</div>
|
||||
</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"}>
|
||||
<Controls handlers={props.handlers} />
|
||||
</Show>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
.controls {
|
||||
background: var(--color-bg-block);
|
||||
padding: 2rem;
|
||||
padding: 2rem 1rem;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
@@ -85,3 +85,9 @@
|
||||
max-height: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: landscape) and (max-height: 400px) {
|
||||
.controls {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
.game {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-rows: 3.8rem 19.5rem 1fr 8.4rem;
|
||||
grid-template-rows: 3.8rem 22rem auto 8.4rem;
|
||||
}
|
||||
|
||||
.game-tabs {
|
||||
@@ -42,3 +42,9 @@
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: landscape) and (max-height: 400px) {
|
||||
.game {
|
||||
grid-template-rows: 3.8rem 1fr 6.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
.info {
|
||||
background: var(--color-bg-block);
|
||||
padding: 3rem 2rem;
|
||||
padding: 2rem;
|
||||
font-size: 1.4rem;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.info__players {
|
||||
@@ -65,3 +66,10 @@
|
||||
color: var(--color-text);
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.info__analyze {
|
||||
position: absolute;
|
||||
bottom: 2rem;
|
||||
width: 100%;
|
||||
padding-right: 4rem;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { Component, Show } from "solid-js";
|
||||
import { Component, Show, createSignal } from "solid-js";
|
||||
import { Handlers } from "../../types";
|
||||
import { state } from "../../state";
|
||||
import "./Info.css";
|
||||
import isSafeLink from "../../utils/isSafeLink";
|
||||
import isLink from "../../utils/isLink";
|
||||
|
||||
const Info: Component<{ handlers: Handlers }> = () => {
|
||||
const Info: Component<{ handlers: Handlers }> = (props) => {
|
||||
const [error, setError] = createSignal(false);
|
||||
|
||||
return (
|
||||
<div class="info">
|
||||
<div className="info__players">
|
||||
@@ -83,22 +85,26 @@ const Info: Component<{ handlers: Handlers }> = () => {
|
||||
</Show>
|
||||
</p>
|
||||
</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}>
|
||||
<p>{state.game.header.DatePretty}</p>
|
||||
</Show>
|
||||
</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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
.load {
|
||||
background: var(--color-bg-block);
|
||||
padding: 2rem;
|
||||
border-bottom-left-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
}
|
||||
|
||||
.load__game-input {
|
||||
height: 50vh;
|
||||
height: 40vh;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
@@ -44,3 +43,15 @@
|
||||
.load__link-input {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
@media (orientation: portrait) {
|
||||
.load__game-input {
|
||||
height: 20vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: landscape) and (max-height: 400px) {
|
||||
.load__game-input {
|
||||
height: 30vh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,35 @@
|
||||
import { Component, createSignal, Show } from "solid-js";
|
||||
import { Handlers } from "../../types";
|
||||
import readFile from "../../utils/readFile";
|
||||
import Scrollable from "./reusable/Scrollable";
|
||||
import { setState, state } from "../../state";
|
||||
import "./Load.css";
|
||||
|
||||
const Load: Component<{ handlers: Handlers; class?: string }> = (props) => {
|
||||
const [data, setData] = createSignal("");
|
||||
const [clipError, setClipError] = createSignal(false);
|
||||
const [inputError, setInputError] = createSignal(false);
|
||||
|
||||
let filePicker: HTMLInputElement | undefined = undefined;
|
||||
|
||||
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
|
||||
class="load__game-input"
|
||||
name="load-game"
|
||||
@@ -20,15 +39,18 @@ const Load: Component<{ handlers: Handlers; class?: string }> = (props) => {
|
||||
onInput={(e) => setData(e.currentTarget.value)}
|
||||
></textarea>
|
||||
<button
|
||||
class="load__game-btn"
|
||||
onClick={() => {
|
||||
if (data()) {
|
||||
props.handlers.load(data());
|
||||
setData("");
|
||||
classList={{ "load__game-btn": true, "btn--error": inputError() }}
|
||||
onClick={async () => {
|
||||
const success = await props.handlers.load(data());
|
||||
|
||||
if (!success) {
|
||||
setInputError(true);
|
||||
setTimeout(() => setInputError(false), 1000);
|
||||
}
|
||||
setData("");
|
||||
}}
|
||||
>
|
||||
LOAD
|
||||
{inputError() ? "Incorrect data" : "LOAD"}
|
||||
</button>
|
||||
<hr />
|
||||
<input
|
||||
@@ -53,7 +75,7 @@ const Load: Component<{ handlers: Handlers; class?: string }> = (props) => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
UPLOAD PGN FILE
|
||||
Upload PGN file
|
||||
</button>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
</Scrollable>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -51,3 +51,9 @@
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (orientation: landscape) and (max-height: 400px) {
|
||||
.moves {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user