feat: move room settings to dialog

pull/63/head
sualko 2020-06-04 14:01:31 +02:00
parent bcc483b918
commit b0edd567f0
7 changed files with 171 additions and 45 deletions

View File

@ -163,3 +163,16 @@
} }
} }
} }
.bbb-form-element {
min-width: 300px;
margin: 2em 0;
input:not([type="checkbox"]) {
width: 100%;
}
em {
white-space: normal;
}
}

View File

@ -72,7 +72,7 @@ const App: React.FC<Props> = () => {
} }
function updateRoom(room: Room) { function updateRoom(room: Room) {
api.updateRoom(room).then(updatedRoom => { return api.updateRoom(room).then(updatedRoom => {
setRooms(rooms.map(room => { setRooms(rooms.map(room => {
if (room.id === updatedRoom.id) { if (room.id === updatedRoom.id) {
return updatedRoom; return updatedRoom;
@ -101,9 +101,6 @@ const App: React.FC<Props> = () => {
<th onClick={() => onOrderBy('name')}> <th onClick={() => onOrderBy('name')}>
{t('bbb', 'Name')} <SortArrow name='name' value={orderBy} direction={sortOrder} /> {t('bbb', 'Name')} <SortArrow name='name' value={orderBy} direction={sortOrder} />
</th> </th>
<th onClick={() => onOrderBy('welcome')}>
{t('bbb', 'Welcome')} <SortArrow name='welcome' value={orderBy} direction={sortOrder} />
</th>
<th onClick={() => onOrderBy('maxParticipants')}> <th onClick={() => onOrderBy('maxParticipants')}>
{t('bbb', 'Max')} <SortArrow name='maxParticipants' value={orderBy} direction={sortOrder} /> {t('bbb', 'Max')} <SortArrow name='maxParticipants' value={orderBy} direction={sortOrder} />
</th> </th>
@ -114,6 +111,7 @@ const App: React.FC<Props> = () => {
{t('bbb', 'Recordings')} {t('bbb', 'Recordings')}
</th> </th>
<th /> <th />
<th />
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

30
ts/Manager/Dialog.tsx Normal file
View File

@ -0,0 +1,30 @@
import React from 'react';
type Props = {
open: boolean;
onClose?: () => void;
title: string;
}
const Dialog: React.FC<Props> = ({open, title, children, onClose = () => undefined}) => {
if (!open) {
return <></>;
}
return (
<>
<div className="oc-dialog-dim"> </div>
<div className="oc-dialog" tabIndex={-1} role="dialog" style={{display:'inline-block', position: 'fixed'}}>
<h2 className="oc-dialog-title">{title}</h2>
<a className="oc-dialog-close" onClick={ev => {ev.preventDefault(); onClose();}}></a>
<div className="oc-dialog-content">
{children}
</div>
</div>
</>
);
};
export default Dialog;

View File

@ -0,0 +1,61 @@
import React, { useState } from 'react';
import Dialog from './Dialog';
import { Room } from './Api';
import { SubmitInput } from './SubmitInput';
const descriptions: { [key: string]: string } = {
name: t('bbb', 'Descriptive name of this room.'),
welcome: t('bbb', 'This message is shown to all users in the chat area after they joined.'),
maxParticipants: t('bbb', 'Sets a limit on the number of participants for this room. Zero means there is no limit.'),
recording: t('bbb', 'If enabled, the moderator is able to start the recording.'),
};
type Props = {
room: Room;
updateProperty: (key: string, value: string | boolean | number) => Promise<void>;
}
const EditRoomDialog: React.FC<Props> = ({ room, updateProperty }) => {
const [open, setOpen] = useState<boolean>(false);
function formElement(label: string, field: string, type: 'text' | 'number' = 'text') {
return (
<div className="bbb-form-element">
<label htmlFor={`bbb-${field}`}>
<h3>{t('bbb', label)}</h3>
</label>
<SubmitInput initialValue={room[field]} type={type} name={field} onSubmitValue={value => updateProperty(field, value)} />
{descriptions[field] && <em>{descriptions[field]}</em>}
</div>
);
}
return (
<>
<a className="icon icon-edit icon-visible"
onClick={ev => { ev.preventDefault(), setOpen(true); }}
title={t('bbb', 'Edit')} />
<Dialog open={open} onClose={() => setOpen(false)} title={t('bbb', 'Edit "{room}"', { room: room.name })}>
{formElement('Name', 'name')}
{formElement('Welcome', 'welcome')}
{formElement('Participant limit', 'maxParticipants', 'number')}
<div>
<div>
<input id={`bbb-record-${room.id}`}
type="checkbox"
className="checkbox"
checked={room.record}
onChange={(event) => updateProperty('record', event.target.checked)} />
<label htmlFor={`bbb-record-${room.id}`}>{t('bbb', 'Recording')}</label>
</div>
<em>{descriptions.recording}</em>
</div>
</Dialog>
</>
);
};
export default EditRoomDialog;

View File

@ -0,0 +1,46 @@
import React, {useState} from 'react';
import { SubmitInput } from './SubmitInput';
type EditableValueProps = {
value: string;
setValue: (key: string, value: string | number) => Promise<void>;
field: string;
type: 'text' | 'number';
}
const EditableValue: React.FC<EditableValueProps> = ({ setValue, field, value: currentValue, type }) => {
const [active, setActive] = useState<boolean>(false);
const submit = (value: string | number) => {
if (value === currentValue) {
setActive(false);
return;
}
setValue(field, value).then(() => {
setActive(false);
});
};
if (active) {
return <SubmitInput
name={field}
autoFocus={true}
onSubmitValue={(value) => submit(type === 'number' ? parseInt(value) : value)}
onClick={event => event.stopPropagation()}
initialValue={currentValue}
type={type}
focus={true}
/>;
}
function onClick(ev) {
ev.stopPropagation();
setActive(true);
}
return <a className="action-rename" onClick={onClick}>{currentValue}</a>;
};
export default EditableValue;

View File

@ -1,24 +1,16 @@
import React, { useState, useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import { CopyToClipboard } from 'react-copy-to-clipboard'; import { CopyToClipboard } from 'react-copy-to-clipboard';
import { SubmitInput } from './SubmitInput'; import { api, Recording, Room } from './Api';
import { Room, Recording, api } from './Api'; import EditRoomDialog from './EditRoomDialog';
import RecordingRow from './RecordingRow'; import RecordingRow from './RecordingRow';
import EditableValue from './EditableValue';
type Props = { type Props = {
room: Room; room: Room;
updateRoom: (room: Room) => void; updateRoom: (room: Room) => Promise<void>;
deleteRoom: (id: number) => void; deleteRoom: (id: number) => void;
} }
type EditableValueProps = {
setValue: (key: string, value: string | number) => void;
setActive: (key: string) => void;
active: string;
field: string;
value: string;
type: 'text' | 'number';
}
type RecordingsNumberProps = { type RecordingsNumberProps = {
recordings: null | Recording[]; recordings: null | Recording[];
showRecordings: boolean; showRecordings: boolean;
@ -41,28 +33,9 @@ const RecordingsNumber: React.FC<RecordingsNumberProps> = ({ recordings, showRec
return <span>0</span>; return <span>0</span>;
}; };
const EditableValue: React.FC<EditableValueProps> = ({ setValue, setActive, active, field, value, type }) => {
if (active === field) {
return <SubmitInput
autoFocus={true}
onSubmitValue={(value) => setValue(field, type === 'number' ? parseInt(value) : value)}
onClick={event => event.stopPropagation()}
initialValue={value}
type={type}
/>;
}
function onClick(ev) {
ev.stopPropagation();
setActive(field);
}
return <a className="action-rename" onClick={onClick}>{value}</a>;
};
const RoomRow: React.FC<Props> = (props) => { const RoomRow: React.FC<Props> = (props) => {
const [activeEdit, setActiveEdit] = useState('');
const [recordings, setRecordings] = useState<Recording[] | null>(null); const [recordings, setRecordings] = useState<Recording[] | null>(null);
const [showRecordings, setShowRecordings] = useState<boolean>(false); const [showRecordings, setShowRecordings] = useState<boolean>(false);
const room = props.room; const room = props.room;
@ -83,12 +56,10 @@ const RoomRow: React.FC<Props> = (props) => {
}, [areRecordingsLoaded]); }, [areRecordingsLoaded]);
function updateRoom(key: string, value: string | boolean | number) { function updateRoom(key: string, value: string | boolean | number) {
props.updateRoom({ return props.updateRoom({
...props.room, ...props.room,
[key]: value, [key]: value,
}); });
setActiveEdit('');
} }
function deleteRow(ev: MouseEvent) { function deleteRow(ev: MouseEvent) {
@ -184,7 +155,7 @@ const RoomRow: React.FC<Props> = (props) => {
} }
function edit(field: string, type: 'text' | 'number' = 'text') { function edit(field: string, type: 'text' | 'number' = 'text') {
return <EditableValue field={field} value={room[field]} active={activeEdit} setActive={setActiveEdit} setValue={updateRoom} type={type} />; return <EditableValue field={field} value={room[field]} setValue={updateRoom} type={type} />;
} }
return ( return (
@ -204,9 +175,6 @@ const RoomRow: React.FC<Props> = (props) => {
<td className="name"> <td className="name">
{edit('name')} {edit('name')}
</td> </td>
<td className="welcome">
{edit('welcome')}
</td>
<td className="max-participants"> <td className="max-participants">
{edit('maxParticipants', 'number')} {edit('maxParticipants', 'number')}
</td> </td>
@ -215,6 +183,9 @@ const RoomRow: React.FC<Props> = (props) => {
<label htmlFor={`bbb-record-${room.id}`}></label> <label htmlFor={`bbb-record-${room.id}`}></label>
</td> </td>
<td><RecordingsNumber recordings={recordings} showRecordings={showRecordings} setShowRecordings={setShowRecordings} /></td> <td><RecordingsNumber recordings={recordings} showRecordings={showRecordings} setShowRecordings={setShowRecordings} /></td>
<td className="edit icon-col">
<EditRoomDialog room={props.room} updateProperty={updateRoom} />
</td>
<td className="remove icon-col"> <td className="remove icon-col">
<a className="icon icon-delete icon-visible" <a className="icon icon-delete icon-visible"
onClick={deleteRow as any} onClick={deleteRow as any}

View File

@ -7,7 +7,9 @@ import {
export interface SubmitInputProps extends InputHTMLAttributes<HTMLInputElement> { export interface SubmitInputProps extends InputHTMLAttributes<HTMLInputElement> {
type?: 'text' | 'number'; type?: 'text' | 'number';
initialValue?: string; initialValue?: string;
name: string;
onSubmitValue: (value: string) => void; onSubmitValue: (value: string) => void;
focus?: boolean;
} }
export interface SubmitInputState { export interface SubmitInputState {
@ -21,7 +23,7 @@ export class SubmitInput extends Component<SubmitInputProps, SubmitInputState> {
constructor(props: SubmitInputProps) { constructor(props: SubmitInputProps) {
super(props); super(props);
this.state.value = props.initialValue || ''; this.state.value = props.initialValue ?? '';
} }
onSubmit = (event: SyntheticEvent<any>) => { onSubmit = (event: SyntheticEvent<any>) => {
@ -33,7 +35,12 @@ export class SubmitInput extends Component<SubmitInputProps, SubmitInputState> {
return <form onSubmit={this.onSubmit}> return <form onSubmit={this.onSubmit}>
<input value={this.state.value} <input value={this.state.value}
type={this.props.type} type={this.props.type}
onChange={event => this.setState({value: event.currentTarget.value})}/> id={`bbb-${this.props.name}`}
name={this.props.name}
onChange={event => this.setState({value: event.currentTarget.value})}
onBlur={() => this.props.onSubmitValue(this.state.value)}
autoFocus={this.props.focus}
/>
</form>; </form>;
} }
} }