diff --git a/package.json b/package.json index 8803c40786..8f3da93f78 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,7 @@ "express-http-proxy": "^2.0.0", "fork-ts-checker-webpack-plugin": "^9.0.0", "globals": "^15.8.0", - "goban": "=8.3.19", + "goban": "=8.3.22", "gulp": "^5.0.0", "gulp-clean-css": "^4.3.0", "gulp-eslint-new": "^2.2.0", diff --git a/src/components/GobanThemePicker/GobanThemePicker.styl b/src/components/GobanThemePicker/GobanThemePicker.styl index e9bb7efad9..995d5f09a8 100644 --- a/src/components/GobanThemePicker/GobanThemePicker.styl +++ b/src/components/GobanThemePicker/GobanThemePicker.styl @@ -32,9 +32,10 @@ .theme-set, .custom-theme-set { display: inline-flex; flex-direction: row; + width: 320px; flex-wrap: wrap; - justify-content: center; - text-align: center; + justify-content: flex-start; + text-align: left; font-size: 10px; .selector { @@ -104,4 +105,10 @@ margin-left: 0.3rem; } } + +} + +.LineText.customize { + margin-top: 1rem; + margin-bottom: 1rem; } \ No newline at end of file diff --git a/src/components/GobanThemePicker/GobanThemePicker.tsx b/src/components/GobanThemePicker/GobanThemePicker.tsx index 5bf7d33c4f..17d24ac49a 100644 --- a/src/components/GobanThemePicker/GobanThemePicker.tsx +++ b/src/components/GobanThemePicker/GobanThemePicker.tsx @@ -17,561 +17,619 @@ import * as React from "react"; import { _, pgettext } from "translate"; -import { Goban, GobanTheme, GobanThemeBackgroundCSS } from "goban"; -import { getSelectedThemes } from "preferences"; -import * as preferences from "preferences"; +import { Goban, GobanTheme /*, GobanThemeBackgroundCSS */ } from "goban"; +import { usePreference } from "preferences"; import { PersistentElement } from "PersistentElement"; -import * as data from "data"; -import { CustomGobanThemeSchema } from "data_schema"; -import { Toggle } from "Toggle"; import { Experiment, Variant, Default } from "../Experiment"; +import { LineText } from "../misc-ui"; +import { Link } from "react-router-dom"; interface GobanThemePickerProperties { size?: number; } -interface GobanThemePickerState { - size: number; - board: string; - white: string; - black: string; - boardCustom: string; - lineCustom: string; - whiteCustom: string; - blackCustom: string; - urlCustom: string; - black_stone_urlCustom: string; - white_stone_urlCustom: string; - show_customize: boolean; +function renderSampleBoard(canvas: HTMLCanvasElement, theme: GobanTheme, size: number): void { + const square_size = size; + canvas.width = square_size; + canvas.height = square_size; + theme.styles = Object.assign( + { + height: size + "px", + width: size + "px", + }, + theme.getReactStyles(), + ) as unknown as { [style_name: string]: string }; + + const ctx = canvas.getContext("2d"); + if (!ctx) { + console.error("Could not get 2d context for canvas"); + return; + } + ctx.clearRect(0, 0, square_size, square_size); + + ctx.beginPath(); + ctx.strokeStyle = theme.getLineColor(); + ctx.moveTo(square_size / 2 - 0.5, square_size / 2 - 0.5); + ctx.lineTo(square_size - 0.5, square_size / 2 - 0.5); + ctx.stroke(); + ctx.beginPath(); + ctx.strokeStyle = theme.getLineColor(); + ctx.moveTo(square_size / 2 - 0.5, square_size / 2 - 0.5); + ctx.lineTo(square_size / 2 - 0.5, square_size - 0.5); + ctx.stroke(); + + ctx.font = "bold " + square_size / 4 + "px Verdana,Courier,Arial,serif"; + ctx.fillStyle = theme.getLabelTextColor(); + ctx.textBaseline = "middle"; + const metrics = ctx.measureText("A"); + const xx = square_size / 2 - metrics.width / 2; + const yy = square_size / 4; + ctx.fillText("A", xx + 0.5, yy + 0.5); } -export class GobanThemePicker extends React.PureComponent< - GobanThemePickerProperties, - GobanThemePickerState -> { - canvases: { [k: string]: JQuery[] } = {}; - selectTheme: { [k: string]: { [k: string]: () => void } } = {}; - - constructor(props: GobanThemePickerProperties) { - super(props); - - const selected = getSelectedThemes(); - - this.state = { - size: props.size || 44, - board: selected.board, - white: selected.white, - black: selected.black, - boardCustom: this.getCustom("board"), - lineCustom: this.getCustom("line"), - whiteCustom: this.getCustom("white"), - blackCustom: this.getCustom("black"), - urlCustom: this.getCustom("url"), - black_stone_urlCustom: this.getCustom("black_stone_url"), - white_stone_urlCustom: this.getCustom("white_stone_url"), - //show_customize: false, - show_customize: - selected.board === "Custom" || - selected.black === "Custom" || - selected.white === "Custom", - }; - for (const k in Goban.THEMES_SORTED) { - this.canvases[k] = []; - this.selectTheme[k] = {}; - for (const theme of Goban.THEMES_SORTED[k]) { - this.canvases[k].push( - $("").attr("width", this.state.size).attr("height", this.state.size), - ); - theme.styles = Object.assign( - { - height: this.state.size + "px", - width: this.state.size + "px", - }, - theme.getReactStyles(), - ) as unknown as { [style_name: string]: string }; - - this.selectTheme[k][theme.theme_name] = () => { - preferences.set( - `goban-theme-${k}` as preferences.ValidPreference, - theme.theme_name, - ); - const up = {}; - (up as any)[k] = theme.theme_name; - this.setState(up); - }; - } +export function GobanBoardThemePicker(props: GobanThemePickerProperties): JSX.Element { + const size = props.size || 44; + const canvases = React.useRef([]); + const selectTheme = React.useRef<{ [k: string]: () => void }>({}); + const [, setCanvasesRenderedCt] = React.useState(0); + const [board, setBoard] = usePreference("goban-theme-board"); + + React.useEffect(() => { + canvases.current.length = 0; + + for (const theme of Goban.THEMES_SORTED.board) { + selectTheme.current[theme.theme_name] = () => { + setBoard(theme.theme_name); + }; + + const canvas = document.createElement("canvas"); + renderSampleBoard(canvas, theme, size); + canvases.current.push(canvas); } - } - componentDidMount() { - setTimeout(() => this.renderPickers(), 50); + setCanvasesRenderedCt(canvases.current.length); + }, [size]); + + const standard_themes = Goban.THEMES_SORTED.board.filter((x) => x.theme_name !== "Custom"); + + return ( +
+
+ {standard_themes.map((theme, idx) => ( +
+ +
+ ))} +
+
+ ); +} + +export function GobanCustomBoardPicker(props: GobanThemePickerProperties): JSX.Element { + const size = props.size || 44; + + const [line_color, _setLineColor] = usePreference("goban-theme-custom-board-line"); + const [background_color, _setBackgroundColor] = usePreference( + "goban-theme-custom-board-background", + ); + const [background_image, _setBackgroundImage] = usePreference("goban-theme-custom-board-url"); + const sample_canvas = React.useRef(); + const [, refresh] = React.useState(0); + const theme = Goban.THEMES_SORTED.board.filter((x) => x.theme_name === "Custom")[0]; + const [board, setBoard] = usePreference("goban-theme-board"); + + const inputStyle = { height: `${size}px`, width: `${size * 1.5}px` }; + + if (!theme) { + requestAnimationFrame(() => refresh((x) => x + 1)); } - getCustom(key: keyof CustomGobanThemeSchema): string { - return data.get(`custom.${key}`, ""); + React.useEffect(() => { + sample_canvas.current = document.createElement("canvas"); + renderSampleBoard(sample_canvas.current, theme, size); + refresh((x) => x + 1); + }, [theme, size, background_color, line_color, background_image]); + + function setBackgroundColor(ev: React.ChangeEvent) { + _setBackgroundColor(ev.target.value); } - setCustom( - key: keyof CustomGobanThemeSchema, - event: - | React.MouseEvent - | React.ChangeEvent, - ) { - if ("value" in event.target) { - data.set(`custom.${key}`, event.target.value); - } else { - data.remove(`custom.${key}`); - } - const up = {}; - (up as any)[`${key}Custom`] = this.getCustom(key); - this.setState(up); - try { - this.renderPickers(); - } catch (e) { - console.error(e); - } - if (key === "url") { - // Changing the custom image should update the board theme - key = "board"; - } + function setLineColor(ev: React.ChangeEvent) { + _setLineColor(ev.target.value); + } - if (key === "line") { - // Changing the line color should update the board theme - key = "board"; - } + function setBackgroundImage(ev: React.ChangeEvent) { + _setBackgroundImage(ev.target.value); + } - // If it's a color code, set to Custom - if (this.state[`${key}Custom`][0] === "#") { - preferences.set(`goban-theme-${key}`, "Custom"); - } else { - preferences.set(`goban-theme-${key}`, this.state[`${key}Custom`]); - } + if (!sample_canvas.current) { + return <>; } - render() { - const inputStyle = { height: `${this.state.size}px`, width: `${this.state.size * 1.5}px` }; - const { - boardCustom, - lineCustom, - whiteCustom, - blackCustom, - urlCustom, - white_stone_urlCustom, - black_stone_urlCustom, - } = this.state; - - const standard_themes = { - board: Goban.THEMES_SORTED.board.filter((x) => x.theme_name !== "Custom"), - white: Goban.THEMES_SORTED.white.filter((x) => x.theme_name !== "Custom"), - black: Goban.THEMES_SORTED.black.filter((x) => x.theme_name !== "Custom"), - }; + return ( +
+ + {pgettext("Create and use a custom board theme", "Customize board")} + - const custom_board = Goban.THEMES_SORTED.board.filter((x) => x.theme_name === "Custom")[0]; - const custom_black = Goban.THEMES_SORTED.black.filter((x) => x.theme_name === "Custom")[0]; - const custom_white = Goban.THEMES_SORTED.white.filter((x) => x.theme_name === "Custom")[0]; - - const active_standard_board_theme = Goban.THEMES_SORTED.board.filter( - (x) => x.theme_name === this.state.board, - )[0]; - - const board_styles = - this.state.board === custom_board.theme_name - ? { - backgroundColor: this.state.boardCustom, - backgroundImage: `url(${this.state.urlCustom})`, - } - : { - backgroundColor: active_standard_board_theme.styles["backgroundColor"], - backgroundImage: active_standard_board_theme.styles["backgroundImage"], - }; - - // If the user has selected a custom theme, we need to show the customisation options - const force_custom_themes_toggle = - this.state.board === "Custom" || - this.state.white === "Custom" || - this.state.black === "Custom"; - - return (
- {standard_themes.board.map((theme, idx) => ( +
setBoard("Custom")} > - +
- ))} -
+
- - -
- {standard_themes.white.map((theme) => ( -
- -
- ))} -
-
- -
- {standard_themes.white.map((theme, idx) => ( -
- -
- ))} -
-
-
- - - -
- {standard_themes.black.map((theme) => ( -
- -
- ))} -
-
- -
- {standard_themes.black.map((theme, idx) => ( -
- -
- ))} -
-
-
- -
- {pgettext("Label for a button to show custom stones", "Customize")} - { - this.setState({ show_customize: checked }); - }} - disabled={force_custom_themes_toggle} + + + + +
+
+ +
+ e.target.select()} + onChange={setBackgroundImage} + /> + + +
+
+ ); +} + +export function renderSampleStone( + canvas: HTMLCanvasElement, + theme: GobanTheme, + size: number, + color: "white" | "black", +): void { + canvas.setAttribute("width", size.toString()); + canvas.setAttribute("height", size.toString()); + theme.styles = Object.assign( + { + height: size + "px", + width: size + "px", + }, + theme.getReactStyles(), + ) as unknown as { [style_name: string]: string }; + + const ctx = canvas.getContext("2d"); + if (!ctx) { + throw new Error("Could not get 2d context for canvas"); + } + const radius = Math.round(size / 2.2); + + if (color === "white") { + const draw = () => { + ctx.clearRect(0, 0, size, size); + theme.placeWhiteStone(ctx, ctx, stones[0], size / 2, size / 2, radius); + }; + const stones = theme.preRenderWhite(radius, 23434, draw); + draw(); + } else { + const draw = () => { + ctx.clearRect(0, 0, size, size); + theme.placeBlackStone(ctx, ctx, stones[0], size / 2, size / 2, radius); + }; + const stones = theme.preRenderBlack(radius, 23434, draw); + draw(); + } +} + +export function GobanWhiteThemePicker(props: GobanThemePickerProperties): JSX.Element { + const size = props.size || 44; + + const canvases = React.useRef([]); + const selectTheme = React.useRef<{ [k: string]: () => void }>({}); + const [, setCanvasesRenderedCt] = React.useState(0); + const [white, setWhite] = usePreference("goban-theme-white"); + const [board] = usePreference("goban-theme-board"); + const [background_color, _setBackgroundColor] = usePreference( + "goban-theme-custom-board-background", + ); + const [background_image, _setBackgroundImage] = usePreference("goban-theme-custom-board-url"); + const [, refresh] = React.useState(0); + + React.useEffect(() => { + canvases.current.length = 0; + + for (const theme of Goban.THEMES_SORTED.white) { + selectTheme.current[theme.theme_name] = () => { + setWhite(theme.theme_name); + }; + + const canvas = document.createElement("canvas"); + renderSampleStone(canvas, theme, size, "white"); + canvases.current.push(canvas); + } + + setCanvasesRenderedCt(canvases.current.length); + refresh((x) => x + 1); + }, [size]); - {(force_custom_themes_toggle || this.state.show_customize) && ( - <> -
+ const standard_themes = Goban.THEMES_SORTED.white.filter((x) => x.theme_name !== "Custom"); + const custom_board = Goban.THEMES_SORTED.board.filter((x) => x.theme_name === "Custom")[0]; + + const active_standard_board_theme = Goban.THEMES_SORTED.board.filter( + (x) => x.theme_name === board, + )[0]; + + if (!active_standard_board_theme) { + requestAnimationFrame(() => refresh((x) => x + 1)); + } + + const board_styles = + board === custom_board.theme_name + ? { + backgroundColor: background_color, + backgroundImage: `url(${background_image})`, + } + : { + backgroundColor: active_standard_board_theme?.styles["backgroundColor"], + backgroundImage: active_standard_board_theme?.styles["backgroundImage"], + }; + + return ( +
+ + +
+ {standard_themes.map((theme) => (
- +
- -
- - - - + ))} +
+ + +
+ {standard_themes.map((theme, idx) => ( +
+
+ ))} +
+
+ +
+ ); +} -
- e.target.select()} - onChange={this.setCustom.bind(this, "url")} - /> -
+export function GobanCustomBlackPicker(props: GobanThemePickerProperties): JSX.Element { + const size = props.size || 44; + + const [url, _setUrl] = usePreference("goban-theme-custom-black-url"); + const [color, _setColor] = usePreference("goban-theme-custom-black-stone-color"); + const [, refresh] = React.useState(0); + const theme = Goban.THEMES_SORTED.black.filter((x) => x.theme_name === "Custom")[0]; + const [black, setBlack] = usePreference("goban-theme-black"); + const [board] = usePreference("goban-theme-board"); + const [background_color, _setBackgroundColor] = usePreference( + "goban-theme-custom-board-background", + ); + const [background_image, _setBackgroundImage] = usePreference("goban-theme-custom-board-url"); + + const inputStyle = { height: `${size}px`, width: `${size * 1.5}px` }; + + if (!theme) { + requestAnimationFrame(() => refresh((x) => x + 1)); + } + + function setColor(ev: React.ChangeEvent) { + _setColor(ev.target.value); + } + + function setUrl(ev: React.ChangeEvent) { + _setUrl(ev.target.value); + } + + const custom_board = Goban.THEMES_SORTED.board.filter((x) => x.theme_name === "Custom")[0]; + + const active_standard_board_theme = Goban.THEMES_SORTED.board.filter( + (x) => x.theme_name === board, + )[0]; + const board_styles = + board === custom_board.theme_name + ? { + backgroundColor: background_color, + backgroundImage: `url(${background_image})`, + } + : { + backgroundColor: active_standard_board_theme?.styles["backgroundColor"], + backgroundImage: active_standard_board_theme?.styles["backgroundImage"], + }; + + return ( +
+ + {pgettext("Create and use a custom board theme", "Customize black stones")} + + +
+
+
+
setBlack("Custom")} + > +
+
+ + + +
+
-
+
+ e.target.select()} + onChange={setUrl} + /> + + +
+
+ ); +} + +export function GobanBlackThemePicker(props: GobanThemePickerProperties): JSX.Element { + const size = props.size || 44; + + const canvases = React.useRef([]); + const selectTheme = React.useRef<{ [k: string]: () => void }>({}); + const [, setCanvasesRenderedCt] = React.useState(0); + const [black, setBlack] = usePreference("goban-theme-black"); + const [board] = usePreference("goban-theme-board"); + const [background_color, _setBackgroundColor] = usePreference( + "goban-theme-custom-board-background", + ); + const [background_image, _setBackgroundImage] = usePreference("goban-theme-custom-board-url"); + const [, refresh] = React.useState(0); + + React.useEffect(() => { + canvases.current.length = 0; + + for (const theme of Goban.THEMES_SORTED.black) { + selectTheme.current[theme.theme_name] = () => { + setBlack(theme.theme_name); + }; + + const canvas = document.createElement("canvas"); + renderSampleStone(canvas, theme, size, "black"); + canvases.current.push(canvas); + } + + setCanvasesRenderedCt(canvases.current.length); + refresh((x) => x + 1); + }, [size]); + + const standard_themes = Goban.THEMES_SORTED.black.filter((x) => x.theme_name !== "Custom"); + const custom_board = Goban.THEMES_SORTED.board.filter((x) => x.theme_name === "Custom")[0]; + + const active_standard_board_theme = Goban.THEMES_SORTED.board.filter( + (x) => x.theme_name === board, + )[0]; + + if (!active_standard_board_theme) { + requestAnimationFrame(() => refresh((x) => x + 1)); + } + + const board_styles = + board === custom_board.theme_name + ? { + backgroundColor: background_color, + backgroundImage: `url(${background_image})`, + } + : { + backgroundColor: active_standard_board_theme?.styles["backgroundColor"], + backgroundImage: active_standard_board_theme?.styles["backgroundImage"], + }; + + return ( +
+ + +
+ {standard_themes.map((theme) => (
- -
- -
- - -
-
- e.target.select()} - onChange={this.setCustom.bind(this, "white_stone_url")} - /> +
-
- -
+ ))} +
+
+ +
+ {standard_themes.map((theme, idx) => (
- +
+ ))} +
+
+
+
+ ); +} -
- - -
-
- e.target.select()} - onChange={this.setCustom.bind(this, "black_stone_url")} - /> -
-
- - )} -
- ); - } +export function GobanCustomWhitePicker(props: GobanThemePickerProperties): JSX.Element { + const size = props.size || 44; - renderPickers() { - const square_size = this.state.size; - - for (let i = 0; i < Goban.THEMES_SORTED.board.length; ++i) { - const theme = Goban.THEMES_SORTED.board[i]; - const canvas = this.canvases.board[i]; - const ctx = (canvas[0] as HTMLCanvasElement).getContext("2d"); - if (!ctx) { - continue; - } - ctx.clearRect(0, 0, square_size, square_size); - - ctx.beginPath(); - ctx.strokeStyle = theme.getLineColor(); - ctx.moveTo(square_size / 2 - 0.5, square_size / 2 - 0.5); - ctx.lineTo(square_size - 0.5, square_size / 2 - 0.5); - ctx.stroke(); - ctx.beginPath(); - ctx.strokeStyle = theme.getLineColor(); - ctx.moveTo(square_size / 2 - 0.5, square_size / 2 - 0.5); - ctx.lineTo(square_size / 2 - 0.5, square_size - 0.5); - ctx.stroke(); - - ctx.font = "bold " + square_size / 4 + "px Verdana,Courier,Arial,serif"; - ctx.fillStyle = theme.getLabelTextColor(); - ctx.textBaseline = "middle"; - const metrics = ctx.measureText("A"); - const xx = square_size / 2 - metrics.width / 2; - const yy = square_size / 4; - ctx.fillText("A", xx + 0.5, yy + 0.5); - } + const [url, _setUrl] = usePreference("goban-theme-custom-white-url"); + const [color, _setColor] = usePreference("goban-theme-custom-white-stone-color"); + const [, refresh] = React.useState(0); + const theme = Goban.THEMES_SORTED.white.filter((x) => x.theme_name === "Custom")[0]; + const [white, setWhite] = usePreference("goban-theme-white"); - for (let i = 0; i < Goban.THEMES_SORTED.white.length; ++i) { - const theme = Goban.THEMES_SORTED.white[i]; - const canvas = this.canvases.white[i]; - const ctx = (canvas[0] as HTMLCanvasElement).getContext("2d"); - if (!ctx) { - continue; - } - const radius = Math.round(square_size / 2.2); - const draw = () => { - ctx.clearRect(0, 0, square_size, square_size); - theme.placeWhiteStone( - ctx, - ctx, - stones[0], - square_size / 2, - square_size / 2, - radius, - ); - }; - const stones = theme.preRenderWhite(radius, 23434, draw); - draw(); - } + const inputStyle = { height: `${size}px`, width: `${size * 1.5}px` }; - for (let i = 0; i < Goban.THEMES_SORTED.black.length; ++i) { - const theme = Goban.THEMES_SORTED.black[i]; - const canvas = this.canvases.black[i]; - const ctx = (canvas[0] as HTMLCanvasElement).getContext("2d"); - if (!ctx) { - continue; - } - const radius = Math.round(square_size / 2.2); - const draw = () => { - ctx.clearRect(0, 0, square_size, square_size); - theme.placeBlackStone( - ctx, - ctx, - stones[0], - square_size / 2, - square_size / 2, - radius, - ); - }; - const stones = theme.preRenderBlack(radius, 23434, draw); - draw(); - } + if (!theme) { + requestAnimationFrame(() => refresh((x) => x + 1)); + } + + function setColor(ev: React.ChangeEvent) { + _setColor(ev.target.value); } -} -function css2react(style: GobanThemeBackgroundCSS): { [k: string]: string } { - const react_style = {}; - for (const k in style) { - const react_key = k.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); - (react_style as any)[react_key] = style[k as keyof GobanThemeBackgroundCSS]; + function setUrl(ev: React.ChangeEvent) { + _setUrl(ev.target.value); } - return react_style; + return ( +
+ + {pgettext("Create and use a custom board theme", "Customize white stones")} + + +
+
+
+
setWhite("Custom")} + > + +
+
+ + + +
+
+ +
+ e.target.select()} + onChange={setUrl} + /> + + +
+
+ ); +} + +export function GobanThemePicker(props: GobanThemePickerProperties): JSX.Element { + return ( +
+ + + + + {pgettext("Link to settings page with more theme options", "More options")} + +
+ ); } function ThemeSample({ @@ -583,7 +641,17 @@ function ThemeSample({ color: "black" | "white"; size: number; }) { - const div = React.useRef(null); + const div = React.useRef(null); + + const [black] = usePreference("goban-theme-black"); + const [white] = usePreference("goban-theme-white"); + const [board] = usePreference("goban-theme-board"); + const [board_bg] = usePreference("goban-theme-custom-board-background"); + const [board_url] = usePreference("goban-theme-custom-board-url"); + const [black_color] = usePreference("goban-theme-custom-black-stone-color"); + const [black_url] = usePreference("goban-theme-custom-black-url"); + const [white_color] = usePreference("goban-theme-custom-white-stone-color"); + const [white_url] = usePreference("goban-theme-custom-white-url"); React.useEffect(() => { if (!div.current) { @@ -612,12 +680,24 @@ function ThemeSample({ theme.placeWhiteStoneSVG(g, undefined, white_stones[0], cx, cy, radius); } - (div.current as any)?.appendChild(svg); + div.current.appendChild(svg); return () => { - (div.current as any)?.removeChild(svg); + div.current?.removeChild(svg); }; - }, [div, div.current]); + }, [ + div, + div.current, + black, + white, + board, + board_bg, + board_url, + black_color, + black_url, + white_color, + white_url, + ]); return
; } diff --git a/src/components/MiniGoban/MiniGoban.tsx b/src/components/MiniGoban/MiniGoban.tsx index b205f976c7..4d4caf1b22 100644 --- a/src/components/MiniGoban/MiniGoban.tsx +++ b/src/components/MiniGoban/MiniGoban.tsx @@ -20,7 +20,7 @@ import { Link } from "react-router-dom"; import { npgettext, interpolate } from "translate"; import * as moment from "moment"; import * as preferences from "preferences"; -import { GobanRenderer, createGoban } from "goban"; +import { GobanRenderer, JGOFMove, createGoban } from "goban"; import * as data from "data"; import { PersistentElement } from "PersistentElement"; import { getUserRating, PROVISIONAL_RATING_CUTOFF } from "rank_utils"; @@ -36,6 +36,7 @@ export interface MiniGobanProps { width?: number; height?: number; displayWidth?: number; + className?: string; // If these are not provided, we look in the game itself (via the id prop)... // Also note that if you pass in a string, you won't get the rank of the player displayed... @@ -53,6 +54,11 @@ export interface MiniGobanProps { title?: boolean; onGobanCreated?: (goban: GobanRenderer) => void; chat?: boolean; + labels_positioning?: "none" | "all" | "top-left" | "top-right" | "bottom-left" | "bottom-right"; + sampleOptions?: { + undo?: boolean; + variation?: JGOFMove[]; + }; } export function MiniGoban(props: MiniGobanProps): JSX.Element { @@ -82,14 +88,31 @@ export function MiniGoban(props: MiniGobanProps): JSX.Element { const [game_name, setGameName] = React.useState(""); const [last_move_opacity] = usePreference("last-move-opacity"); + const draw_top_labels = + props.labels_positioning === "all" || + props.labels_positioning === "top-left" || + props.labels_positioning === "top-right"; + const draw_bottom_labels = + props.labels_positioning === "all" || + props.labels_positioning === "bottom-left" || + props.labels_positioning === "bottom-right"; + const draw_left_labels = + props.labels_positioning === "all" || + props.labels_positioning === "top-left" || + props.labels_positioning === "bottom-left"; + const draw_right_labels = + props.labels_positioning === "all" || + props.labels_positioning === "top-right" || + props.labels_positioning === "bottom-right"; + React.useEffect(() => { goban.current = createGoban( { board_div: goban_div.current, - draw_top_labels: false, - draw_bottom_labels: false, - draw_left_labels: false, - draw_right_labels: false, + draw_top_labels, + draw_bottom_labels, + draw_left_labels, + draw_right_labels, connect_to_chat: !!props.chat, game_id: props.game_id, review_id: props.review_id, @@ -99,6 +122,7 @@ export function MiniGoban(props: MiniGobanProps): JSX.Element { width: props.width || (props.json ? props.json.width : 19), height: props.height || (props.json ? props.json.height : 19), last_move_opacity: last_move_opacity, + variation_stone_opacity: preferences.get("variation-stone-opacity"), }, props.json, ); @@ -107,6 +131,19 @@ export function MiniGoban(props: MiniGobanProps): JSX.Element { props.onGobanCreated(goban.current); } + if (props.sampleOptions?.undo) { + (window as any)["mini_goban"] = goban.current; + //goban.current.visual_undo_request_indicator = true; + goban.current.engine.undo_requested = goban.current.engine.cur_move.move_number; + } + + if (props.sampleOptions?.variation) { + goban.current.setMode("analyze"); + for (const move of props.sampleOptions.variation) { + goban.current.engine.place(move.x, move.y); + } + } + goban.current.on("update", () => { const engine = goban.current?.engine; if (!engine) { @@ -333,13 +370,13 @@ export function MiniGoban(props: MiniGobanProps): JSX.Element { } if (props.noLink || (!props.game_id && !props.review_id)) { - return
{inner}
; + return
{inner}
; } else { if (props.game_id) { return ( {inner} @@ -349,7 +386,7 @@ export function MiniGoban(props: MiniGobanProps): JSX.Element { return ( {inner} diff --git a/src/components/PersistentElement/PersistentElement.tsx b/src/components/PersistentElement/PersistentElement.tsx index 54048c0fc9..d143fa9f6f 100644 --- a/src/components/PersistentElement/PersistentElement.tsx +++ b/src/components/PersistentElement/PersistentElement.tsx @@ -36,6 +36,8 @@ export function PersistentElement(props: PersistentElementProps): JSX.Element { return () => { cont.removeChild(elt); }; + } else { + console.warn("PersistentElement: element not found", props.elt); } } }, [container.current, props.elt]); diff --git a/src/lib/SettingsCommon.tsx b/src/lib/SettingsCommon.tsx index fa65d0e11c..a59000b3d5 100644 --- a/src/lib/SettingsCommon.tsx +++ b/src/lib/SettingsCommon.tsx @@ -65,9 +65,10 @@ export function PreferenceLine(props: { title: string | JSX.Element; description?: string; children: React.ReactNode; + className?: string; }): JSX.Element { return ( -
+
{props.title} {props.description && ( diff --git a/src/lib/configure-goban.tsx b/src/lib/configure-goban.tsx index 89bd01fa3e..32ce9ed41c 100644 --- a/src/lib/configure-goban.tsx +++ b/src/lib/configure-goban.tsx @@ -29,18 +29,14 @@ import { toast } from "toast"; (window as any)["GobanThemes"] = Goban.THEMES; (window as any)["GobanEngine"] = GobanEngine; -data.setDefault("custom.black", "#000000"); -data.setDefault("custom.white", "#FFFFFF"); -data.setDefault("custom.board", "#DCB35C"); -data.setDefault("custom.line", "#000000"); -data.setDefault("custom.url", ""); - let previous_toast: any = null; export function configure_goban() { - data.watch("experiments.svg", () => { - const v = data.get("experiments.svg"); + data.watch("experiments.canvas", () => { + const v = data.get("experiments.canvas"); if (v === "enabled") { + setGobanRenderer("canvas"); + } else { setGobanRenderer("svg"); } }); @@ -105,7 +101,7 @@ export function configure_goban() { getClockDrift: (): number => get_clock_drift(), getNetworkLatency: (): number => get_network_latency(), getLocation: (): string => window.location.pathname, - getShowMoveNumbers: (): boolean => !!preferences.get("show-move-numbers"), + //getShowMoveNumbers: (): boolean => !!preferences.get("show-move-numbers"), getShowVariationMoveNumbers: (): boolean => preferences.get("show-variation-move-numbers"), getMoveTreeNumbering: (): "none" | "move-number" | "move-coordinates" => preferences.get("move-tree-numbering"), @@ -116,15 +112,20 @@ export function configure_goban() { watchSelectedThemes: (cb) => preferences.watchSelectedThemes(cb), getSelectedThemes: () => preferences.getSelectedThemes(), - customBlackStoneColor: (): string => data.get("custom.black", ""), - customBlackTextColor: (): string => data.get("custom.white", ""), - customWhiteStoneColor: (): string => data.get("custom.white", ""), - customWhiteTextColor: (): string => data.get("custom.black", ""), - customBoardColor: (): string => data.get("custom.board", ""), - customBoardLineColor: (): string => data.get("custom.line", ""), - customBoardUrl: (): string => data.get("custom.url", ""), - customBlackStoneUrl: (): string => data.get("custom.black_stone_url", ""), - customWhiteStoneUrl: (): string => data.get("custom.white_stone_url", ""), + getShowUndoRequestIndicator: (): boolean => + preferences.get("visual-undo-request-indicator"), + + customBlackStoneColor: (): string => + preferences.get("goban-theme-custom-black-stone-color"), + customBlackTextColor: (): string => preferences.get("goban-theme-custom-white-stone-color"), + customWhiteStoneColor: (): string => + preferences.get("goban-theme-custom-white-stone-color"), + customWhiteTextColor: (): string => preferences.get("goban-theme-custom-black-stone-color"), + customBoardColor: (): string => preferences.get("goban-theme-custom-board-background"), + customBoardLineColor: (): string => preferences.get("goban-theme-custom-board-line"), + customBoardUrl: (): string => preferences.get("goban-theme-custom-board-url"), + customBlackStoneUrl: (): string => preferences.get("goban-theme-custom-black-url"), + customWhiteStoneUrl: (): string => preferences.get("goban-theme-custom-white-url"), addCoordinatesToChatInput: (coordinates: string): void => { const chat_input = $(".chat-input"); diff --git a/src/lib/data_schema.ts b/src/lib/data_schema.ts index f3be4ae18d..93c9584400 100644 --- a/src/lib/data_schema.ts +++ b/src/lib/data_schema.ts @@ -108,6 +108,7 @@ interface ChatSchema { "split-sizes": number[]; } +/* export interface CustomGobanThemeSchema { black: string; white: string; @@ -117,6 +118,7 @@ export interface CustomGobanThemeSchema { black_stone_url: string; white_stone_url: string; } +*/ type SoundSchema = { "enabled.disconnected": boolean; @@ -223,7 +225,7 @@ export interface DataSchema Prefixed, Prefixed, Prefixed, - Prefixed, + //Prefixed, Prefixed, Prefixed, Prefixed, diff --git a/src/lib/preferences.ts b/src/lib/preferences.ts index a20a8016ab..ff9a154977 100644 --- a/src/lib/preferences.ts +++ b/src/lib/preferences.ts @@ -51,8 +51,17 @@ export const defaults = { "goban-theme-black": null as null | string, "goban-theme-board": null as null | string, "goban-theme-white": null as null | string, - "goban-theme-black_stone_url": null as null | string, - "goban-theme-white_stone_url": null as null | string, + //"goban-theme-black_stone_url": null as null | string, + //"goban-theme-white_stone_url": null as null | string, + "goban-theme-removal-graphic": "square" as "square" | "x", + "goban-theme-removal-scale": 0.9, + "goban-theme-custom-board-background": "#DCB35C", + "goban-theme-custom-board-url": "", + "goban-theme-custom-board-line": "#000000", + "goban-theme-custom-black-stone-color": "#000000", + "goban-theme-custom-black-url": "", + "goban-theme-custom-white-stone-color": "#ffffff", + "goban-theme-custom-white-url": "", "hide-ranks": false, "label-positioning": "all" as LabelPosition, "label-positioning-puzzles": "all" as LabelPosition, @@ -232,7 +241,7 @@ export function dump(): void { data.dump("preferences.", true); } -export function getSelectedThemes(): { board: string; black: string; white: string } { +export function getSelectedThemes(): GobanSelectedThemes { //let default_plain = $.browser.mobile || ($(window).width() * (window.devicePixelRatio || 1)) <= 768; const default_plain = $(window).width() * (window.devicePixelRatio || 1) <= 768; @@ -241,6 +250,8 @@ export function getSelectedThemes(): { board: string; black: string; white: stri //let black = get("goban-theme-black") || (default_plain ? "Plain" : "Plain"); let white = get("goban-theme-white") || (default_plain ? "Plain" : "Shell"); let black = get("goban-theme-black") || (default_plain ? "Plain" : "Slate"); + const removal_graphic = get("goban-theme-removal-graphic"); + const removal_scale = get("goban-theme-removal-scale"); if (!(board in Goban.THEMES["board"])) { board = default_plain ? "Plain" : "Kaya"; @@ -259,6 +270,8 @@ export function getSelectedThemes(): { board: string; black: string; white: stri board: board, white: white, black: black, + "removal-graphic": removal_graphic as any, + "removal-scale": removal_scale, }; } @@ -271,15 +284,31 @@ export function watchSelectedThemes(cb: (themes: GobanSelectedThemes) => void) { cb(getSelectedThemes()); }; - watch("goban-theme-board", call_cb); - watch("goban-theme-black", call_cb); dont_call_right_away = false; - watch("goban-theme-white", call_cb); + const keys: (keyof PreferencesType)[] = [ + "goban-theme-board", + "goban-theme-black", + "goban-theme-white", + "goban-theme-removal-graphic", + "goban-theme-removal-scale", + "goban-theme-custom-board-background", + "goban-theme-custom-board-url", + "goban-theme-custom-board-line", + "goban-theme-custom-black-stone-color", + "goban-theme-custom-black-url", + "goban-theme-custom-white-stone-color", + "goban-theme-custom-white-url", + ]; + + for (const key of keys) { + watch(key, call_cb); + } + return { remove: () => { - unwatch("goban-theme-board", call_cb); - unwatch("goban-theme-black", call_cb); - unwatch("goban-theme-white", call_cb); + for (const key of keys) { + unwatch(key, call_cb); + } }, }; } @@ -312,3 +341,34 @@ export function usePreference( return [value, setStateAndPreference]; } + +function migrate() { + function migrate_key(from: string, to: keyof PreferencesType) { + try { + if (data.get(from as keyof DataSchema, null) !== null) { + set(to, data.get(from as any) || ""); + data.remove(from as any); + } + } catch (e) { + console.log(e); + } + } + + // Migrate old goban theme preferences to a consistent place + // Introduced 2024-08-06, safe for removal 2025-03-01 + migrate_key("custom.black", "goban-theme-custom-black-stone-color"); + migrate_key("custom.white", "goban-theme-custom-white-stone-color"); + migrate_key("custom.board", "goban-theme-custom-board-background"); + migrate_key("custom.line", "goban-theme-custom-board-line"); + migrate_key("custom.url", "goban-theme-custom-board-url"); + migrate_key("custom.black_stone_url", "goban-theme-custom-black-url"); + migrate_key("custom.white_stone_url", "goban-theme-custom-white-url"); + migrate_key("preferences.goban-theme-black_stone_url", "goban-theme-custom-black-url"); + migrate_key("preferences.goban-theme-white_stone_url", "goban-theme-custom-white-url"); +} + +try { + migrate(); +} catch (e) { + console.error(e); +} diff --git a/src/views/Game/Game.tsx b/src/views/Game/Game.tsx index 01400c0358..720ac35516 100644 --- a/src/views/Game/Game.tsx +++ b/src/views/Game/Game.tsx @@ -1124,7 +1124,6 @@ export function Game(): JSX.Element | null { draw_left_labels: label_position === "all" || label_position.indexOf("left") >= 0, draw_right_labels: label_position === "all" || label_position.indexOf("right") >= 0, draw_bottom_labels: label_position === "all" || label_position.indexOf("bottom") >= 0, - visual_undo_request_indicator: preferences.get("visual-undo-request-indicator"), variation_stone_opacity: preferences.get("variation-stone-opacity"), onScoreEstimationUpdated: () => { goban.current?.redraw(true); @@ -1381,9 +1380,11 @@ export function Game(): JSX.Element | null { "double-click-submit-correspondence", ); } + /* goban.current.visual_undo_request_indicator = preferences.get( "visual-undo-request-indicator", ); + */ } catch (e) { console.error(e.stack); } diff --git a/src/views/Settings/GamePreferences.tsx b/src/views/Settings/GamePreferences.tsx index b458381599..4f72120cad 100644 --- a/src/views/Settings/GamePreferences.tsx +++ b/src/views/Settings/GamePreferences.tsx @@ -44,7 +44,6 @@ export function GamePreferences(): JSX.Element { getSubmitMode("correspondence"), ); const [chat_mode, _setChatMode] = usePreference("chat-mode"); - const [board_labeling, setBoardLabeling] = usePreference("board-labeling"); const [auto_advance, setAutoAdvance] = usePreference("auto-advance-after-submit"); const [always_disable_analysis, setAlwaysDisableAnalysis] = @@ -54,13 +53,7 @@ export function GamePreferences(): JSX.Element { const [autoplay_delay, _setAutoplayDelay]: [number, (x: number) => void] = React.useState( preferences.get("autoplay-delay") / 1000, ); - const [last_move_opacity, _setLastMoveOpacity] = usePreference("last-move-opacity"); - const [variation_stone_opacity, _setVariationStoneOpacity] = - usePreference("variation-stone-opacity"); const [variation_move_count, _setVariationMoveCount] = usePreference("variation-move-count"); - const [visual_undo_request_indicator, setVisualUndoRequestIndicator] = usePreference( - "visual-undo-request-indicator", - ); const [zen_mode_by_default, _setZenModeByDefault] = usePreference("start-in-zen-mode"); function setDockDelay(ev: React.ChangeEvent) { @@ -116,20 +109,6 @@ export function GamePreferences(): JSX.Element { function setCorrSubmitMode(value: string) { setSubmitMode("correspondence", value); } - function setLastMoveOpacity(ev: React.ChangeEvent) { - const value = parseFloat(ev.target.value); - - if (value >= 0.0 && value <= 1.0) { - _setLastMoveOpacity(value); - } - } - function setVariationStoneOpacity(ev: React.ChangeEvent) { - const value = parseFloat(ev.target.value); - - if (value >= 0.0 && value <= 1.0) { - _setVariationStoneOpacity(value); - } - } function setVariationMoveCount(ev: React.ChangeEvent) { const value = parseInt(ev.target.value); @@ -170,18 +149,6 @@ export function GamePreferences(): JSX.Element { - - - - - - - - - - - -   - {last_move_opacity} - - - - - - -   - {variation_stone_opacity} - - - void] = React.useState( data.get("experiments.v6") === "enabled", ); - const [enable_svg, setEnableSVG]: [boolean, (x: boolean) => void] = React.useState( - data.get("experiments.svg") === "enabled", - ); const [show_slow_internet_warning, setShowSlowInternetWarning] = usePreference( "show-slow-internet-warning", ); @@ -321,16 +318,6 @@ export function GeneralPreferences(props: SettingGroupPageProps): JSX.Element { /> - - { - data.set("experiments.svg", tf ? "enabled" : undefined); - setEnableSVG(tf); - }} - /> - - {_("Ask me")} diff --git a/src/views/Settings/Settings.styl b/src/views/Settings/Settings.styl index f2ab4c4ea7..2bc27f5fa7 100644 --- a/src/views/Settings/Settings.styl +++ b/src/views/Settings/Settings.styl @@ -51,6 +51,14 @@ font-weight: bold; } + &.title-on-top { + align-items: flex-start; + + .PreferenceLineTitle { + padding-top: 0.5rem; + } + } + .PreferenceLineDescription { display: inline-block; width: 20rem; @@ -59,6 +67,7 @@ font-weight: normal; font-style: italic; } + .PreferenceLineBody { display: inline-flex; align-items: center; @@ -66,11 +75,23 @@ min-width: 10rem; } + &.body-as-column { + .PreferenceLineBody { + flex-direction: column; + align-content: flex-start; + align-items: flex-start; + } + } + + span { margin-right: 0.2rem; margin-left: 0.2rem; } } + + + .PreferenceDropdown { diff --git a/src/views/Settings/Settings.tsx b/src/views/Settings/Settings.tsx index 8c75cc2717..e429b7f8d2 100644 --- a/src/views/Settings/Settings.tsx +++ b/src/views/Settings/Settings.tsx @@ -51,6 +51,7 @@ import { EmailPreferences } from "./EmailPreferences"; import { HelpSettings } from "./HelpSettings"; import { Supporter } from "Supporter"; import { GoTVPreferences } from "./GoTVPreferences"; +import { ThemePreferences } from "./ThemePreferences"; export function Settings(): JSX.Element { const { category } = useParams(); @@ -106,6 +107,7 @@ export function Settings(): JSX.Element { { key: "general", label: _("General Preferences") }, { key: "sound", label: _("Sound Preferences") }, { key: "game", label: _("Game Preferences") }, + { key: "theme", label: _("Themes & Visuals") }, { key: "chat", label: _("Chat Preferences") }, { key: "gotv", label: interpolate(_("%s Preferences"), ["GoTV"]) }, { @@ -149,6 +151,9 @@ export function Settings(): JSX.Element { case "game": SelectedPage = GamePreferences; break; + case "theme": + SelectedPage = ThemePreferences; + break; case "chat": SelectedPage = ChatPreferences; break; diff --git a/src/views/Settings/ThemePreferences.styl b/src/views/Settings/ThemePreferences.styl new file mode 100644 index 0000000000..6c0dcc0d30 --- /dev/null +++ b/src/views/Settings/ThemePreferences.styl @@ -0,0 +1,50 @@ +/* + * Copyright (C) Online-Go.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +.ThemePreferences { + .select-custom { + font-size: larger; + font-weight: bold; + display: inline-flex; + align-items: center; + justify-content: center; + align-content: center; + + .title { + display: inline-block; + padding-right: 1rem; + } + } + + .small.board { + padding-left: 0; + } + + .MiniGoban.inline { + height: 1.5rem; + width: 10rem; + margin-left: 3rem; + } + + div.with-sample-goban { + .left { + display: inline-flex; + align-items: center; + width: 12rem; + } + } +} \ No newline at end of file diff --git a/src/views/Settings/ThemePreferences.tsx b/src/views/Settings/ThemePreferences.tsx new file mode 100644 index 0000000000..f82c70d013 --- /dev/null +++ b/src/views/Settings/ThemePreferences.tsx @@ -0,0 +1,434 @@ +/* + * Copyright (C) Online-Go.com + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import * as React from "react"; +import * as data from "data"; +import { _, pgettext } from "translate"; +import { usePreference } from "preferences"; +import * as preferences from "preferences"; +import { PreferenceDropdown, PreferenceLine } from "SettingsCommon"; +//import { ReportsCenterSettings } from "ReportsCenter"; +//import * as preferences from "preferences"; +import { + GobanBlackThemePicker, + GobanWhiteThemePicker, + GobanBoardThemePicker, + GobanCustomBoardPicker, + GobanCustomWhitePicker, + GobanCustomBlackPicker, +} from "GobanThemePicker"; +import { useData } from "hooks"; +import { MiniGoban } from "MiniGoban"; +import { GobanEngineConfig } from "goban"; +import { Toggle } from "Toggle"; + +const sample_board_data: GobanEngineConfig = { + width: 3, + height: 3, + + initial_state: { + black: "abbbbaga", // cspell: disable-line + white: "acbccccbca", // cspell: disable-line + }, + removed: + "aa" + // cspell: disable-line + "abbbba" + // cspell: disable-line + "aa", // cspell: disable-line + marks: { + "score-white": "aaabbabb", // cspell: disable-line + }, +}; + +export function ThemePreferences(): JSX.Element | null { + const [stone_removal_graphic, _setStoneRemovalGraphic] = usePreference( + "goban-theme-removal-graphic", + ); + const [theme] = useData("theme", "light"); + + const [removal_scale] = usePreference("goban-theme-removal-scale"); + const setTheme = React.useCallback((theme: string) => { + data.set("theme", theme, data.Replication.REMOTE_OVERWRITES_LOCAL); + }, []); + const setThemeLight = React.useCallback(setTheme.bind(null, "light"), [setTheme]); + const setThemeDark = React.useCallback(setTheme.bind(null, "dark"), [setTheme]); + const setThemeAccessible = React.useCallback(setTheme.bind(null, "accessible"), [setTheme]); + const setStoneRemovalGraphic = React.useCallback((graphic: "square" | "x") => { + console.log("Setting with remote replication"); + preferences.set( + "goban-theme-removal-graphic", + graphic, + data.Replication.REMOTE_OVERWRITES_LOCAL, + ); + }, []); + const [board_labeling, setBoardLabeling] = usePreference("board-labeling"); + const [label_positioning, setLabelPositioning] = usePreference("label-positioning"); + const [visual_undo_request_indicator, setVisualUndoRequestIndicator] = usePreference( + "visual-undo-request-indicator", + ); + const [last_move_opacity, _setLastMoveOpacity] = usePreference("last-move-opacity"); + /* + const [variation_stone_opacity, _setVariationStoneOpacity] = + usePreference("variation-stone-opacity"); + */ + + //const [show_move_numbers, _setShowMoveNumbers] = usePreference("show-move-numbers"); + const [show_variation_move_numbers, _setShowVariationMoveNumbers] = usePreference( + "show-variation-move-numbers", + ); + + const toggleRemovalScale = React.useCallback((tf: boolean) => { + if (tf) { + preferences.set( + "goban-theme-removal-scale", + 0.9, + data.Replication.REMOTE_OVERWRITES_LOCAL, + ); + } else { + preferences.set( + "goban-theme-removal-scale", + 1.0, + data.Replication.REMOTE_OVERWRITES_LOCAL, + ); + } + }, []); + + /* + const toggleShowMoveNumbers = React.useCallback((tf: boolean) => { + _setShowMoveNumbers(tf); + }, []); + */ + + const toggleShowVariationMoveNumbers = React.useCallback((tf: boolean) => { + _setShowVariationMoveNumbers(tf); + }, []); + + function setLastMoveOpacity(ev: React.ChangeEvent) { + const value = parseFloat(ev.target.value); + + if (value >= 0.0 && value <= 1.0) { + _setLastMoveOpacity(value); + } + } + + /* + function setVariationStoneOpacity(ev: React.ChangeEvent) { + const value = parseFloat(ev.target.value); + + if (value >= 0.0 && value <= 1.0) { + _setVariationStoneOpacity(value); + } + } + */ + + const [canvas_enabled, setCanvasEnabled] = useData("experiments.canvas"); + const enable_svg = canvas_enabled !== "enabled"; + + const sample_goban_key = + (enable_svg ? "svg" : "canvas") + + board_labeling + + label_positioning + + //stone_removal_graphic + + //removal_scale + + visual_undo_request_indicator + + last_move_opacity; + + return ( +
+ +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +
+
+ +
+ + +
+
+ + + + + + + + + + +
+
+ + +   + {last_move_opacity} + +
+ + +
+
+ + {/* + +
+
+ +
+ + +
+
+ */} + + +
+
+ +
+ + +
+
+ + {/* + + + +   + {variation_stone_opacity} + + + */} + + +
+
+ +
+ + +
+
+ + + { + //data.set("experiments.svg", tf ? "enabled" : undefined); + setCanvasEnabled(tf ? "enabled" : undefined); + }} + /> + +
+ ); +} diff --git a/yarn.lock b/yarn.lock index 0b69edd0e7..5edb25e491 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5995,10 +5995,10 @@ glogg@^2.2.0: dependencies: sparkles "^2.1.0" -goban@=8.3.19: - version "8.3.19" - resolved "https://registry.yarnpkg.com/goban/-/goban-8.3.19.tgz#a409176318d68db476281a95ea76a94537825973" - integrity sha512-eC/Qa62SmmHVCU7qtv5k8/iUDi9aXZDHogb0BREWaBogwd9t6kchWxFEihODTpRjoJqy7FU6Q0wS8UbLdFzj/Q== +goban@=8.3.22: + version "8.3.22" + resolved "https://registry.yarnpkg.com/goban/-/goban-8.3.22.tgz#296e321cc263153c8be6ba160e21407b1b5f6f82" + integrity sha512-UtjWP4toxcwC59Xp/25jUhbHO9x026UXyJb4rNVbpVIXTtbKPo5ALel4g+hwHnXJHfA7gz4WTstZ+SziTt5Llw== dependencies: eventemitter3 "^5.0.0"