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);
|
||||
}
|
||||
|
||||
if (!$this->permission->isAdmin($room, $this->userId)) {
|
||||
if (!$this->permission->isUser($room, $this->userId)) {
|
||||
return new DataResponse([], Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Access } from './Api';
|
||||
import { Access, Permission } from './Api';
|
||||
|
||||
export const AccessOptions = {
|
||||
[Access.Public]: t('bbb', 'Public'),
|
||||
|
@ -8,3 +8,9 @@ export const AccessOptions = {
|
|||
[Access.Internal]: t('bbb', 'Internal'),
|
||||
[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 {
|
||||
display: flex;
|
||||
}
|
||||
|
|
|
@ -123,17 +123,18 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
|
|||
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">
|
||||
<label htmlFor={'bbb-moderator'}>
|
||||
<h3>Moderator</h3>
|
||||
<label htmlFor={'bbb-sharing'}>
|
||||
<h3>{t('bbb', 'Sharing')}</h3>
|
||||
</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">
|
||||
<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) {
|
||||
switch(access) {
|
||||
case Access.Public:
|
||||
|
@ -172,7 +194,7 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
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 ?
|
||||
<EditableValue field={field} value={room[field]} setValue={updateRoom} type={type} options={options} />
|
||||
:
|
||||
|
@ -235,11 +257,11 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
}
|
||||
{!adminRoom &&
|
||||
<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 className="bbb-shrink">
|
||||
{adminRoom &&
|
||||
{(adminRoom || true ) &&
|
||||
<RecordingsNumber recordings={recordings} showRecordings={showRecordings} setShowRecordings={setShowRecordings} />
|
||||
}
|
||||
</td>
|
||||
|
@ -265,7 +287,7 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
<td colSpan={11}>
|
||||
<table>
|
||||
<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>
|
||||
</table>
|
||||
</td>
|
||||
|
|
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||
import { api, ShareWith, ShareType, RoomShare, Room, Permission } from '../Common/Api';
|
||||
import './ShareWith.scss';
|
||||
import ShareSelection from '../Common/ShareSelection';
|
||||
import { PermissionsOptions } from '../Common/Translation';
|
||||
|
||||
type Props = {
|
||||
room: Room;
|
||||
|
@ -45,9 +46,7 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
|||
setShares((allShares ? [...allShares] : []).filter(share => share.id !== id));
|
||||
}
|
||||
|
||||
async function toggleAdminShare(share: RoomShare) {
|
||||
const newPermission = share.permission === Permission.Admin ? Permission.Moderator : Permission.Admin;
|
||||
|
||||
async function setSharePermission(share: RoomShare, newPermission: number) {
|
||||
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[]) {
|
||||
const currentUser = OC.getCurrentUser();
|
||||
const ROOM_OWNER_ID = -1;
|
||||
const ownShare = {
|
||||
id: -1,
|
||||
id: ROOM_OWNER_ID,
|
||||
roomId: room.id,
|
||||
shareType: ShareType.User,
|
||||
shareWith: currentUser.uid,
|
||||
shareWithDisplayName: currentUser.displayName,
|
||||
permission: Permission.Admin,
|
||||
};
|
||||
|
||||
return (
|
||||
<ul className="bbb-shareWith">
|
||||
{[ownShare, ...shares].map(share => {
|
||||
|
@ -85,20 +107,13 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
|||
</div>
|
||||
<div className="bbb-shareWith__item__label">
|
||||
<h5>{displayName}
|
||||
{(share.permission === Permission.Moderator && permission === Permission.User) && ` (${t('bbb', 'moderator')})`}
|
||||
{(share.permission === Permission.Admin) && ` (${t('bbb', 'admin')})`}</h5>
|
||||
{(share.id === ROOM_OWNER_ID || !isOwner) && ` (${permissionLabel(share.permission)})`}
|
||||
</h5>
|
||||
</div>
|
||||
{(share.id > -1 && permission === Permission.Moderator && isOwner) && <div className="bbb-shareWith__item__action">
|
||||
<button className="action-item"
|
||||
onClick={ev => {
|
||||
ev.preventDefault();
|
||||
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">
|
||||
{(share.id > ROOM_OWNER_ID && isOwner) && selectPermission(share.permission, (value) => {
|
||||
setSharePermission(share, value);
|
||||
})}
|
||||
{(share.id > ROOM_OWNER_ID && isOwner) && <div className="bbb-shareWith__item__action">
|
||||
<button className="action-item"
|
||||
onClick={ev => {ev.preventDefault(); deleteRoomShare(share.id);}}
|
||||
title={t('bbb', 'Delete')}>
|
||||
|
@ -116,8 +131,6 @@ const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setSh
|
|||
|
||||
return (
|
||||
<>
|
||||
{shares ? renderShares(shares) : loading}
|
||||
|
||||
{isOwner ?
|
||||
<ShareSelection
|
||||
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.')}
|
||||
</em>
|
||||
}
|
||||
|
||||
{shares ? renderShares(shares) : loading}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue