From 5d7d69a8f566f876a25ff219799a2c3931b96acb Mon Sep 17 00:00:00 2001 From: Joshua Nascimento Date: Sat, 9 Sep 2023 15:52:50 -0400 Subject: [PATCH 1/8] Initial concept for ingame sgf add to library function --- src/views/Game/GameDock.tsx | 25 ++++++++++- src/views/Game/GameLibraryModal.styl | 44 +++++++++++++++++++ src/views/Game/GameLibraryModal.tsx | 66 ++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/views/Game/GameLibraryModal.styl create mode 100644 src/views/Game/GameLibraryModal.tsx diff --git a/src/views/Game/GameDock.tsx b/src/views/Game/GameDock.tsx index d21531dc56..9365f3010e 100644 --- a/src/views/Game/GameDock.tsx +++ b/src/views/Game/GameDock.tsx @@ -20,7 +20,7 @@ import * as data from "data"; import * as preferences from "preferences"; import { MAX_DOCK_DELAY } from "SettingsCommon"; import { useUser } from "hooks"; -import { api1, post, del } from "requests"; +import { api1, post, del, get } from "requests"; import { Dock } from "Dock"; import { Link } from "react-router-dom"; import { toast } from "toast"; @@ -28,6 +28,7 @@ import { _, pgettext } from "translate"; import { openACLModal } from "ACLModal"; import { openGameLinkModal } from "./GameLinkModal"; import { openGameLogModal } from "./GameLogModal"; +import { openGameLibraryModal } from "./GameLibraryModal"; import { sfx } from "sfx"; import { alert } from "swal_config"; import { challengeFromBoardPosition } from "ChallengeModal/ForkModal"; @@ -311,6 +312,18 @@ export function GameDock({ ? engine.playerToMove() : engine.playerNotToMove(); + const showLibraryModal = () => { + const user = data.get("user"); + const promise = get("library/%%", user.id); + promise + .then(async (library) => { + const test = await library.collections; + console.log(typeof library.collections); + openGameLibraryModal(test); + }) + .catch(errorAlerter); + }; + return ( {(tournament_id || null) && ( @@ -501,6 +514,16 @@ export function GameDock({ {_("Download SGF")} )} + + {/* Flotsam */} + { + + + {_("Yur")} + + + } + {sgf_download_enabled && sgf_with_ai_review_url && ( diff --git a/src/views/Game/GameLibraryModal.styl b/src/views/Game/GameLibraryModal.styl new file mode 100644 index 0000000000..aba2bf3ebc --- /dev/null +++ b/src/views/Game/GameLibraryModal.styl @@ -0,0 +1,44 @@ +/* + * 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 . + */ + +.GameLibraryModal.Modal { + width: 100%; + max-width 40rem; + justify-content: center; + align-items: center; + display: flex; + + + h1 { + text-align: center; + } + + // long specifier to make sure this specific column is not overriden + #collection-list .collection-row .cell.rengo-list-buttons { + text-align: center; + width: 10rem; + padding-right: 1rem; + //vertical-align: baseline; // attempting to line up with rest of cell rows + + + .cell { + padding-left: 0.5em; + display: table-cell; + text-align: left; + position: relative; + } +} \ No newline at end of file diff --git a/src/views/Game/GameLibraryModal.tsx b/src/views/Game/GameLibraryModal.tsx new file mode 100644 index 0000000000..49a5879e11 --- /dev/null +++ b/src/views/Game/GameLibraryModal.tsx @@ -0,0 +1,66 @@ +/* + * 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 { openModal, Modal } from "Modal"; +//import { get } from "requests"; +//import * as data from "data"; +//import { errorAlerter } from "misc"; + +interface Events {} + +interface GameLibraryModalProperties { + userLibrary: any; +} + +export class GameLibraryModal extends Modal { + constructor(props) { + super(props); + } + + render() { + const data: any = JSON.stringify(this.props.userLibrary); + const json = JSON.parse(data); + json.map((data) => { + console.log(data[1]); + }); + return ( +
+
+

Libraries

+ {json.map((data, index) => ( +
+ + + + +

{data[index]}

+
+ + + +
+ ))} +
+
+ ); + } +} + +export function openGameLibraryModal(userLibrary: any): void { + openModal(); +} From 86d22ebb1ed42b0b3665839a39888fc5350d741f Mon Sep 17 00:00:00 2001 From: Joshua Nascimento Date: Tue, 12 Sep 2023 17:52:04 -0400 Subject: [PATCH 2/8] Commit code up to functionality of obtaining current sgf file from api call to see if this works --- src/views/Game/GameDock.tsx | 9 ++++----- src/views/Game/GameLibraryModal.tsx | 28 +++++++++++++++++++--------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/views/Game/GameDock.tsx b/src/views/Game/GameDock.tsx index 9365f3010e..8205caeb81 100644 --- a/src/views/Game/GameDock.tsx +++ b/src/views/Game/GameDock.tsx @@ -318,8 +318,7 @@ export function GameDock({ promise .then(async (library) => { const test = await library.collections; - console.log(typeof library.collections); - openGameLibraryModal(test); + openGameLibraryModal(test, game_id); }) .catch(errorAlerter); }; @@ -515,11 +514,11 @@ export function GameDock({
)} - {/* Flotsam */} + {/* Add To Library functionality */} { - + - {_("Yur")} + {_("Save to Library")} } diff --git a/src/views/Game/GameLibraryModal.tsx b/src/views/Game/GameLibraryModal.tsx index 49a5879e11..2d3808db14 100644 --- a/src/views/Game/GameLibraryModal.tsx +++ b/src/views/Game/GameLibraryModal.tsx @@ -17,14 +17,14 @@ import * as React from "react"; import { openModal, Modal } from "Modal"; -//import { get } from "requests"; -//import * as data from "data"; -//import { errorAlerter } from "misc"; +import { api1 } from "requests"; +import { errorAlerter } from "misc"; interface Events {} interface GameLibraryModalProperties { userLibrary: any; + gameID: number; } export class GameLibraryModal extends Modal { @@ -32,12 +32,22 @@ export class GameLibraryModal extends Modal { + //goto url endpoint that downloads the sgf file + fetch(api1(`games/${this.props.gameID}/sgf`)) + .then((data) => { + // data should contain the sgf file which is automatically downloaded when a user visits the same endpoint + const file = data; + console.log(file); + }) + .catch(errorAlerter); + + // If im correct then we call post("me/games/sgf/%%", collection_id, file) to have this file saved to the given library + }; + render() { const data: any = JSON.stringify(this.props.userLibrary); const json = JSON.parse(data); - json.map((data) => { - console.log(data[1]); - }); return (
@@ -51,7 +61,7 @@ export class GameLibraryModal extends Modal{data[index]} - +
))} @@ -61,6 +71,6 @@ export class GameLibraryModal extends Modal); +export function openGameLibraryModal(userLibrary: any, gameID: number): void { + openModal(); } From 051fe4d03d2d3c94ffed3f7e14116ea16d70efae Mon Sep 17 00:00:00 2001 From: Joshua Nascimento Date: Sun, 17 Sep 2023 15:37:44 -0400 Subject: [PATCH 3/8] Created custom fetch request specifying content-type to correct the 415 error previously occuring --- src/views/Game/GameLibraryModal.tsx | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/views/Game/GameLibraryModal.tsx b/src/views/Game/GameLibraryModal.tsx index 2d3808db14..dd8926c254 100644 --- a/src/views/Game/GameLibraryModal.tsx +++ b/src/views/Game/GameLibraryModal.tsx @@ -32,6 +32,7 @@ export class GameLibraryModal extends Modal { //goto url endpoint that downloads the sgf file fetch(api1(`games/${this.props.gameID}/sgf`)) @@ -44,6 +45,29 @@ export class GameLibraryModal extends Modal response.json()) + .then((data) => { + const blob = new Blob([data as BlobPart]); + const file = new File([blob], "foo.txt", { type: "application/json" }); + return file; + }) + .catch(errorAlerter); + + // This part comes later once i manipulate the response correctly + //post("me/games/sgf/%%", collection_id, gameSGF).catch(errorAlerter); + } render() { const data: any = JSON.stringify(this.props.userLibrary); @@ -61,7 +85,7 @@ export class GameLibraryModal extends Modal{data[index]} - +
))} From 7cdafc65a883e18cb4bae83f8afc9df886b53bfd Mon Sep 17 00:00:00 2001 From: Joshua Nascimento Date: Tue, 19 Sep 2023 21:56:18 -0400 Subject: [PATCH 4/8] Sucessfully implemented barebones feature to add game directly to a pre-existing SGF Library --- src/views/Game/GameDock.tsx | 4 ++-- src/views/Game/GameLibraryModal.tsx | 25 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/views/Game/GameDock.tsx b/src/views/Game/GameDock.tsx index 8205caeb81..b541d79a75 100644 --- a/src/views/Game/GameDock.tsx +++ b/src/views/Game/GameDock.tsx @@ -317,8 +317,8 @@ export function GameDock({ const promise = get("library/%%", user.id); promise .then(async (library) => { - const test = await library.collections; - openGameLibraryModal(test, game_id); + const userLibrary = await library.collections; + openGameLibraryModal(userLibrary, game_id); }) .catch(errorAlerter); }; diff --git a/src/views/Game/GameLibraryModal.tsx b/src/views/Game/GameLibraryModal.tsx index dd8926c254..affff2ea89 100644 --- a/src/views/Game/GameLibraryModal.tsx +++ b/src/views/Game/GameLibraryModal.tsx @@ -17,7 +17,7 @@ import * as React from "react"; import { openModal, Modal } from "Modal"; -import { api1 } from "requests"; +import { api1, post } from "requests"; import { errorAlerter } from "misc"; interface Events {} @@ -48,20 +48,27 @@ export class GameLibraryModal extends Modal response.json()) + .then((response) => response.blob()) .then((data) => { - const blob = new Blob([data as BlobPart]); - const file = new File([blob], "foo.txt", { type: "application/json" }); - return file; + const gameFile = new File([data as BlobPart], `Game# ${this.props.gameID}.sgf`, { + type: "application/x-go-sgf", + lastModified: new Date().getTime(), + }); + const reader = new FileReader(); + reader.readAsText(gameFile); + reader.onload = function () { + //console.log(collection); + }; + post("me/games/sgf/%%", collection[0], gameFile).catch(errorAlerter); }) .catch(errorAlerter); @@ -85,7 +92,7 @@ export class GameLibraryModal extends Modal{data[index]} - + ))} From 6d53307688303489eec1b30bf341f949b8458580 Mon Sep 17 00:00:00 2001 From: Joshua Nascimento Date: Wed, 20 Sep 2023 19:06:21 -0400 Subject: [PATCH 5/8] added text field so user can enter in a custom SGF File name --- src/views/Game/GameLibraryModal.tsx | 45 ++++++++++++----------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/src/views/Game/GameLibraryModal.tsx b/src/views/Game/GameLibraryModal.tsx index affff2ea89..a0c7ce6d7b 100644 --- a/src/views/Game/GameLibraryModal.tsx +++ b/src/views/Game/GameLibraryModal.tsx @@ -19,6 +19,7 @@ import * as React from "react"; import { openModal, Modal } from "Modal"; import { api1, post } from "requests"; import { errorAlerter } from "misc"; +import { _ } from "translate"; interface Events {} @@ -27,27 +28,19 @@ interface GameLibraryModalProperties { gameID: number; } -export class GameLibraryModal extends Modal { +export class GameLibraryModal extends Modal { constructor(props) { super(props); + this.state = { + gameName: "", + }; } - /* - addToLibrary = () => { - //goto url endpoint that downloads the sgf file - fetch(api1(`games/${this.props.gameID}/sgf`)) - .then((data) => { - // data should contain the sgf file which is automatically downloaded when a user visits the same endpoint - const file = data; - console.log(file); - }) - .catch(errorAlerter); - - // If im correct then we call post("me/games/sgf/%%", collection_id, file) to have this file saved to the given library + setGameName = (ev) => { + console.log("HERE " + ev.target.value); + this.setState({ gameName: ev.target.value }); }; - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars async addToLibrary(collection) { const url = api1(`games/${this.props.gameID}/sgf`); await fetch(url, { @@ -59,21 +52,15 @@ export class GameLibraryModal extends Modal response.blob()) .then((data) => { - const gameFile = new File([data as BlobPart], `Game# ${this.props.gameID}.sgf`, { + const sgfName = `${this.state.gameName}.sgf`; + const gameFile = new File([data as BlobPart], sgfName, { type: "application/x-go-sgf", lastModified: new Date().getTime(), }); - const reader = new FileReader(); - reader.readAsText(gameFile); - reader.onload = function () { - //console.log(collection); - }; + this.setState({ gameName: "" }); post("me/games/sgf/%%", collection[0], gameFile).catch(errorAlerter); }) .catch(errorAlerter); - - // This part comes later once i manipulate the response correctly - //post("me/games/sgf/%%", collection_id, gameSGF).catch(errorAlerter); } render() { @@ -83,13 +70,19 @@ export class GameLibraryModal extends Modal

Libraries

- {json.map((data, index) => ( + + {json.map((data) => (
-

{data[index]}

+

{data[1]}

From 6bfcb7b7988475f211b08e2960dcf64081d75b31 Mon Sep 17 00:00:00 2001 From: Joshua Nascimento Date: Wed, 20 Sep 2023 22:10:20 -0400 Subject: [PATCH 6/8] Added ability to create a collection when a user has no collections within their sgf library --- src/views/Game/GameDock.tsx | 2 +- src/views/Game/GameLibraryModal.tsx | 71 +++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/views/Game/GameDock.tsx b/src/views/Game/GameDock.tsx index b541d79a75..1bcc44dbec 100644 --- a/src/views/Game/GameDock.tsx +++ b/src/views/Game/GameDock.tsx @@ -318,7 +318,7 @@ export function GameDock({ promise .then(async (library) => { const userLibrary = await library.collections; - openGameLibraryModal(userLibrary, game_id); + openGameLibraryModal(user.id, userLibrary, game_id); }) .catch(errorAlerter); }; diff --git a/src/views/Game/GameLibraryModal.tsx b/src/views/Game/GameLibraryModal.tsx index a0c7ce6d7b..7b5fb05bd9 100644 --- a/src/views/Game/GameLibraryModal.tsx +++ b/src/views/Game/GameLibraryModal.tsx @@ -24,6 +24,7 @@ import { _ } from "translate"; interface Events {} interface GameLibraryModalProperties { + userID: number; userLibrary: any; gameID: number; } @@ -33,14 +34,25 @@ export class GameLibraryModal extends Modal { - console.log("HERE " + ev.target.value); this.setState({ gameName: ev.target.value }); }; + setCollectionName = (ev) => { + this.setState({ collectionName: ev.target.value }); + }; + async addToLibrary(collection) { const url = api1(`games/${this.props.gameID}/sgf`); await fetch(url, { @@ -63,9 +75,20 @@ export class GameLibraryModal extends Modal { + const collection_id = [res.collection_id]; + console.log("HERE " + collection_id); + this.addToLibrary(collection_id).catch(errorAlerter); + }) + .catch(errorAlerter); + } + render() { - const data: any = JSON.stringify(this.props.userLibrary); - const json = JSON.parse(data); return (
@@ -76,25 +99,43 @@ export class GameLibraryModal extends Modal - {json.map((data) => ( -
- - - - -

{data[1]}

-
+ {this.state.collections.length > 0 ? ( + this.state.collections.map((data) => ( +
+ + + + +

{data[1]}

+
+ + + +
+ )) + ) : ( +
- + +
- ))} + )}
); } } -export function openGameLibraryModal(userLibrary: any, gameID: number): void { - openModal(); +export function openGameLibraryModal(userID: number, userLibrary: any, gameID: number): void { + openModal( + , + ); } From 4383c405f93f16d54e027711d2c9a5d958613dc3 Mon Sep 17 00:00:00 2001 From: Joshua Nascimento Date: Thu, 21 Sep 2023 19:32:31 -0400 Subject: [PATCH 7/8] Basic styling for component as well as checks for default values in textfields --- src/views/Game/GameDock.tsx | 1 + src/views/Game/GameLibraryModal.styl | 47 ++++++++++++++++------------ src/views/Game/GameLibraryModal.tsx | 41 ++++++++++++++++-------- 3 files changed, 56 insertions(+), 33 deletions(-) diff --git a/src/views/Game/GameDock.tsx b/src/views/Game/GameDock.tsx index 1bcc44dbec..7993f3fb83 100644 --- a/src/views/Game/GameDock.tsx +++ b/src/views/Game/GameDock.tsx @@ -313,6 +313,7 @@ export function GameDock({ : engine.playerNotToMove(); const showLibraryModal = () => { + // Pull user data prior to opening modal preventing component from rendering prior to obtaining needed data const user = data.get("user"); const promise = get("library/%%", user.id); promise diff --git a/src/views/Game/GameLibraryModal.styl b/src/views/Game/GameLibraryModal.styl index aba2bf3ebc..ca0fb04e65 100644 --- a/src/views/Game/GameLibraryModal.styl +++ b/src/views/Game/GameLibraryModal.styl @@ -17,28 +17,35 @@ .GameLibraryModal.Modal { width: 100%; - max-width 40rem; - justify-content: center; - align-items: center; + max-width: 40rem; + padding: 1rem; display: flex; - - - h1 { - text-align: center; - } + flex-direction: row; + align-items: center; - // long specifier to make sure this specific column is not overriden - #collection-list .collection-row .cell.rengo-list-buttons { - text-align: center; - width: 10rem; - padding-right: 1rem; - //vertical-align: baseline; // attempting to line up with rest of cell rows + .file-name-inputfield{ + margin: 1rem; + } +} +.collection-list { + display:flex; + width: 100%; + flex-direction: column; + align-content: stretch; + align-items: center; +} +.collection-row { + width: 100% + display:flex; + justify-content: space-evenly; + align-items: center; +} - .cell { - padding-left: 0.5em; - display: table-cell; - text-align: left; - position: relative; - } +.cell { + max-width: 20rem; + padding-left: 0.5em; + display: table-cell; + text-align: left; + position: relative; } \ No newline at end of file diff --git a/src/views/Game/GameLibraryModal.tsx b/src/views/Game/GameLibraryModal.tsx index 7b5fb05bd9..a57de3de8f 100644 --- a/src/views/Game/GameLibraryModal.tsx +++ b/src/views/Game/GameLibraryModal.tsx @@ -40,9 +40,9 @@ export class GameLibraryModal extends Modal { @@ -53,36 +53,50 @@ export class GameLibraryModal extends Modal { + this.setState({ gameName: "" }); + this.setState({ collectionName: "" }); + }; + async addToLibrary(collection) { const url = api1(`games/${this.props.gameID}/sgf`); await fetch(url, { method: "GET", - // Specifically set header otherwise 415 error + // Specify the content-type of the request otherwise the "games/%%/sgf" endpoint will respond with a 415 error code headers: { "Content-Type": "application/json", }, }) .then((response) => response.blob()) .then((data) => { - const sgfName = `${this.state.gameName}.sgf`; - const gameFile = new File([data as BlobPart], sgfName, { + // Pull sgfName from value implemented by user setting a default value if empty + let gameName = this.state.gameName; + if (this.state.gameName === "") { + gameName = `My Game #${this.props.gameID}`; + } + const gameFile = new File([data as BlobPart], `${gameName}.sgf`, { type: "application/x-go-sgf", lastModified: new Date().getTime(), }); - this.setState({ gameName: "" }); + // Create post request adding SGF File to the given collection using collection's id post("me/games/sgf/%%", collection[0], gameFile).catch(errorAlerter); + this.resetTextFields(); + this.close(); // Closes modal after creating post request }) .catch(errorAlerter); } - createCollection() { - console.log("clicked!"); + async createCollection() { + if (this.state.collectionName === "") { + await this.setState({ collectionName: "My_Collection" }); + } + // Create a new library within the user's libraries post("library/%%/collections", this.props.userID, { name: this.state.collectionName, }) .then((res) => { + // Pull the newly created collection's id to be used to identify which collection we add our SGF File too const collection_id = [res.collection_id]; - console.log("HERE " + collection_id); this.addToLibrary(collection_id).catch(errorAlerter); }) .catch(errorAlerter); @@ -94,17 +108,16 @@ export class GameLibraryModal extends Modal

Libraries

+ {/* If user has collections in their library map and render them. Otherwise; allow user to create collection */} {this.state.collections.length > 0 ? ( this.state.collections.map((data) => (
- - -

{data[1]}

@@ -122,6 +135,8 @@ export class GameLibraryModal extends Modal + + From 5b426dce87e9e1dc99085306fa12ab257baec397 Mon Sep 17 00:00:00 2001 From: Joshua Nascimento Date: Sat, 30 Sep 2023 01:02:09 -0400 Subject: [PATCH 8/8] changed position of save to library in tooltip bar --- src/views/Game/GameDock.tsx | 14 ++++---- src/views/Game/GameLibraryModal.tsx | 56 +++++++++++++++++++---------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/src/views/Game/GameDock.tsx b/src/views/Game/GameDock.tsx index 7993f3fb83..07ea2107b0 100644 --- a/src/views/Game/GameDock.tsx +++ b/src/views/Game/GameDock.tsx @@ -516,13 +516,6 @@ export function GameDock({ )} {/* Add To Library functionality */} - { - - - {_("Save to Library")} - - - } {sgf_download_enabled && sgf_with_ai_review_url && ( @@ -531,6 +524,13 @@ export function GameDock({ )} + { + + + {_("Save to Library")} + + + } {sgf_download_enabled && sgf_with_comments_url && ( diff --git a/src/views/Game/GameLibraryModal.tsx b/src/views/Game/GameLibraryModal.tsx index a57de3de8f..8603012d2a 100644 --- a/src/views/Game/GameLibraryModal.tsx +++ b/src/views/Game/GameLibraryModal.tsx @@ -36,6 +36,7 @@ export class GameLibraryModal extends Modal

Libraries

- + {/* If user has collections in their library map and render them. Otherwise; allow user to create collection */} {this.state.collections.length > 0 ? ( - this.state.collections.map((data) => ( -
- -

{data[1]}

-
- - - -
- )) + <> + + + + + + {this.state.collections.map((data) => ( +
+ +

{data[1]}

+
+ + + +
+ ))} + ) : ( -
+
+ + + + +