add prettier and reformat project
continuous-integration/drone/push Build is passing Details

This commit is contained in:
open-schnick 2023-04-10 23:28:28 +02:00
parent c86873fc28
commit 7d17cfa28c
No known key found for this signature in database
GPG Key ID: B1FA75F86D28E80E
38 changed files with 2197 additions and 879 deletions

15
frontend/.prettierrc Normal file
View File

@ -0,0 +1,15 @@
{
"useTabs": false,
"tabWidth": 4,
"semi": false,
"singleQuote": true,
"jsxSingleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"embeddedLanguageFormatting": "auto",
"endOfLine": "lf",
"printWidth": 100,
"vueIndentScriptAndStyle": false
}

View File

@ -6,7 +6,6 @@ Using vite and react plus the jitsi react sdk for the frontend.
Use yarn: Use yarn:
```sh ```sh
yarn run dev yarn run dev
``` ```

View File

@ -1,5 +1,4 @@
#!/usr/bin/env sh #!/usr/bin/env sh
yarn build yarn build
scp -r dist/* ffs:/home/ffsys/apps/static/jitsi-rooms scp -r dist/* ffs:/home/ffsys/apps/static/jitsi-rooms

View File

@ -1,13 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FF Jitsi Rooms</title> <title>FF Jitsi Rooms</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
</body> </body>
</html> </html>

View File

@ -1,28 +1,34 @@
{ {
"name": "jitsi-roomsv2", "name": "jitsi-roomsv2",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.4.0", "@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/react-fontawesome": "^0.2.0", "@fortawesome/react-fontawesome": "^0.2.0",
"@jitsi/react-sdk": "^1.3.0", "@jitsi/react-sdk": "^1.3.0",
"jotai": "^2.0.3", "jotai": "^2.0.3",
"just-curry-it": "^5.3.0", "just-curry-it": "^5.3.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.26", "@types/react": "^18.0.26",
"@types/react-dom": "^18.0.9", "@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^3.0.0", "@vitejs/plugin-react": "^3.0.0",
"typescript": "^4.9.3", "eslint": "^8.38.0",
"vite": "^4.0.0" "eslint-config-airbnb": "^19.0.4",
} "eslint-plugin-import": "^2.27.5",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"typescript": "^4.9.3",
"vite": "^4.0.0"
}
} }

View File

@ -1,6 +1,6 @@
.App { .App {
height: 100%; height: 100%;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: 100%; width: 100%;
} }

View File

@ -1,39 +1,34 @@
import { Provider } from "jotai"; import { Provider } from 'jotai'
import { useState } from "react"; import { useState } from 'react'
import "./App.css"; import './App.css'
import Meeting from "./components/meeting/Meeting"; import Meeting from './components/meeting/Meeting'
import Sidebar from "./components/sidebar/Sidebar"; import Sidebar from './components/sidebar/Sidebar'
import useBackendData from "./hooks/useBackendData"; import useBackendData from './hooks/useBackendData'
import useConferenceData from "./hooks/useConferenceData"; import useConferenceData from './hooks/useConferenceData'
import useLocalUser from "./hooks/useLocalUser"; import useLocalUser from './hooks/useLocalUser'
import { useRoomName } from "./hooks/useRoomName"; import { useRoomName } from './hooks/useRoomName'
function App() { function App() {
const { userInfo, setUserInfo } = useLocalUser(); const { userInfo, setUserInfo } = useLocalUser()
const { roomData, sendMessage } = useBackendData(userInfo); const { roomData, sendMessage } = useBackendData(userInfo)
const { conferenceData, setConferenceData } = useConferenceData( const { conferenceData, setConferenceData } = useConferenceData(sendMessage, setUserInfo)
sendMessage, const [meetingStarted, setMeetingStarted] = useState(false)
setUserInfo
);
const [meetingStarted, setMeetingStarted] = useState(false);
console.log(roomData); console.log(roomData)
if (roomData && userInfo) { if (roomData && userInfo) {
return ( return (
<div className="App"> <div className="App">
<Sidebar usersData={roomData} <Sidebar usersData={roomData} sendMessage={sendMessage} />
sendMessage={sendMessage} <Meeting
/> conferenceData={conferenceData}
<Meeting setConferenceData={setConferenceData}
conferenceData={conferenceData} userInfo={userInfo}
setConferenceData={setConferenceData} />
userInfo={userInfo} </div>
/> )
</div> }
); return <h2>🌀 Loading...</h2>
}
return <h2>🌀 Loading...</h2>;
} }
export default App; export default App

View File

@ -1,13 +1,13 @@
const ISPROD = window.location.protocol == "https:"; const ISPROD = window.location.protocol == 'https:'
const JITSI_DOMAIN = "thisisnotajitsi.filefighter.de"; const JITSI_DOMAIN = 'thisisnotajitsi.filefighter.de'
const USE_REMOTE_BACKEND = true; const USE_REMOTE_BACKEND = true
const getWebsocketUrl = () => { const getWebsocketUrl = () => {
if (ISPROD) return "wss://" + window.location.host + "/ws" if (ISPROD) return 'wss://' + window.location.host + '/ws'
if (USE_REMOTE_BACKEND) return "wss://" + "discord.filefighter.de/ws" if (USE_REMOTE_BACKEND) return 'wss://' + 'discord.filefighter.de/ws'
return "ws://" + "localhost:9160/ws" return 'ws://' + 'localhost:9160/ws'
} }
const WEBSOCKET_URL = getWebsocketUrl(); const WEBSOCKET_URL = getWebsocketUrl()
const USER_COOKIE_NAME = "jitsi-rooms-user"; const USER_COOKIE_NAME = 'jitsi-rooms-user'
export { JITSI_DOMAIN, WEBSOCKET_URL, USER_COOKIE_NAME }; export { JITSI_DOMAIN, WEBSOCKET_URL, USER_COOKIE_NAME }

View File

@ -1,24 +1,24 @@
function getCookie(cname: string) { function getCookie(cname: string) {
let name = cname + "="; let name = cname + '='
let decodedCookie = decodeURIComponent(document.cookie); let decodedCookie = decodeURIComponent(document.cookie)
let ca = decodedCookie.split(";"); let ca = decodedCookie.split(';')
for (let i = 0; i < ca.length; i++) { for (let i = 0; i < ca.length; i++) {
let c = ca[i]; let c = ca[i]
while (c.charAt(0) == " ") { while (c.charAt(0) == ' ') {
c = c.substring(1); c = c.substring(1)
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length)
}
} }
if (c.indexOf(name) == 0) { return ''
return c.substring(name.length, c.length);
}
}
return "";
} }
function setCookie(cname: string, cvalue: string, exdays = 30) { function setCookie(cname: string, cvalue: string, exdays = 30) {
const d = new Date(); const d = new Date()
d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000); d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000)
let expires = "expires=" + d.toUTCString(); let expires = 'expires=' + d.toUTCString()
document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'
} }
export { getCookie, setCookie }; export { getCookie, setCookie }

View File

@ -1,49 +1,45 @@
export interface ConferenceData { export interface ConferenceData {
roomName: string; // the room name of the conference roomName: string // the room name of the conference
id: string; // the id of the local participant id: string // the id of the local participant
displayName: string; // the display name of the local participant displayName: string // the display name of the local participant
avatarURL: string; // the avatar URL of the local participant avatarURL: string // the avatar URL of the local participant
breakoutRoom: boolean; // whether the current room is a breakout room breakoutRoom: boolean // whether the current room is a breakout room
} }
export interface DisplayNameChangeEvent { export interface DisplayNameChangeEvent {
id: string; // the id of the participant that changed their display name id: string // the id of the participant that changed their display name
displayname: string; // the new display name (WTF why is capilization different) displayname: string // the new display name (WTF why is capilization different)
} }
function videoConferenceJoinedListener( function videoConferenceJoinedListener(
setConferenceData: (newData: ConferenceData) => void, setConferenceData: (newData: ConferenceData) => void,
videoConferenceJoinedEvent: ConferenceData videoConferenceJoinedEvent: ConferenceData
) { ) {
setConferenceData(videoConferenceJoinedEvent); setConferenceData(videoConferenceJoinedEvent)
} }
function displayNameChangeListenerWrapper( function displayNameChangeListenerWrapper(
setDisplayNameChangedEvent: (newData: DisplayNameChangeEvent) => void, setDisplayNameChangedEvent: (newData: DisplayNameChangeEvent) => void,
displayNameChangeEvent: DisplayNameChangeEvent displayNameChangeEvent: DisplayNameChangeEvent
) { ) {
setDisplayNameChangedEvent(displayNameChangeEvent); setDisplayNameChangedEvent(displayNameChangeEvent)
} }
function displayNameChangeListener( function displayNameChangeListener(
conferenceData: ConferenceData | undefined, conferenceData: ConferenceData | undefined,
setConferenceData: (newData: ConferenceData) => void, setConferenceData: (newData: ConferenceData) => void,
displayNameChangeEvent: DisplayNameChangeEvent displayNameChangeEvent: DisplayNameChangeEvent
) { ) {
console.log( console.log('[Rooms] displayNameChangeListener', displayNameChangeEvent, conferenceData)
"[Rooms] displayNameChangeListener", if (conferenceData && conferenceData.id == displayNameChangeEvent.id)
displayNameChangeEvent, setConferenceData({
conferenceData ...conferenceData,
); displayName: displayNameChangeEvent.displayname,
if (conferenceData && conferenceData.id == displayNameChangeEvent.id) } as ConferenceData)
setConferenceData({
...conferenceData,
displayName: displayNameChangeEvent.displayname,
} as ConferenceData);
} }
export { export {
videoConferenceJoinedListener, videoConferenceJoinedListener,
displayNameChangeListenerWrapper, displayNameChangeListenerWrapper,
displayNameChangeListener, displayNameChangeListener,
}; }

View File

@ -1,14 +1,13 @@
function getHslColor(i: number) { function getHslColor(i: number) {
return "hsl(" + i % 360 + ',' + return 'hsl(' + (i % 360) + ',' + '70%,' + '72%)'
'70%,' +
'72%)'
} }
function hashCode(str: string) { // java String#hashCode function hashCode(str: string) {
var hash = 0; // java String#hashCode
for (var i = 0; i < str.length; i++) { var hash = 0
hash = str.charCodeAt(i) + ((hash << 5) - hash); for (var i = 0; i < str.length; i++) {
} hash = str.charCodeAt(i) + ((hash << 5) - hash)
return hash; }
return hash
} }
// function intToRGB(i: number) { // function intToRGB(i: number) {
@ -20,10 +19,8 @@ function hashCode(str: string) { // java String#hashCode
// } // }
function getColorForUserName(userName: string) { function getColorForUserName(userName: string) {
const hash = hashCode(userName) const hash = hashCode(userName)
return getHslColor(hash) return getHslColor(hash)
} }
export { getColorForUserName } export { getColorForUserName }

View File

@ -1,21 +1,21 @@
export interface RoomData { export interface RoomData {
roomName: string; roomName: string
participants: Participant[]; participants: Participant[]
} }
export interface Participant { export interface Participant {
avatarURL: string; avatarURL: string
displayName: string; displayName: string
jid: string; jid: string
email: string; email: string
} }
export interface User { export interface User {
uuid: string, uuid: string
name: string name: string
} }
export interface UsersData { export interface UsersData {
roomsData: RoomData[]; roomsData: RoomData[]
usersWithOutRoom: User[]; usersWithOutRoom: User[]
} }

View File

@ -1,46 +1,43 @@
.chat-input{ .chat-input {
min-width: 0; min-width: 0;
} }
.chat{ .chat {
background-color: #181c25; background-color: #181c25;
border-radius: 8px; border-radius: 8px;
padding: 0.5em 0; padding: 0.5em 0;
width: 100%; width: 100%;
margin: 0 -3px; margin: 0 -3px;
} }
.chat-sender{ .chat-sender {
font-size: 0.9em; font-size: 0.9em;
/* color: #213547; */ /* color: #213547; */
} }
.chat-input-form{ .chat-input-form {
display: flex; display: flex;
} }
.chat-messages {
.chat-messages{ max-height: 220px;
max-height: 220px; overflow-y: scroll;
overflow-y: scroll; display: flex;
display: flex; flex-direction: column-reverse;
flex-direction: column-reverse;
} }
.chat-bubble{ .chat-bubble {
background-color: #2b2a33; background-color: #2b2a33;
overflow-wrap: break-word; overflow-wrap: break-word;
margin-bottom: 7px; margin-bottom: 7px;
border-radius: 8px; border-radius: 8px;
padding: 3px 4px ; padding: 3px 4px;
white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */
white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ white-space: -pre-wrap; /* Opera 4-6 */
white-space: -pre-wrap; /* Opera 4-6 */ white-space: -o-pre-wrap; /* Opera 7 */
white-space: -o-pre-wrap; /* Opera 7 */ white-space: pre-wrap; /* css-3 */
white-space: pre-wrap; /* css-3 */ word-wrap: break-word; /* Internet Explorer 5.5+ */
word-wrap: break-word; /* Internet Explorer 5.5+ */ white-space: -webkit-pre-wrap; /* Newer versions of Chrome/Safari*/
white-space: -webkit-pre-wrap; /* Newer versions of Chrome/Safari*/ word-break: break-all;
word-break: break-all; white-space: normal;
white-space: normal;
} }

View File

@ -1,57 +1,64 @@
import { useState } from "react"; import { useState } from 'react'
import { FormEventHandler } from "react"; import { FormEventHandler } from 'react'
import useAllChat from "../../hooks/useAllChat"; import useAllChat from '../../hooks/useAllChat'
import "./Chat.css" import './Chat.css'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faPaperPlane } from "@fortawesome/free-regular-svg-icons"; import { faPaperPlane } from '@fortawesome/free-regular-svg-icons'
import { getColorForUserName } from "../../background/style/colorednames"; import { getColorForUserName } from '../../background/style/colorednames'
interface Props { interface Props {
sendMessage: Function sendMessage: Function
} }
function Chat({ sendMessage }: Props) { function Chat({ sendMessage }: Props) {
const [chatInput, setChatInput] = useState("") const [chatInput, setChatInput] = useState('')
const { chatMesages } = useAllChat() const { chatMesages } = useAllChat()
const onInput: React.ChangeEventHandler<HTMLInputElement> = (event) => { const onInput: React.ChangeEventHandler<HTMLInputElement> = (event) => {
setChatInput(event.target.value); setChatInput(event.target.value)
event.preventDefault(); event.preventDefault()
}; }
const onSubmit: FormEventHandler<HTMLFormElement> = (event) => { const onSubmit: FormEventHandler<HTMLFormElement> = (event) => {
event.preventDefault(); event.preventDefault()
sendMessage(JSON.stringify({ content: chatInput })); sendMessage(JSON.stringify({ content: chatInput }))
setChatInput("") setChatInput('')
} }
return ( return (
<div className="chat"> <div className="chat">
<div className="chat-messages"> <div className="chat-messages">
{chatMesages {chatMesages
.slice() .slice()
.reverse() // reverse because of css, don't aks me .reverse() // reverse because of css, don't aks me
.map(message => { .map((message) => {
const nameStyle = { const nameStyle = {
color: getColorForUserName(message.sender.name) color: getColorForUserName(message.sender.name),
} }
return ( return (
<div className="chat-bubble" key={message.uuid}> <span className="chat-sender" style={nameStyle}>{message.sender.name} <br /> </span> {message.content} </div> <div className="chat-bubble" key={message.uuid}>
); {' '}
}) <span className="chat-sender" style={nameStyle}>
} {message.sender.name} <br />{' '}
</div> </span>{' '}
<form className="chat-input-form" onSubmit={onSubmit}> {message.content}{' '}
<input </div>
className="chat-input" )
placeholder="Say something funny :D" })}
type="text" </div>
value={chatInput} <form className="chat-input-form" onSubmit={onSubmit}>
onChange={onInput} <input
/> className="chat-input"
<button type="submit"><FontAwesomeIcon icon={faPaperPlane} /></button> placeholder="Say something funny :D"
</form> type="text"
</div> value={chatInput}
); onChange={onInput}
/>
<button type="submit">
<FontAwesomeIcon icon={faPaperPlane} />
</button>
</form>
</div>
)
} }
export default Chat export default Chat

View File

@ -1,31 +1,26 @@
import { JitsiMeeting } from "@jitsi/react-sdk"; import { JitsiMeeting } from '@jitsi/react-sdk'
import { JITSI_DOMAIN } from "../../background/constants"; import { JITSI_DOMAIN } from '../../background/constants'
import { UserInfo } from "./types"; import { UserInfo } from './types'
import curry from "just-curry-it"; import curry from 'just-curry-it'
import { import {
ConferenceData, ConferenceData,
displayNameChangeListener, displayNameChangeListener,
displayNameChangeListenerWrapper, displayNameChangeListenerWrapper,
videoConferenceJoinedListener, videoConferenceJoinedListener,
} from "../../background/jitsi/eventListeners"; } from '../../background/jitsi/eventListeners'
import useJitsiEventCapture from "../../hooks/useJitsiEventCapture"; import useJitsiEventCapture from '../../hooks/useJitsiEventCapture'
interface Props { interface Props {
roomName: string; roomName: string
userInfo: UserInfo; userInfo: UserInfo
conferenceData: ConferenceData | undefined; conferenceData: ConferenceData | undefined
setConferenceData: (newData: ConferenceData) => void; setConferenceData: (newData: ConferenceData) => void
} }
function JitsiEntrypoint({ function JitsiEntrypoint({ roomName, userInfo, conferenceData, setConferenceData }: Props) {
roomName,
userInfo,
conferenceData,
setConferenceData,
}: Props) {
const { setDisplayNameChangeEvent } = useJitsiEventCapture( const { setDisplayNameChangeEvent } = useJitsiEventCapture(
curry(displayNameChangeListener)(conferenceData)(setConferenceData) curry(displayNameChangeListener)(conferenceData)(setConferenceData)
); )
return ( return (
<JitsiMeeting <JitsiMeeting
@ -42,20 +37,20 @@ function JitsiEntrypoint({
userInfo={userInfo} userInfo={userInfo}
onApiReady={(externalApi) => { onApiReady={(externalApi) => {
externalApi.addEventListener( externalApi.addEventListener(
"videoConferenceJoined", 'videoConferenceJoined',
curry(videoConferenceJoinedListener)(setConferenceData) curry(videoConferenceJoinedListener)(setConferenceData)
); )
externalApi.addEventListener( externalApi.addEventListener(
"displayNameChange", 'displayNameChange',
curry(displayNameChangeListenerWrapper)(setDisplayNameChangeEvent) curry(displayNameChangeListenerWrapper)(setDisplayNameChangeEvent)
); )
}} }}
getIFrameRef={(iframeRef) => { getIFrameRef={(iframeRef) => {
iframeRef.style.height = "100%"; iframeRef.style.height = '100%'
iframeRef.style.width = "100%"; iframeRef.style.width = '100%'
}} }}
/> />
); )
} }
export default JitsiEntrypoint; export default JitsiEntrypoint

View File

@ -1,4 +1,4 @@
export interface UserInfo { export interface UserInfo {
displayName: string; displayName: string
email: string; email: string
} }

View File

@ -1,39 +1,33 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from 'react'
import { ConferenceData } from "../../background/jitsi/eventListeners"; import { ConferenceData } from '../../background/jitsi/eventListeners'
import useMeetingStarted from "../../hooks/useMeetingStarted"; import useMeetingStarted from '../../hooks/useMeetingStarted'
import { useRoomName } from "../../hooks/useRoomName"; import { useRoomName } from '../../hooks/useRoomName'
import JitsiEntrypoint from "../jitsi/JitsiEntrypoint"; import JitsiEntrypoint from '../jitsi/JitsiEntrypoint'
import { UserInfo } from "../jitsi/types"; import { UserInfo } from '../jitsi/types'
import MeetingNameInput from "./MeetingNameInput"; import MeetingNameInput from './MeetingNameInput'
interface Props { interface Props {
conferenceData: ConferenceData | undefined; conferenceData: ConferenceData | undefined
setConferenceData: (newData: ConferenceData) => void; setConferenceData: (newData: ConferenceData) => void
userInfo: UserInfo; userInfo: UserInfo
} }
function Meeting({ conferenceData, setConferenceData, userInfo }: Props) { function Meeting({ conferenceData, setConferenceData, userInfo }: Props) {
const [meetingStarted, setMeetingStarted] = useMeetingStarted()
const { roomName } = useRoomName()
const [meetingStarted, setMeetingStarted] = useMeetingStarted() if (meetingStarted) {
const { roomName } = useRoomName() return (
<JitsiEntrypoint
conferenceData={conferenceData}
roomName={roomName}
userInfo={userInfo}
setConferenceData={setConferenceData}
/>
)
}
if (meetingStarted) { return <MeetingNameInput roomName={roomName} currentUser={userInfo.displayName} />
return (
<JitsiEntrypoint
conferenceData={conferenceData}
roomName={roomName}
userInfo={userInfo}
setConferenceData={setConferenceData}
/>
);
}
return (
<MeetingNameInput
roomName={roomName}
currentUser={userInfo.displayName}
/>
);
} }
export default Meeting; export default Meeting

View File

@ -1,4 +1,4 @@
.meeting-name-input { .meeting-name-input {
width: 100%; width: 100%;
text-align: center; text-align: center;
} }

View File

@ -1,41 +1,33 @@
import { FormEventHandler } from "react"; import { FormEventHandler } from 'react'
import useMeetingStarted from "../../hooks/useMeetingStarted"; import useMeetingStarted from '../../hooks/useMeetingStarted'
import { useRoomName } from "../../hooks/useRoomName"; import { useRoomName } from '../../hooks/useRoomName'
import "./MeetingNameInput.css"; import './MeetingNameInput.css'
function MeetingNameInput(props: { function MeetingNameInput(props: { roomName: string; currentUser: string }) {
roomName: string; currentUser: string; const { roomName, updateRoomName, updateAndSubmitRoomName, submitRoomName } = useRoomName()
}) { const [_, setMeetingStarted] = useMeetingStarted()
const { roomName, updateRoomName, updateAndSubmitRoomName, submitRoomName } = useRoomName() const onInput: React.ChangeEventHandler<HTMLInputElement> = (event) => {
const [_, setMeetingStarted] = useMeetingStarted() updateRoomName(event.target.value)
event.preventDefault()
}
const onInput: React.ChangeEventHandler<HTMLInputElement> = (event) => { const onSubmit: FormEventHandler<HTMLFormElement> = (event) => {
updateRoomName(event.target.value); submitRoomName()
event.preventDefault(); setMeetingStarted(true)
}; event.preventDefault()
}
const onSubmit: FormEventHandler<HTMLFormElement> = (event) => { console.log('[Rooms] MeetingName input comp')
submitRoomName() return (
setMeetingStarted(true) <div className="meeting-name-input">
event.preventDefault(); <h1>Greetings {props.currentUser}</h1>
} <form onSubmit={onSubmit}>
<input placeholder="Roomname" type="text" value={roomName} onChange={onInput} />
console.log("[Rooms] MeetingName input comp"); <button type="submit">Enter the adventure</button>
return ( </form>
<div className="meeting-name-input"> </div>
<h1>Greetings {props.currentUser}</h1> )
<form onSubmit={onSubmit}>
<input
placeholder="Roomname"
type="text"
value={roomName}
onChange={onInput}
/>
<button type="submit">Enter the adventure</button>
</form>
</div>
);
} }
export default MeetingNameInput; export default MeetingNameInput

View File

@ -1,39 +1,39 @@
.sidebar { .sidebar {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
background-color: #121115; background-color: #121115;
} }
.sidebar h3 { .sidebar h3 {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.sidebar-footer { .sidebar-footer {
margin-top: auto; margin-top: auto;
} }
.sidebar-small { .sidebar-small {
min-width: 60px; min-width: 60px;
max-width: 60px; max-width: 60px;
} }
.sidebar-full { .sidebar-full {
width: 220px; width: 220px;
} }
.sidebar-hidden { .sidebar-hidden {
width: 0; width: 0;
padding: 0; padding: 0;
} }
.sidebar-hidden > .sidebar-footer > .sidebar-toggle { .sidebar-hidden > .sidebar-footer > .sidebar-toggle {
position: absolute; position: absolute;
bottom: 0; bottom: 0;
} }

View File

@ -1,56 +1,60 @@
import SidebarHeader from "./SidebarHeader"; import SidebarHeader from './SidebarHeader'
import useSidebarVisibility from "./useSidebarVisibility"; import useSidebarVisibility from './useSidebarVisibility'
import "./Sidebar.css"; import './Sidebar.css'
import { UsersData } from "../../background/types/roomData"; import { UsersData } from '../../background/types/roomData'
import useMeetingStarted from "../../hooks/useMeetingStarted"; import useMeetingStarted from '../../hooks/useMeetingStarted'
import { useRoomName } from "../../hooks/useRoomName"; import { useRoomName } from '../../hooks/useRoomName'
import Chat from "../chat/Chat"; import Chat from '../chat/Chat'
interface Props { interface Props {
usersData: UsersData; usersData: UsersData
sendMessage: Function sendMessage: Function
} }
function Sidebar(props: Props) { function Sidebar(props: Props) {
const { sidebarVisibility, toggleSidebarVisibility, sidebarToggleText } = const { sidebarVisibility, toggleSidebarVisibility, sidebarToggleText } = useSidebarVisibility()
useSidebarVisibility(); const [_, setMeetingStarted] = useMeetingStarted()
const [_, setMeetingStarted] = useMeetingStarted() const { updateAndSubmitRoomName: updateAndSubmitRoomName } = useRoomName()
const { updateAndSubmitRoomName: updateAndSubmitRoomName } = useRoomName();
return ( return (
<div className={`sidebar sidebar-${sidebarVisibility}`}> <div className={`sidebar sidebar-${sidebarVisibility}`}>
<SidebarHeader sidebarVisibility={sidebarVisibility} /> <SidebarHeader sidebarVisibility={sidebarVisibility} />
<div className="sidebar-body"> <div className="sidebar-body">
{props.usersData.roomsData.map((roomData) => { {props.usersData.roomsData.map((roomData) => {
return ( return (
<> <>
<h3> <h3>
<a href="#" onClick={() => { <a
updateAndSubmitRoomName(roomData.roomName) href="#"
setMeetingStarted(true) onClick={() => {
}}> updateAndSubmitRoomName(roomData.roomName)
{roomData.roomName} setMeetingStarted(true)
</a> }}
</h3> >
{roomData.participants.map((participant) => ( {roomData.roomName}
<div key={participant.jid}> {participant.displayName} </div> </a>
))} </h3>
</> {roomData.participants.map((participant) => (
); <div key={participant.jid}> {participant.displayName} </div>
})} ))}
</div> </>
<div> )
<h3> No room</h3> })}
{props.usersData.usersWithOutRoom.map((user) => ( </div>
<div key={user.uuid}>{user.name}</div> <div>
))} <h3> No room</h3>
</div> {props.usersData.usersWithOutRoom.map((user) => (
<div className="sidebar-footer"> <div key={user.uuid}>{user.name}</div>
{sidebarVisibility === "full" && <Chat sendMessage={props.sendMessage} />} ))}
<button className="sidebar-toggle" onClick={toggleSidebarVisibility}>{sidebarToggleText}</button> </div>
</div> <div className="sidebar-footer">
</div> {sidebarVisibility === 'full' && <Chat sendMessage={props.sendMessage} />}
); <button className="sidebar-toggle" onClick={toggleSidebarVisibility}>
{sidebarToggleText}
</button>
</div>
</div>
)
} }
export default Sidebar; export default Sidebar

View File

@ -1,16 +1,16 @@
import { sidebarVisibilityOptions } from "./useSidebarVisibility"; import { sidebarVisibilityOptions } from './useSidebarVisibility'
interface props { interface props {
sidebarVisibility: sidebarVisibilityOptions; sidebarVisibility: sidebarVisibilityOptions
} }
function SidebarHeader({ sidebarVisibility }: props) { function SidebarHeader({ sidebarVisibility }: props) {
if (sidebarVisibility == "full") { if (sidebarVisibility == 'full') {
return <h2>hi there :)</h2>; return <h2>hi there :)</h2>
} else if (sidebarVisibility == "small") { } else if (sidebarVisibility == 'small') {
return <h2>hi</h2>; return <h2>hi</h2>
} }
return <></>; return <></>
} }
export default SidebarHeader; export default SidebarHeader

View File

@ -1,28 +1,27 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from 'react'
export type sidebarVisibilityOptions = "full" | "small" | "hidden"; export type sidebarVisibilityOptions = 'full' | 'small' | 'hidden'
function useSidebarVisibility() { function useSidebarVisibility() {
const [sidebarVisibility, setSideBarVisibility] = const [sidebarVisibility, setSideBarVisibility] = useState<sidebarVisibilityOptions>('full')
useState<sidebarVisibilityOptions>("full");
const sidebarToggleText = getSidebarToggleText(sidebarVisibility); const sidebarToggleText = getSidebarToggleText(sidebarVisibility)
const toggleSidebarVisibility = useCallback(() => { const toggleSidebarVisibility = useCallback(() => {
if (sidebarVisibility === "full") { if (sidebarVisibility === 'full') {
setSideBarVisibility("small"); setSideBarVisibility('small')
} else if (sidebarVisibility === "small") { } else if (sidebarVisibility === 'small') {
setSideBarVisibility("hidden"); setSideBarVisibility('hidden')
} else if (sidebarVisibility === "hidden") { } else if (sidebarVisibility === 'hidden') {
setSideBarVisibility("full"); setSideBarVisibility('full')
} }
}, [setSideBarVisibility, sidebarVisibility, sidebarToggleText]); }, [setSideBarVisibility, sidebarVisibility, sidebarToggleText])
return { sidebarVisibility, toggleSidebarVisibility, sidebarToggleText }; return { sidebarVisibility, toggleSidebarVisibility, sidebarToggleText }
} }
const getSidebarToggleText = (sidebarVisibility: sidebarVisibilityOptions) => { const getSidebarToggleText = (sidebarVisibility: sidebarVisibilityOptions) => {
if (sidebarVisibility === "full") return "<-"; if (sidebarVisibility === 'full') return '<-'
if (sidebarVisibility === "small") return "<-"; if (sidebarVisibility === 'small') return '<-'
if (sidebarVisibility === "hidden") return "->"; if (sidebarVisibility === 'hidden') return '->'
}; }
export default useSidebarVisibility; export default useSidebarVisibility

View File

@ -1,21 +1,21 @@
import { atom, useAtom } from "jotai"; import { atom, useAtom } from 'jotai'
import { atomWithReducer, useReducerAtom } from 'jotai/utils' import { atomWithReducer, useReducerAtom } from 'jotai/utils'
import { User } from "../background/types/roomData"; import { User } from '../background/types/roomData'
interface ChatMessage { interface ChatMessage {
content: string content: string
sender: User sender: User
uuid: string uuid: string
timestamp: number timestamp: number
} }
export const allChatMessagesAtom = atomWithReducer([], (list: ChatMessage[], item: ChatMessage) => list.concat(item)) export const allChatMessagesAtom = atomWithReducer([], (list: ChatMessage[], item: ChatMessage) =>
list.concat(item)
)
const useAllChat = () => { const useAllChat = () => {
const [chatMessages, addChatMessage] = useAtom(allChatMessagesAtom) const [chatMessages, addChatMessage] = useAtom(allChatMessagesAtom)
return { chatMesages: chatMessages, addChatMessage } return { chatMesages: chatMessages, addChatMessage }
} }
export default useAllChat export default useAllChat

View File

@ -1,30 +1,29 @@
import { useEffect } from "react"; import { useEffect } from 'react'
import { UserInfo } from "../components/jitsi/types"; import { UserInfo } from '../components/jitsi/types'
import useAllChat from "./useAllChat"; import useAllChat from './useAllChat'
import useRoomData from "./useRoomData"; import useRoomData from './useRoomData'
import useWebSocketConnection from "./useWebSocketConnection"; import useWebSocketConnection from './useWebSocketConnection'
function useBackendData(userInfo: UserInfo) { function useBackendData(userInfo: UserInfo) {
console.log("[Rooms] useBackendData"); console.log('[Rooms] useBackendData')
const { onMessage, sendMessage, disconnect } = const { onMessage, sendMessage, disconnect } = useWebSocketConnection(userInfo)
useWebSocketConnection(userInfo);
const { roomData, setRoomData } = useRoomData(); const { roomData, setRoomData } = useRoomData()
const { chatMesages, addChatMessage } = useAllChat() const { chatMesages, addChatMessage } = useAllChat()
useEffect(() => { useEffect(() => {
onMessage((messageString) => { onMessage((messageString) => {
console.log("[Rooms] message from ws", messageString); console.log('[Rooms] message from ws', messageString)
const messageObject = JSON.parse(messageString); const messageObject = JSON.parse(messageString)
!!messageObject.roomsData && setRoomData(messageObject); !!messageObject.roomsData && setRoomData(messageObject)
!!messageObject.content && addChatMessage(messageObject); !!messageObject.content && addChatMessage(messageObject)
return disconnect; return disconnect
}); })
}, [onMessage, setRoomData, disconnect]); }, [onMessage, setRoomData, disconnect])
return { roomData, sendMessage }; return { roomData, sendMessage }
} }
export default useBackendData; export default useBackendData

View File

@ -1,28 +1,28 @@
import { useState } from "react"; import { useState } from 'react'
import { ConferenceData } from "../background/jitsi/eventListeners"; import { ConferenceData } from '../background/jitsi/eventListeners'
import { UserInfo } from "../components/jitsi/types"; import { UserInfo } from '../components/jitsi/types'
function useConferenceData( function useConferenceData(
sendMessage: (message: string) => void, sendMessage: (message: string) => void,
setUserInfo: (newData: UserInfo) => void setUserInfo: (newData: UserInfo) => void
) { ) {
const [conferenceData, setConferenceDataLocal] = useState<ConferenceData>(); const [conferenceData, setConferenceDataLocal] = useState<ConferenceData>()
const setConferenceData = (newData: ConferenceData) => { const setConferenceData = (newData: ConferenceData) => {
console.log("[Rooms] set conferenceData", conferenceData); console.log('[Rooms] set conferenceData', conferenceData)
if (conferenceData?.roomName !== newData.roomName) { if (conferenceData?.roomName !== newData.roomName) {
// We joined a meeting // We joined a meeting
sendMessage(JSON.stringify({ roomName: newData.roomName })); sendMessage(JSON.stringify({ roomName: newData.roomName }))
}
if (conferenceData?.displayName !== newData.displayName) {
// We updated the username
sendMessage(JSON.stringify({ displayName: newData.displayName }))
}
setConferenceDataLocal(newData)
setUserInfo({ displayName: newData.displayName, email: '' })
} }
if (conferenceData?.displayName !== newData.displayName) {
// We updated the username
sendMessage(JSON.stringify({ displayName: newData.displayName }));
}
setConferenceDataLocal(newData);
setUserInfo({ displayName: newData.displayName, email: "" });
};
return { conferenceData, setConferenceData }; return { conferenceData, setConferenceData }
} }
export default useConferenceData; export default useConferenceData

View File

@ -1,28 +1,28 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from 'react'
import { DisplayNameChangeEvent } from "../background/jitsi/eventListeners"; import { DisplayNameChangeEvent } from '../background/jitsi/eventListeners'
function useJitsiEventCapture(displayNameChangeHandler: Function) { function useJitsiEventCapture(displayNameChangeHandler: Function) {
const [displayNameChangeEvent, setDisplayNameChangeEventLocal] = const [displayNameChangeEvent, setDisplayNameChangeEventLocal] =
useState<DisplayNameChangeEvent>(); useState<DisplayNameChangeEvent>()
const setDisplayNameChangeEvent = useCallback( const setDisplayNameChangeEvent = useCallback(
(newValue: DisplayNameChangeEvent) => { (newValue: DisplayNameChangeEvent) => {
console.log("[Rooms] newValue", newValue, displayNameChangeEvent); console.log('[Rooms] newValue', newValue, displayNameChangeEvent)
if (displayNameChangeEvent != newValue) { if (displayNameChangeEvent != newValue) {
setDisplayNameChangeEventLocal(newValue); setDisplayNameChangeEventLocal(newValue)
} }
}, },
[setDisplayNameChangeEventLocal] [setDisplayNameChangeEventLocal]
); )
useEffect(() => { useEffect(() => {
console.log("[Rooms] displayNameChangeHandler", displayNameChangeEvent); console.log('[Rooms] displayNameChangeHandler', displayNameChangeEvent)
displayNameChangeHandler && displayNameChangeHandler &&
displayNameChangeEvent && displayNameChangeEvent &&
displayNameChangeHandler(displayNameChangeEvent); displayNameChangeHandler(displayNameChangeEvent)
}, [displayNameChangeEvent]); }, [displayNameChangeEvent])
return { setDisplayNameChangeEvent }; return { setDisplayNameChangeEvent }
} }
export default useJitsiEventCapture; export default useJitsiEventCapture

View File

@ -1,34 +1,32 @@
import { useState } from "react"; import { useState } from 'react'
import { USER_COOKIE_NAME } from "../background/constants"; import { USER_COOKIE_NAME } from '../background/constants'
import { getCookie, setCookie } from "../background/cookies"; import { getCookie, setCookie } from '../background/cookies'
import { UserInfo } from "../components/jitsi/types"; import { UserInfo } from '../components/jitsi/types'
function useLocalUser() { function useLocalUser() {
const [userInfo, setUserInfoLocal] = useState<UserInfo>(() => const [userInfo, setUserInfoLocal] = useState<UserInfo>(() => getUserInfoFromCookie())
getUserInfoFromCookie()
);
const setUserInfo = (newData: UserInfo) => { const setUserInfo = (newData: UserInfo) => {
storeUserInfoInCookie(newData); storeUserInfoInCookie(newData)
setUserInfoLocal(newData); setUserInfoLocal(newData)
}; }
return { userInfo, setUserInfo }; return { userInfo, setUserInfo }
} }
function getUserInfoFromCookie(): UserInfo { function getUserInfoFromCookie(): UserInfo {
let cookie = getCookie(USER_COOKIE_NAME); let cookie = getCookie(USER_COOKIE_NAME)
console.log("[Rooms] getUserNameFromCookie", cookie); console.log('[Rooms] getUserNameFromCookie', cookie)
if (cookie) return JSON.parse(cookie); if (cookie) return JSON.parse(cookie)
return { return {
displayName: "Unknown traveller", displayName: 'Unknown traveller',
email: "", email: '',
}; }
} }
function storeUserInfoInCookie(userInfo: UserInfo) { function storeUserInfoInCookie(userInfo: UserInfo) {
console.log("[Rooms] storeUserInfoInCookie", userInfo); console.log('[Rooms] storeUserInfoInCookie', userInfo)
setCookie(USER_COOKIE_NAME, JSON.stringify(userInfo)); setCookie(USER_COOKIE_NAME, JSON.stringify(userInfo))
} }
export default useLocalUser; export default useLocalUser

View File

@ -1,7 +1,7 @@
import { atom, useAtom } from "jotai"; import { atom, useAtom } from 'jotai'
const meetingStarted = atom(false) const meetingStarted = atom(false)
const useMeetingStarted = () => useAtom(meetingStarted); const useMeetingStarted = () => useAtom(meetingStarted)
export default useMeetingStarted export default useMeetingStarted

View File

@ -1,10 +1,10 @@
import { useState } from "react"; import { useState } from 'react'
import { UsersData } from "../background/types/roomData"; import { UsersData } from '../background/types/roomData'
function useRoomData() { function useRoomData() {
const [roomData, setRoomData] = useState<UsersData>(); const [roomData, setRoomData] = useState<UsersData>()
return { roomData, setRoomData }; return { roomData, setRoomData }
} }
export default useRoomData; export default useRoomData

View File

@ -1,55 +1,53 @@
import { atom, useAtom } from "jotai"; import { atom, useAtom } from 'jotai'
import { useCallback, useState } from "react"; import { useCallback, useState } from 'react'
const roomNameAtom = atom(getRoomNameFromUrl()) const roomNameAtom = atom(getRoomNameFromUrl())
function useRoomName() { function useRoomName() {
const [roomName, setRoomName] = useAtom(roomNameAtom) const [roomName, setRoomName] = useAtom(roomNameAtom)
const updateRoomName = useCallback( const updateRoomName = useCallback(
(newName: string) => { (newName: string) => {
setRoomName(newName); setRoomName(newName)
console.log("[Rooms] update room name", newName); console.log('[Rooms] update room name', newName)
}, },
[setRoomName] [setRoomName]
); )
const updateAndSubmitRoomName = useCallback( const updateAndSubmitRoomName = useCallback(
(newName: string) => { (newName: string) => {
setRoomName(newName); setRoomName(newName)
setRoomNameInUrl(newName); setRoomNameInUrl(newName)
setRoomNameInTitle(newName); setRoomNameInTitle(newName)
console.log("[Rooms] update and submit room name", newName); console.log('[Rooms] update and submit room name', newName)
}, },
[setRoomName] [setRoomName]
) )
const submitRoomName = useCallback(() => { const submitRoomName = useCallback(() => {
setRoomNameInUrl(roomName); setRoomNameInUrl(roomName)
setRoomNameInTitle(roomName); setRoomNameInTitle(roomName)
}, [roomName]); }, [roomName])
return { roomName, updateRoomName, updateAndSubmitRoomName, submitRoomName }; return { roomName, updateRoomName, updateAndSubmitRoomName, submitRoomName }
} }
function getRoomNameFromUrl(): string { function getRoomNameFromUrl(): string {
const pathName = location.pathname; const pathName = location.pathname
const roomName = pathName const roomName = pathName.substring(pathName.lastIndexOf('/'), pathName.length).replace('/', '')
.substring(pathName.lastIndexOf("/"), pathName.length) console.log('[Rooms] Got roomName from url', roomName)
.replace("/", ""); setRoomNameInTitle(roomName)
console.log("[Rooms] Got roomName from url", roomName); return roomName
setRoomNameInTitle(roomName);
return roomName;
} }
function setRoomNameInUrl(roomName: string) { function setRoomNameInUrl(roomName: string) {
history.pushState(history.state, "_", roomName); history.pushState(history.state, '_', roomName)
} }
function setRoomNameInTitle(roomName: string) { function setRoomNameInTitle(roomName: string) {
if (!!roomName) { if (!!roomName) {
document.title = roomName; document.title = roomName
} }
} }
export { useRoomName }; export { useRoomName }

View File

@ -1,50 +1,44 @@
import { useCallback, useState } from "react"; import { useCallback, useState } from 'react'
import { WEBSOCKET_URL } from "../background/constants"; import { WEBSOCKET_URL } from '../background/constants'
import { UserInfo } from "../components/jitsi/types"; import { UserInfo } from '../components/jitsi/types'
const createWebSocketConnection = (userInfo: UserInfo) => { const createWebSocketConnection = (userInfo: UserInfo) => {
const webSocket = new WebSocket(WEBSOCKET_URL); const webSocket = new WebSocket(WEBSOCKET_URL)
console.log("[Rooms] createWebSocketConnection"); console.log('[Rooms] createWebSocketConnection')
webSocket.addEventListener("open", (_: Event) => webSocket.addEventListener('open', (_: Event) => webSocket.send(JSON.stringify(userInfo)))
webSocket.send(JSON.stringify(userInfo))
);
return webSocket; return webSocket
};
function useWebSocketConnection(userInfo: UserInfo) {
console.log("[Rooms] useWebSocketConnection");
const [webSocketConnection] = useState<WebSocket>(() =>
createWebSocketConnection(userInfo)
);
const sendMessage = useCallback(
(message: string) => {
//if the socket's open, send a message:
if (webSocketConnection.readyState === WebSocket.OPEN) {
console.log("[Rooms] sending to ws", message);
webSocketConnection.send(message);
}
},
[webSocketConnection]
);
const onMessage = useCallback(
(messageHandler: (messageHandler: string) => void) => {
const wsMessageHandler = (ev: MessageEvent<any>) => {
messageHandler(ev.data);
};
webSocketConnection.addEventListener("message", wsMessageHandler);
},
[webSocketConnection]
);
const disconnect = useCallback(webSocketConnection.close, [
webSocketConnection,
]);
return { onMessage, sendMessage, disconnect };
} }
export default useWebSocketConnection; function useWebSocketConnection(userInfo: UserInfo) {
console.log('[Rooms] useWebSocketConnection')
const [webSocketConnection] = useState<WebSocket>(() => createWebSocketConnection(userInfo))
const sendMessage = useCallback(
(message: string) => {
//if the socket's open, send a message:
if (webSocketConnection.readyState === WebSocket.OPEN) {
console.log('[Rooms] sending to ws', message)
webSocketConnection.send(message)
}
},
[webSocketConnection]
)
const onMessage = useCallback(
(messageHandler: (messageHandler: string) => void) => {
const wsMessageHandler = (ev: MessageEvent<any>) => {
messageHandler(ev.data)
}
webSocketConnection.addEventListener('message', wsMessageHandler)
},
[webSocketConnection]
)
const disconnect = useCallback(webSocketConnection.close, [webSocketConnection])
return { onMessage, sendMessage, disconnect }
}
export default useWebSocketConnection

View File

@ -1,96 +1,95 @@
:root { :root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif; font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px; font-size: 16px;
line-height: 24px; line-height: 24px;
font-weight: 400; font-weight: 400;
color-scheme: light dark; color-scheme: light dark;
color: rgba(255, 255, 255, 0.87); color: rgba(255, 255, 255, 0.87);
background-color: #181c25; background-color: #181c25;
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
height: 100%; height: 100%;
margin: 0; margin: 0;
overflow-y: hidden; overflow-y: hidden;
} }
a { a {
font-weight: 500; font-weight: 500;
color: #646cff; color: #646cff;
text-decoration: inherit; text-decoration: inherit;
} }
a:hover { a:hover {
color: #535bf2; color: #535bf2;
} }
body { body {
margin: 0; margin: 0;
display: flex; display: flex;
place-items: center; place-items: center;
min-width: 320px; min-width: 320px;
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
h1 { h1 {
font-size: 3.2em; font-size: 3.2em;
line-height: 1.1; line-height: 1.1;
} }
button { button {
border-radius: 8px; border-radius: 8px;
border: 1px solid transparent; border: 1px solid transparent;
padding: 0.6em 1.2em; padding: 0.6em 1.2em;
font-size: 1em; font-size: 1em;
font-weight: 500; font-weight: 500;
font-family: inherit; font-family: inherit;
background-color: #1a1a1a; background-color: #1a1a1a;
cursor: pointer; cursor: pointer;
transition: border-color 0.25s; transition: border-color 0.25s;
} }
button:hover { button:hover {
border-color: #646cff; border-color: #646cff;
} }
button:focus, button:focus,
button:focus-visible { button:focus-visible {
outline: 4px auto -webkit-focus-ring-color; outline: 4px auto -webkit-focus-ring-color;
} }
@media (prefers-color-scheme: light) { @media (prefers-color-scheme: light) {
:root { :root {
color: #213547; color: #213547;
background-color: #ffffff; background-color: #ffffff;
} }
a:hover { a:hover {
color: #747bff; color: #747bff;
} }
button { button {
background-color: #f9f9f9; background-color: #f9f9f9;
} }
} }
#root { #root {
height: 100%; height: 100%;
width: 100%; width: 100%;
} }
input { input {
font-size: 1em; font-size: 1em;
margin: 0.3em; margin: 0.3em;
border: 0; border: 0;
height: 2.3em; height: 2.3em;
padding-left: 0.4em; padding-left: 0.4em;
box-shadow: none; box-shadow: none;
box-sizing: border-box; box-sizing: border-box;
font-size: 17px; font-size: 17px;
font-family: "Oxygen", sans-serif; font-family: 'Oxygen', sans-serif;
transition: top 0.1s ease-in-out; transition: top 0.1s ease-in-out;
background-color: #2b2a33; background-color: #2b2a33;
border-color: #646cff; border-color: #646cff;
border-radius: 8px; border-radius: 8px;
} }

View File

@ -5,9 +5,9 @@ import App from './App'
import './index.css' import './index.css'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode> <React.StrictMode>
<Provider> <Provider>
<App /> <App />
</Provider> </Provider>
</React.StrictMode>, </React.StrictMode>
) )

View File

@ -1,21 +1,21 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"], "lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false, "allowJs": false,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": false, "esModuleInterop": false,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "Node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx" "jsx": "react-jsx"
}, },
"include": ["src"], "include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]
} }

View File

@ -1,9 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "Node",
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]
} }

View File

@ -3,5 +3,5 @@ import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
}) })

File diff suppressed because it is too large Load Diff