feat: sharing rooms with moderators and users

Dialog and permissions management
pull/281/head
Sebastien Marinier 2024-05-29 17:09:06 +02:00
parent fbf33378aa
commit 9fed698723
6 changed files with 88 additions and 34 deletions

View File

@ -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);
} }

View File

@ -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'),
};

View File

@ -282,6 +282,10 @@ pre {
} }
} }
.bbb-simple-menu {
min-width: auto;
}
.bbb-input-container { .bbb-input-container {
display: flex; display: flex;
} }

View File

@ -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}`}

View File

@ -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>

View File

@ -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}
</> </>
); );
}; };