From 6f3899fb5fb850b4f75054eb295fb0c6b11238d7 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Tue, 31 Oct 2023 14:32:46 +0100 Subject: [PATCH] add editor view, with possibility to edit name --- ci/.drone.yml | 1 - ci/build_react.msh | 2 +- front/components/TitleInput.tsx | 24 +++++++++++++++ front/style/colors.css | 8 +++++ front/style/editor.css | 20 +++++++++++++ front/style/title_input.css | 17 +++++++++++ front/views/Editor.tsx | 29 ++++++++++++++++++ package.json | 1 + public/api/index.php | 29 ++++++++++++++++++ public/index.php | 9 +++++- sql/setup-tables.sql | 8 +++-- src/Api/TacticEndpoint.php | 44 +++++++++++++++++++++++++++ src/Controller/EditorController.php | 36 ++++++++++++++++++++++ src/Data/TacticInfo.php | 36 ++++++++++++++++++++++ src/Gateway/TacticInfoGateway.php | 46 +++++++++++++++++++++++++++++ src/react-display-file.php | 12 ++++++++ vite.config.ts | 4 +-- 17 files changed, 319 insertions(+), 7 deletions(-) create mode 100644 front/components/TitleInput.tsx create mode 100644 front/style/colors.css create mode 100644 front/style/editor.css create mode 100644 front/style/title_input.css create mode 100644 front/views/Editor.tsx create mode 100644 public/api/index.php create mode 100644 src/Api/TacticEndpoint.php create mode 100644 src/Controller/EditorController.php create mode 100644 src/Data/TacticInfo.php create mode 100644 src/Gateway/TacticInfoGateway.php diff --git a/ci/.drone.yml b/ci/.drone.yml index e3ae556..e4d5305 100644 --- a/ci/.drone.yml +++ b/ci/.drone.yml @@ -46,4 +46,3 @@ steps: commands: - chmod +x ci/deploy.sh - ci/deploy.sh - diff --git a/ci/build_react.msh b/ci/build_react.msh index 9bd9d52..d253914 100755 --- a/ci/build_react.msh +++ b/ci/build_react.msh @@ -27,5 +27,5 @@ echo "];" >> views-mappings.php chmod +r views-mappings.php // moshell does not supports file patterns -bash <<< "mv dist/* public/* front/assets/ /outputs/public/" +bash <<< "mv dist/* public/* front/assets/ front/style/ /outputs/public/" mv views-mappings.php /outputs/ diff --git a/front/components/TitleInput.tsx b/front/components/TitleInput.tsx new file mode 100644 index 0000000..655071d --- /dev/null +++ b/front/components/TitleInput.tsx @@ -0,0 +1,24 @@ +import React, {useRef, useState} from "react"; +import "../style/title_input.css"; + +export default function TitleInput({default_value, on_validated}: { + default_value: string, + on_validated: (a: string) => void +}) { + const [value, setValue] = useState(default_value); + const ref = useRef(null); + + return ( + setValue(event.target.value)} + onBlur={_ => on_validated(value)} + onKeyDown={event => { + if (event.key == 'Enter') + ref.current?.blur(); + }} + /> + ) +} \ No newline at end of file diff --git a/front/style/colors.css b/front/style/colors.css new file mode 100644 index 0000000..34bdbb5 --- /dev/null +++ b/front/style/colors.css @@ -0,0 +1,8 @@ + + +:root { + --main-color: #ffffff; + --second-color: #ccde54; + + --background-color: #d2cdd3; +} \ No newline at end of file diff --git a/front/style/editor.css b/front/style/editor.css new file mode 100644 index 0000000..2ed88d6 --- /dev/null +++ b/front/style/editor.css @@ -0,0 +1,20 @@ +@import "colors.css"; + + +#main { + height: 100%; + width: 100%; + background-color: var(--background-color); +} + +#topbar { + display: flex; + background-color: var(--main-color); + + justify-content: space-between; + align-items: stretch; +} + +.title_input { + width: 25ch; +} \ No newline at end of file diff --git a/front/style/title_input.css b/front/style/title_input.css new file mode 100644 index 0000000..57af59b --- /dev/null +++ b/front/style/title_input.css @@ -0,0 +1,17 @@ +.title_input { + background: transparent; + border-top: none; + border-right: none; + border-left: none; + text-align: center; + + border-bottom-width: 2px; + border-bottom-color: transparent; +} + +.title_input:focus { + outline: none; + + border-bottom-color: blueviolet; +} + diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx new file mode 100644 index 0000000..6fa49e6 --- /dev/null +++ b/front/views/Editor.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import "../style/editor.css"; +import TitleInput from "../components/TitleInput"; + + +export default function Editor({id, name}: { id: number, name: string }) { + return ( +
+
+
LEFT
+ update_tactic_name(id, name)}/> +
RIGHT
+
+
+ ) +} + +function update_tactic_name(id: number, new_name: string) { + //FIXME avoid absolute path as they would not work on staging server + fetch(`/api/tactic/${id}/edit/name`, { + method: "POST", + body: JSON.stringify({ + name: new_name + }) + }).then(response => { + if (!response.ok) + alert("could not update tactic name!") + }) +} \ No newline at end of file diff --git a/package.json b/package.json index 5a18741..0af1e0e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@types/node": "^16.18.59", "@types/react": "^18.2.31", "@types/react-dom": "^18.2.14", + "node-promises": "^0.2.1", "react": "^18.2.0", "react-dom": "^18.2.0", "typescript": "^5.2.2", diff --git a/public/api/index.php b/public/api/index.php new file mode 100644 index 0000000..c0a8f8f --- /dev/null +++ b/public/api/index.php @@ -0,0 +1,29 @@ +setBasePath("/api"); + +$tacticEndpoint = new TacticEndpoint(new TacticInfoGateway($con)); +$router->map("POST", "/tactic/[i:id]/edit/name", fn(int $id) => $tacticEndpoint->update_name($id)); +$router->map("GET", "/tactic/[i:id]", fn(int $id) => $tacticEndpoint->get_tactic_info($id)); +$router->map("POST", "/tactic/new", fn() => $tacticEndpoint->new_tactic()); + +$match = $router->match(); + +if ($match == null) { + echo "404 not found"; + header($_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found'); + exit(1); +} + +call_user_func_array($match['target'], $match['params']); \ No newline at end of file diff --git a/public/index.php b/public/index.php index 4c5290b..91e15f6 100644 --- a/public/index.php +++ b/public/index.php @@ -7,7 +7,10 @@ require "../sql/database.php"; use \Twig\Loader\FilesystemLoader; use App\Connexion; use App\Controller\SampleFormController; +use App\Controller\EditorController; + use App\Gateway\FormResultGateway; +use App\Gateway\TacticInfoGateway; /** * relative path of the index.php's directory from the server's document root. @@ -35,10 +38,14 @@ $router = new AltoRouter(); $router->setBasePath($basePath); $sampleFormController = new SampleFormController(new FormResultGateway($con), $twig); +$editorController = new EditorController(new TacticInfoGateway($con)); + $router->map("GET", "/", fn() => $sampleFormController->displayForm()); $router->map("POST", "/submit", fn() => $sampleFormController->submitForm($_POST)); $router->map("GET", "/twig", fn() => $sampleFormController->displayFormTwig()); $router->map("POST", "/submit-twig", fn() => $sampleFormController->submitFormTwig($_POST)); +$router->map("GET", "/tactic/new", fn() => $editorController->makeNew()); +$router->map("GET", "/tactic/[i:id]/edit", fn(int $id) => $editorController->edit($id)); $match = $router->match(); @@ -49,4 +56,4 @@ if ($match == null) { exit(1); } -call_user_func($match['target']); +call_user_func_array($match['target'], $match['params']); diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index 0c6fbe7..949e902 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -1,8 +1,12 @@ -- drop tables here DROP TABLE IF EXISTS FormEntries; +DROP TABLE IF EXISTS TacticInfo; CREATE TABLE FormEntries(name varchar, description varchar); - - +CREATE TABLE TacticInfo( + id integer PRIMARY KEY AUTOINCREMENT, + name varchar, + creation_date timestamp +); \ No newline at end of file diff --git a/src/Api/TacticEndpoint.php b/src/Api/TacticEndpoint.php new file mode 100644 index 0000000..67af25b --- /dev/null +++ b/src/Api/TacticEndpoint.php @@ -0,0 +1,44 @@ +tactics = $tactics; + } + + + public function update_name(int $tactic_id) { + $request_body = file_get_contents('php://input'); + $data = json_decode($request_body); + + $new_name = $data->name; + + $this->tactics->update($tactic_id, $new_name); + } + + public function new_tactic() { + $request_body = file_get_contents('php://input'); + $data = json_decode($request_body); + + $initial_name = $data->name; + $id = $this->tactics->insert($initial_name)->getId(); + + echo "{id: $id}"; + } + + public function get_tactic_info(int $id) { + $tactic_info = $this->tactics->get($id); + echo json_encode($tactic_info); + } + +} \ No newline at end of file diff --git a/src/Controller/EditorController.php b/src/Controller/EditorController.php new file mode 100644 index 0000000..1c0c2ed --- /dev/null +++ b/src/Controller/EditorController.php @@ -0,0 +1,36 @@ +tactics = $tactics; + } + + private function openEditor(TacticInfo $tactic) { + send_react_front("views/Editor.tsx", ["name" => $tactic->getName(), "id" => $tactic->getId()]); + } + + public function makeNew() { + $info = $this->tactics->insert(self::TACTIC_DEFAULT_NAME); + $this->openEditor($info); + } + + public function edit(int $id) { + $tactic = $this->tactics->get($id); + + $this->openEditor($tactic); + } + +} \ No newline at end of file diff --git a/src/Data/TacticInfo.php b/src/Data/TacticInfo.php new file mode 100644 index 0000000..c8db912 --- /dev/null +++ b/src/Data/TacticInfo.php @@ -0,0 +1,36 @@ +id = $id; + $this->name = $name; + $this->creation_date = $creation_date; + } + + public function getId(): int { + return $this->id; + } + + public function getName(): string { + return $this->name; + } + + public function getCreationDate(): int { + return $this->creation_date; + } + + public function jsonSerialize() { + return get_object_vars($this); + } +} \ No newline at end of file diff --git a/src/Gateway/TacticInfoGateway.php b/src/Gateway/TacticInfoGateway.php new file mode 100644 index 0000000..9cc39db --- /dev/null +++ b/src/Gateway/TacticInfoGateway.php @@ -0,0 +1,46 @@ +con = $con; + } + + public function get(int $id): TacticInfo { + $row = $this->con->fetch( + "SELECT * FROM TacticInfo WHERE id = :id", + [":id" => [$id, PDO::PARAM_INT]] + )[0]; + + return new TacticInfo($id, $row["name"], strtotime($row["creation_date"])); + } + + public function insert(string $name): TacticInfo { + $row = $this->con->fetch( + "INSERT INTO TacticInfo(name, creation_date) VALUES(:name, CURRENT_TIMESTAMP) RETURNING id, creation_date", + [":name" => [$name, PDO::PARAM_STR]] + )[0]; + return new TacticInfo(intval($row["id"]), $name, strtotime($row["creation_date"])); + } + + public function update(int $id, string $name) { + $this->con->exec( + "UPDATE TacticInfo SET name = :name WHERE id = :id", + [ + ":name" => [$name, PDO::PARAM_STR], + ":id" => [$id, PDO::PARAM_INT] + ] + ); + } + +} \ No newline at end of file diff --git a/src/react-display-file.php b/src/react-display-file.php index 55d9656..f937fed 100755 --- a/src/react-display-file.php +++ b/src/react-display-file.php @@ -23,6 +23,17 @@ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> Document + + + + @@ -39,6 +50,7 @@ see ViewRenderer.tsx::renderView for more info renderView(Component, ) + diff --git a/vite.config.ts b/vite.config.ts index 55456dc..af2a9bb 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,7 +6,7 @@ import fs from "fs"; function resolve_entries(dirname: string): [string, string][] { //exclude assets - if (dirname == "front/assets") { + if (dirname == "front/assets" || dirname == "front/style") { return [] } @@ -29,7 +29,7 @@ export default defineConfig({ manifest: true, rollupOptions: { input: Object.fromEntries(resolve_entries("front")), - preserveEntrySignatures: "allow-extension" + preserveEntrySignatures: "allow-extension", } }, plugins: [