diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..108abc7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* eol=lf \ No newline at end of file diff --git a/.vscode/TableTop.code-workspace b/.vscode/TableTop.code-workspace index 56a28a9..2a0ed79 100644 --- a/.vscode/TableTop.code-workspace +++ b/.vscode/TableTop.code-workspace @@ -1,7 +1,7 @@ { "folders": [ { - "path": "C:\\Users\\josep\\Documents\\Programming\\TypeScript\\TableTop" + "path": ".." } ] } \ No newline at end of file diff --git a/e2e/select-theme.spec.ts b/e2e/select-theme.spec.ts index 0303695..c30fb2b 100644 --- a/e2e/select-theme.spec.ts +++ b/e2e/select-theme.spec.ts @@ -1,4 +1,5 @@ import { firefox, chromium, webkit, Browser, Page } from "playwright"; +import { v4 as uuidv4} from "uuid"; jest.setTimeout(10000); @@ -42,6 +43,14 @@ describe.each([ { page = await browser.newPage({ colorScheme: preferredTheme }); await page.goto("http://localhost:8080"); + + const roomNameInput = await page.waitForSelector( + "input[type=text]:near(:text('Room Name'))" + ); + + await roomNameInput.type(uuidv4()); + + await (await page.$("input[type=submit]:text('Join!')")).click(); }); afterEach(async () => @@ -73,6 +82,14 @@ describe.each([ { page = await browser.newPage({ colorScheme: "light" }); await page.goto("http://localhost:8080"); + + const roomNameInput = await page.waitForSelector( + "input[type=text]:near(:text('Room Name'))" + ); + + await roomNameInput.type(uuidv4()); + + await (await page.$("input[type=submit]:text('Join!')")).click(); }); afterEach(async () => @@ -80,6 +97,7 @@ describe.each([ await page.close(); }); + // Flaky test, fails on random expects in random browsers. it("Selects that theme regardless of browser preference.", async () => { const selector = `input[type=radio]:left-of(:text("Dark"))`; @@ -94,6 +112,14 @@ describe.each([ await page.reload(); + const roomNameInput = await page.waitForSelector( + "input[type=text]:near(:text('Room Name'))" + ); + + await roomNameInput.type(uuidv4()); + + await (await page.$("input[type=submit]:text('Join!')")).click(); + const reloadedOption = await page.waitForSelector(selector); expect(await reloadedOption.isChecked()).toBe(true); diff --git a/package-lock.json b/package-lock.json index 14005d6..df0dc1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "@semantic-release/git": "^9.0.0", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^13.2.1", "@types/jest": "^26.0.24", "@types/react": "^16.14.2", "@types/react-dom": "^16.9.10", @@ -1718,6 +1719,22 @@ "react-dom": "*" } }, + "node_modules/@testing-library/user-event": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.2.1.tgz", + "integrity": "sha512-cczlgVl+krjOb3j1625usarNEibI0IFRJrSWX9UsJ1HKYFgCQv9Nb7QAipUDXl3Xdz8NDTsiS78eAkPSxlzTlw==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -18368,6 +18385,15 @@ "@testing-library/dom": "^7.28.1" } }, + "@testing-library/user-event": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.2.1.tgz", + "integrity": "sha512-cczlgVl+krjOb3j1625usarNEibI0IFRJrSWX9UsJ1HKYFgCQv9Nb7QAipUDXl3Xdz8NDTsiS78eAkPSxlzTlw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", diff --git a/package.json b/package.json index e8d78cd..aeab05f 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "@semantic-release/git": "^9.0.0", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^11.2.2", + "@testing-library/user-event": "^13.2.1", "@types/jest": "^26.0.24", "@types/react": "^16.14.2", "@types/react-dom": "^16.9.10", diff --git a/src/App.tsx b/src/App.tsx index 2bdc264..a312888 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,16 +1,51 @@ import * as React from "react" +import { v4 as uuidv4 } from "uuid"; + +import { useLocalStore } from "./store/local"; +import { useSharedStoreFactory } from "./store/shared"; import Tabletop from "~/components/tabletop/Tabletop"; import { RectangularGrid } from "~/components/tabletop/RectangularGrid"; import ControlPanel from "./components/control-panel/ControlPanel"; +import JoinForm from "./components/join-form/JoinForm"; -export const App = () => -( -
- - } /> -
+export const SharedStoreContext = React.createContext( + useSharedStoreFactory(uuidv4()) ); +export const App = () => +{ + const { room } = useLocalStore(); + + const useSharedStore = React.useMemo( + () => useSharedStoreFactory(room), + [ room ] + ); + + return ( +
+ { + room === "" + ? ( + + ) + : ( + + + } /> + + ) + } +
+ ) +} + export default App; \ No newline at end of file diff --git a/src/components/control-panel/ActorList.spec.tsx b/src/components/control-panel/ActorList.spec.tsx index abc5a8e..7ab0be4 100644 --- a/src/components/control-panel/ActorList.spec.tsx +++ b/src/components/control-panel/ActorList.spec.tsx @@ -1,20 +1,42 @@ import React from "react"; +import { UseStore } from "zustand"; import { fireEvent, render } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { useSharedStore } from "~/store/shared"; +import { useLocalStore } from "~/store/local"; +import { SharedState, useSharedStoreFactory } from "~/store/shared"; import { Actor } from "~/core/Actor"; import ActorList, { actorListItemTestId, } from "./ActorList"; +import { SharedStoreContext } from "~/App"; + +import { v4 as uuidv4 } from "uuid"; describe("Connected ActorList", () => { + let useSharedStore: UseStore; + + beforeEach(() => + { + useLocalStore.setState(() => ({ room: uuidv4() })); + useSharedStore = useSharedStoreFactory(useLocalStore.getState().room); + }); + + afterEach(() => + { + useLocalStore.setState(() => ({ room: "" })); + }); + it("Displays 'no actors' when there are no actors in state.", () => { useSharedStore.setState(() => ({ actors: [] })); - const { getByText } = render(); + const { getByText } = render( + + + + ); expect(getByText(/no actors/i)).toBeInTheDocument(); }); @@ -36,7 +58,11 @@ describe("Connected ActorList", () => for (let actorNumber of actorList) useSharedStore.getState().addActor({ id: `${actorNumber}` } as Actor); - const { getAllByTestId } = render(); + const { getAllByTestId } = render( + + + + ); expect(getAllByTestId(actorListItemTestId)).toHaveLength(actorList.length); }); @@ -46,9 +72,13 @@ describe("Connected ActorList", () => useSharedStore.setState(() => ({ actors: [] })); useSharedStore.getState().addActor({ id: `1`, name: `Actor 1` } as Actor); - const { getByText } = render(); + const { queryByText } = render( + + + + ); - expect(getByText("Actor 1")).toBeInTheDocument(); + expect(queryByText("Actor 1")).toBeInTheDocument(); }); it("Displays Actors sorted from highest initiative to lowest.", () => @@ -57,7 +87,11 @@ describe("Connected ActorList", () => useSharedStore.getState().addActor({ id: `1`, name: `Actor 1`, initiative: 1 } as Actor); useSharedStore.getState().addActor({ id: `2`, name: `Actor 2`, initiative: 2 } as Actor); - const { getAllByTestId } = render(); + const { getAllByTestId } = render( + + + + ); expect(getAllByTestId(actorListItemTestId)[0]).toHaveTextContent("Actor 2"); expect(getAllByTestId(actorListItemTestId)[1]).toHaveTextContent("Actor 1"); @@ -70,7 +104,11 @@ describe("Connected ActorList", () => useSharedStore.getState().addActor({ id: "1", name: "Actor 1", initiative: 2 } as Actor); useSharedStore.getState().addActor({ id: "2", name: "Actor 2", initiative: 1 } as Actor); - const { getAllByTestId } = render(); + const { getAllByTestId } = render( + + + + ); fireEvent.click(getAllByTestId(actorListItemTestId)[0]); diff --git a/src/components/control-panel/ActorList.tsx b/src/components/control-panel/ActorList.tsx index 39007f2..e446c66 100644 --- a/src/components/control-panel/ActorList.tsx +++ b/src/components/control-panel/ActorList.tsx @@ -1,5 +1,6 @@ import React from "react"; -import { useSharedStore } from "~/store/shared"; +import { SharedStoreContext } from "~/App"; +// import { useSharedStore } from "~/store/shared"; import "./ActorList.module.css"; import styles from "./ActorList.module.css"; @@ -9,6 +10,8 @@ export const actorListItemTestId = "actor-list-item"; export const ActorList = () => { + const useSharedStore = React.useContext(SharedStoreContext); + const { actors, removeActor } = useSharedStore(({ actors, removeActor }) => ({ actors, diff --git a/src/components/control-panel/AddActor.spec.tsx b/src/components/control-panel/AddActor.spec.tsx index 53ca8f9..8dec6a9 100644 --- a/src/components/control-panel/AddActor.spec.tsx +++ b/src/components/control-panel/AddActor.spec.tsx @@ -1,8 +1,12 @@ import React from "react"; -import { fireEvent, render } from "@testing-library/react"; +import { act, fireEvent, render } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { useSharedStore } from "~/store/shared"; +import { v4 as uuidv4 } from "uuid"; +import { UseStore } from "zustand"; + +import { SharedState, useSharedStoreFactory } from "~/store/shared"; +import { SharedStoreContext } from "~/App"; import AddActor, { AddActor as DisconnectedAddActor} from "./AddActor"; @@ -23,10 +27,15 @@ describe("Connected AddActor", () => { it("Adds an actor when clicked.", () => { + let useSharedStore: UseStore; + + useSharedStore = useSharedStoreFactory(uuidv4()); useSharedStore.setState(() => ({ actors: [] })); const { getByText } = render( - + + + ); const button = getByText("Add Actor"); diff --git a/src/components/control-panel/AddActor.tsx b/src/components/control-panel/AddActor.tsx index 63b96ab..fa42be3 100644 --- a/src/components/control-panel/AddActor.tsx +++ b/src/components/control-panel/AddActor.tsx @@ -1,13 +1,15 @@ import React from "react"; -import { useSharedStore } from "~/store/shared"; import { v4 as uuid } from "uuid"; +import { SharedStoreContext } from "~/App"; import { Button } from "~/components/util/Button"; import "./AddActor.module.css"; export const AddActor = () => { + const useSharedStore = React.useContext(SharedStoreContext); + const addActor = useSharedStore((state) => state.addActor); return ( diff --git a/src/components/control-panel/ControlPanel.spec.tsx b/src/components/control-panel/ControlPanel.spec.tsx index 6d06ae8..032f001 100644 --- a/src/components/control-panel/ControlPanel.spec.tsx +++ b/src/components/control-panel/ControlPanel.spec.tsx @@ -2,10 +2,15 @@ import React from "react"; import { fireEvent, render } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { useSharedStore } from "~/store/shared"; +import { v4 as uuidv4 } from "uuid"; + +import { useSharedStoreFactory, SharedState } from "~/store/shared"; +import { UseStore } from "zustand"; import { useLocalStore } from "~/store/local"; import ControlPanel from "./ControlPanel"; +import { SharedStoreContext } from "~/App"; +import { act } from "react-dom/test-utils"; Object.defineProperty(window, 'matchMedia', { writable: true, @@ -23,11 +28,35 @@ Object.defineProperty(window, 'matchMedia', { describe("Connected ControlPanel", () => { + let useSharedStore: UseStore; + + beforeEach(() => + { + act(() => + { + useLocalStore.setState(() => ({ room: uuidv4() })); + }); + + useSharedStore = useSharedStoreFactory(useLocalStore.getState().room); + }); + + afterEach(() => + { + act(() => + { + useLocalStore.setState(() => ({ room: "" })); + }); + }); + it("Adds an actor to app store when 'Add Actor' is pressed.", () => { useSharedStore.setState(() => ({ actors: [] })); - const { getByText } = render(); + const { getByText } = render( + + + + ); fireEvent.click(getByText("Add Actor")); @@ -44,7 +73,11 @@ describe("Connected ControlPanel", () => y: 0 }) - const { getByText } = render(); + const { getByText } = render( + + + + ); expect(getByText("Actor 1")).toBeInTheDocument(); }); @@ -103,7 +136,11 @@ describe("Connected ControlPanel", () => for (let actor of testActors) useSharedStore.getState().addActor(actor); - const { getAllByText } = render(); + const { getAllByText } = render( + + + + ); const entries = getAllByText(/.*/i, { selector: "li" }) @@ -125,7 +162,11 @@ describe("Connected ControlPanel", () => y: 0 }); - const { getByText } = render(); + const { getByText } = render( + + + + ); fireEvent.click(getByText("Actor 1")); diff --git a/src/components/join-form/JoinForm.module.css b/src/components/join-form/JoinForm.module.css new file mode 100644 index 0000000..ff77e42 --- /dev/null +++ b/src/components/join-form/JoinForm.module.css @@ -0,0 +1,54 @@ +form { + display: grid; + row-gap: 0.5rem; +} + +label { + justify-self: start; + + font-weight: bold; +} + +input[type=text] { + justify-self: center; + width: 25vw; + + color: var(--black); + background-color: var(--white); + + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + + font-size: 1.2rem; + border: 2px solid #111; + + padding: 0.5rem; +} + +input[type=text]:focus { + border-color: var(--brand-primary); + border-radius: 0.5rem; +} + + +input[type=submit] { + justify-self: end; + + width: 33.3%; + + appearance: none; + + border: none; + outline: none; + + color: var(--white); + background-color: var(--brand-primary); + + padding: 0.5rem 1rem; + + font-family: Germania One, Impact, system-ui, sans-serif; + font-size: 1.5rem; + + cursor: pointer; +} \ No newline at end of file diff --git a/src/components/join-form/JoinForm.spec.tsx b/src/components/join-form/JoinForm.spec.tsx new file mode 100644 index 0000000..da6ea88 --- /dev/null +++ b/src/components/join-form/JoinForm.spec.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { fireEvent, render } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import "@testing-library/jest-dom/extend-expect"; + +import JoinForm, { joinFormTestId } from "./JoinForm"; +import { useLocalStore } from "~/store/local"; + +describe("JoinForm component", () => +{ + it("Is a form.", () => + { + const { getByTestId } = render(); + expect(getByTestId(joinFormTestId).tagName.toLowerCase()).toBe("form"); + }); + + it("Has an input field labelled 'Room Name'.", () => + { + const { getByLabelText } = render(); + expect((getByLabelText("Room Name") as HTMLInputElement).type).toBe("text"); + }); + + it("Has a submit button that says 'Join!'.", () => + { + const { getByText } = render(); + expect((getByText("Join!") as HTMLInputElement).type).toBe("submit") + }); + + it.each([ + [ "room" ], + [ "antechamber" ] + ])("Sets room code ('%s') when you press 'Join!'.", (roomName) => + { + useLocalStore.setState(() => ({ room: "", })); + + global.HTMLFormElement.prototype.submit = jest.fn(); + const { getByLabelText, getByTestId } = render(); + + userEvent.type(getByLabelText(/room name/i), roomName); + fireEvent.submit(getByTestId(joinFormTestId)); + + expect(useLocalStore.getState().room).toBe(roomName); + }); +}); \ No newline at end of file diff --git a/src/components/join-form/JoinForm.tsx b/src/components/join-form/JoinForm.tsx new file mode 100644 index 0000000..c1798a0 --- /dev/null +++ b/src/components/join-form/JoinForm.tsx @@ -0,0 +1,34 @@ +import React from "react"; +import { useLocalStore } from "~/store/local"; + +export const joinFormTestId = "join-form" + +import "./JoinForm.module.css"; + +export const JoinForm = () => +{ + const { setRoom } = useLocalStore(); + const [ roomName, setRoomName ] = React.useState(""); + + return ( +
setRoom(roomName)}> + + setRoomName(target.value)} + /> + +
+ ); +} + +export default JoinForm; \ No newline at end of file diff --git a/src/components/tabletop/Tabletop.spec.tsx b/src/components/tabletop/Tabletop.spec.tsx index fe9621d..40279a5 100644 --- a/src/components/tabletop/Tabletop.spec.tsx +++ b/src/components/tabletop/Tabletop.spec.tsx @@ -2,11 +2,13 @@ import * as React from "react"; import { render } from "@testing-library/react"; import "@testing-library/jest-dom/extend-expect"; -import { useSharedStore } from "~/store/shared"; +import { useSharedStoreFactory } from "~/store/shared"; +import { v4 as uuidv4 } from "uuid"; import Tabletop, { Tabletop as DisconnectedTabletop } from "./Tabletop"; import { tokenTestId } from "./Token"; import { Actor } from "~/core/Actor"; +import { SharedStoreContext } from "~/App"; describe("Disconnected Tabletop component", () => { @@ -54,6 +56,7 @@ describe("Connected Tabletop component", () => "Has a number of Tokens equal to the number of Actors in the app store", (actorList, expectedTokenCount) => { + const useSharedStore = useSharedStoreFactory(uuidv4()); for (var actor of actorList) useSharedStore.setState( @@ -67,7 +70,9 @@ describe("Connected Tabletop component", () => ); const { queryAllByTestId } = render( - + + + ); expect(queryAllByTestId(tokenTestId).length).toBe(expectedTokenCount); diff --git a/src/components/tabletop/Tabletop.tsx b/src/components/tabletop/Tabletop.tsx index 0edd704..5813184 100644 --- a/src/components/tabletop/Tabletop.tsx +++ b/src/components/tabletop/Tabletop.tsx @@ -1,6 +1,5 @@ import * as React from "react"; - -import { useSharedStore } from "~/store/shared"; +import { SharedStoreContext } from "~/App"; import Token from "./Token"; @@ -10,6 +9,8 @@ type TabletopProps = React.HTMLAttributes<{}> & }; export const Tabletop = ({ grid, }: TabletopProps) => { + const useSharedStore = React.useContext(SharedStoreContext); + const { actors, setActorPosition diff --git a/src/components/tabletop/Token.spec.tsx b/src/components/tabletop/Token.spec.tsx index 60f9a88..ef26af5 100644 --- a/src/components/tabletop/Token.spec.tsx +++ b/src/components/tabletop/Token.spec.tsx @@ -3,17 +3,19 @@ import { fireEvent } from "@testing-library/react"; import { renderSVG } from "~/test-utilities"; import "@testing-library/jest-dom/extend-expect"; +import { v4 as uuidv4 } from "uuid"; + import "~/pointer-event"; import { useLocalStore } from "~/store/local"; import { identityTransform, scale, translation } from "~/core/Transform"; import Token, { Token as DisconnectedToken, tokenTestId } from "./Token"; -import { useSharedStore } from "~/store/shared"; import { Actor } from "~/core/Actor"; -import create from "zustand"; +import { useSharedStoreFactory } from "~/store/shared"; +import { SharedStoreContext } from "~/App"; -describe("Disconnected Token", () => +describe("Token", () => { it("Has a SVG rect.", () => { @@ -354,12 +356,16 @@ describe("Disconnected Token", () => "Follows pointer when user clicks and drags, relative to cursor. (#%#)", (transform, [ initX, initY ], [ endX, endY ], [ expectedX, expectedY ]) => { + const useSharedStore = useSharedStoreFactory(uuidv4()); + useSharedStore.setState(() => ({ actors: [] })); useSharedStore.getState().addActor({ id: "1", x: 0, y: 0 } as Actor); useLocalStore.getState().setViewTransform(transform); const Component = () => { + const useSharedStore = React.useContext(SharedStoreContext); + const { x, y } = useSharedStore((state) => { const index = state.actors.findIndex(({ id }) => id === "1"); @@ -383,7 +389,11 @@ describe("Disconnected Token", () => ); }; - const { getByTestId } = renderSVG(); + const { getByTestId } = renderSVG( + + + + ); const token = getByTestId(tokenTestId); @@ -410,6 +420,8 @@ describe("Disconnected Token", () => it("Snaps to grid when snapToGrid is true", () => { + const useSharedStore = useSharedStoreFactory(uuidv4()); + useSharedStore.setState(() => ({ actors: [] })); useSharedStore.getState().addActor({ id: "1", x: 0, y: 0 } as Actor); @@ -418,6 +430,8 @@ describe("Disconnected Token", () => const Component = () => { + const useSharedStore = React.useContext(SharedStoreContext); + const { x, y } = useSharedStore((state) => { const index = state.actors.findIndex(({ id }) => id === "1"); @@ -441,7 +455,11 @@ describe("Disconnected Token", () => ); }; - const { getByTestId } = renderSVG(); + const { getByTestId } = renderSVG( + + + + ); const token = getByTestId(tokenTestId); diff --git a/src/store/local.ts b/src/store/local.ts index 34f56e9..6a68c6e 100644 --- a/src/store/local.ts +++ b/src/store/local.ts @@ -13,6 +13,9 @@ type LocalState = snapToGrid: boolean, setSnapToGrid: (snapToGrid: boolean) => void, + + room: string, + setRoom: (roomName: string) => void, }; export const useLocalStore = create( @@ -36,5 +39,9 @@ export const useLocalStore = create( setSnapToGrid: (snapToGrid: boolean) => set((_) => ({ snapToGrid })), + + room: "", + setRoom: (roomName) => + set(() => ({ room: roomName })), }) ); \ No newline at end of file diff --git a/src/store/shared.ts b/src/store/shared.ts index 768e187..d23315f 100644 --- a/src/store/shared.ts +++ b/src/store/shared.ts @@ -6,7 +6,7 @@ import { WebrtcProvider } from "y-webrtc"; import { Actor } from "~/core/Actor"; -type SharedState = +export type SharedState = { actors: Actor[], addActor: (actor: Actor) => void, @@ -14,48 +14,51 @@ type SharedState = setActorPosition: (actorId: string, x: number, y: number) => void, }; -const doc = new Y.Doc(); -new WebrtcProvider("repeated-pleasant-games", doc); +export const useSharedStoreFactory = (roomName: string) => +{ + const doc = new Y.Doc(); + new WebrtcProvider(roomName, doc); -export const useSharedStore = create( - yjs( - doc, - "shared-state", - (set) => - ({ - actors: [], - addActor: - (actor: Actor) => - set((state) => ({ actors: [ ...state.actors, actor ] })), - removeActor: - (actorId: string) => - set( - (state) => - ({ - actors: state.actors.filter(({ id }) => id !== actorId) - }) - ), - setActorPosition: - (actorId: string, x: number, y: number) => - set( - (state) => - ({ - actors: state.actors.map( - (actor) => - { - if (actor.id === actorId) - return { - ...actor, - x, - y, - }; + return create( + yjs( + doc, + "shared-state", + (set) => + ({ + actors: [], + addActor: + (actor: Actor) => + set((state) => ({ actors: [ ...state.actors, actor ] })), + removeActor: + (actorId: string) => + set( + (state) => + ({ + actors: state.actors.filter(({ id }) => id !== actorId) + }) + ), + setActorPosition: + (actorId: string, x: number, y: number) => + set( + (state) => + ({ + actors: state.actors.map( + (actor) => + { + if (actor.id === actorId) + return { + ...actor, + x, + y, + }; - else - return actor; - } - ) - }) - ) - }) - ) -); \ No newline at end of file + else + return actor; + } + ) + }) + ) + }) + ) + ); +}; \ No newline at end of file diff --git a/src/styles/style.css b/src/styles/style.css index 4f25470..1003fb7 100644 --- a/src/styles/style.css +++ b/src/styles/style.css @@ -63,6 +63,8 @@ body { } .control-panel { + place-self: start; + position: absolute; z-index: 1;