mirror of https://github.com/sualko/cloud_bbb
feat: sharing rooms with moderators and users
Dialog and permissions managementpull/281/head
parent
fbf33378aa
commit
9fed698723
|
@ -88,12 +88,18 @@ class ServerController extends Controller {
|
||||||
return new DataResponse([], Http::STATUS_NOT_FOUND);
|
return new DataResponse([], Http::STATUS_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$this->permission->isAdmin($room, $this->userId)) {
|
if (!$this->permission->isUser($room, $this->userId)) {
|
||||||
return new DataResponse([], Http::STATUS_FORBIDDEN);
|
return new DataResponse([], Http::STATUS_FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
$recordings = $this->server->getRecordings($room);
|
$recordings = $this->server->getRecordings($room);
|
||||||
|
|
||||||
|
if (!$this->permission->isAdmin($room, $this->userId)) {
|
||||||
|
$recordings = array_filter($recordings, function ($recording) {
|
||||||
|
return $recording['published'];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return new DataResponse($recordings);
|
return new DataResponse($recordings);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Access } from './Api';
|
import { Access, Permission } from './Api';
|
||||||
|
|
||||||
export const AccessOptions = {
|
export const AccessOptions = {
|
||||||
[Access.Public]: t('bbb', 'Public'),
|
[Access.Public]: t('bbb', 'Public'),
|
||||||
|
@ -8,3 +8,9 @@ export const AccessOptions = {
|
||||||
[Access.Internal]: t('bbb', 'Internal'),
|
[Access.Internal]: t('bbb', 'Internal'),
|
||||||
[Access.InternalRestricted]: t('bbb', 'Internal restricted'),
|
[Access.InternalRestricted]: t('bbb', 'Internal restricted'),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const PermissionsOptions = {
|
||||||
|
[Permission.Admin]: t('bbb', 'admin'),
|
||||||
|
[Permission.Moderator]: t('bbb', 'moderator'),
|
||||||
|
[Permission.User]: t('bbb', 'user'),
|
||||||
|
};
|
||||||
|
|
|
@ -282,6 +282,10 @@ pre {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bbb-simple-menu {
|
||||||
|
min-width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
.bbb-input-container {
|
.bbb-input-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,17 +123,18 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
|
||||||
updateProperty('access', value);
|
updateProperty('access', value);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{room.access === Access.InternalRestricted && <div className="bbb-form-element bbb-form-shareWith">
|
|
||||||
<ShareWith permission={Permission.User} room={room} shares={shares} setShares={setShares} />
|
|
||||||
<em>{descriptions.internalRestrictedShareWith}</em>
|
|
||||||
</div>}
|
|
||||||
|
|
||||||
<div className="bbb-form-element">
|
<div className="bbb-form-element">
|
||||||
<label htmlFor={'bbb-moderator'}>
|
<label htmlFor={'bbb-sharing'}>
|
||||||
<h3>Moderator</h3>
|
<h3>{t('bbb', 'Sharing')}</h3>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{!room.everyoneIsModerator && <ShareWith permission={Permission.Moderator} room={room} shares={shares} setShares={setShares} />}
|
{<ShareWith permission={Permission.User} room={room} shares={shares} setShares={setShares} />}
|
||||||
|
|
||||||
|
{room.access === Access.InternalRestricted &&
|
||||||
|
<div className="bbb-form-element bbb-form-shareWith">
|
||||||
|
<span className="icon icon-details icon-visible"></span><em>{`${t('bbb', 'Access')} : ${descriptions.internalRestrictedShareWith}`}</em>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div className="bbb-mt-1">
|
<div className="bbb-mt-1">
|
||||||
<input id={`bbb-everyoneIsModerator-${room.id}`}
|
<input id={`bbb-everyoneIsModerator-${room.id}`}
|
||||||
|
|
|
@ -155,6 +155,28 @@ const RoomRow: React.FC<Props> = (props) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function publishRecording(recording: Recording, publish: boolean) {
|
||||||
|
api.publishRecording(recording.id, publish).then(success=> {
|
||||||
|
if (recordings === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setRecordings(recordings.map(recordItem => {
|
||||||
|
if (recordItem.id === recording.id) {
|
||||||
|
recordItem.published = success;
|
||||||
|
recordItem.state = success ? 'published' : 'unpublished';
|
||||||
|
}
|
||||||
|
return recordItem;
|
||||||
|
}));
|
||||||
|
}).catch(err => {
|
||||||
|
console.warn('Could not modify publishing state', err);
|
||||||
|
OC.dialogs.info(
|
||||||
|
t('bbb', 'Could not modify publishing state'),
|
||||||
|
t('bbb', 'Server error'),
|
||||||
|
() => undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function accessToIcon(access: string) {
|
function accessToIcon(access: string) {
|
||||||
switch(access) {
|
switch(access) {
|
||||||
case Access.Public:
|
case Access.Public:
|
||||||
|
@ -172,7 +194,7 @@ const RoomRow: React.FC<Props> = (props) => {
|
||||||
return <span></span>;
|
return <span></span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function edit(field: string, type: 'text' | 'number' = 'text', canEdit: boolean = true, options?) {
|
function edit(field: string, type: 'text' | 'number' = 'text', canEdit = true, options?) {
|
||||||
return canEdit ?
|
return canEdit ?
|
||||||
<EditableValue field={field} value={room[field]} setValue={updateRoom} type={type} options={options} />
|
<EditableValue field={field} value={room[field]} setValue={updateRoom} type={type} options={options} />
|
||||||
:
|
:
|
||||||
|
@ -235,11 +257,11 @@ const RoomRow: React.FC<Props> = (props) => {
|
||||||
}
|
}
|
||||||
{!adminRoom &&
|
{!adminRoom &&
|
||||||
<td className="record bbb-shrink">
|
<td className="record bbb-shrink">
|
||||||
<span className={"icon "+(room.record ? "icon-checkmark" : "icon-close")+" icon-visible"}></span>
|
<span className={'icon '+(room.record ? 'icon-checkmark' : 'icon-close')+' icon-visible'}></span>
|
||||||
</td>
|
</td>
|
||||||
}
|
}
|
||||||
<td className="bbb-shrink">
|
<td className="bbb-shrink">
|
||||||
{adminRoom &&
|
{(adminRoom || true ) &&
|
||||||
<RecordingsNumber recordings={recordings} showRecordings={showRecordings} setShowRecordings={setShowRecordings} />
|
<RecordingsNumber recordings={recordings} showRecordings={showRecordings} setShowRecordings={setShowRecordings} />
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
@ -265,7 +287,7 @@ const RoomRow: React.FC<Props> = (props) => {
|
||||||
<td colSpan={11}>
|
<td colSpan={11}>
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{recordings?.sort((r1, r2) => r1.startTime - r2.startTime).map(recording => <RecordingRow key={recording.id} recording={recording} deleteRecording={deleteRecording} storeRecording={storeRecording} />)}
|
{recordings?.sort((r1, r2) => r1.startTime - r2.startTime).map(recording => <RecordingRow key={recording.id} isAdmin={adminRoom} recording={recording} deleteRecording={deleteRecording} storeRecording={storeRecording} publishRecording={publishRecording} />)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
||||||
import { api, ShareWith, ShareType, RoomShare, Room, Permission } from '../Common/Api';
|
import { api, ShareWith, ShareType, RoomShare, Room, Permission } from '../Common/Api';
|
||||||
import './ShareWith.scss';
|
import './ShareWith.scss';
|
||||||
import ShareSelection from '../Common/ShareSelection';
|
import ShareSelection from '../Common/ShareSelection';
|
||||||
|
import { PermissionsOptions } from '../Common/Translation';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
room: Room;
|
room: Room;
|
||||||
|
@ -45,9 +46,7 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
||||||
setShares((allShares ? [...allShares] : []).filter(share => share.id !== id));
|
setShares((allShares ? [...allShares] : []).filter(share => share.id !== id));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleAdminShare(share: RoomShare) {
|
async function setSharePermission(share: RoomShare, newPermission: number) {
|
||||||
const newPermission = share.permission === Permission.Admin ? Permission.Moderator : Permission.Admin;
|
|
||||||
|
|
||||||
return addRoomShare(share.shareWith, share.shareType, share.shareWithDisplayName || share.shareWith, newPermission);
|
return addRoomShare(share.shareWith, share.shareType, share.shareWithDisplayName || share.shareWith, newPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,17 +58,40 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ucFirst(s: string)
|
||||||
|
{
|
||||||
|
return s && s[0].toUpperCase() + s.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPermission(value: Permission, onChange: (value: number) => void) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="bbb-form-element bbb-simple-menu">
|
||||||
|
<select name="permission" value={value} onChange={(event) => onChange(Number(event.target.value))}>
|
||||||
|
{Object.keys(PermissionsOptions).map(key => {
|
||||||
|
const label = PermissionsOptions[key];
|
||||||
|
return <option key={key} value={key}>{ucFirst(label)}</option>;
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function permissionLabel(permission: Permission) {
|
||||||
|
return PermissionsOptions[permission] ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
function renderShares(shares: RoomShare[]) {
|
function renderShares(shares: RoomShare[]) {
|
||||||
const currentUser = OC.getCurrentUser();
|
const currentUser = OC.getCurrentUser();
|
||||||
|
const ROOM_OWNER_ID = -1;
|
||||||
const ownShare = {
|
const ownShare = {
|
||||||
id: -1,
|
id: ROOM_OWNER_ID,
|
||||||
roomId: room.id,
|
roomId: room.id,
|
||||||
shareType: ShareType.User,
|
shareType: ShareType.User,
|
||||||
shareWith: currentUser.uid,
|
shareWith: currentUser.uid,
|
||||||
shareWithDisplayName: currentUser.displayName,
|
shareWithDisplayName: currentUser.displayName,
|
||||||
permission: Permission.Admin,
|
permission: Permission.Admin,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="bbb-shareWith">
|
<ul className="bbb-shareWith">
|
||||||
{[ownShare, ...shares].map(share => {
|
{[ownShare, ...shares].map(share => {
|
||||||
|
@ -85,20 +107,13 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
||||||
</div>
|
</div>
|
||||||
<div className="bbb-shareWith__item__label">
|
<div className="bbb-shareWith__item__label">
|
||||||
<h5>{displayName}
|
<h5>{displayName}
|
||||||
{(share.permission === Permission.Moderator && permission === Permission.User) && ` (${t('bbb', 'moderator')})`}
|
{(share.id === ROOM_OWNER_ID || !isOwner) && ` (${permissionLabel(share.permission)})`}
|
||||||
{(share.permission === Permission.Admin) && ` (${t('bbb', 'admin')})`}</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
{(share.id > -1 && permission === Permission.Moderator && isOwner) && <div className="bbb-shareWith__item__action">
|
{(share.id > ROOM_OWNER_ID && isOwner) && selectPermission(share.permission, (value) => {
|
||||||
<button className="action-item"
|
setSharePermission(share, value);
|
||||||
onClick={ev => {
|
})}
|
||||||
ev.preventDefault();
|
{(share.id > ROOM_OWNER_ID && isOwner) && <div className="bbb-shareWith__item__action">
|
||||||
toggleAdminShare(share);
|
|
||||||
}}
|
|
||||||
title={t('bbb', 'Share')}>
|
|
||||||
<span className={`icon icon-shared icon-visible ${share.permission === Permission.Admin ? 'bbb-icon-selected' : 'bbb-icon-unselected'}`}></span>
|
|
||||||
</button>
|
|
||||||
</div>}
|
|
||||||
{(share.id > -1 && isOwner) && <div className="bbb-shareWith__item__action">
|
|
||||||
<button className="action-item"
|
<button className="action-item"
|
||||||
onClick={ev => {ev.preventDefault(); deleteRoomShare(share.id);}}
|
onClick={ev => {ev.preventDefault(); deleteRoomShare(share.id);}}
|
||||||
title={t('bbb', 'Delete')}>
|
title={t('bbb', 'Delete')}>
|
||||||
|
@ -116,8 +131,6 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{shares ? renderShares(shares) : loading}
|
|
||||||
|
|
||||||
{isOwner ?
|
{isOwner ?
|
||||||
<ShareSelection
|
<ShareSelection
|
||||||
selectShare={(shareOption) => addRoomShare(shareOption.value.shareWith, shareOption.value.shareType, shareOption.label, permission)}
|
selectShare={(shareOption) => addRoomShare(shareOption.value.shareWith, shareOption.value.shareType, shareOption.label, permission)}
|
||||||
|
@ -127,6 +140,8 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
||||||
<span className="icon icon-details icon-visible"></span> {t('bbb', 'You are not allowed to change this option, because this room is shared with you.')}
|
<span className="icon icon-details icon-visible"></span> {t('bbb', 'You are not allowed to change this option, because this room is shared with you.')}
|
||||||
</em>
|
</em>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{shares ? renderShares(shares) : loading}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue