diff --git a/package.json b/package.json
index 4167e9c0..410cc164 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,12 @@
"typecheck": "tsc --noEmit -p tsconfig.json"
},
"dependencies": {
+ "@emotion/react": "^11.11.4",
+ "@emotion/styled": "^11.11.0",
+ "@types/react-router-dom": "^5.3.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "@emotion/react": "^11.11.4",
- "@emotion/styled": "^11.11.0"
+ "react-router-dom": "^6.22.3"
},
"devDependencies": {
"@commitlint/cli": "^19.2.0",
diff --git a/src/App.tsx b/src/App.tsx
index b6de5dd1..dad596f4 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,16 +1,37 @@
+import { createBrowserRouter, RouterProvider } from "react-router-dom";
import "./App.css";
-import { Header } from "./components/header.tsx";
import { Production } from "./components/production.tsx";
-import { DeviceSelector } from "./components/device-select.tsx";
+import { ErrorPage } from "./components/router-error.tsx";
+import { useDevicePermissions } from "./hooks/device-permission.ts";
+import { LandingPage } from "./components/landing-page/landing-page.tsx";
+
+const router = createBrowserRouter([
+ {
+ path: "/",
+ element: ,
+ errorElement: ,
+ },
+ {
+ path: "production/:productionId",
+ // loader: productionLoader,
+ element: ,
+ },
+]);
const App = () => {
- return (
- <>
-
-
-
- >
- );
+ const { denied, permission } = useDevicePermissions();
+
+ if (denied) {
+ return (
+
+ Permission denied, reload browser and/or reset permissions to try again.
+
+ );
+ }
+
+ if (!permission) return Waiting for device permissions
;
+
+ return ;
};
export default App;
diff --git a/src/components/landing-page/create-production.tsx b/src/components/landing-page/create-production.tsx
new file mode 100644
index 00000000..30a97b25
--- /dev/null
+++ b/src/components/landing-page/create-production.tsx
@@ -0,0 +1,29 @@
+import { DisplayContainerHeader } from "./display-container-header.tsx";
+import {
+ DecorativeLabel,
+ FormContainer,
+ FormInput,
+ FormLabel,
+ SubmitButton,
+} from "./form-elements.tsx";
+
+export const CreateProduction = () => {
+ return (
+
+ Create Production
+
+ Production Name
+
+
+
+ Line
+
+
+
+ Line
+
+
+ Create Production
+
+ );
+};
diff --git a/src/components/landing-page/display-container-header.tsx b/src/components/landing-page/display-container-header.tsx
new file mode 100644
index 00000000..83813b80
--- /dev/null
+++ b/src/components/landing-page/display-container-header.tsx
@@ -0,0 +1,8 @@
+import styled from "@emotion/styled";
+
+export const DisplayContainerHeader = styled.h2`
+ font-size: 3rem;
+ font-weight: bold;
+ line-height: 1;
+ margin: 0 0 1rem;
+`;
diff --git a/src/components/landing-page/form-elements.tsx b/src/components/landing-page/form-elements.tsx
new file mode 100644
index 00000000..9a474ad9
--- /dev/null
+++ b/src/components/landing-page/form-elements.tsx
@@ -0,0 +1,31 @@
+import styled from "@emotion/styled";
+
+export const FormContainer = styled.div``;
+
+export const FormInput = styled.input`
+ width: 100%;
+`;
+
+export const FormSelect = styled.select`
+ width: 100%;
+`;
+
+export const FormLabel = styled.label`
+ display: block;
+ padding: 0 0 1rem;
+ input,
+ select {
+ font-size: 1.6rem;
+ display: inline-block;
+ }
+`;
+
+export const DecorativeLabel = styled.span`
+ display: block;
+ white-space: nowrap;
+ padding: 0 1rem 1rem 0;
+`;
+
+export const SubmitButton = styled.button`
+ font-size: 1.6rem;
+`;
diff --git a/src/components/header.tsx b/src/components/landing-page/header.tsx
similarity index 82%
rename from src/components/header.tsx
rename to src/components/landing-page/header.tsx
index c38f6c2b..a404712c 100644
--- a/src/components/header.tsx
+++ b/src/components/landing-page/header.tsx
@@ -1,6 +1,6 @@
import styled from "@emotion/styled";
import { FC } from "react";
-import { backgroundColour } from "../css-helpers/defaults";
+import { backgroundColour } from "../../css-helpers/defaults.ts";
const HeaderWrapper = styled.div`
width: 100%;
diff --git a/src/components/landing-page/join-production.tsx b/src/components/landing-page/join-production.tsx
new file mode 100644
index 00000000..2b5b956a
--- /dev/null
+++ b/src/components/landing-page/join-production.tsx
@@ -0,0 +1,49 @@
+import { DisplayContainerHeader } from "./display-container-header.tsx";
+import {
+ DecorativeLabel,
+ FormLabel,
+ FormContainer,
+ FormInput,
+ FormSelect,
+ SubmitButton,
+} from "./form-elements.tsx";
+
+export const JoinProduction = () => {
+ return (
+
+ Join Production
+
+ Production ID
+
+
+
+ Username
+
+
+
+ Input
+
+
+
+
+
+
+ Output
+
+
+
+
+
+
+
+ Line
+
+
+
+
+
+
+ Join
+
+ );
+};
diff --git a/src/components/landing-page/landing-page.tsx b/src/components/landing-page/landing-page.tsx
new file mode 100644
index 00000000..b75b8ed5
--- /dev/null
+++ b/src/components/landing-page/landing-page.tsx
@@ -0,0 +1,38 @@
+import styled from "@emotion/styled";
+import { Header } from "./header.tsx";
+import { JoinProduction } from "./join-production.tsx";
+import { CreateProduction } from "./create-production.tsx";
+import { ProductionsList } from "./productions-list.tsx";
+
+const FlexContainer = styled.div`
+ display: flex;
+`;
+
+const DisplayContainer = styled.div`
+ flex-basis: 100%;
+ display: flex;
+
+ &:first-of-type {
+ border-right: 1px solid white;
+ padding: 5rem 2rem 5rem 5rem;
+ justify-content: flex-end;
+ }
+ &:last-of-type {
+ padding: 5rem 5rem 5rem 2rem;
+ }
+`;
+
+export const LandingPage = () => (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+);
diff --git a/src/components/landing-page/productions-list.tsx b/src/components/landing-page/productions-list.tsx
new file mode 100644
index 00000000..c7ed8d42
--- /dev/null
+++ b/src/components/landing-page/productions-list.tsx
@@ -0,0 +1,69 @@
+import styled from "@emotion/styled";
+
+const ProductionListContainer = styled.div`
+ display: flex;
+ padding: 2rem 0 2rem 2rem;
+ flex-wrap: wrap;
+`;
+
+const ProductionItem = styled.div`
+ flex: 1 0 calc(25% - 2rem);
+ min-width: 20rem;
+ border: 1px solid #424242;
+ border-radius: 0.5rem;
+ padding: 2rem;
+ margin: 0 2rem 2rem 0;
+`;
+
+const ProductionName = styled.div`
+ font-size: 1.4rem;
+ font-weight: bold;
+ margin: 0 0 1rem;
+ word-break: break-word;
+`;
+
+const ProductionId = styled.div`
+ font-size: 2rem;
+ color: #9e9e9e;
+`;
+
+export const ProductionsList = () => {
+ return (
+
+
+ Mello
+ 123
+
+
+ Bolibompa
+ 4
+
+
+ Nyheterna
+ 928
+
+
+ Sikta mot Stjärnorna
+ 38974
+
+
+ Idol
+ 5
+
+
+
+ IdolIdol Idol Idol Idol Idol Idol Idol Idol Idol Idol Idol Idol Idol
+ Idol Idol Idol Idol Idol Idol Idol Idol Idol Idol
+
+ 5
+
+
+
+ IdolIdolIdolIdolIdolIdolIdolIdolIdolIdolIdolIdolIdolIdol
+ IdolIdolIdolIdolIdolIdolIdolIdolIdolIdol
+
+ 5
+
+
+ );
+};
diff --git a/src/components/production.tsx b/src/components/production.tsx
index e6193143..8525d107 100644
--- a/src/components/production.tsx
+++ b/src/components/production.tsx
@@ -8,5 +8,9 @@ const UserList = styled.div`
`;
export const Production: FC = () => {
+ // const { participants } = useLoaderData();
+ // Mute/Unmute mic
+ // Mute/Unmute speaker
+ // Show active sink and mic
return A User;
};
diff --git a/src/components/router-error.tsx b/src/components/router-error.tsx
new file mode 100644
index 00000000..8aee6e0d
--- /dev/null
+++ b/src/components/router-error.tsx
@@ -0,0 +1,19 @@
+import { useRouteError } from "react-router-dom";
+
+export const ErrorPage = () => {
+ const error = useRouteError();
+ console.error(error);
+
+ if (error instanceof Error) {
+ return (
+
+
Oops!
+
Sorry, an unexpected error has occurred.
+
+ {`${error.name} ${error.message}`}
+
+
+ );
+ }
+ return {`Oops ${JSON.stringify(error)}`}
;
+};
diff --git a/src/hooks/device-permission.ts b/src/hooks/device-permission.ts
new file mode 100644
index 00000000..c1453a41
--- /dev/null
+++ b/src/hooks/device-permission.ts
@@ -0,0 +1,21 @@
+import { useEffect, useState } from "react";
+
+export const useDevicePermissions = () => {
+ const [permission, setPermission] = useState(false);
+ const [denied, setDenied] = useState(false);
+
+ useEffect(() => {
+ navigator.mediaDevices
+ .getUserMedia({ audio: true, video: false })
+ .then(() => {
+ setDenied(false);
+ setPermission(true);
+ })
+ .catch(() => {
+ setDenied(true);
+ setPermission(false);
+ });
+ }, []);
+
+ return { permission, denied };
+};
diff --git a/src/index.css b/src/index.css
index 5aecfa1b..12a0f620 100644
--- a/src/index.css
+++ b/src/index.css
@@ -91,6 +91,7 @@ video {
font: inherit;
vertical-align: baseline;
}
+
/* HTML5 display-role reset for older browsers */
article,
aside,
@@ -105,17 +106,21 @@ nav,
section {
display: block;
}
+
body {
line-height: 1;
}
+
ol,
ul {
list-style: none;
}
+
blockquote,
q {
quotes: none;
}
+
blockquote:before,
blockquote:after,
q:before,
@@ -123,6 +128,7 @@ q:after {
content: "";
content: none;
}
+
table {
border-collapse: collapse;
border-spacing: 0;
@@ -148,7 +154,9 @@ html {
box-sizing: border-box;
/* easy REM sizing (e.g. 1.2rem = 12px) */
font-size: 62.5%;
+ height: 100%;
}
+
*,
*:before,
*:after {
@@ -160,6 +168,7 @@ html {
body {
font-size: 1.6rem;
+ height: 100%;
}
/* image size contstrained to container size */
@@ -169,6 +178,10 @@ img {
height: auto;
}
+#root {
+ height: 100%;
+}
+
/*
* media queries here, or as part of layout component
@media only screen and (min-width: 768px) {
diff --git a/yarn.lock b/yarn.lock
index b846697b..b7191fcf 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -512,6 +512,11 @@
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
+"@remix-run/router@1.15.3":
+ version "1.15.3"
+ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.15.3.tgz#d2509048d69dbb72d5389a14945339f1430b2d3c"
+ integrity sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==
+
"@rollup/rollup-android-arm-eabi@4.13.0":
version "4.13.0"
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz#b98786c1304b4ff8db3a873180b778649b5dff2b"
@@ -670,6 +675,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
+"@types/history@^4.7.11":
+ version "4.7.11"
+ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
+ integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==
+
"@types/json-schema@^7.0.12":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
@@ -704,6 +714,23 @@
dependencies:
"@types/react" "*"
+"@types/react-router-dom@^5.3.3":
+ version "5.3.3"
+ resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83"
+ integrity sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==
+ dependencies:
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+ "@types/react-router" "*"
+
+"@types/react-router@*":
+ version "5.1.20"
+ resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.20.tgz#88eccaa122a82405ef3efbcaaa5dcdd9f021387c"
+ integrity sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==
+ dependencies:
+ "@types/history" "^4.7.11"
+ "@types/react" "*"
+
"@types/react@*", "@types/react@^18.2.64":
version "18.2.66"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.66.tgz#d2eafc8c4e70939c5432221adb23d32d76bfe451"
@@ -2996,6 +3023,21 @@ react-is@^16.13.1, react-is@^16.7.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+react-router-dom@^6.22.3:
+ version "6.22.3"
+ resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.22.3.tgz#9781415667fd1361a475146c5826d9f16752a691"
+ integrity sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==
+ dependencies:
+ "@remix-run/router" "1.15.3"
+ react-router "6.22.3"
+
+react-router@6.22.3:
+ version "6.22.3"
+ resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.22.3.tgz#9d9142f35e08be08c736a2082db5f0c9540a885e"
+ integrity sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==
+ dependencies:
+ "@remix-run/router" "1.15.3"
+
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"