mirror of https://github.com/sualko/cloud_bbb
Merge pull request #281 from arawa/feature/share_moderators_and_users
Feature/share moderators and userspull/285/head
commit
29aa147ce6
|
@ -14,6 +14,7 @@ return [
|
|||
['name' => 'server#check', 'url' => '/server/check', 'verb' => 'POST'],
|
||||
['name' => 'server#version', 'url' => '/server/version', 'verb' => 'GET'],
|
||||
['name' => 'server#delete_record', 'url' => '/server/record/{recordId}', 'verb' => 'DELETE'],
|
||||
['name' => 'server#publish_record', 'url' => '/server/record/{recordId}/publish', 'verb' => 'POST'],
|
||||
['name' => 'join#index', 'url' => '/b/{token}/{moderatorToken}', 'verb' => 'GET', 'defaults' => ['moderatorToken' => '']],
|
||||
['name' => 'restriction#user', 'url' => '/restrictions/user', 'verb' => 'GET'],
|
||||
['name' => 'hook#meetingEnded', 'url' => '/hook/ended/{token}/{mac}', 'verb' => 'GET'],
|
||||
|
|
|
@ -10,6 +10,7 @@ use BigBlueButton\Parameters\GetRecordingsParameters;
|
|||
use BigBlueButton\Parameters\InsertDocumentParameters;
|
||||
use BigBlueButton\Parameters\IsMeetingRunningParameters;
|
||||
use BigBlueButton\Parameters\JoinMeetingParameters;
|
||||
use BigBlueButton\Parameters\PublishRecordingsParameters;
|
||||
use OCA\BigBlueButton\AppInfo\Application;
|
||||
use OCA\BigBlueButton\AvatarRepository;
|
||||
use OCA\BigBlueButton\Crypto;
|
||||
|
@ -262,6 +263,14 @@ class API {
|
|||
return $response->isDeleted();
|
||||
}
|
||||
|
||||
public function publishRecording(string $recordingId, bool $published): bool {
|
||||
$publishParams = new PublishRecordingsParameters($recordingId, $published);
|
||||
|
||||
$response = $this->getServer()->publishRecordings($publishParams);
|
||||
|
||||
return $response->isPublished();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return (array|bool|int|string)[]
|
||||
*
|
||||
|
|
|
@ -11,6 +11,7 @@ use OCP\AppFramework\Controller;
|
|||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserManager;
|
||||
|
||||
|
@ -24,6 +25,9 @@ class RoomShareController extends Controller {
|
|||
/** @var IUserManager */
|
||||
private $userManager;
|
||||
|
||||
/** @var IGroupManager */
|
||||
private $groupManager;
|
||||
|
||||
/** @var RoomService */
|
||||
private $roomService;
|
||||
|
||||
|
@ -37,6 +41,7 @@ class RoomShareController extends Controller {
|
|||
IRequest $request,
|
||||
RoomShareService $service,
|
||||
IUserManager $userManager,
|
||||
IGroupManager $groupManager,
|
||||
RoomService $roomService,
|
||||
CircleHelper $circleHelper,
|
||||
$userId
|
||||
|
@ -44,6 +49,7 @@ class RoomShareController extends Controller {
|
|||
parent::__construct($appName, $request);
|
||||
$this->service = $service;
|
||||
$this->userManager = $userManager;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->roomService = $roomService;
|
||||
$this->circleHelper = $circleHelper;
|
||||
$this->userId = $userId;
|
||||
|
@ -90,6 +96,14 @@ class RoomShareController extends Controller {
|
|||
}
|
||||
|
||||
$roomShare->setShareWithDisplayName($circle->getName());
|
||||
} elseif ($roomShare->getShareType() === RoomShare::SHARE_TYPE_GROUP) {
|
||||
$shareWithGroup = $this->groupManager->get($roomShare->getShareWith());
|
||||
|
||||
if ($shareWithGroup === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$roomShare->setShareWithDisplayName($shareWithGroup->getDisplayName());
|
||||
}
|
||||
|
||||
$shares[] = $roomShare;
|
||||
|
|
|
@ -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_values(array_filter($recordings, function ($recording) {
|
||||
return $recording['published'];
|
||||
}));
|
||||
}
|
||||
|
||||
return new DataResponse($recordings);
|
||||
}
|
||||
|
||||
|
@ -118,6 +124,27 @@ class ServerController extends Controller {
|
|||
return new DataResponse($success);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function publishRecord(string $recordId, bool $published): DataResponse {
|
||||
$record = $this->server->getRecording($recordId);
|
||||
|
||||
$room = $this->service->findByUid($record['meetingId']);
|
||||
|
||||
if ($room === null) {
|
||||
return new DataResponse(false, Http::STATUS_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$this->permission->isAdmin($room, $this->userId)) {
|
||||
return new DataResponse(false, Http::STATUS_FORBIDDEN);
|
||||
}
|
||||
|
||||
$success = $this->server->publishRecording($recordId, $published);
|
||||
|
||||
return new DataResponse($success);
|
||||
}
|
||||
|
||||
public function check(?string $url, ?string $secret): DataResponse {
|
||||
if ($url === null || empty($url) || $secret === null || empty($secret)) {
|
||||
return new DataResponse(false);
|
||||
|
|
|
@ -74,6 +74,7 @@ class Room extends Entity implements JsonSerializable {
|
|||
public $cleanLayout;
|
||||
public $joinMuted;
|
||||
public $running;
|
||||
public $permission;
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('maxParticipants', 'integer');
|
||||
|
@ -86,6 +87,7 @@ class Room extends Entity implements JsonSerializable {
|
|||
$this->addType('cleanLayout', 'boolean');
|
||||
$this->addType('joinMuted', 'boolean');
|
||||
$this->addType('running', 'boolean');
|
||||
$this->addType('permission', 'integer');
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
|
@ -102,6 +104,7 @@ class Room extends Entity implements JsonSerializable {
|
|||
'everyoneIsModerator' => boolval($this->everyoneIsModerator),
|
||||
'requireModerator' => boolval($this->requireModerator),
|
||||
'shared' => boolval($this->shared),
|
||||
'permission' => $this->permission,
|
||||
'moderatorToken' => $this->moderatorToken,
|
||||
'listenOnly' => boolval($this->listenOnly),
|
||||
'mediaCheck' => boolval($this->mediaCheck),
|
||||
|
|
|
@ -12,6 +12,16 @@ class RoomMapper extends QBMapper {
|
|||
parent::__construct($db, 'bbb_rooms', Room::class);
|
||||
}
|
||||
|
||||
private function joinShares(IQueryBuilder $qb): IQueryBuilder {
|
||||
$qb->select('r.*')
|
||||
->from($this->tableName, 'r')
|
||||
->leftJoin('r', 'bbb_room_shares', 's', $qb->expr()->eq('r.id', 's.room_id'))
|
||||
->addSelect($qb->createFunction('count(case when `s`.`permission` IN ('.
|
||||
RoomShare::PERMISSION_ADMIN.','.RoomShare::PERMISSION_MODERATOR.','.RoomShare::PERMISSION_USER
|
||||
.') then 1 else null end) as shared'));
|
||||
return $qb;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws DoesNotExistException
|
||||
|
@ -19,10 +29,7 @@ class RoomMapper extends QBMapper {
|
|||
public function find(int $id): Room {
|
||||
/* @var $qb IQueryBuilder */
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('r.*')
|
||||
->from($this->tableName, 'r')
|
||||
->leftJoin('r', 'bbb_room_shares', 's', $qb->expr()->eq('r.id', 's.room_id'))
|
||||
->addSelect($qb->createFunction('count(case when `s`.`permission` = 0 then 1 else null end) as shared'))
|
||||
$this->joinShares($qb)
|
||||
->where($qb->expr()->eq('r.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||
->groupBy('r.id');
|
||||
;
|
||||
|
@ -38,10 +45,7 @@ class RoomMapper extends QBMapper {
|
|||
public function findByUid(string $uid): Room {
|
||||
/* @var $qb IQueryBuilder */
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('r.*')
|
||||
->from($this->tableName, 'r')
|
||||
->leftJoin('r', 'bbb_room_shares', 's', $qb->expr()->eq('r.id', 's.room_id'))
|
||||
->addSelect($qb->createFunction('count(case when `s`.`permission` = 0 then 1 else null end) as shared'))
|
||||
$this->joinShares($qb)
|
||||
->where($qb->expr()->eq('r.uid', $qb->createNamedParameter($uid)))
|
||||
->groupBy('r.id');
|
||||
;
|
||||
|
@ -70,25 +74,20 @@ class RoomMapper extends QBMapper {
|
|||
public function findAll(string $userId, array $groupIds, array $circleIds): array {
|
||||
/* @var $qb IQueryBuilder */
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('r.*')
|
||||
->from($this->tableName, 'r')
|
||||
->leftJoin('r', 'bbb_room_shares', 's', $qb->expr()->eq('r.id', 's.room_id'))
|
||||
->addSelect($qb->createFunction('count(case when `s`.`permission` = 0 then 1 else null end) as shared'))
|
||||
$this->joinShares($qb)
|
||||
->addSelect($qb->createFunction('min(case when '.$qb->expr()->eq('r.user_id', $qb->createNamedParameter($userId)).' then '.RoomShare::PERMISSION_ADMIN.' else `s`.`permission` end) as permission'))
|
||||
->where(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->eq('r.user_id', $qb->createNamedParameter($userId)),
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('s.permission', $qb->createNamedParameter(RoomShare::PERMISSION_ADMIN, IQueryBuilder::PARAM_INT)),
|
||||
$qb->expr()->eq('s.share_type', $qb->createNamedParameter(RoomShare::SHARE_TYPE_USER, IQueryBuilder::PARAM_INT)),
|
||||
$qb->expr()->eq('s.share_with', $qb->createNamedParameter($userId))
|
||||
),
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('s.permission', $qb->createNamedParameter(RoomShare::PERMISSION_ADMIN, IQueryBuilder::PARAM_INT)),
|
||||
$qb->expr()->eq('s.share_type', $qb->createNamedParameter(RoomShare::SHARE_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
|
||||
$qb->expr()->in('s.share_with', $qb->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY))
|
||||
),
|
||||
$qb->expr()->andX(
|
||||
$qb->expr()->eq('s.permission', $qb->createNamedParameter(RoomShare::PERMISSION_ADMIN, IQueryBuilder::PARAM_INT)),
|
||||
$qb->expr()->eq('s.share_type', $qb->createNamedParameter(RoomShare::SHARE_TYPE_CIRCLE, IQueryBuilder::PARAM_INT)),
|
||||
$qb->expr()->in('s.share_with', $qb->createNamedParameter($circleIds, IQueryBuilder::PARAM_STR_ARRAY))
|
||||
)
|
||||
|
@ -99,7 +98,7 @@ class RoomMapper extends QBMapper {
|
|||
/** @var array<Room> */
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array<Room>
|
||||
*/
|
||||
|
|
|
@ -9,6 +9,7 @@ use OCA\BigBlueButton\Db\RoomShare;
|
|||
use OCA\BigBlueButton\Service\RoomService;
|
||||
use OCA\BigBlueButton\Service\RoomShareService;
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IRequest;
|
||||
use OCP\IUserManager;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
@ -19,6 +20,7 @@ class RoomShareControllerTest extends TestCase {
|
|||
private $roomService;
|
||||
private $circleHelper;
|
||||
private $userManager;
|
||||
private $groupManager;
|
||||
private $controller;
|
||||
|
||||
private $userId = 'user_foo';
|
||||
|
@ -29,6 +31,7 @@ class RoomShareControllerTest extends TestCase {
|
|||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->service = $this->createMock(RoomShareService::class);
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->groupManager = $this->createMock(IGroupManager::class);
|
||||
$this->roomService = $this->createMock(RoomService::class);
|
||||
$this->circleHelper = $this->createMock(CircleHelper::class);
|
||||
|
||||
|
@ -37,6 +40,7 @@ class RoomShareControllerTest extends TestCase {
|
|||
$this->request,
|
||||
$this->service,
|
||||
$this->userManager,
|
||||
$this->groupManager,
|
||||
$this->roomService,
|
||||
$this->circleHelper,
|
||||
$this->userId
|
||||
|
|
|
@ -39,6 +39,7 @@ export interface Room {
|
|||
everyoneIsModerator: boolean;
|
||||
requireModerator: boolean;
|
||||
shared: boolean;
|
||||
permission: Permission;
|
||||
moderatorToken: string;
|
||||
listenOnly: boolean,
|
||||
mediaCheck: boolean,
|
||||
|
@ -200,6 +201,14 @@ class Api {
|
|||
return response.data;
|
||||
}
|
||||
|
||||
public async publishRecording(id: string, publish: boolean,) {
|
||||
const response = await axios.post(this.getUrl(`server/record/${id}/publish`), {
|
||||
published: publish,
|
||||
});
|
||||
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async storeRecording(recording: Recording, path: string) {
|
||||
const startDate = new Date(recording.startTime);
|
||||
const filename = `${encodeURIComponent(recording.name + ' ' + startDate.toISOString())}.url`;
|
||||
|
|
|
@ -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'),
|
||||
};
|
||||
|
|
|
@ -161,8 +161,23 @@ pre {
|
|||
.bbb-shrink {
|
||||
width: 44px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
input[type="checkbox"]{
|
||||
|
||||
&+label:before {
|
||||
border-radius: 3px;
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
&:disabled+label:before {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
&:not(input:checked):disabled+label:before {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 14px 6px;
|
||||
|
@ -282,6 +297,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}
|
||||
|
|
|
@ -4,11 +4,29 @@ import { Recording } from '../Common/Api';
|
|||
|
||||
type Props = {
|
||||
recording: Recording;
|
||||
isAdmin : boolean;
|
||||
deleteRecording: (recording: Recording) => void;
|
||||
storeRecording: (recording: Recording) => void;
|
||||
publishRecording: (recording: Recording, publish: boolean) => void;
|
||||
}
|
||||
|
||||
const RecordingRow: React.FC<Props> = ({recording, deleteRecording, storeRecording}) => {
|
||||
const RecordingRow: React.FC<Props> = ({recording, isAdmin, deleteRecording, storeRecording, publishRecording}) => {
|
||||
|
||||
|
||||
function checkPublished(recording: Recording, onChange: (value: boolean) => void) {
|
||||
return (
|
||||
<div>
|
||||
<input id={'bbb-record-state-' + recording.id}
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={recording.state === 'published'}
|
||||
onChange={(event) => onChange(event.target.checked)} />
|
||||
<label htmlFor={'bbb-record-state-' + recording.id}>{t('bbb', 'Published')}</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<tr key={recording.id}>
|
||||
<td className="start icon-col">
|
||||
|
@ -40,10 +58,17 @@ const RecordingRow: React.FC<Props> = ({recording, deleteRecording, storeRecordi
|
|||
<td>
|
||||
{recording.type}
|
||||
</td>
|
||||
<td>
|
||||
{isAdmin && checkPublished(recording, (checked) => {
|
||||
publishRecording(recording, checked);
|
||||
})}
|
||||
</td>
|
||||
<td className="remove icon-col">
|
||||
<button className="action-item" onClick={() => deleteRecording(recording)} title={t('bbb', 'Delete')}>
|
||||
<span className="icon icon-delete icon-visible"></span>
|
||||
</button>
|
||||
{isAdmin &&
|
||||
<button className="action-item" onClick={() => deleteRecording(recording)} title={t('bbb', 'Delete')}>
|
||||
<span className="icon icon-delete icon-visible"></span>
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { api, Recording, Room, Restriction, Access } from '../Common/Api';
|
||||
import { api, Recording, Room, Restriction, Access, Permission } from '../Common/Api';
|
||||
import EditRoom from './EditRoom';
|
||||
import RecordingRow from './RecordingRow';
|
||||
import EditableValue from './EditableValue';
|
||||
|
@ -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,8 +194,11 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
return <span></span>;
|
||||
}
|
||||
|
||||
function edit(field: string, type: 'text' | 'number' = 'text', options?) {
|
||||
return <EditableValue field={field} value={room[field]} setValue={updateRoom} type={type} options={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} />
|
||||
:
|
||||
<span>{room[field]}</span>;
|
||||
}
|
||||
|
||||
function cloneRow() {
|
||||
|
@ -189,6 +214,8 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
const maxParticipantsLimit = props.restriction?.maxParticipants || -1;
|
||||
const minParticipantsLimit = (props.restriction?.maxParticipants || -1) < 1 ? 0 : 1;
|
||||
|
||||
const adminRoom = room.permission === null || room.permission === Permission.Admin;
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr className={showRecordings ? 'selected-row' : ''}>
|
||||
|
@ -214,7 +241,7 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
</button>
|
||||
</td>
|
||||
<td className="name">
|
||||
{edit('name')}
|
||||
{edit('name', 'text', adminRoom)}
|
||||
</td>
|
||||
<td className="bbb-shrink">
|
||||
{room.userId !== OC.currentUser && <img src={avatarUrl} alt="Avatar" className="bbb-avatar" />}
|
||||
|
@ -224,35 +251,43 @@ const RoomRow: React.FC<Props> = (props) => {
|
|||
{accessToIcon(room.access)}
|
||||
</td>
|
||||
<td className="max-participants bbb-shrink">
|
||||
{edit('maxParticipants', 'number', {min: minParticipantsLimit, max: maxParticipantsLimit < 0 ? undefined : maxParticipantsLimit})}
|
||||
{edit('maxParticipants', 'number', adminRoom, {min: minParticipantsLimit, max: maxParticipantsLimit < 0 ? undefined : maxParticipantsLimit})}
|
||||
</td>
|
||||
<td className="record bbb-shrink">
|
||||
<input id={'bbb-record-' + room.id} type="checkbox" className="checkbox" disabled={!props.restriction?.allowRecording} checked={room.record} onChange={(event) => updateRoom('record', event.target.checked)} />
|
||||
<input id={'bbb-record-' + room.id} type="checkbox" className="checkbox" disabled={!adminRoom || !props.restriction?.allowRecording} checked={room.record} onChange={(event) => updateRoom('record', event.target.checked)} />
|
||||
<label htmlFor={'bbb-record-' + room.id}></label>
|
||||
</td>
|
||||
<td className="bbb-shrink"><RecordingsNumber recordings={recordings} showRecordings={showRecordings} setShowRecordings={setShowRecordings} /></td>
|
||||
<td className="edit icon-col">
|
||||
<EditRoom room={props.room} restriction={props.restriction} updateProperty={updateRoom} />
|
||||
<td className="bbb-shrink">
|
||||
{<RecordingsNumber recordings={recordings} showRecordings={showRecordings} setShowRecordings={setShowRecordings} />}
|
||||
</td>
|
||||
<td className="clone icon-col">
|
||||
{adminRoom &&
|
||||
<button
|
||||
className="action-item"
|
||||
onClick={cloneRow}
|
||||
title={t('bbb', 'Clone room')}>
|
||||
<span className="icon icon-template-add icon-visible"></span>
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
<td className="edit icon-col">
|
||||
{adminRoom &&
|
||||
<EditRoom room={props.room} restriction={props.restriction} updateProperty={updateRoom} />
|
||||
}
|
||||
</td>
|
||||
<td className="remove icon-col">
|
||||
{adminRoom &&
|
||||
<button className="action-item" onClick={deleteRow as any} title={t('bbb', 'Delete')}>
|
||||
<span className="icon icon-delete icon-visible"></span>
|
||||
</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
{showRecordings && <tr className="recordings-row">
|
||||
<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