mirror of https://github.com/sualko/cloud_bbb
feat: move room settings to dialog
parent
bcc483b918
commit
b0edd567f0
|
@ -163,3 +163,16 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bbb-form-element {
|
||||
min-width: 300px;
|
||||
margin: 2em 0;
|
||||
|
||||
input:not([type="checkbox"]) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
em {
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
|
@ -72,7 +72,7 @@ const App: React.FC<Props> = () => {
|
|||
}
|
||||
|
||||
function updateRoom(room: Room) {
|
||||
api.updateRoom(room).then(updatedRoom => {
|
||||
return api.updateRoom(room).then(updatedRoom => {
|
||||
setRooms(rooms.map(room => {
|
||||
if (room.id === updatedRoom.id) {
|
||||
return updatedRoom;
|
||||
|
@ -101,9 +101,6 @@ const App: React.FC<Props> = () => {
|
|||
<th onClick={() => onOrderBy('name')}>
|
||||
{t('bbb', 'Name')} <SortArrow name='name' value={orderBy} direction={sortOrder} />
|
||||
</th>
|
||||
<th onClick={() => onOrderBy('welcome')}>
|
||||
{t('bbb', 'Welcome')} <SortArrow name='welcome' value={orderBy} direction={sortOrder} />
|
||||
</th>
|
||||
<th onClick={() => onOrderBy('maxParticipants')}>
|
||||
{t('bbb', 'Max')} <SortArrow name='maxParticipants' value={orderBy} direction={sortOrder} />
|
||||
</th>
|
||||
|
@ -114,6 +111,7 @@ const App: React.FC<Props> = () => {
|
|||
{t('bbb', 'Recordings')}
|
||||
</th>
|
||||
<th />
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,24 +1,16 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { SubmitInput } from './SubmitInput';
|
||||
import { Room, Recording, api } from './Api';
|
||||
import { api, Recording, Room } from './Api';
|
||||
import EditRoomDialog from './EditRoomDialog';
|
||||
import RecordingRow from './RecordingRow';
|
||||
import EditableValue from './EditableValue';
|
||||
|
||||
type Props = {
|
||||
room: Room;
|
||||
updateRoom: (room: Room) => void;
|
||||
updateRoom: (room: Room) => Promise<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 = {
|
||||
recordings: null | Recording[];
|
||||
showRecordings: boolean;
|
||||
|
@ -41,28 +33,9 @@ const RecordingsNumber: React.FC<RecordingsNumberProps> = ({ recordings, showRec
|
|||
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 [activeEdit, setActiveEdit] = useState('');
|
||||
const [recordings, setRecordings] = useState<Recording[] | null>(null);
|
||||
const [showRecordings, setShowRecordings] = useState<boolean>(false);
|
||||
const room = props.room;
|
||||
|
@ -83,12 +56,10 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
}, [areRecordingsLoaded]);
|
||||
|
||||
function updateRoom(key: string, value: string | boolean | number) {
|
||||
props.updateRoom({
|
||||
return props.updateRoom({
|
||||
...props.room,
|
||||
[key]: value,
|
||||
});
|
||||
|
||||
setActiveEdit('');
|
||||
}
|
||||
|
||||
function deleteRow(ev: MouseEvent) {
|
||||
|
@ -184,7 +155,7 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
}
|
||||
|
||||
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 (
|
||||
|
@ -204,9 +175,6 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
<td className="name">
|
||||
{edit('name')}
|
||||
</td>
|
||||
<td className="welcome">
|
||||
{edit('welcome')}
|
||||
</td>
|
||||
<td className="max-participants">
|
||||
{edit('maxParticipants', 'number')}
|
||||
</td>
|
||||
|
@ -215,6 +183,9 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
<label htmlFor={`bbb-record-${room.id}`}></label>
|
||||
</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">
|
||||
<a className="icon icon-delete icon-visible"
|
||||
onClick={deleteRow as any}
|
||||
|
|
|
@ -7,7 +7,9 @@ import {
|
|||
export interface SubmitInputProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||
type?: 'text' | 'number';
|
||||
initialValue?: string;
|
||||
name: string;
|
||||
onSubmitValue: (value: string) => void;
|
||||
focus?: boolean;
|
||||
}
|
||||
|
||||
export interface SubmitInputState {
|
||||
|
@ -21,7 +23,7 @@ export class SubmitInput extends Component<SubmitInputProps, SubmitInputState> {
|
|||
|
||||
constructor(props: SubmitInputProps) {
|
||||
super(props);
|
||||
this.state.value = props.initialValue || '';
|
||||
this.state.value = props.initialValue ?? '';
|
||||
}
|
||||
|
||||
onSubmit = (event: SyntheticEvent<any>) => {
|
||||
|
@ -33,7 +35,12 @@ export class SubmitInput extends Component<SubmitInputProps, SubmitInputState> {
|
|||
return <form onSubmit={this.onSubmit}>
|
||||
<input value={this.state.value}
|
||||
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>;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue