changed About to Settings, added username change field

optimize-reads
Simon Huang 5 years ago
parent 8ddcaa8de2
commit 38600326df

10
package-lock.json generated

@ -1492,6 +1492,11 @@
"@hapi/hoek": "^8.3.0"
}
},
"@hookform/error-message": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@hookform/error-message/-/error-message-0.0.4.tgz",
"integrity": "sha512-as5acnxkWuF5XrzHhZlzdXUk4zpHNzFLe+uGyaJtSrMbDVTvcNL62ESRK5DTVGPix8shJnt6G2LfrobHBPpvYg=="
},
"@iconify/icons-simple-icons": {
"version": "1.0.47",
"resolved": "https://registry.npmjs.org/@iconify/icons-simple-icons/-/icons-simple-icons-1.0.47.tgz",
@ -12032,6 +12037,11 @@
"resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz",
"integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA=="
},
"react-hook-form": {
"version": "6.9.2",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.9.2.tgz",
"integrity": "sha512-vCPEbHVCRvsoqrQARgQ7a3VrXzqbFOO53gHFRdQzLzHMT9kxum3wfcSi8A1b49KPRsomvsqexH4tBUJMneEu+Q=="
},
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",

@ -6,6 +6,7 @@
"@capacitor/android": "^2.4.0",
"@capacitor/core": "2.4.0",
"@capacitor/ios": "^2.4.0",
"@hookform/error-message": "0.0.4",
"@ionic/react": "^5.0.7",
"@ionic/react-router": "^5.0.7",
"@testing-library/jest-dom": "^4.2.4",
@ -24,6 +25,7 @@
"node-sass": "^4.14.1",
"react": "^16.13.0",
"react-dom": "^16.13.0",
"react-hook-form": "^6.9.2",
"react-player": "^2.6.0",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",

@ -24,7 +24,7 @@ const collection = {
requests: [
{
createdAt: 'timestamp',
time: '01:25:44', // Relevant for 'play', 'pause' types
data: '01:25:44', // Contents of data depend on type of request
type: 'updateState',
senderId: 'userId',
},

@ -1,42 +0,0 @@
import { IonCol, IonGrid, IonIcon, IonRow, IonRouterLink } from '@ionic/react';
import { logoGithub } from 'ionicons/icons';
import { Icon } from '@iconify/react';
import discordIcon from '@iconify/icons-simple-icons/discord';
import React from 'react';
import './About.css';
type AboutProps = {
pane: string;
};
const About: React.FC<AboutProps> = ({ pane }) => {
return (
<IonGrid style={{ display: pane === 'about' ? null : 'none' }} class="about-grid">
<IonRow>
<IonCol>
<span>Any feedback, questions, or issues? </span>
<span role="img" aria-label="Turtle">
🐢🐢
</span>
</IonCol>
</IonRow>
<IonRow class="externals-row">
<IonCol size="3"></IonCol>
<IonCol size="3">
<IonRouterLink href="https://github.com/shuang854/Turtle" target="_blank">
<IonIcon icon={logoGithub} class="about-icons"></IonIcon>
</IonRouterLink>
</IonCol>
<IonCol size="3">
<IonRouterLink href="https://discord.gg/NEw3Msu" target="_blank">
<Icon icon={discordIcon} className="about-icons"></Icon>
</IonRouterLink>
</IonCol>
<IonCol size="3"></IonCol>
</IonRow>
</IonGrid>
);
};
export default About;

@ -19,3 +19,10 @@ ion-fab-button {
--background: var(--ion-color-secondary-shade);
--background-activated: var(--ion-color-secondary);
}
ion-list-header {
color: var(--ion-color-primary);
font-size: 20px;
border-bottom: 1px solid
var(--ion-item-border-color, var(--ion-border-color, var(--ion-color-step-150, rgba(0, 0, 0, 0.13))));
}

@ -1,10 +1,10 @@
import { IonCard, IonIcon, IonSegment, IonSegmentButton } from '@ionic/react';
import { chatboxOutline, informationCircleOutline, peopleOutline } from 'ionicons/icons';
import React, { useState } from 'react';
import About from './About';
import './Frame.css';
import Messages from './Messages';
import OnlineList from './OnlineList';
import Settings from './Settings';
type FrameProps = {
ownerId: string;
@ -26,7 +26,7 @@ const Frame: React.FC<FrameProps> = ({ ownerId, roomId, userId, userList, joinTi
<IonSegmentButton value="online" onClick={() => setPane('online')}>
<IonIcon icon={peopleOutline}></IonIcon>
</IonSegmentButton>
<IonSegmentButton value="about" onClick={() => setPane('about')}>
<IonSegmentButton value="settings" onClick={() => setPane('settings')}>
<IonIcon icon={informationCircleOutline}></IonIcon>
</IonSegmentButton>
</IonSegment>
@ -40,7 +40,7 @@ const Frame: React.FC<FrameProps> = ({ ownerId, roomId, userId, userList, joinTi
joinTime={joinTime}
></Messages>
<OnlineList pane={pane} roomId={roomId} userId={userId} userList={userList}></OnlineList>
<About pane={pane}></About>
<Settings pane={pane} roomId={roomId} userId={userId}></Settings>
</IonCard>
);
};

@ -41,6 +41,7 @@
.message-toolbar {
padding-left: 5px;
--background: var(--ion-color-light);
border-top: 1px solid #777;
}
.footer-ios ion-toolbar:first-of-type {

@ -68,11 +68,11 @@ const Messages: React.FC<MessagesProps> = ({ pane, ownerId, roomId, userId, user
for (const req of requests) {
if (req.createdAt > joinTime && req.type !== 'updateState') {
arr.push({
content: processType(req.type, req.time),
content: processType(req.type, req.data),
createdAt: req.createdAt,
id: req.senderId + req.createdAt,
senderId: req.senderId,
type: 'system',
type: req.type,
});
}
}
@ -86,16 +86,18 @@ const Messages: React.FC<MessagesProps> = ({ pane, ownerId, roomId, userId, user
}, [roomId, joinTime]);
// Convert request type to message content
const processType = (type: string, time: number): string => {
const processType = (type: string, data: any): string => {
switch (type) {
case 'change':
return 'changed the video.';
case 'join':
return 'joined the room.';
case 'pause':
return 'paused the video at ' + secondsToTimestamp(time);
return 'paused the video at ' + secondsToTimestamp(data);
case 'play':
return 'played the video from ' + secondsToTimestamp(time);
return 'played the video from ' + secondsToTimestamp(data);
case 'nameChange':
return data.prev + ' changed their name to ' + data.curr;
default:
return '';
}
@ -170,7 +172,7 @@ const Messages: React.FC<MessagesProps> = ({ pane, ownerId, roomId, userId, user
return (
<IonRow key={msg.id}>
<IonCol size="auto" class="system-msg">
<span>{getName(msg.senderId) + ' ' + msg.content}</span>
<span>{msg.type === 'nameChange' ? msg.content : getName(msg.senderId) + ' ' + msg.content}</span>
</IonCol>
</IonRow>
);

@ -2,13 +2,6 @@
height: calc(100% - 162px + 52px);
}
.online-header {
border-bottom: 1px solid var(--ion-color-primary);
color: var(--ion-color-primary);
font-size: 20px;
align-items: center;
}
.online-list {
padding-top: 0;
padding-bottom: 0;

@ -66,7 +66,7 @@ const OnlineList: React.FC<OnlineListProps> = ({ pane, roomId, userId, userList
return (
<IonContent style={{ display: pane === 'online' ? null : 'none' }} class="online-content">
<IonListHeader class="online-header">Online</IonListHeader>
<IonListHeader>Online</IonListHeader>
<IonList class="online-list">
{Array.from(userList.values()).map((user) => {
return (
@ -78,7 +78,7 @@ const OnlineList: React.FC<OnlineListProps> = ({ pane, roomId, userId, userList
</IonList>
<IonRow>
<IonCol class="clipboard-col">
<IonListHeader class="online-header">Invite friends!</IonListHeader>
<IonListHeader>Invite friends!</IonListHeader>
<IonToolbar class="clipboard-toolbar">
<IonInput readonly value={window.location.href} ref={inputRef} class="clipboard-input"></IonInput>
<IonFabButton slot="end" size="small" onClick={copyLink} class="send-button">

@ -28,7 +28,7 @@ const ReactPlayerFrame: React.FC<ReactPlayerFrameProps> = ({ ownerId, userId, ro
db.collection('rooms')
.doc(roomId)
.update({
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: currTime, type: 'play' }),
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: currTime, type: 'play' }),
});
}
}
@ -48,7 +48,7 @@ const ReactPlayerFrame: React.FC<ReactPlayerFrameProps> = ({ ownerId, userId, ro
db.collection('rooms')
.doc(roomId)
.update({
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: currTime, type: 'pause' }),
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: currTime, type: 'pause' }),
});
}
}
@ -88,7 +88,7 @@ const ReactPlayerFrame: React.FC<ReactPlayerFrameProps> = ({ ownerId, userId, ro
db.collection('rooms')
.doc(roomId)
.update({
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: 0, type: 'updateState' }),
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: 0, type: 'updateState' }),
});
}
};
@ -119,7 +119,7 @@ const ReactPlayerFrame: React.FC<ReactPlayerFrameProps> = ({ ownerId, userId, ro
player.current?.seekTo(realTimeState);
roomRef.update({
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: 0, type: 'updateState' }),
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: 0, type: 'updateState' }),
});
}
}

@ -58,7 +58,7 @@ const SubscriptionFrame: React.FC<SubscriptionFrameProps> = ({ ownerId, userId,
db.collection('rooms')
.doc(roomId)
.update({
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: e.data.time, type: type }),
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: e.data.time, type: type }),
});
}
};
@ -167,7 +167,7 @@ const SubscriptionFrame: React.FC<SubscriptionFrameProps> = ({ ownerId, userId,
seekTo(actual?.time);
roomRef.update({
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: 0, type: 'updateState' }),
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: 0, type: 'updateState' }),
});
}
}

@ -25,7 +25,7 @@ const RoomHeader: React.FC<RoomHeaderProps> = ({ roomId, userId, ownerId }) => {
.collection('rooms')
.doc(roomId)
.update({
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, time: 0, type: 'change' }),
requests: arrayUnion({ createdAt: Date.now(), senderId: userId, data: 0, type: 'change' }),
});
setVideoUrl('');

@ -1,6 +1,24 @@
.settings-header {
text-align: left;
}
.name-item {
font-size: 14px;
color: var(--ion-color-secondary);
}
.name-input {
color: #fff;
}
.error-message {
padding-top: 4px;
padding-left: 16px;
color: #e61a61;
}
.about-grid {
margin-top: 50px;
max-width: 500px;
margin-top: 20px;
font-size: 20px;
font-style: bold;
color: var(--ion-color-secondary);

@ -0,0 +1,130 @@
import { ErrorMessage } from '@hookform/error-message';
import discordIcon from '@iconify/icons-simple-icons/discord';
import { Icon } from '@iconify/react';
import {
IonCol,
IonContent,
IonGrid,
IonIcon,
IonInput,
IonItem,
IonLabel,
IonListHeader,
IonRouterLink,
IonRow,
IonToast,
} from '@ionic/react';
import { logoGithub } from 'ionicons/icons';
import React, { useState } from 'react';
import { Controller, useForm } from 'react-hook-form';
import { arrayUnion, db, rtdb } from '../services/firebase';
import './Settings.css';
type SettingsProps = {
pane: string;
roomId: string;
userId: string;
};
const Settings: React.FC<SettingsProps> = ({ pane, roomId, userId }) => {
const { control, errors, setValue, getValues } = useForm({ mode: 'onChange' });
const [showNameChange, setShowNameChange] = useState(false);
// Update databases with new username
const changeName = async () => {
const newName = getValues('username');
if (newName !== '') {
const snapshot = await db.collection('users').doc(userId).get();
const prevName = snapshot.data()?.name;
db.collection('users').doc(userId).update({
name: newName,
});
rtdb.ref('/rooms/' + roomId + '/' + userId).set({ name: newName });
// Send 'nameChange' request for all clients to get a message about the name change
db.collection('rooms')
.doc(roomId)
.update({
requests: arrayUnion({
createdAt: Date.now(),
senderId: userId,
data: { prev: prevName, curr: newName },
type: 'nameChange',
}),
});
setShowNameChange(true);
}
};
const onEnter = (e: React.KeyboardEvent<HTMLIonInputElement>) => {
if (e.key === 'Enter') {
if (!errors.username) {
changeName();
setValue('username', '');
}
}
};
return (
<IonContent style={{ display: pane === 'settings' ? null : 'none' }}>
<IonListHeader class="settings-header">Settings</IonListHeader>
<IonItem class="name-item">
<IonLabel>Change Username</IonLabel>
<Controller
name="username"
render={({ onChange, onBlur, value }) => (
<IonInput
onIonChange={onChange}
onKeyDown={(e) => onEnter(e)}
placeholder="New name"
maxlength={20}
value={value}
class="name-input"
/>
)}
control={control}
rules={{
minLength: { value: 4, message: '⚠ Must be at least 4 characters long' },
pattern: { value: /^\w+$/, message: '⚠ Must be alphanumeric' },
}}
></Controller>
</IonItem>
<ErrorMessage name="username" errors={errors} as="span" className="error-message"></ErrorMessage>
<IonGrid class="about-grid">
<IonRow>
<IonCol>
<span>Any feedback, questions, or issues? </span>
<span role="img" aria-label="Turtle">
🐢🐢
</span>
</IonCol>
</IonRow>
<IonRow class="externals-row">
<IonCol size="3"></IonCol>
<IonCol size="3">
<IonRouterLink href="https://github.com/shuang854/Turtle" target="_blank">
<IonIcon icon={logoGithub} class="about-icons"></IonIcon>
</IonRouterLink>
</IonCol>
<IonCol size="3">
<IonRouterLink href="https://discord.gg/NEw3Msu" target="_blank">
<Icon icon={discordIcon} className="about-icons"></Icon>
</IonRouterLink>
</IonCol>
<IonCol size="3"></IonCol>
</IonRow>
</IonGrid>
<IonToast
color="primary"
duration={2000}
isOpen={showNameChange}
onDidDismiss={() => setShowNameChange(false)}
position="top"
message="Username changed successfully"
></IonToast>
</IonContent>
);
};
export default Settings;

@ -64,7 +64,7 @@ const Room: React.FC<RouteComponentProps<{ roomId: string }>> = ({ match }) => {
// Keep track of online user presence in realtime database rooms
roomRef.on('value', async (snapshot) => {
// Populate list of users in a room
// Populate list of usernames in a room
const map: Map<string, string> = new Map<string, string>();
snapshot.forEach((childSnapshot) => {
if (childSnapshot.key !== null && childSnapshot.key !== 'userCount') {

Loading…
Cancel
Save