From 7289a956b34332f8b83406e7a0c2a38aa09c241b Mon Sep 17 00:00:00 2001 From: maxime Date: Mon, 25 Mar 2024 15:45:40 +0100 Subject: [PATCH 1/5] add tactic preview in home --- src/assets/court/full_court.svg | 156 ++++++++++++------------ src/assets/court/half_court.svg | 85 +++++++------ src/components/Visualizer.tsx | 30 ++++- src/components/arrows/BendableArrow.tsx | 12 +- src/components/editor/BasketCourt.tsx | 6 +- src/domains/TacticContentDomains.ts | 21 ++-- src/pages/Editor.tsx | 21 ++-- src/pages/HomePage.tsx | 118 +++++++----------- src/pages/VisualizerPage.tsx | 12 +- src/style/court.css | 23 ++-- src/style/editor.css | 3 - src/style/home/home.css | 42 +++++++ src/style/home/personnal_space.css | 7 ++ src/style/visualizer.css | 12 +- 14 files changed, 308 insertions(+), 240 deletions(-) diff --git a/src/assets/court/full_court.svg b/src/assets/court/full_court.svg index 5bfc0de..07fe19f 100644 --- a/src/assets/court/full_court.svg +++ b/src/assets/court/full_court.svg @@ -1,77 +1,77 @@ - - - - - - - - + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - + + + + + - + @@ -79,9 +79,9 @@ - + - + @@ -89,9 +89,9 @@ - + - + @@ -99,9 +99,9 @@ - + - + @@ -109,9 +109,9 @@ - + - + @@ -119,9 +119,9 @@ - + - + @@ -129,7 +129,7 @@ - + diff --git a/src/assets/court/half_court.svg b/src/assets/court/half_court.svg index f621f93..e42536f 100644 --- a/src/assets/court/half_court.svg +++ b/src/assets/court/half_court.svg @@ -1,45 +1,45 @@ - - - - - - - + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - + @@ -47,9 +47,9 @@ - + - + @@ -57,9 +57,9 @@ - + - + @@ -67,7 +67,6 @@ - diff --git a/src/components/Visualizer.tsx b/src/components/Visualizer.tsx index 69a941a..691c53f 100644 --- a/src/components/Visualizer.tsx +++ b/src/components/Visualizer.tsx @@ -26,13 +26,19 @@ import { CourtAction } from "./editor/CourtAction.tsx" import { BasketCourt, Court } from "./editor/BasketCourt.tsx" import { TacticService } from "../service/MutableTacticService.ts" import { useAppFetcher } from "../App.tsx" +import { mapIdentifiers } from "../domains/TacticContentDomains.ts" export interface VisualizerProps { tacticId: number stepId?: number + visualizerId: string | number } -export function Visualizer({ tacticId, stepId }: VisualizerProps) { +export function Visualizer({ + visualizerId, + tacticId, + stepId, +}: VisualizerProps) { const [panicMessage, setPanicMessage] = useState(null) const [courtType, setCourtType] = useState() const [stepsTree, setStepsTree] = useState() @@ -68,6 +74,7 @@ export function Visualizer({ tacticId, stepId }: VisualizerProps) { return ( (null) const [content, setContent] = useState(null) - const [parentContent, setParentContent] = useState() + const [parentContent, setParentContent] = useState(null) - const isNotInit = !content || !parentContent + const isNotInit = !content useEffect(() => { async function init() { @@ -116,12 +125,21 @@ export function StepVisualizer({ parentContent = parentResult } - setContent(contentResult) - setParentContent(parentContent) + setContent( + mapIdentifiers(contentResult, (id) => `${id}-${visualizerId}`), + ) + if (parentContent) { + setParentContent( + mapIdentifiers( + parentContent, + (id) => `${id}-${visualizerId}-parent`, + ), + ) + } } if (isNotInit) init() - }, [isNotInit, service, stepId, stepsTree]) + }, [isNotInit, visualizerId, service, stepId, stepsTree]) if (panicMessage) { return

{panicMessage}

diff --git a/src/components/arrows/BendableArrow.tsx b/src/components/arrows/BendableArrow.tsx index b730bbb..9443a9d 100644 --- a/src/components/arrows/BendableArrow.tsx +++ b/src/components/arrows/BendableArrow.tsx @@ -544,14 +544,22 @@ export default function BendableArrow({
{style?.head?.call(style)}
{style?.tail?.call(style)}
diff --git a/src/components/editor/BasketCourt.tsx b/src/components/editor/BasketCourt.tsx index 903767f..b728ecb 100644 --- a/src/components/editor/BasketCourt.tsx +++ b/src/components/editor/BasketCourt.tsx @@ -65,9 +65,6 @@ export function BasketCourt({ origin={previewAction.origin} color={previewAction.isInvalid ? "red" : "black"} isEditable={true} - //do nothing on interacted, not really possible as it's a preview arrow - onActionDeleted={() => {}} - onActionChanges={() => {}} /> )} @@ -76,9 +73,10 @@ export function BasketCourt({ export function Court({ courtType }: { courtType: CourtType }) { const CourtSvg = courtType === "PLAIN" ? PlainCourt : HalfCourt + const courtSpecificClassName = courtType === "PLAIN" ? "plain-court" : "half-court" return (
- +
) } diff --git a/src/domains/TacticContentDomains.ts b/src/domains/TacticContentDomains.ts index bd256c7..b2aebb1 100644 --- a/src/domains/TacticContentDomains.ts +++ b/src/domains/TacticContentDomains.ts @@ -499,13 +499,20 @@ export function drainTerminalStateOnChildContent( } export function mapToParentContent(content: StepContent): StepContent { + return mapIdentifiers(content, (id) => id + "-parent") +} + +export function mapIdentifiers( + content: StepContent, + f: (id: string) => string, +): StepContent { function mapToParentActions(actions: Action[]): Action[] { return actions.map((a) => ({ ...a, - target: a.target + "-parent", + target: typeof a.target === "string" ? f(a.target) : a.target, segments: a.segments.map((s) => ({ ...s, - next: typeof s.next === "string" ? s.next + "-parent" : s.next, + next: typeof s.next === "string" ? f(s.next) : s.next, })), })) } @@ -517,10 +524,10 @@ export function mapToParentContent(content: StepContent): StepContent { if (p.type == "player") { return { ...p, - id: p.id + "-parent", + id: f(p.id), actions: mapToParentActions(p.actions), path: p.path && { - items: p.path.items.map((p) => p + "-parent"), + items: p.path.items.map(f), }, } } @@ -528,10 +535,10 @@ export function mapToParentContent(content: StepContent): StepContent { ...p, pos: p.pos.type == "follows" - ? { ...p.pos, attach: p.pos.attach + "-parent" } + ? { ...p.pos, attach: f(p.pos.attach) } : p.pos, - id: p.id + "-parent", - originPlayerId: p.originPlayerId + "-parent", + id: f(p.id), + originPlayerId: f(p.originPlayerId), actions: mapToParentActions(p.actions), } }), diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index 7a9a929..0f80915 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -158,6 +158,7 @@ export default function Editor({ guestMode }: EditorProps) { interface EditorPageWrapperProps { service: MutableTacticService + openVisualizer(): void } @@ -759,17 +760,15 @@ function EditorPage({ />
-
- } - courtRef={courtRef} - previewAction={previewAction} - renderComponent={renderComponent} - renderActions={renderActions} - /> -
+ } + courtRef={courtRef} + previewAction={previewAction} + renderComponent={renderComponent} + renderActions={renderActions} + />
) diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 1a0d656..1aecb4b 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -3,6 +3,7 @@ import { useNavigate } from "react-router-dom" import { useEffect, useState } from "react" import { User } from "../model/User.ts" import { useAppFetcher } from "../App.tsx" +import { Visualizer } from "../components/Visualizer.tsx" interface Tactic { id: number @@ -44,48 +45,42 @@ export default function HomePage() { tactics!.sort((a, b) => b.creationDate - a.creationDate) const lastTactics = tactics.slice(0, 5) - return ( - - ) + return } function Home({ lastTactics, - allTactics, + tactics, teams, }: { lastTactics: Tactic[] - allTactics: Tactic[] + tactics: Tactic[] teams: Team[] }) { return (
- +
) } function Body({ lastTactics, - allTactics, + tactics, teams, }: { lastTactics: Tactic[] - allTactics: Tactic[] + tactics: Tactic[] teams: Team[] }) { const widthPersonalSpace = 78 const widthSideMenu = 100 - widthPersonalSpace return (
- +
@@ -94,11 +89,11 @@ function Body({ function SideMenu({ width, - lastTactics, + tactics, teams, }: { width: number - lastTactics: Tactic[] + tactics: Tactic[] teams: Team[] }) { return ( @@ -108,8 +103,8 @@ function SideMenu({ width: width + "%", }}>
- - + +
) @@ -117,10 +112,10 @@ function SideMenu({ function PersonalSpace({ width, - allTactics, + tactics, }: { width: number - allTactics: Tactic[] + tactics: Tactic[] }) { return (
- +
) } @@ -142,69 +137,46 @@ function TitlePersonalSpace() { ) } -function TableData({ allTactics }: { allTactics: Tactic[] }) { - const nbRow = Math.floor(allTactics.length / 3) + 1 - const listTactic = Array(nbRow) - for (let i = 0; i < nbRow; i++) { - listTactic[i] = Array(0) - } - let i = 0 - let j = 0 - allTactics.forEach((tactic) => { - listTactic[i].push(tactic) - j++ - if (j === 3) { - i++ - j = 0 - } - }) +function TacticGrid({ tactics }: { tactics: Tactic[] }) { + return ( +
+ {tactics.map((team) => ( + + ))} +
+ ) +} +function TacticCard({ tactic }: { tactic: Tactic }) { const navigate = useNavigate() - - i = 0 - while (i < nbRow) { - listTactic[i] = listTactic[i].map((tactic: Tactic) => ( - { - navigate("/tactic/" + tactic.id + "/edit") - }}> - {truncateString(tactic.name, 25)} - - )) - i++ - } - if (nbRow == 1) { - if (listTactic[0].length < 3) { - for (let i = 0; i <= 3 - listTactic[0].length; i++) { - listTactic[0].push() - } - } - } - - return listTactic.map((tactic, rowIndex) => ( - {tactic} - )) + return ( +
+
navigate(`/tactic/${tactic.id}/edit`)}> + +
+

{tactic.name}

+
+ ) } -function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) { +function BodyPersonalSpace({ tactics }: { tactics: Tactic[] }) { return (
- {allTactics.length == 0 ? ( + {tactics.length == 0 ? (

Aucune tactique créée !

) : ( - - - - -
+ )}
) } -function Team({ teams }: { teams: Team[] }) { +function LastTeamsSideMenu({ teams }: { teams: Team[] }) { const navigate = useNavigate() return (
@@ -219,7 +191,7 @@ function Team({ teams }: { teams: Team[] }) { ) } -function Tactic({ lastTactics }: { lastTactics: Tactic[] }) { +function LastTacticsSideMenu({ tactics }: { tactics: Tactic[] }) { const navigate = useNavigate() return ( @@ -233,7 +205,7 @@ function Tactic({ lastTactics }: { lastTactics: Tactic[] }) { +
- + ) } diff --git a/src/pages/VisualizerPage.tsx b/src/pages/VisualizerPage.tsx index 489dcab..971a0dd 100644 --- a/src/pages/VisualizerPage.tsx +++ b/src/pages/VisualizerPage.tsx @@ -177,11 +177,13 @@ function VisualizerPageContent({ const contentNode = (
- +
+ +
) diff --git a/src/style/court.css b/src/style/court.css index 77ca11c..c2d391b 100644 --- a/src/style/court.css +++ b/src/style/court.css @@ -1,24 +1,33 @@ -.court-image-div { - position: relative; - background-color: white; - height: 80vh; -} - .court-container { display: flex; align-content: center; align-items: center; justify-content: center; - background-color: black; + background-color: white; + padding: 5%; +} + +.court-container:has(.plain-court) { + width: 80%; +} + +.court-container:has(.half-court) { + height: 80%; } .court-image { height: 100%; width: 100%; + background-color: white; user-select: none; } .court-image * { stroke: var(--selected-team-secondarycolor); } + +.half-court { + max-height: 70vh; + width: fit-content; +} \ No newline at end of file diff --git a/src/style/editor.css b/src/style/editor.css index 8c20276..58e3808 100644 --- a/src/style/editor.css +++ b/src/style/editor.css @@ -102,10 +102,7 @@ } #court-div { - background-color: var(--background-color); - height: 100%; - width: 100%; display: flex; align-items: center; diff --git a/src/style/home/home.css b/src/style/home/home.css index 479773a..ea208d7 100644 --- a/src/style/home/home.css +++ b/src/style/home/home.css @@ -42,3 +42,45 @@ body { margin-left: 5%; margin-top: 5%; } + +#tactics-grid { + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 6px; +} + +.tactic-card { + position: relative; + display: flex; + + align-items: center; + justify-content: center; + + flex-direction: column; + width: 32%; +} + +.tactic-card-title { + pointer-events: none; + z-index: 1000; + position: absolute; + padding: 0 10px 0 10px; + border-radius: 200px; + background: rgba(236, 235, 235, 0.87); +} + +.tactic-card-preview { + display: flex; + justify-content: center; + cursor: pointer; + overflow: hidden; +} + +.tactic-card-preview .court-container { + overflow: hidden; +} + +.tactic-card-preview * { + pointer-events: none; +} diff --git a/src/style/home/personnal_space.css b/src/style/home/personnal_space.css index 6dc71b1..2f398d8 100644 --- a/src/style/home/personnal_space.css +++ b/src/style/home/personnal_space.css @@ -16,6 +16,13 @@ border: 3px var(--home-main-color) solid; border-radius: 0.5cap; align-self: center; + overflow-y: scroll; + -ms-overflow-style: none; + scrollbar-width: none; +} + +#body-personal-space::-webkit-scrollbar { + display: none; } #body-personal-space > p { diff --git a/src/style/visualizer.css b/src/style/visualizer.css index e992ea8..9abfed7 100644 --- a/src/style/visualizer.css +++ b/src/style/visualizer.css @@ -27,7 +27,7 @@ display: flex; justify-content: center; align-items: center; - width: 100%; + width: 50%; height: 100%; } @@ -70,3 +70,13 @@ overflow: scroll; height: 100%; } + +#court-div { + height: 100%; + width: 70%; + + display: flex; + align-items: center; + justify-content: center; + align-content: center; +} From 98eed72af655c00feacdca37e452d7b1cb660d13 Mon Sep 17 00:00:00 2001 From: maxime Date: Mon, 25 Mar 2024 17:09:18 +0100 Subject: [PATCH 2/5] add possibility to delete a tactic from the home page --- src/assets/icon/bin.svg | 1 + src/pages/HomePage.tsx | 166 ++++++++++++++++++++++------------- src/pages/VisualizerPage.tsx | 2 +- src/style/home/home.css | 24 ++++- src/style/visualizer.css | 6 +- 5 files changed, 133 insertions(+), 66 deletions(-) create mode 100644 src/assets/icon/bin.svg diff --git a/src/assets/icon/bin.svg b/src/assets/icon/bin.svg new file mode 100644 index 0000000..1ef1c60 --- /dev/null +++ b/src/assets/icon/bin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 1aecb4b..e99a32e 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,9 +1,9 @@ import "../style/home/home.css" import { useNavigate } from "react-router-dom" -import { useEffect, useState } from "react" -import { User } from "../model/User.ts" +import { createContext, Dispatch, useContext, useEffect, useReducer } from "react" import { useAppFetcher } from "../App.tsx" import { Visualizer } from "../components/Visualizer.tsx" +import BinSvg from "../assets/icon/bin.svg?react" interface Tactic { id: number @@ -19,12 +19,55 @@ interface Team { second_color: string } +enum HomePageStateActionKind { + UPDATE_TACTICS = "UPDATE_TACTICS", + UPDATE_TEAMS = "UPDATE_TEAMS", + INIT = "INIT" +} + +type HomePageStateAction = { + type: HomePageStateActionKind.UPDATE_TACTICS, + tactics: Tactic[] +} | { + type: HomePageStateActionKind.UPDATE_TEAMS, + teams: Team[] +} | { + type: HomePageStateActionKind.INIT, + state: HomePageState +} + +interface HomePageState { + tactics: Tactic[] + teams: Team[] +} + +function homePageStateReducer(state: HomePageState, action: HomePageStateAction): HomePageState { + switch (action.type) { + case HomePageStateActionKind.UPDATE_TACTICS: + return { ...state!, tactics: action.tactics } + + case HomePageStateActionKind.UPDATE_TEAMS: + return { ...state!, teams: action.teams } + + case HomePageStateActionKind.INIT: + return action.state + } +} + +interface HomeStateContextMutable { + state: HomePageState, + dispatch: Dispatch +} + +const HomeStateContext = createContext(null) + +function useHomeState() { + return useContext(HomeStateContext) +} + export default function HomePage() { - type UserDataResponse = { user?: User; tactics: Tactic[]; teams: Team[] } - const [{ tactics, teams }, setInfo] = useState({ - tactics: [], - teams: [], - }) + const [state, dispatch] = useReducer(homePageStateReducer, { tactics: [], teams: [] }) + const navigate = useNavigate() const fetcher = useAppFetcher() @@ -32,70 +75,50 @@ export default function HomePage() { useEffect(() => { async function initUserData() { const response = await fetcher.fetchAPIGet("user-data") + if (response.status == 401) { navigate("/login") return // if unauthorized } - setInfo(await response.json()) + type UserDataResponse = { teams: Team[], tactics: Tactic[] } + const { teams, tactics }: UserDataResponse = await response.json() + tactics.sort((a, b) => b.creationDate - a.creationDate) + dispatch({ type: HomePageStateActionKind.INIT, state: { teams, tactics } }) } initUserData() }, [fetcher, navigate]) - tactics!.sort((a, b) => b.creationDate - a.creationDate) - const lastTactics = tactics.slice(0, 5) - return + return ( + + + + ) } -function Home({ - lastTactics, - tactics, - teams, -}: { - lastTactics: Tactic[] - tactics: Tactic[] - teams: Team[] -}) { +function Home() { return (
- +
) } -function Body({ - lastTactics, - tactics, - teams, -}: { - lastTactics: Tactic[] - tactics: Tactic[] - teams: Team[] -}) { +function Body() { const widthPersonalSpace = 78 const widthSideMenu = 100 - widthPersonalSpace return (
- +
) } -function SideMenu({ - width, - tactics, - teams, -}: { - width: number - tactics: Tactic[] - teams: Team[] -}) { +function SideMenu({ width }: { width: number }) { return (
- - + +
) } function PersonalSpace({ - width, - tactics, -}: { - width: number - tactics: Tactic[] -}) { + width, + }: { width: number }) { return (
- +
) } @@ -149,6 +168,8 @@ function TacticGrid({ tactics }: { tactics: Tactic[] }) { function TacticCard({ tactic }: { tactic: Tactic }) { const navigate = useNavigate() + const fetcher = useAppFetcher() + const { state: { tactics }, dispatch } = useHomeState()! return (
-

{tactic.name}

+
+

{tactic.name}

+ { + const response = await fetcher.fetchAPI(`tactics/${tactic.id}`, {}, "DELETE") + if (!response.ok) { + throw Error(`Cannot delete tactic ${tactic.id}!`) + } + dispatch({ + type: HomePageStateActionKind.UPDATE_TACTICS, + tactics: tactics.filter(t => t.id !== tactic.id), + }) + }} + /> +
) } -function BodyPersonalSpace({ tactics }: { tactics: Tactic[] }) { +function BodyPersonalSpace() { + const tactics = useHomeState()!.state.tactics return (
{tactics.length == 0 ? ( @@ -176,7 +213,7 @@ function BodyPersonalSpace({ tactics }: { tactics: Tactic[] }) { ) } -function LastTeamsSideMenu({ teams }: { teams: Team[] }) { +function LastTeamsSideMenu() { const navigate = useNavigate() return (
@@ -186,12 +223,12 @@ function LastTeamsSideMenu({ teams }: { teams: Team[] }) { +
- +
) } -function LastTacticsSideMenu({ tactics }: { tactics: Tactic[] }) { +function LastTacticsSideMenu() { const navigate = useNavigate() return ( @@ -205,26 +242,29 @@ function LastTacticsSideMenu({ tactics }: { tactics: Tactic[] }) { + - + ) } -function SetButtonTactic({ tactics }: { tactics: Tactic[] }) { +function SetButtonTactic() { + const tactics = useHomeState()!.state.tactics.slice(0, 5) const lastTactics = tactics.map((tactic) => ( - + )) return
{lastTactics}
} -function SetButtonTeam({ teams }: { teams: Team[] }) { +function SetButtonTeam() { + const teams = useHomeState()!.state.teams + const listTeam = teams.map((team) => ( - + )) return
{listTeam}
} -function ButtonTeam({ team }: { team: Team }) { +function TeamCard({ team }: { team: Team }) { const name = truncateString(team.name, 20) const navigate = useNavigate() @@ -242,7 +282,7 @@ function ButtonTeam({ team }: { team: Team }) { ) } -function ButtonLastTactic({ tactic }: { tactic: Tactic }) { +function LastTacticCard({ tactic }: { tactic: Tactic }) { const name = truncateString(tactic.name, 20) const navigate = useNavigate() diff --git a/src/pages/VisualizerPage.tsx b/src/pages/VisualizerPage.tsx index 971a0dd..9a66766 100644 --- a/src/pages/VisualizerPage.tsx +++ b/src/pages/VisualizerPage.tsx @@ -205,7 +205,7 @@ function VisualizerPageContent({ -
+
{isStepsTreeVisible ? ( Date: Tue, 26 Mar 2024 00:35:31 +0100 Subject: [PATCH 3/5] add JSON export --- src/App.tsx | 5 +- src/assets/icon/account.png | Bin 507 -> 0 bytes src/assets/icon/export.svg | 3 + src/assets/icon/json.svg | 1 + src/components/Visualizer.tsx | 2 +- src/components/editor/BasketCourt.tsx | 27 +-- src/components/editor/CourtAction.tsx | 2 +- src/components/editor/StepsTree.tsx | 2 +- src/domains/ActionsDomains.ts | 2 +- src/domains/PlayerDomains.ts | 2 +- src/domains/StepsDomain.ts | 6 +- src/domains/TacticContentDomains.ts | 2 +- src/editor/ContentVersions.ts | 2 +- src/model/tactic/Action.ts | 2 +- src/model/tactic/CourtObjects.ts | 2 +- src/model/tactic/Player.ts | 2 +- src/model/tactic/{Tactic.ts => TacticInfo.ts} | 7 + src/pages/Editor.tsx | 36 +++- src/pages/HomePage.tsx | 138 +++++++++++----- src/pages/NewTacticPage.tsx | 2 +- src/pages/VisualizerPage.tsx | 33 +++- src/pages/popup/ExportTacticPopup.tsx | 154 ++++++++++++++++++ src/service/APITacticService.ts | 2 +- src/service/LocalStorageTacticService.ts | 2 +- src/service/MutableTacticService.ts | 6 +- src/style/court.css | 2 +- src/style/editor.css | 11 +- src/style/export_tactic_popup.css | 65 ++++++++ src/style/home/home.css | 52 ++++-- src/style/visualizer.css | 12 +- src/visualizer/VisualizerState.ts | 6 +- 31 files changed, 487 insertions(+), 103 deletions(-) delete mode 100644 src/assets/icon/account.png create mode 100644 src/assets/icon/export.svg create mode 100644 src/assets/icon/json.svg rename src/model/tactic/{Tactic.ts => TacticInfo.ts} (86%) create mode 100644 src/pages/popup/ExportTacticPopup.tsx create mode 100644 src/style/export_tactic_popup.css diff --git a/src/App.tsx b/src/App.tsx index f1ab122..2901b45 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,7 +23,6 @@ import { import { BASE } from "./Constants.ts" import { Authentication, Fetcher } from "./app/Fetcher.ts" import { User } from "./model/User.ts" -import { VisualizerPage } from "./pages/VisualizerPage.tsx" const HomePage = lazy(() => import("./pages/HomePage.tsx")) const LoginPage = lazy(() => import("./pages/LoginPage.tsx")) @@ -33,6 +32,7 @@ const CreateTeamPage = lazy(() => import("./pages/CreateTeamPage.tsx")) const TeamPanelPage = lazy(() => import("./pages/TeamPanel.tsx")) const NewTacticPage = lazy(() => import("./pages/NewTacticPage.tsx")) const Editor = lazy(() => import("./pages/Editor.tsx")) +const VisualizerPage = lazy(() => import("./pages/VisualizerPage.tsx")) const Settings = lazy(() => import("./pages/Settings.tsx")) const TOKEN_REFRESH_INTERVAL_MS = 60 * 1000 @@ -148,14 +148,13 @@ export default function App() { element={suspense( - , , )} /> , + )} /> 25Zg zH(#4B7cD{=|_c`Ze4&X!=@5HN#C|jLhiPtH;F@Sq1U)Y5Y zec9tJ#vni|@uhg*Wat3YqZB|i&IR^40)$AR2LtW&hXADI#ZC|PCD+i78Q~lv9e{t^ zp8CAm`qa+Lw?+^F9G&pm0N@cIJ2v@7-<5b6f!Cr- zUj+EE%c;&v)qs>?a7aBnr2yoeqQ@>G9o|I|WE5a5MF}TIMWpRh+(nlva>?TwL&z4H zs?(+vv+HB6ku&TnD=fy*X^i8rL(V%&wy9tAhlcWhh8^ia|0_7M^^)VL@HCVTJJ-*S z3Y6loBMsjBZeaLDhZI7S!_F^95W0&!BZRa;qywWA)!)LFaTKS1I2fiTIU?r(ty4-R xzf9@^Oy1~O&WHv5`c*S^2f6S`~nTSY#9m*AXNYW002ovPDHLkV1k67-3$N# diff --git a/src/assets/icon/export.svg b/src/assets/icon/export.svg new file mode 100644 index 0000000..d87d8dc --- /dev/null +++ b/src/assets/icon/export.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icon/json.svg b/src/assets/icon/json.svg new file mode 100644 index 0000000..0761585 --- /dev/null +++ b/src/assets/icon/json.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Visualizer.tsx b/src/components/Visualizer.tsx index 691c53f..7a6dad4 100644 --- a/src/components/Visualizer.tsx +++ b/src/components/Visualizer.tsx @@ -12,7 +12,7 @@ import { StepContent, StepInfoNode, TacticComponent, -} from "../model/tactic/Tactic.ts" +} from "../model/tactic/TacticInfo.ts" import { getParent } from "../domains/StepsDomain.ts" import { computeRelativePositions, diff --git a/src/components/editor/BasketCourt.tsx b/src/components/editor/BasketCourt.tsx index b728ecb..37596c0 100644 --- a/src/components/editor/BasketCourt.tsx +++ b/src/components/editor/BasketCourt.tsx @@ -6,10 +6,12 @@ import { ComponentId, CourtType, TacticComponent, -} from "../../model/tactic/Tactic" +} from "../../model/tactic/TacticInfo.ts" import PlainCourt from "../../assets/court/full_court.svg?react" import HalfCourt from "../../assets/court/half_court.svg?react" +import "../../style/court.css" + export interface BasketCourtProps { components: TacticComponent[] parentComponents: TacticComponent[] | null @@ -28,16 +30,16 @@ export interface ActionPreview extends Action { } export function BasketCourt({ - components, - parentComponents, - previewAction, + components, + parentComponents, + previewAction, - renderComponent, - renderActions, + renderComponent, + renderActions, - courtImage, - courtRef, -}: BasketCourtProps) { + courtImage, + courtRef, + }: BasketCourtProps) { const [court, setCourt] = useState(courtRef.current) //force update once the court reference is set @@ -73,10 +75,9 @@ export function BasketCourt({ export function Court({ courtType }: { courtType: CourtType }) { const CourtSvg = courtType === "PLAIN" ? PlainCourt : HalfCourt - const courtSpecificClassName = courtType === "PLAIN" ? "plain-court" : "half-court" + const courtSpecificClassName = + courtType === "PLAIN" ? "plain-court" : "half-court" return ( -
- -
+ ) } diff --git a/src/components/editor/CourtAction.tsx b/src/components/editor/CourtAction.tsx index 54a6be2..0c20c26 100644 --- a/src/components/editor/CourtAction.tsx +++ b/src/components/editor/CourtAction.tsx @@ -2,7 +2,7 @@ import { Action, ActionKind } from "../../model/tactic/Action" import BendableArrow from "../arrows/BendableArrow" import { RefObject } from "react" import { MoveToHead, ScreenHead } from "../actions/ArrowAction" -import { ComponentId } from "../../model/tactic/Tactic" +import { ComponentId } from "../../model/tactic/TacticInfo.ts" export interface CourtActionProps { origin: ComponentId diff --git a/src/components/editor/StepsTree.tsx b/src/components/editor/StepsTree.tsx index 0029a5c..1729e59 100644 --- a/src/components/editor/StepsTree.tsx +++ b/src/components/editor/StepsTree.tsx @@ -1,5 +1,5 @@ import "../../style/steps_tree.css" -import { StepInfoNode } from "../../model/tactic/Tactic" +import { StepInfoNode } from "../../model/tactic/TacticInfo.ts" import BendableArrow from "../arrows/BendableArrow" import { ReactNode, useMemo, useRef } from "react" import AddSvg from "../../assets/icon/add.svg?react" diff --git a/src/domains/ActionsDomains.ts b/src/domains/ActionsDomains.ts index 2fa3229..02364a4 100644 --- a/src/domains/ActionsDomains.ts +++ b/src/domains/ActionsDomains.ts @@ -9,7 +9,7 @@ import { ComponentId, StepContent, TacticComponent, -} from "../model/tactic/Tactic.ts" +} from "../model/tactic/TacticInfo.ts" import { overlaps } from "../geo/Box.ts" import { Action, ActionKind, moves } from "../model/tactic/Action.ts" import { removeBall, updateComponent } from "./TacticContentDomains.ts" diff --git a/src/domains/PlayerDomains.ts b/src/domains/PlayerDomains.ts index 8c13c64..0dc565e 100644 --- a/src/domains/PlayerDomains.ts +++ b/src/domains/PlayerDomains.ts @@ -9,7 +9,7 @@ import { ComponentId, StepContent, TacticComponent, -} from "../model/tactic/Tactic.ts" +} from "../model/tactic/TacticInfo.ts" import { removeComponent, updateComponent } from "./TacticContentDomains.ts" import { diff --git a/src/domains/StepsDomain.ts b/src/domains/StepsDomain.ts index a120c23..25b21d8 100644 --- a/src/domains/StepsDomain.ts +++ b/src/domains/StepsDomain.ts @@ -1,4 +1,4 @@ -import { StepInfoNode } from "../model/tactic/Tactic.ts" +import { StepInfoNode } from "../model/tactic/TacticInfo.ts" export function addStepNode( root: StepInfoNode, @@ -110,3 +110,7 @@ export function getParent( } return null } + +export function countSteps(tree: StepInfoNode): number { + return 1 + tree.children.reduce((tot, node) => tot + countSteps(node), 0) +} diff --git a/src/domains/TacticContentDomains.ts b/src/domains/TacticContentDomains.ts index b2aebb1..af8d2dd 100644 --- a/src/domains/TacticContentDomains.ts +++ b/src/domains/TacticContentDomains.ts @@ -18,7 +18,7 @@ import { ComponentId, StepContent, TacticComponent, -} from "../model/tactic/Tactic.ts" +} from "../model/tactic/TacticInfo.ts" import { overlaps } from "../geo/Box.ts" import { RackedCourtObject, RackedPlayer } from "../editor/RackedItems.ts" diff --git a/src/editor/ContentVersions.ts b/src/editor/ContentVersions.ts index 26cf6f1..3521eab 100644 --- a/src/editor/ContentVersions.ts +++ b/src/editor/ContentVersions.ts @@ -1,4 +1,4 @@ -import { StepContent } from "../model/tactic/Tactic.ts" +import { StepContent } from "../model/tactic/TacticInfo.ts" export class ContentVersions { private index = 0 diff --git a/src/model/tactic/Action.ts b/src/model/tactic/Action.ts index d4e459f..b26ecae 100644 --- a/src/model/tactic/Action.ts +++ b/src/model/tactic/Action.ts @@ -1,5 +1,5 @@ import { Pos } from "../../geo/Pos" -import { ComponentId } from "./Tactic" +import { ComponentId } from "./TacticInfo.ts" export enum ActionKind { SCREEN = "SCREEN", diff --git a/src/model/tactic/CourtObjects.ts b/src/model/tactic/CourtObjects.ts index 55eaf6f..6c46a64 100644 --- a/src/model/tactic/CourtObjects.ts +++ b/src/model/tactic/CourtObjects.ts @@ -1,4 +1,4 @@ -import { Component, Frozable } from "./Tactic" +import { Component, Frozable } from "./TacticInfo.ts" import { Pos } from "../../geo/Pos.ts" export const BALL_ID = "ball" diff --git a/src/model/tactic/Player.ts b/src/model/tactic/Player.ts index bd781ac..fe32ef3 100644 --- a/src/model/tactic/Player.ts +++ b/src/model/tactic/Player.ts @@ -1,4 +1,4 @@ -import { Component, ComponentId, Frozable } from "./Tactic" +import { Component, ComponentId, Frozable } from "./TacticInfo.ts" import { Pos } from "../../geo/Pos.ts" export type PlayerId = string diff --git a/src/model/tactic/Tactic.ts b/src/model/tactic/TacticInfo.ts similarity index 86% rename from src/model/tactic/Tactic.ts rename to src/model/tactic/TacticInfo.ts index ccf6d43..0138351 100644 --- a/src/model/tactic/Tactic.ts +++ b/src/model/tactic/TacticInfo.ts @@ -2,6 +2,12 @@ import { Player, PlayerPhantom } from "./Player" import { Action } from "./Action" import { CourtObject } from "./CourtObjects" +export interface Tactic { + readonly name: string + readonly courtType: CourtType + readonly root: TacticStep +} + export interface TacticInfo { readonly id: number readonly name: string @@ -12,6 +18,7 @@ export interface TacticInfo { export interface TacticStep { readonly stepId: number readonly content: StepContent + readonly children: TacticStep[] } export interface StepContent { diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index 0f80915..aaad380 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -22,7 +22,7 @@ import { StepContent, StepInfoNode, TacticComponent, -} from "../model/tactic/Tactic" +} from "../model/tactic/TacticInfo.ts" import SavingState, { SaveState, @@ -97,12 +97,14 @@ import SplitLayout from "../components/SplitLayout.tsx" import { MutableTacticService, ServiceError, + TacticService, } from "../service/MutableTacticService.ts" import { LocalStorageTacticService } from "../service/LocalStorageTacticService.ts" import { APITacticService } from "../service/APITacticService.ts" import { useNavigate, useParams } from "react-router-dom" import { ContentVersions } from "../editor/ContentVersions.ts" import { useAppFetcher } from "../App.tsx" +import ExportTacticPopup from "./popup/ExportTacticPopup.tsx" const ERROR_STYLE: CSSProperties = { borderColor: "red", @@ -132,6 +134,8 @@ interface EditorService { setName(name: string): Promise openVisualizer(): Promise + + getTacticService(): TacticService } export default function Editor({ guestMode }: EditorProps) { @@ -356,6 +360,10 @@ function EditorPageWrapper({ async openVisualizer(): Promise { openVisualizer() }, + + getTacticService(): TacticService { + return service + }, } }, [stepsTree, service, stepsVersions, setStepContent, openVisualizer]) @@ -423,10 +431,7 @@ function EditorPage({ const [isStepsTreeVisible, setStepsTreeVisible] = useState(true) - const courtBounds = useCallback( - () => courtRef.current!.getBoundingClientRect(), - [courtRef], - ) + const [showExportPopup, setShowExportPopup] = useState(false) const [editorContentCurtainWidth, setEditorContentCurtainWidth] = useState(80) @@ -438,6 +443,11 @@ function EditorPage({ : new Map() }, [content, courtRef]) + const courtBounds = useCallback( + () => courtRef.current!.getBoundingClientRect(), + [courtRef], + ) + const setComponents = (action: SetStateAction) => { service.setContent((c) => ({ ...c, @@ -809,6 +819,15 @@ function EditorPage({ return (
+ {showExportPopup && ( +
+ setShowExportPopup(false)} + /> +
+ )}
@@ -834,10 +853,15 @@ function EditorPage({ VISUALISER +
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index e99a32e..413e88b 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,9 +1,18 @@ import "../style/home/home.css" import { useNavigate } from "react-router-dom" -import { createContext, Dispatch, useContext, useEffect, useReducer } from "react" +import { + createContext, + Dispatch, + useContext, + useEffect, useMemo, + useReducer, +} from "react" import { useAppFetcher } from "../App.tsx" import { Visualizer } from "../components/Visualizer.tsx" import BinSvg from "../assets/icon/bin.svg?react" +import ExportSvg from "../assets/icon/export.svg?react" +import ExportTacticPopup from "./popup/ExportTacticPopup.tsx" +import { APITacticService } from "../service/APITacticService.ts" interface Tactic { id: number @@ -22,26 +31,40 @@ interface Team { enum HomePageStateActionKind { UPDATE_TACTICS = "UPDATE_TACTICS", UPDATE_TEAMS = "UPDATE_TEAMS", + SET_EXPORTING_TACTIC = "SET_EXPORTING_TACTIC", INIT = "INIT" } -type HomePageStateAction = { - type: HomePageStateActionKind.UPDATE_TACTICS, +type HomePageStateAction = + | { + type: HomePageStateActionKind.UPDATE_TACTICS tactics: Tactic[] -} | { - type: HomePageStateActionKind.UPDATE_TEAMS, +} + | { + type: HomePageStateActionKind.UPDATE_TEAMS teams: Team[] -} | { - type: HomePageStateActionKind.INIT, +} + | { + type: HomePageStateActionKind.INIT state: HomePageState +} | { + type: HomePageStateActionKind.SET_EXPORTING_TACTIC, + tacticId: number | undefined } interface HomePageState { tactics: Tactic[] teams: Team[] + /** + * The home page displays a popup to export a certains tactic + */ + exportingTacticId?: number } -function homePageStateReducer(state: HomePageState, action: HomePageStateAction): HomePageState { +function homePageStateReducer( + state: HomePageState, + action: HomePageStateAction, +): HomePageState { switch (action.type) { case HomePageStateActionKind.UPDATE_TACTICS: return { ...state!, tactics: action.tactics } @@ -49,13 +72,16 @@ function homePageStateReducer(state: HomePageState, action: HomePageStateAction) case HomePageStateActionKind.UPDATE_TEAMS: return { ...state!, teams: action.teams } + case HomePageStateActionKind.SET_EXPORTING_TACTIC: + return { ...state!, exportingTacticId: action.tacticId } + case HomePageStateActionKind.INIT: return action.state } } interface HomeStateContextMutable { - state: HomePageState, + state: HomePageState dispatch: Dispatch } @@ -66,8 +92,10 @@ function useHomeState() { } export default function HomePage() { - const [state, dispatch] = useReducer(homePageStateReducer, { tactics: [], teams: [] }) - + const [state, dispatch] = useReducer(homePageStateReducer, { + tactics: [], + teams: [], + }) const navigate = useNavigate() const fetcher = useAppFetcher() @@ -80,18 +108,32 @@ export default function HomePage() { navigate("/login") return // if unauthorized } - type UserDataResponse = { teams: Team[], tactics: Tactic[] } + type UserDataResponse = { teams: Team[]; tactics: Tactic[] } const { teams, tactics }: UserDataResponse = await response.json() tactics.sort((a, b) => b.creationDate - a.creationDate) - dispatch({ type: HomePageStateActionKind.INIT, state: { teams, tactics } }) + dispatch({ + type: HomePageStateActionKind.INIT, + state: { teams, tactics }, + }) } initUserData() }, [fetcher, navigate]) + const tacticExportService = useMemo(() => + state.exportingTacticId ? new APITacticService(fetcher, state.exportingTacticId!) : null + , [fetcher, state.exportingTacticId], + ) return ( + {tacticExportService &&
+ dispatch({ type: HomePageStateActionKind.SET_EXPORTING_TACTIC, tacticId: undefined })} + /> +
}
) @@ -111,9 +153,7 @@ function Body() { return (
- +
) } @@ -133,9 +173,7 @@ function SideMenu({ width }: { width: number }) { ) } -function PersonalSpace({ - width, - }: { width: number }) { +function PersonalSpace({ width }: { width: number }) { return (
+
navigate(`/tactic/${tactic.id}/edit`)} + >
navigate(`/tactic/${tactic.id}/edit`)}> + className={"tactic-card-preview"}>

{tactic.name}

- { - const response = await fetcher.fetchAPI(`tactics/${tactic.id}`, {}, "DELETE") - if (!response.ok) { - throw Error(`Cannot delete tactic ${tactic.id}!`) - } - dispatch({ - type: HomePageStateActionKind.UPDATE_TACTICS, - tactics: tactics.filter(t => t.id !== tactic.id), - }) - }} - /> +
+ { + e.stopPropagation() + dispatch({ + type: HomePageStateActionKind.SET_EXPORTING_TACTIC, + tacticId: tactic.id, + }) + }} + /> + { + e.stopPropagation() + const response = await fetcher.fetchAPI( + `tactics/${tactic.id}`, + {}, + "DELETE", + ) + if (!response.ok) { + throw Error(`Cannot delete tactic ${tactic.id}!`) + } + dispatch({ + type: HomePageStateActionKind.UPDATE_TACTICS, + tactics: tactics.filter((t) => t.id !== tactic.id), + }) + }} + /> + +
+
) @@ -258,9 +320,7 @@ function SetButtonTactic() { function SetButtonTeam() { const teams = useHomeState()!.state.teams - const listTeam = teams.map((team) => ( - - )) + const listTeam = teams.map((team) => ) return
{listTeam}
} diff --git a/src/pages/NewTacticPage.tsx b/src/pages/NewTacticPage.tsx index 68e4be1..8d83f43 100644 --- a/src/pages/NewTacticPage.tsx +++ b/src/pages/NewTacticPage.tsx @@ -3,7 +3,7 @@ import "../style/new_tactic_panel.css" import plainCourt from "../assets/court/full_court.svg" import halfCourt from "../assets/court/half_court.svg" -import { CourtType } from "../model/tactic/Tactic.ts" +import { CourtType } from "../model/tactic/TacticInfo.ts" import { useCallback } from "react" import { useAppFetcher, useUser } from "../App.tsx" import { useNavigate } from "react-router-dom" diff --git a/src/pages/VisualizerPage.tsx b/src/pages/VisualizerPage.tsx index 9a66766..f27e7ad 100644 --- a/src/pages/VisualizerPage.tsx +++ b/src/pages/VisualizerPage.tsx @@ -1,15 +1,15 @@ import { ServiceError, TacticService } from "../service/MutableTacticService.ts" import { useNavigate, useParams } from "react-router-dom" -import { useCallback, useEffect, useMemo, useState } from "react" import { useVisualizer, VisualizerState, VisualizerStateActionKind, } from "../visualizer/VisualizerState.ts" +import { useCallback, useEffect, useMemo, useState } from "react" import { getParent } from "../domains/StepsDomain.ts" import { mapToParentContent } from "../domains/TacticContentDomains.ts" import StepsTree from "../components/editor/StepsTree.tsx" -import { StepInfoNode } from "../model/tactic/Tactic.ts" +import { StepInfoNode } from "../model/tactic/TacticInfo.ts" import SplitLayout from "../components/SplitLayout.tsx" import { LocalStorageTacticService } from "../service/LocalStorageTacticService.ts" import { APITacticService } from "../service/APITacticService.ts" @@ -17,12 +17,13 @@ import { APITacticService } from "../service/APITacticService.ts" import "../style/visualizer.css" import { VisualizerFrame } from "../components/Visualizer.tsx" import { useAppFetcher } from "../App.tsx" +import ExportTacticPopup from "./popup/ExportTacticPopup.tsx" export interface VisualizerPageProps { guestMode: boolean } -export function VisualizerPage({ guestMode }: VisualizerPageProps) { +export default function VisualizerPage({ guestMode }: VisualizerPageProps) { const { tacticId: idStr } = useParams() const navigate = useNavigate() @@ -48,6 +49,8 @@ interface VisualizerService { selectStep(step: number): Promise openEditor(): Promise + + getTacticService(): TacticService } interface ServedVisualizerPageProps { @@ -101,7 +104,7 @@ function ServedVisualizerPage({ } if (state === null) init() - }, [service, state]) + }, [dispatch, service, state]) const visualizerService: VisualizerService = useMemo( () => ({ @@ -125,8 +128,12 @@ function ServedVisualizerPage({ async openEditor() { openEditor() }, + + getTacticService(): TacticService { + return service + }, }), - [openEditor, service, state], + [dispatch, openEditor, service, state], ) if (panicMessage) { @@ -162,6 +169,8 @@ function VisualizerPageContent({ const [editorContentCurtainWidth, setEditorContentCurtainWidth] = useState(80) + const [showExportPopup, setShowExportPopup] = useState(false) + const stepsTreeNode = (
+ {showExportPopup && ( +
+ setShowExportPopup(false)} + /> +
+ )}

{tacticName}

+
) } +function TacticImportArea() { + const fetcher = useAppFetcher() + const { dispatch } = useHomeState()! + + const handleDrop = useCallback( + async (file: File) => { + importTacticFromFile(fetcher, file, (tactic) => { + dispatch({ + type: HomePageStateActionKind.ADD_TACTIC, + tactic: { + name: tactic.name, + id: tactic.id, + creationDate: new Date().getDate(), + }, + }) + }) + }, + [dispatch, fetcher], + ) + + return ( +
+ +
+ ) +} + function PersonalSpace({ width }: { width: number }) { return (
navigate(`/tactic/${tactic.id}/edit`)} - > -
+ onClick={() => navigate(`/tactic/${tactic.id}/edit`)}> +
t.id !== tactic.id), + type: HomePageStateActionKind.REMOVE_TACTIC, + tacticId: tactic.id, }) }} /> + { + e.stopPropagation() + const service = new APITacticService( + fetcher, + tactic.id, + ) + const context = await service.getContext() + if (typeof context === "string") + throw Error(context) -
+ const plainTactic = await loadPlainTactic( + context, + service, + ) + const { name, id } = await importTactic( + fetcher, + plainTactic, + ) + dispatch({ + type: HomePageStateActionKind.ADD_TACTIC, + tactic: { name, id, creationDate: 0 }, + }) + }} + /> +
) diff --git a/src/pages/popup/ExportTacticPopup.tsx b/src/pages/popup/ExportTacticPopup.tsx index 0e16641..e89dbe3 100644 --- a/src/pages/popup/ExportTacticPopup.tsx +++ b/src/pages/popup/ExportTacticPopup.tsx @@ -6,12 +6,7 @@ import { useEffect, useState } from "react" import "../../style/export_tactic_popup.css" import JsonIcon from "../../assets/icon/json.svg?react" -import { - StepInfoNode, - Tactic, - TacticStep, -} from "../../model/tactic/TacticInfo.ts" -import { countSteps } from "../../domains/StepsDomain.ts" +import { loadPlainTactic } from "../../domains/TacticPersistenceDomain.ts" export interface ExportTacticPopupProps { service: TacticService @@ -87,34 +82,7 @@ async function exportInJson( service: TacticService, onProgress: (p: number) => void, ) { - const tree = context.stepsTree - - const treeSize = countSteps(tree) - const totalStepsCompleted = new Uint16Array(1) - - async function transformToStep( - stepInfoNode: StepInfoNode, - ): Promise { - const contentResult = await service.getContent(stepInfoNode.id) - if (typeof contentResult === "string") throw Error(contentResult) - - Atomics.add(totalStepsCompleted, 0, 1) - onProgress((Atomics.load(totalStepsCompleted, 0) / treeSize) * 100) - - return { - stepId: stepInfoNode.id, - content: contentResult, - children: await Promise.all( - stepInfoNode.children.map(transformToStep), - ), - } - } - - const tactic: Tactic = { - name: context.name, - courtType: context.courtType, - root: await transformToStep(tree), - } + const tactic = await loadPlainTactic(context, service, onProgress) const e = document.createElement("a") e.setAttribute( diff --git a/src/service/APITacticService.ts b/src/service/APITacticService.ts index 20df41d..fe2f082 100644 --- a/src/service/APITacticService.ts +++ b/src/service/APITacticService.ts @@ -44,13 +44,13 @@ export class APITacticService implements MutableTacticService { } async addStep( - parent: StepInfoNode, + parentId: number, content: StepContent, ): Promise { const response = await this.fetcher.fetchAPI( `tactics/${this.tacticId}/steps`, { - parentId: parent.id, + parentId: parentId, content, }, ) diff --git a/src/service/LocalStorageTacticService.ts b/src/service/LocalStorageTacticService.ts index 05f3076..d439145 100644 --- a/src/service/LocalStorageTacticService.ts +++ b/src/service/LocalStorageTacticService.ts @@ -55,7 +55,7 @@ export class LocalStorageTacticService implements MutableTacticService { } async addStep( - parent: StepInfoNode, + parentId: number, content: StepContent, ): Promise { const root: StepInfoNode = JSON.parse( @@ -65,7 +65,7 @@ export class LocalStorageTacticService implements MutableTacticService { const nodeId = getAvailableId(root) const node = { id: nodeId, children: [] } - const resultTree = addStepNode(root, parent, node) + const resultTree = addStepNode(root, parentId, node) localStorage.setItem( GUEST_MODE_STEP_ROOT_NODE_INFO_STORAGE_KEY, diff --git a/src/service/MutableTacticService.ts b/src/service/MutableTacticService.ts index 5c9331d..c39618d 100644 --- a/src/service/MutableTacticService.ts +++ b/src/service/MutableTacticService.ts @@ -23,7 +23,7 @@ export interface TacticService { export interface MutableTacticService extends TacticService { addStep( - parent: StepInfoNode, + parentId: number, content: StepContent, ): Promise diff --git a/src/style/home/home.css b/src/style/home/home.css index 3ec3e19..38821ba 100644 --- a/src/style/home/home.css +++ b/src/style/home/home.css @@ -16,6 +16,7 @@ margin: 0; height: 100%; background-color: var(--home-second-color); + user-select: none; } .data { @@ -85,10 +86,13 @@ align-content: center; align-items: center; justify-content: center; + + * { + pointer-events: all; + } } .tactic-card-export-btn { - pointer-events: all; height: 100%; * { @@ -97,7 +101,6 @@ } .tactic-card-remove-btn { - pointer-events: all; height: 100%; } @@ -105,6 +108,10 @@ pointer-events: none !important; } +.tactic-card-duplicate-btn { + fill: #494949; +} + .tactic-card-preview { pointer-events: none; display: flex; diff --git a/src/style/home/side_menu.css b/src/style/home/side_menu.css index aeb50b4..4beede8 100644 --- a/src/style/home/side_menu.css +++ b/src/style/home/side_menu.css @@ -15,11 +15,19 @@ #side-menu-content { width: 90%; + height: 100%; + display: flex; + flex-direction: column; + gap: 25px; } + +#tactic-import-area { + height: 100%; +} + .titre-side-menu { border-bottom: var(--home-main-color) solid 3px; width: 100%; - margin-bottom: 3%; } #side-menu .title { @@ -30,7 +38,6 @@ text-transform: uppercase; background-color: var(--home-main-color); padding: 3%; - margin-bottom: 0px; margin-right: 3%; } diff --git a/src/style/template/header.css b/src/style/template/header.css index 6d64f46..78efc54 100644 --- a/src/style/template/header.css +++ b/src/style/template/header.css @@ -1,6 +1,7 @@ @import url(../theme/default.css); #header { + user-select: none; text-align: center; background-color: var(--home-main-color); margin: 0; @@ -19,6 +20,7 @@ width: 50px; height: 50px; border-radius: 20%; + -webkit-user-drag: none; } #header-left, diff --git a/vite.config.ts b/vite.config.ts index 8d932e7..c0758f7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from "vite" import react from "@vitejs/plugin-react" import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js" import svgr from "vite-plugin-svgr" +import { visualizer } from "rollup-plugin-visualizer" // https://vitejs.dev/config/ export default defineConfig({ @@ -17,5 +18,6 @@ export default defineConfig({ relativeCSSInjection: true, }), svgr(), + visualizer() ], }) From 262cf9744552092f4718a7a23fe97ff50157acdb Mon Sep 17 00:00:00 2001 From: maxime Date: Mon, 1 Apr 2024 19:00:44 +0200 Subject: [PATCH 5/5] Apply suggestions --- src/pages/Editor.tsx | 1 - src/pages/HomePage.tsx | 13 ++++++------- src/pages/VisualizerPage.tsx | 1 - src/pages/popup/ExportTacticPopup.tsx | 8 ++------ src/style/actions/arrow_action.css | 2 +- vite.config.ts | 1 + 6 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index 0b4d560..8927e6b 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -823,7 +823,6 @@ function EditorPage({
setShowExportPopup(false)} />
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index b227ec9..57bacb5 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -74,19 +74,19 @@ function homePageStateReducer( ): HomePageState { switch (action.type) { case HomePageStateActionKind.UPDATE_TACTICS: - return { ...state!, tactics: action.tactics } + return { ...state, tactics: action.tactics } case HomePageStateActionKind.UPDATE_TEAMS: - return { ...state!, teams: action.teams } + return { ...state, teams: action.teams } case HomePageStateActionKind.SET_EXPORTING_TACTIC: - return { ...state!, exportingTacticId: action.tacticId } + return { ...state, exportingTacticId: action.tacticId } case HomePageStateActionKind.ADD_TACTIC: - return { ...state!, tactics: [action.tactic, ...state.tactics] } + return { ...state, tactics: [action.tactic, ...state.tactics] } case HomePageStateActionKind.REMOVE_TACTIC: - return { ...state!, tactics: state.tactics.filter(t => t.id !== action.tacticId) } + return { ...state, tactics: state.tactics.filter(t => t.id !== action.tacticId) } case HomePageStateActionKind.INIT: return action.state @@ -147,7 +147,6 @@ export default function HomePage() {
dispatch({ type: HomePageStateActionKind.SET_EXPORTING_TACTIC, @@ -223,7 +222,7 @@ function TacticImportArea() { handleChange={handleDrop} types={["json"]} hoverTitle="Déposez ici" - label="Séléctionnez ou déposez un fichier ici"> + label="Séléctionnez ou déposez un fichier ici"/>
) } diff --git a/src/pages/VisualizerPage.tsx b/src/pages/VisualizerPage.tsx index f27e7ad..37c0487 100644 --- a/src/pages/VisualizerPage.tsx +++ b/src/pages/VisualizerPage.tsx @@ -202,7 +202,6 @@ function VisualizerPageContent({
setShowExportPopup(false)} />
diff --git a/src/pages/popup/ExportTacticPopup.tsx b/src/pages/popup/ExportTacticPopup.tsx index e89dbe3..0009634 100644 --- a/src/pages/popup/ExportTacticPopup.tsx +++ b/src/pages/popup/ExportTacticPopup.tsx @@ -10,13 +10,11 @@ import { loadPlainTactic } from "../../domains/TacticPersistenceDomain.ts" export interface ExportTacticPopupProps { service: TacticService - show: boolean onHide: () => void } export default function ExportTacticPopup({ service, - show, onHide, }: ExportTacticPopupProps) { const [context, setContext] = useState() @@ -46,8 +44,6 @@ export default function ExportTacticPopup({ return () => window.removeEventListener("keyup", onKeyUp) }, [onHide]) - if (!show) return <> - if (panicMessage) return

{panicMessage}

return ( @@ -68,7 +64,7 @@ export default function ExportTacticPopup({ ) setExportPercentage(0) }}> -

Export in JSON

+

Exporter en JSON

@@ -87,7 +83,7 @@ async function exportInJson( const e = document.createElement("a") e.setAttribute( "href", - "data:application/octet-stream," + + "data:application/json;charset=utf-8," + encodeURIComponent(JSON.stringify(tactic, null, 2)), ) e.setAttribute("download", `${context.name}.json`) diff --git a/src/style/actions/arrow_action.css b/src/style/actions/arrow_action.css index 77bfa4c..097a207 100644 --- a/src/style/actions/arrow_action.css +++ b/src/style/actions/arrow_action.css @@ -5,7 +5,7 @@ .arrow-action-icon { user-select: none; -moz-user-select: none; - -webkit-user-drag: none; + pointer-events: none; max-width: 17px; max-height: 17px; } diff --git a/vite.config.ts b/vite.config.ts index c0758f7..9f40c89 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -8,6 +8,7 @@ import { visualizer } from "rollup-plugin-visualizer" export default defineConfig({ build: { target: "es2021", + cssTarget: ["chrome112"] }, test: { environment: "jsdom",