Merge pull request 'Add a guest mode for the editor' (#38) from guest-edit into master
continuous-integration/drone/push Build is passing Details

Reviewed-on: #38
pull/39/head
Maxime BATISTA 1 year ago
commit 5e7d6fa4bd

@ -4,6 +4,10 @@ export interface SaveState {
} }
export class SaveStates { 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 = { static readonly Ok: SaveState = {
className: "save-state-ok", className: "save-state-ok",
message: "saved", message: "saved",

@ -85,6 +85,7 @@
color: green; color: green;
} }
.save-state-saving { .save-state-saving,
.save-state-guest {
color: gray; color: gray;
} }

@ -27,9 +27,12 @@ const ERROR_STYLE: CSSProperties = {
borderColor: "red", borderColor: "red",
} }
const GUEST_MODE_CONTENT_STORAGE_KEY = "guest_mode_content"
const GUEST_MODE_TITLE_STORAGE_KEY = "guest_mode_title"
export interface EditorViewProps { export interface EditorViewProps {
tactic: Tactic tactic: Tactic
onContentChange: (tactic: TacticContent) => Promise<boolean> onContentChange: (tactic: TacticContent) => Promise<SaveState>
onNameChange: (name: string) => Promise<boolean> onNameChange: (name: string) => Promise<boolean>
} }
@ -50,31 +53,55 @@ export default function Editor({
name: string name: string
content: string content: string
}) { }) {
const isInGuestMode = id == -1
const storage_content = localStorage.getItem(GUEST_MODE_CONTENT_STORAGE_KEY)
const editorContent =
isInGuestMode && storage_content != null ? storage_content : content
const storage_name = localStorage.getItem(GUEST_MODE_TITLE_STORAGE_KEY)
const editorName = isInGuestMode && storage_name != null ? storage_name : name
return ( return (
<EditorView <EditorView
tactic={{ name, id, content: JSON.parse(content) }} tactic={{ name: editorName, id, content: JSON.parse(editorContent) }}
onContentChange={(content: TacticContent) => onContentChange={async (content: TacticContent) => {
fetchAPI(`tactic/${id}/save`, { content }).then((r) => r.ok) if (isInGuestMode) {
localStorage.setItem(
GUEST_MODE_CONTENT_STORAGE_KEY,
JSON.stringify(content),
)
return SaveStates.Guest
} }
onNameChange={(name: string) => return fetchAPI(`tactic/${id}/save`, { content }).then((r) =>
fetchAPI(`tactic/${id}/edit/name`, { name }).then((r) => r.ok) r.ok ? SaveStates.Ok : SaveStates.Err,
)
}}
onNameChange={async (name: string) => {
if (isInGuestMode) {
localStorage.setItem(GUEST_MODE_TITLE_STORAGE_KEY, name)
return true //simulate that the name has been changed
} }
return fetchAPI(`tactic/${id}/edit/name`, { name }).then(
(r) => r.ok,
)
}}
/> />
) )
} }
function EditorView({ function EditorView({
tactic: { name, content: initialContent }, tactic: { id, name, content: initialContent },
onContentChange, onContentChange,
onNameChange, onNameChange,
}: EditorViewProps) { }: EditorViewProps) {
const isInGuestMode = id == -1
const [style, setStyle] = useState<CSSProperties>({}) const [style, setStyle] = useState<CSSProperties>({})
const [content, setContent, saveState] = useContentState( const [content, setContent, saveState] = useContentState(
initialContent, initialContent,
(content) => isInGuestMode ? SaveStates.Guest : SaveStates.Ok,
onContentChange(content).then((success) => onContentChange,
success ? SaveStates.Ok : SaveStates.Err,
),
) )
const [allies, setAllies] = useState( const [allies, setAllies] = useState(
getRackPlayers(Team.Allies, content.players), getRackPlayers(Team.Allies, content.players),
@ -220,12 +247,14 @@ function getRackPlayers(team: Team, players: Player[]): RackedPlayer[] {
function useContentState<S>( function useContentState<S>(
initialContent: S, initialContent: S,
initialSaveState: SaveState,
saveStateCallback: (s: S) => Promise<SaveState>, saveStateCallback: (s: S) => Promise<SaveState>,
): [S, Dispatch<SetStateAction<S>>, SaveState] { ): [S, Dispatch<SetStateAction<S>>, SaveState] {
const [content, setContent] = useState(initialContent) const [content, setContent] = useState(initialContent)
const [savingState, setSavingState] = useState(SaveStates.Ok) const [savingState, setSavingState] = useState(initialSaveState)
const setContentSynced = useCallback((newState: SetStateAction<S>) => { const setContentSynced = useCallback(
(newState: SetStateAction<S>) => {
setContent((content) => { setContent((content) => {
const state = const state =
typeof newState === "function" typeof newState === "function"
@ -239,7 +268,9 @@ function useContentState<S>(
} }
return state return state
}) })
}, [saveStateCallback]) },
[saveStateCallback],
)
return [content, setContentSynced, savingState] return [content, setContentSynced, savingState]
} }

@ -88,7 +88,9 @@ function getRoutes(): AltoRouter {
//tactic-related //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]/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/[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 //team-related
$ar->map("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s))); $ar->map("GET", "/team/new", Action::auth(fn(SessionHandle $s) => getTeamController()->displayCreateTeam($s)));

@ -19,7 +19,7 @@ class API {
if ($response instanceof JsonHttpResponse) { if ($response instanceof JsonHttpResponse) {
header('Content-type: application/json'); header('Content-type: application/json');
echo $response->getJson(); echo $response->getJson();
} else if (get_class($response) != HttpResponse::class) { } elseif (get_class($response) != HttpResponse::class) {
throw new Exception("API returned unknown Http Response"); throw new Exception("API returned unknown Http Response");
} }
} }

@ -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 * 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 * @param SessionHandle $session
* @return ViewHttpResponse the editor view * @return ViewHttpResponse the editor view
*/ */
public function createNew(SessionHandle $session): ViewHttpResponse { 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); return $this->openEditorFor($tactic);
} }
@ -55,6 +73,4 @@ class EditorController {
return $this->openEditorFor($tactic); return $this->openEditorFor($tactic);
} }
} }

@ -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 int $id
* @param SessionHandle $session * @param SessionHandle $session
* @return HttpResponse * @return HttpResponse

@ -99,5 +99,4 @@ class TacticInfoGateway {
]); ]);
return $stmnt->rowCount() == 1; return $stmnt->rowCount() == 1;
} }
} }

Loading…
Cancel
Save