add editor view, with possibility to edit name

pull/8/head
maxime.batista 2 years ago committed by Override-6
parent 947cda494a
commit 6f3899fb5f
Signed by untrusted user who does not match committer: maxime.batista
GPG Key ID: 8002CC4B4DD9ECA5

@ -46,4 +46,3 @@ steps:
commands:
- chmod +x ci/deploy.sh
- ci/deploy.sh

@ -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/

@ -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<HTMLInputElement>(null);
return (
<input className="title_input"
ref={ref}
type="text"
value={value}
onChange={event => setValue(event.target.value)}
onBlur={_ => on_validated(value)}
onKeyDown={event => {
if (event.key == 'Enter')
ref.current?.blur();
}}
/>
)
}

@ -0,0 +1,8 @@
:root {
--main-color: #ffffff;
--second-color: #ccde54;
--background-color: #d2cdd3;
}

@ -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;
}

@ -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;
}

@ -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 (
<div id="main">
<div id="topbar">
<div>LEFT</div>
<TitleInput default_value={name} on_validated={name => update_tactic_name(id, name)}/>
<div>RIGHT</div>
</div>
</div>
)
}
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!")
})
}

@ -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",

@ -0,0 +1,29 @@
<?php
require "../../config.php";
require "../../vendor/autoload.php";
require "../../sql/database.php";
use App\Api\TacticEndpoint;
use App\Connexion;
use App\Gateway\TacticInfoGateway;
$con = new Connexion(get_database());
$router = new AltoRouter();
$router->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']);

@ -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']);

@ -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
);

@ -0,0 +1,44 @@
<?php
namespace App\Api;
use App\Gateway\TacticInfoGateway;
/**
* API endpoint related to tactics
*/
class TacticEndpoint {
private TacticInfoGateway $tactics;
/**
* @param TacticInfoGateway $tactics
*/
public function __construct(TacticInfoGateway $tactics) {
$this->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);
}
}

@ -0,0 +1,36 @@
<?php
namespace App\Controller;
use App\Data\TacticInfo;
use App\Gateway\TacticInfoGateway;
class EditorController {
const TACTIC_DEFAULT_NAME = "Nouvelle tactique";
private TacticInfoGateway $tactics;
/**
* @param TacticInfoGateway $tactics
*/
public function __construct(TacticInfoGateway $tactics) {
$this->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);
}
}

@ -0,0 +1,36 @@
<?php
namespace App\Data;
class TacticInfo implements \JsonSerializable {
private int $id;
private string $name;
private int $creation_date;
/**
* @param int $id
* @param string $name
* @param int $creation_date
*/
public function __construct(int $id, string $name, int $creation_date) {
$this->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);
}
}

@ -0,0 +1,46 @@
<?php
namespace App\Gateway;
use App\Connexion;
use App\Data\TacticInfo;
use \PDO;
class TacticInfoGateway {
private Connexion $con;
/**
* @param Connexion $con
*/
public function __construct(Connexion $con) {
$this->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]
]
);
}
}

@ -23,6 +23,17 @@
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- remove default screen margin,
html and body to take full screen size -->
<style>
body, html, #root {
height: 100%;
width: 100%;
margin: 0;
}
</style>
</head>
<body>
@ -39,6 +50,7 @@ see ViewRenderer.tsx::renderView for more info
renderView(Component, <?= json_encode($arguments) ?>)
</script>
<script>
</script>

@ -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: [

Loading…
Cancel
Save