diff --git a/front/components/editor/SavingState.tsx b/front/components/editor/SavingState.tsx index df03628..68c2285 100644 --- a/front/components/editor/SavingState.tsx +++ b/front/components/editor/SavingState.tsx @@ -4,6 +4,10 @@ export interface SaveState { } export class SaveStates { + static readonly Guest: SaveState = { + className: "save-state-guest", + message: "you are not connected, your changes will not be saved.", + } static readonly Ok: SaveState = { className: "save-state-ok", message: "saved", diff --git a/front/style/editor.css b/front/style/editor.css index d832fc3..eefa561 100644 --- a/front/style/editor.css +++ b/front/style/editor.css @@ -85,6 +85,7 @@ color: green; } -.save-state-saving { +.save-state-saving, +.save-state-guest { color: gray; } diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index dddf5cc..13f7684 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -29,7 +29,7 @@ const ERROR_STYLE: CSSProperties = { export interface EditorViewProps { tactic: Tactic - onContentChange: (tactic: TacticContent) => Promise + onContentChange: (tactic: TacticContent) => Promise onNameChange: (name: string) => Promise } @@ -50,31 +50,43 @@ export default function Editor({ name: string content: string }) { + const isInGuestMode = id == -1 + return ( - fetchAPI(`tactic/${id}/save`, { content }).then((r) => r.ok) - } - onNameChange={(name: string) => - fetchAPI(`tactic/${id}/edit/name`, { name }).then((r) => r.ok) - } + onContentChange={async (content: TacticContent) => { + if (isInGuestMode) { + return SaveStates.Guest + } + return fetchAPI(`tactic/${id}/save`, { content }).then((r) => + r.ok ? SaveStates.Ok : SaveStates.Err, + ) + }} + onNameChange={async (name: string) => { + if (isInGuestMode) { + return true //simulate that the name has been changed + } + return fetchAPI(`tactic/${id}/edit/name`, { name }).then( + (r) => r.ok, + ) + }} /> ) } function EditorView({ - tactic: { name, content: initialContent }, + tactic: { id, name, content: initialContent }, onContentChange, onNameChange, }: EditorViewProps) { + const isInGuestMode = id == -1 + const [style, setStyle] = useState({}) const [content, setContent, saveState] = useContentState( initialContent, - (content) => - onContentChange(content).then((success) => - success ? SaveStates.Ok : SaveStates.Err, - ), + isInGuestMode ? SaveStates.Guest : SaveStates.Ok, + onContentChange, ) const [allies, setAllies] = useState( getRackPlayers(Team.Allies, content.players), @@ -220,26 +232,30 @@ function getRackPlayers(team: Team, players: Player[]): RackedPlayer[] { function useContentState( initialContent: S, + initialSaveState: SaveState, saveStateCallback: (s: S) => Promise, ): [S, Dispatch>, SaveState] { const [content, setContent] = useState(initialContent) - const [savingState, setSavingState] = useState(SaveStates.Ok) + const [savingState, setSavingState] = useState(initialSaveState) - const setContentSynced = useCallback((newState: SetStateAction) => { - setContent((content) => { - const state = - typeof newState === "function" - ? (newState as (state: S) => S)(content) - : newState - if (state !== content) { - setSavingState(SaveStates.Saving) - saveStateCallback(state) - .then(setSavingState) - .catch(() => setSavingState(SaveStates.Err)) - } - return state - }) - }, [saveStateCallback]) + const setContentSynced = useCallback( + (newState: SetStateAction) => { + setContent((content) => { + const state = + typeof newState === "function" + ? (newState as (state: S) => S)(content) + : newState + if (state !== content) { + setSavingState(SaveStates.Saving) + saveStateCallback(state) + .then(setSavingState) + .catch(() => setSavingState(SaveStates.Err)) + } + return state + }) + }, + [saveStateCallback], + ) return [content, setContentSynced, savingState] } diff --git a/public/index.php b/public/index.php index c31e289..78ee4d6 100644 --- a/public/index.php +++ b/public/index.php @@ -88,7 +88,9 @@ function getRoutes(): AltoRouter { //tactic-related $ar->map("GET", "/tactic/[i:id]/view", Action::auth(fn(int $id, SessionHandle $s) => getVisualizerController()->openVisualizer($id, $s))); $ar->map("GET", "/tactic/[i:id]/edit", Action::auth(fn(int $id, SessionHandle $s) => getEditorController()->openEditor($id, $s))); - $ar->map("GET", "/tactic/new", Action::auth(fn(SessionHandle $s) => getEditorController()->createNew($s))); + // don't require an authentication to run this action. + // If the user is not connected, the tactic will never save. + $ar->map("GET", "/tactic/new", Action::noAuth(fn(SessionHandle $s) => getEditorController()->createNew($s))); //team-related $ar->map("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); diff --git a/src/Api/API.php b/src/Api/API.php index f79e1b5..da00749 100644 --- a/src/Api/API.php +++ b/src/Api/API.php @@ -19,7 +19,7 @@ class API { if ($response instanceof JsonHttpResponse) { header('Content-type: application/json'); echo $response->getJson(); - } else if (get_class($response) != HttpResponse::class) { + } elseif (get_class($response) != HttpResponse::class) { throw new Exception("API returned unknown Http Response"); } } diff --git a/src/App/Controller/EditorController.php b/src/App/Controller/EditorController.php index d2a2bc4..3994093 100644 --- a/src/App/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -28,13 +28,31 @@ class EditorController { ]); } + /** + * @return ViewHttpResponse the editor view for a test tactic. + */ + private function openTestEditor(): ViewHttpResponse { + return ViewHttpResponse::react("views/Editor.tsx", [ + "id" => -1, //-1 id means that the editor will not support saves + "content" => '{"players": []}', + "name" => TacticModel::TACTIC_DEFAULT_NAME, + ]); + } + /** * creates a new empty tactic, with default name + * If the given session does not contain a connected account, + * open a test editor. * @param SessionHandle $session * @return ViewHttpResponse the editor view */ public function createNew(SessionHandle $session): ViewHttpResponse { - $tactic = $this->model->makeNewDefault($session->getAccount()->getId()); + $account = $session->getAccount(); + + if ($account == null) { + return $this->openTestEditor(); + } + $tactic = $this->model->makeNewDefault($account->getId()); return $this->openEditorFor($tactic); } @@ -55,6 +73,4 @@ class EditorController { return $this->openEditorFor($tactic); } - - } diff --git a/src/App/Controller/VisualizerController.php b/src/App/Controller/VisualizerController.php index 271c4e9..631468e 100644 --- a/src/App/Controller/VisualizerController.php +++ b/src/App/Controller/VisualizerController.php @@ -20,7 +20,7 @@ class VisualizerController { } /** - * opens a visualisation page for the tactic specified by its identifier in the url. + * Opens a visualisation page for the tactic specified by its identifier in the url. * @param int $id * @param SessionHandle $session * @return HttpResponse diff --git a/src/Core/Gateway/TacticInfoGateway.php b/src/Core/Gateway/TacticInfoGateway.php index 6b66f2c..447c7a5 100644 --- a/src/Core/Gateway/TacticInfoGateway.php +++ b/src/Core/Gateway/TacticInfoGateway.php @@ -99,5 +99,4 @@ class TacticInfoGateway { ]); return $stmnt->rowCount() == 1; } - }