Merge pull request #407 from arawa/chore/upgrade_packages

chore: upgrade lib and tools, some deprecated
pull/408/head
Baptiste Fotia 2025-12-12 09:33:53 +01:00 committed by GitHub
commit c5a0129d9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
49 changed files with 22053 additions and 7418 deletions

View File

@ -21,34 +21,34 @@ install-composer-deps-dev: composer.phar
php composer.phar install -o
js-init:
yarn install
npm install
yarn-update:
yarn update
npm-update:
npm update
# Building
build-js: js-init
yarn run dev
npm run dev
build-js-production: js-init
yarn run build
npm run build
watch-js: js-init
yarn run watch
npm run watch
# Linting
lint: js-init
yarn run lint
npm run lint
lint-fix: js-init
yarn run fix
npm run fix
# Style linting
stylelint: js-init
yarn run lint:style
npm run lint:style
stylelint-fix: js-init
yarn run lint:fix:style
npm run lint:fix:style
phplint:
./vendor/bin/php-cs-fixer fix --dry-run

View File

@ -1,6 +1,3 @@
module.exports = {
plugins: [
'@babel/plugin-syntax-dynamic-import',
],
presets: ['@babel/preset-env'],
}
const babelConfig = require('@nextcloud/babel-config')
module.exports = babelConfig

View File

@ -17,7 +17,7 @@
"nextcloud/coding-standard": "^1.1.0",
"phpstan/phpstan": "^2.1.16",
"nextcloud/ocp": "^29.0 || ^30.0 || ^31.0",
"vimeo/psalm": "5.9.0 || ^6.1.0",
"vimeo/psalm": "^6.1.0",
"psr/container": "^1.1.2 || ^1.1.4 || ^2.0.2"
},
"config": {

2745
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@ use \OCA\BigBlueButton\Middleware\HookMiddleware;
use \OCA\BigBlueButton\Middleware\JoinMiddleware;
use \OCA\BigBlueButton\Search\Provider;
use \OCP\AppFramework\App;
use \OCP\IConfig;
use \OCP\IAppConfig;
use \OCP\Settings\IManager as ISettingsManager;
use \OCP\User\Events\UserDeletedEvent;
use OCP\AppFramework\Bootstrap\IBootContext;
@ -66,11 +66,11 @@ class Application extends App implements IBootstrap {
public function boot(IBootContext $context): void {
$context->injectFn([$this, 'registerAdminPage']);
Util::addScript('bbb', 'filelist');
Util::addScript('bbb', 'bbb-filelist');
}
public function registerAdminPage(ISettingsManager $settingsManager, INavigationManager $navigationManager, IURLGenerator $urlGenerator, IConfig $config):void {
if ($config->getAppValue(self::ID, 'app.navigation') === 'true') {
public function registerAdminPage(ISettingsManager $settingsManager, INavigationManager $navigationManager, IURLGenerator $urlGenerator, IAppConfig $config):void {
if ($config->getValueBool(self::ID, 'app.navigation')) {
$this->registerAsNavigationEntry($navigationManager, $urlGenerator, $config);
} else {
$this->registerAsPersonalSetting($settingsManager);
@ -78,11 +78,11 @@ class Application extends App implements IBootstrap {
}
private function registerAsPersonalSetting(ISettingsManager $settingsManager): void {
$settingsManager->registerSetting(ISettingsManager::KEY_PERSONAL_SETTINGS, \OCA\BigBlueButton\Settings\Personal::class);
$settingsManager->registerSetting(ISettingsManager::SETTINGS_PERSONAL, \OCA\BigBlueButton\Settings\Personal::class);
}
private function registerAsNavigationEntry(INavigationManager $navigationManager, IURLGenerator $urlGenerator, IConfig $config): void {
$name = $config->getAppValue(self::ID, 'app.navigation.name', 'BBB');
private function registerAsNavigationEntry(INavigationManager $navigationManager, IURLGenerator $urlGenerator, IAppConfig $config): void {
$name = $config->getValueString(self::ID, 'app.navigation.name', 'BBB');
$navigationManager->add(function () use ($urlGenerator, $name) {
return [

View File

@ -4,8 +4,8 @@ namespace OCA\BigBlueButton;
use OCA\BigBlueButton\AppInfo\Application;
use OCA\BigBlueButton\Db\Room;
use OCP\IAppConfig;
use OCP\IAvatarManager;
use OCP\IConfig;
use OCP\IURLGenerator;
use OCP\Security\ISecureRandom;
@ -13,27 +13,11 @@ class AvatarRepository {
public const CONF_KEY_PATH = 'avatar.path';
public const CONF_KEY_URL = 'avatar.url';
/** @var IAvatarManager */
private $avatarManager;
/** @var ISecureRandom */
private $random;
/** @var IURLGenerator */
private $urlGenerator;
/** @var IConfig */
private $config;
public function __construct(
IAvatarManager $avatarManager,
IURLGenerator $urlGenerator,
ISecureRandom $random,
IConfig $config) {
$this->avatarManager = $avatarManager;
$this->urlGenerator = $urlGenerator;
$this->random = $random;
$this->config = $config;
private IAvatarManager $avatarManager,
private IURLGenerator $urlGenerator,
private ISecureRandom $random,
private IAppConfig $config) {
}
public function getAvatarUrl(Room $room, string $userId): string {
@ -137,7 +121,7 @@ class AvatarRepository {
}
private function getRootPath(): string {
$path = $this->config->getAppValue(Application::ID, self::CONF_KEY_PATH);
$path = $this->config->getValueString(Application::ID, self::CONF_KEY_PATH);
if (empty($path)) {
return '';
@ -147,7 +131,7 @@ class AvatarRepository {
}
private function getBaseUrl(): string {
$url = $this->config->getAppValue(Application::ID, self::CONF_KEY_URL);
$url = $this->config->getValueString(Application::ID, self::CONF_KEY_URL);
if (empty($url)) {
return '';

View File

@ -20,73 +20,35 @@ use OCA\BigBlueButton\UrlHelper;
use OCP\App\IAppManager;
use OCP\Defaults;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IAppConfig;
use OCP\IL10N;
use OCP\IRequest;
use OCP\IURLGenerator;
class API {
/** @var IConfig */
private $config;
/** @var IURLGenerator */
private $urlGenerator;
/** @var BigBlueButton|null */
private $server;
/** @var Crypto */
private $crypto;
/** @var IEventDispatcher */
private $eventDispatcher;
/** @var IL10N */
private $l10n;
/** @var UrlHelper */
private $urlHelper;
/** @var Defaults */
private $defaults;
/** @var IAppManager */
private $appManager;
/** @var AvatarRepository */
private $avatarRepository;
/** @var IRequest */
private $request;
public function __construct(
IConfig $config,
IURLGenerator $urlGenerator,
Crypto $crypto,
IEventDispatcher $eventDispatcher,
IL10N $l10n,
UrlHelper $urlHelper,
Defaults $defaults,
IAppManager $appManager,
AvatarRepository $avatarRepository,
IRequest $request
private IAppConfig $config,
private IURLGenerator $urlGenerator,
private Crypto $crypto,
private IEventDispatcher $eventDispatcher,
private IL10N $l10n,
private UrlHelper $urlHelper,
private Defaults $defaults,
private IAppManager $appManager,
private AvatarRepository $avatarRepository,
private IRequest $request
) {
$this->config = $config;
$this->urlGenerator = $urlGenerator;
$this->crypto = $crypto;
$this->eventDispatcher = $eventDispatcher;
$this->l10n = $l10n;
$this->urlHelper = $urlHelper;
$this->defaults = $defaults;
$this->appManager = $appManager;
$this->avatarRepository = $avatarRepository;
$this->request = $request;
$this->server = null;
}
private function getServer(): BigBlueButton {
if (!$this->server) {
$apiUrl = $this->config->getAppValue('bbb', 'api.url');
$secret = $this->config->getAppValue('bbb', 'api.secret');
$apiUrl = $this->config->getValueString('bbb', 'api.url');
$secret = $this->config->getValueString('bbb', 'api.secret');
$this->server = new BigBlueButton($apiUrl, $secret);
}
@ -123,7 +85,7 @@ class API {
$joinMeetingParams->addUserData('bbb_show_public_chat_on_login', false);
}
if ($this->config->getAppValue('bbb', 'join.theme') === 'true') {
if ($this->config->getValueBool('bbb', 'join.theme')) {
$primaryColor = $this->defaults->getColorPrimary();
$textColor = $this->defaults->getTextColorPrimary();
@ -160,7 +122,7 @@ class API {
}
if ($response->getMessageKey() !== 'duplicateWarning') {
$this->eventDispatcher->dispatch(MeetingStartedEvent::class, new MeetingStartedEvent($room));
$this->eventDispatcher->dispatchTyped(new MeetingStartedEvent($room));
}
return $response->getCreationTime();
@ -179,7 +141,7 @@ class API {
$createMeetingParams->addMeta('bbb-origin', \method_exists($this->defaults, 'getProductName') ? $this->defaults->getProductName() : 'Nextcloud');
$createMeetingParams->addMeta('bbb-origin-server-name', $this->request->getServerHost());
$analyticsCallbackUrl = $this->config->getAppValue('bbb', 'api.meta_analytics-callback-url');
$analyticsCallbackUrl = $this->config->getValueString('bbb', 'api.meta_analytics-callback-url');
if (!empty($analyticsCallbackUrl)) {
// For more details: https://github.com/bigbluebutton/bigbluebutton/blob/develop/record-and-playback/core/scripts/post_events/post_events_analytics_callback.rb
$createMeetingParams->addMeta('analytics-callback-url', $analyticsCallbackUrl);

View File

@ -50,7 +50,7 @@ class CircleHelper {
if ($this->api === null) {
if ($this->appManager->isEnabledForUser('circles') && class_exists('\OCA\Circles\Api\v1\Circles')) {
$container = $this->app->getContainer();
$this->api = $container->query(\OCA\Circles\Api\v1\Circles::class);
$this->api = $container->get('OCA\Circles\Api\v1\Circles');
} else {
$this->api = false;
}

View File

@ -25,7 +25,7 @@ class ClearAvatarCache extends Command {
$this->setDescription('Clear all avatars in cache');
}
protected function execute(InputInterface $input, OutputInterface $output) {
protected function execute(InputInterface $input, OutputInterface $output): int {
$stats = $this->avatarRepository->clearAllRooms();
$output->writeln("Removed " . $stats["files"] . " avatars in " . $stats["rooms"] . " rooms");

View File

@ -67,7 +67,7 @@ class HookController extends Controller {
$this->avatarRepository->clearRoom($room->uid);
$this->eventDispatcher->dispatch(MeetingEndedEvent::class, new MeetingEndedEvent($room, $recordingmarks));
$this->eventDispatcher->dispatchTyped(new MeetingEndedEvent($room, $recordingmarks));
}
/**
@ -78,7 +78,7 @@ class HookController extends Controller {
* @return void
*/
public function recordingReady(): void {
$this->eventDispatcher->dispatch(RecordingReadyEvent::class, new RecordingReadyEvent($this->getRoom()));
$this->eventDispatcher->dispatchTyped(new RecordingReadyEvent($this->getRoom()));
}
private function getRoom(): ?Room {

View File

@ -16,7 +16,7 @@ use OCP\AppFramework\Db\Entity;
* @method void setMaxRooms(int $number)
* @method void setMaxParticipants(int $number)
* @method void setAllowRecording(bool $allow)
* @method void setGroupName(string $groupName)
* @method void setGroupName(?string $groupName)
*/
class Restriction extends Entity implements JsonSerializable {
public const ALL_ID = '';

View File

@ -91,7 +91,7 @@ class RestrictionService {
return $this->mapper->insert($restriction);
}
public function update(int $id, string $groupId, int $maxRooms, array $roomTypes, int $maxParticipants, bool $allowRecording): Restriction {
public function update(int $id, string $groupId, int $maxRooms, array $roomTypes, int $maxParticipants, bool $allowRecording): Restriction | null {
try {
$restriction = $this->mapper->find($id);
@ -104,10 +104,11 @@ class RestrictionService {
return $this->mapper->update($restriction);
} catch (Exception $e) {
$this->handleException($e);
return null;
}
}
public function delete(int $id): Restriction {
public function delete(int $id): Restriction | null {
try {
$restriction = $this->mapper->find($id);
$this->mapper->delete($restriction);
@ -115,6 +116,7 @@ class RestrictionService {
return $restriction;
} catch (Exception $e) {
$this->handleException($e);
return null;
}
}

View File

@ -12,7 +12,7 @@ use OCA\BigBlueButton\Event\RoomDeletedEvent;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\EventDispatcher\IEventDispatcher;
use OCP\IConfig;
use OCP\IAppConfig;
use OCP\IUser;
use OCP\Search\ISearchQuery;
use OCP\Security\ISecureRandom;
@ -21,7 +21,7 @@ class RoomService {
/** @var RoomMapper */
private $mapper;
/** @var IConfig */
/** @var IAppConfig */
private $config;
/** @var IEventDispatcher */
@ -32,7 +32,7 @@ class RoomService {
public function __construct(
RoomMapper $mapper,
IConfig $config,
IAppConfig $config,
IEventDispatcher $eventDispatcher,
ISecureRandom $random) {
$this->mapper = $mapper;
@ -96,7 +96,7 @@ class RoomService {
public function create(string $name, string $welcome, int $maxParticipants, bool $record, string $access, string $userId): \OCP\AppFramework\Db\Entity {
$room = new Room();
$mediaCheck = $this->config->getAppValue('bbb', 'join.mediaCheck', 'true') === 'true';
$mediaCheck = $this->config->getValueBool('bbb', 'join.mediaCheck', true);
$room->setUid($this->humanReadableRandom(16));
$room->setName($name);
@ -118,7 +118,7 @@ class RoomService {
$createdRoom = $this->mapper->insert($room);
$this->eventDispatcher->dispatch(RoomCreatedEvent::class, new RoomCreatedEvent($createdRoom));
$this->eventDispatcher->dispatchTyped(new RoomCreatedEvent($createdRoom));
return $createdRoom;
}
@ -195,7 +195,7 @@ class RoomService {
$this->mapper->delete($room);
$this->eventDispatcher->dispatch(RoomDeletedEvent::class, new RoomDeletedEvent($room));
$this->eventDispatcher->dispatchTyped(new RoomDeletedEvent($room));
return $room;
} catch (Exception $e) {

View File

@ -52,7 +52,7 @@ class RoomShareService {
}
}
public function create(int $roomId, int $shareType, string $shareWith, int $permission): RoomShare {
public function create(int $roomId, int $shareType, string $shareWith, int $permission): RoomShare | null {
try {
$roomShare = $this->mapper->findByRoomAndEntity($roomId, $shareWith, $shareType);
@ -67,13 +67,13 @@ class RoomShareService {
$createdRoomShare = $this->mapper->insert($roomShare);
$this->eventDispatcher->dispatch(RoomShareCreatedEvent::class, new RoomShareCreatedEvent($createdRoomShare));
$this->eventDispatcher->dispatchTyped(new RoomShareCreatedEvent($createdRoomShare));
return $createdRoomShare;
}
}
public function update(int $id, int $roomId, int $shareType, string $shareWith, int $permission): RoomShare {
public function update(int $id, int $roomId, int $shareType, string $shareWith, int $permission): RoomShare | null {
try {
$roomShare = $this->mapper->find($id);
@ -85,19 +85,21 @@ class RoomShareService {
return $this->mapper->update($roomShare);
} catch (Exception $e) {
$this->handleException($e);
return null;
}
}
public function delete(int $id): RoomShare {
public function delete(int $id): RoomShare | null {
try {
$roomShare = $this->mapper->find($id);
$this->mapper->delete($roomShare);
$this->eventDispatcher->dispatch(RoomShareDeletedEvent::class, new RoomShareDeletedEvent($roomShare));
$this->eventDispatcher->dispatchTyped(new RoomShareDeletedEvent($roomShare));
return $roomShare;
} catch (Exception $e) {
$this->handleException($e);
return null;
}
}
}

View File

@ -3,20 +3,17 @@
namespace OCA\BigBlueButton\Settings;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IAppConfig;
use OCP\Settings\ISettings;
class Admin implements ISettings {
/** @var IConfig */
private $config;
/**
* Admin constructor.
*
* @param IConfig $config
* @param IAppConfig $config
*/
public function __construct(IConfig $config) {
$this->config = $config;
public function __construct(private IAppConfig $config) {
}
/**
@ -24,12 +21,12 @@ class Admin implements ISettings {
*/
public function getForm() {
$parameters = [
'api.url' => $this->config->getAppValue('bbb', 'api.url'),
'api.secret' => $this->config->getAppValue('bbb', 'api.secret'),
'app.navigation' => $this->config->getAppValue('bbb', 'app.navigation') === 'true' ? 'checked' : '',
'join.theme' => $this->config->getAppValue('bbb', 'join.theme') === 'true' ? 'checked' : '',
'app.shortener' => $this->config->getAppValue('bbb', 'app.shortener'),
'join.mediaCheck' => $this->config->getAppValue('bbb', 'join.mediaCheck', 'true') === 'true' ? 'checked' : '',
'api.url' => $this->config->getValueString('bbb', 'api.url'),
'api.secret' => $this->config->getValueString('bbb', 'api.secret'),
'app.navigation' => $this->config->getValueBool('bbb', 'app.navigation') ? 'checked' : '',
'join.theme' => $this->config->getValueBool('bbb', 'join.theme') ? 'checked' : '',
'app.shortener' => $this->config->getValueString('bbb', 'app.shortener'),
'join.mediaCheck' => $this->config->getValueBool('bbb', 'join.mediaCheck', true) ? 'checked' : '',
];
return new TemplateResponse('bbb', 'admin', $parameters);

View File

@ -3,24 +3,16 @@
namespace OCA\BigBlueButton;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IAppConfig;
use OCP\IL10N;
class TemplateProvider {
/** @var IConfig */
private $config;
/** @var IL10N */
private $l;
/**
* Admin constructor.
*
* @param IConfig $config
*/
public function __construct(IConfig $config, IL10N $l) {
$this->config = $config;
$this->l = $l;
public function __construct(private IAppConfig $config, private IL10N $l) {
}
/**
@ -29,13 +21,13 @@ class TemplateProvider {
public function getManager(): TemplateResponse {
$warning = '';
if (empty($this->config->getAppValue('bbb', 'api.url')) || empty($this->config->getAppValue('bbb', 'api.secret'))) {
if (empty($this->config->getValueString('bbb', 'api.url')) || empty($this->config->getValueString('bbb', 'api.secret'))) {
$warning = $this->l->t('API URL or secret not configured. Please contact your administrator.');
}
return new TemplateResponse('bbb', 'manager', [
'warning' => $warning,
'shortener' => $this->config->getAppValue('bbb', 'app.shortener', ''),
'shortener' => $this->config->getValueString('bbb', 'app.shortener', ''),
]);
}
}

View File

@ -3,26 +3,19 @@
namespace OCA\BigBlueButton;
use OCA\BigBlueButton\Db\Room;
use OCP\IConfig;
use OCP\IAppConfig;
use OCP\IURLGenerator;
class UrlHelper {
/** @var IConfig */
private $config;
/** @var IURLGenerator */
private $urlGenerator;
public function __construct(
IConfig $config,
IURLGenerator $urlGenerator
private IAppConfig $config,
private IURLGenerator $urlGenerator
) {
$this->config = $config;
$this->urlGenerator = $urlGenerator;
}
public function linkToInvitationAbsolute(Room $room): string {
$url = $this->config->getAppValue('bbb', 'app.shortener', '');
$url = $this->config->getValueString('bbb', 'app.shortener', '');
if (empty($url) || strpos($url, 'https://') !== 0 || strpos($url, '{token}') === false) {
return $this->urlGenerator->linkToRouteAbsolute('bbb.join.index', ['token' => $room->getUid()]);

19434
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -37,9 +37,10 @@
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^17.8.1",
"@commitlint/travis-cli": "^16.2.3",
"@nextcloud/axios": "^1.11.0",
"@nextcloud/dialogs": "^3.1.2",
"@nextcloud/router": "^2.0.0",
"@nextcloud/axios": "^2.5.1",
"@nextcloud/dialogs": "^6.0.1",
"@nextcloud/files": "^3.12.0",
"@nextcloud/router": "^3.0.1",
"@octokit/rest": "^18.0.4",
"archiver": "^5.0.0",
"colors": "^1.4.0",
@ -55,8 +56,8 @@
},
"husky": {
"hooks": {
"pre-commit": "yarn lint",
"pre-push": "yarn test:php:unit",
"pre-commit": "npm run lint",
"pre-push": "npm run test:php:unit",
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
@ -64,27 +65,30 @@
"extends @nextcloud/browserslist-config"
],
"engines": {
"node": ">=16.0.0"
"node": "^20.0.0",
"npm": "^10.0.0"
},
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/core": "^7.12.0",
"@babel/eslint-parser": "^7.27.1",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/preset-env": "^7.9.0",
"@nextcloud/browserslist-config": "^2.2.0",
"@nextcloud/babel-config": "^1.2.0",
"@nextcloud/browserslist-config": "^3.0.1",
"@nextcloud/eslint-plugin": "^2.0.0",
"@nextcloud/files": "^2.1.0",
"@types/bootstrap": "^5.1.9",
"@nextcloud/paths": "^2.3.0",
"@nextcloud/webpack-vue-config": "^5.5.1",
"@types/bootstrap": "^5.2.10",
"@types/inquirer": "^8.2.0",
"@types/jquery": "^3.3.35",
"@types/node": "^17.0.21",
"@types/react": "^17.0.40",
"@types/webpack": "^5.28.0",
"@types/webpack-env": "^1.15.2",
"@types/react": "^17.0.89",
"@types/react-dom": "^17.0.26",
"@types/react-transition-group": "^4.4.12",
"@types/webpack": "^5.28.5",
"@types/webpack-env": "^1.18.2",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"babel-loader": "^8.1.0",
"css-loader": "^6.7.1",
"dotenv-cli": "^8.0.0",
"eslint": "^8.11.0",
"eslint-config-standard": "^17.0",
@ -92,7 +96,7 @@
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.6.0",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react": "^7.37.4",
"eslint-plugin-standard": "^5.0.0",
"eslint-webpack-plugin": "^3.1.1",
"file-loader": "^6.0.0",
@ -100,12 +104,11 @@
"inquirer": "^8.2.6",
"install": "^0.13.0",
"npm-run-all": "^4.1.5",
"raw-loader": "^4.0.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-flip-move": "^3.0.4",
"react-hot-loader": "^4.12.20",
"react-select": "^5.2.2",
"sass-loader": "^12.6.0",
"style-loader": "^3.3.1",
"stylelint": "^14.5.3",
"stylelint-config-recommended-scss": "^5.0.2",

View File

@ -24,7 +24,7 @@
</extraFiles>
<issueHandlers>
<UndefinedClass>
<errorLevel type="suppress">
<errorLevel type="info">
<referencedClass name="OC" />
</errorLevel>
</UndefinedClass>
@ -36,5 +36,6 @@
<referencedClass name="Doctrine\DBAL\Schema\Table" />
</errorLevel>
</UndefinedDocblockClass>
<MissingOverrideAttribute errorLevel="suppress" />
</issueHandlers>
</psalm>

View File

@ -2,8 +2,8 @@
/** @var $l \OCP\IL10N */
/** @var $_ array */
script('bbb', 'admin');
script('bbb', 'restrictions');
\OCP\Util::addScript('bbb', 'bbb', 'admin');
\OCP\Util::addScript('bbb', 'bbb-restrictions');
?>
<div id="bbb-settings" class="section">

View File

@ -2,7 +2,7 @@
/** @var $_ array */
/** @var $l \OCP\IL10N */
style('core', 'guest');
script('bbb', 'join');
\OCP\Util::addScript('bbb', 'bbb-join');
?>
<form method="get" action="?">
<fieldset class="warning bbb">

View File

@ -1,5 +1,5 @@
<?php
script('bbb', 'manager');
\OCP\Util::addScript('bbb', 'bbb-manager');
?>
<div id="bbb-app">

View File

@ -2,7 +2,7 @@
/** @var $_ array */
/** @var $l \OCP\IL10N */
style('core', 'guest');
script('bbb', 'waiting');
\OCP\Util::addScript('bbb', 'bbb-waiting');
?>
<div class="update bbb">

View File

@ -118,7 +118,7 @@ class Api {
public async updateRestriction(restriction: Restriction) {
if (!restriction.id) {
const newRestriction = await this.createRestriction(
restriction.groupId
restriction.groupId,
);
restriction.id = newRestriction.id;
@ -166,7 +166,7 @@ class Api {
return response.data;
}
public async createRoom(name: string, access: Access = Access.Public, maxParticipants = 0) {
public async createRoom(name: string, access: Access = Access.Public, maxParticipants = 0): Promise<Room> {
const response = await axios.post(this.getUrl('rooms'), {
name,
welcome: '',
@ -175,7 +175,7 @@ class Api {
access,
});
return response.data;
return response.data as Room;
}
public async updateRoom(room: Room) {
@ -202,7 +202,7 @@ class Api {
return response.data;
}
public async publishRecording(id: string, publish: boolean,) {
public async publishRecording(id: string, publish: boolean) {
const response = await axios.post(this.getUrl(`server/record/${id}/publish`), {
published: publish,
});

View File

@ -9,7 +9,7 @@ type Props = {
invert?: boolean;
}
const EditableSelection: React.FC<Props> = ({ setValue, field, values: currentValues, options, placeholder, invert = false }) => {
const EditableSelection = ({ setValue, field, values: currentValues, options, placeholder, invert = false }: Props): JSX.Element => {
const [active, setActive] = useState<boolean>(false);
currentValues = currentValues || [];

View File

@ -13,7 +13,7 @@ type Props = {
placeholder?: string;
}
const ShareSelection: React.FC<Props> = (props) => {
const ShareSelection = (props: Props): JSX.Element => {
const [search, setSearch] = useState<string>('');
const [hasFocus, setFocus] = useState<boolean>(false);
const [showSearchResults, setShowSearchResults] = useState<boolean>(false);

View File

@ -18,5 +18,5 @@ export const PermissionsOptions = {
};
export function html_sanitize_and_parse(str: string): string {
return parse(DOMPurify.sanitize(str, { USE_PROFILES: { html: true } }));
return parse(DOMPurify.sanitize(str, { USE_PROFILES: { html: true } })) as string;
}

View File

@ -31,11 +31,7 @@ function sortRooms(key: SortKey, orderBy: SortOrder) {
};
}
type Props = {
}
const App: React.FC<Props> = () => {
const App = () => {
const [isLoaded, setLoaded] = useState(false);
const [error, setError] = useState<string>('');
const [restriction, setRestriction] = useState<Restriction>();

View File

@ -4,9 +4,15 @@ type Props = {
open: boolean;
onClose?: () => void;
title: string;
children: React.ReactNode;
}
const Dialog: React.FC<Props> = ({open, title, children, onClose = () => undefined}) => {
const Dialog = ({
open,
title,
children,
onClose = () => undefined,
}: Props): JSX.Element => {
if (!open) {
return <></>;

View File

@ -8,7 +8,7 @@ type Props = {
updateProperty: (key: string, value: string | boolean | number | null) => Promise<void>;
}
const EditRoom: React.FC<Props> = ({ room, restriction, updateProperty }) => {
const EditRoom = ({ room, restriction, updateProperty }: Props): JSX.Element => {
const [open, setOpen] = useState<boolean>(false);
return (

View File

@ -33,7 +33,7 @@ type Props = {
setOpen: (open: boolean) => void;
}
const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, open, setOpen }) => {
const EditRoomDialog = ({ room, restriction, updateProperty, open, setOpen }: Props): JSX.Element => {
const [shares, setShares] = useState<RoomShare[]>();
const maxParticipantsLimit = (restriction?.maxParticipants || 0) < 0 ? undefined : restriction?.maxParticipants;
@ -69,7 +69,7 @@ const EditRoomDialog: React.FC<Props> = ({ room, restriction, updateProperty, op
<h3>{label}</h3>
</label>
<SubmitInput initialValue={room[field]} type={type} name={field} onSubmitValue={value => updateProperty(field, value)} min={minParticipantsLimit} max={maxParticipantsLimit} />
<SubmitInput initialValue={room[field]} type={type} name={field} onSubmitValue={(value) => updateProperty(field, value)} min={minParticipantsLimit} max={maxParticipantsLimit} />
{descriptions[field] && <em>{html_sanitize_and_parse(descriptions[field])}</em>}
</div>
);

View File

@ -13,7 +13,7 @@ type EditableValueProps = {
};
}
const EditableValue: React.FC<EditableValueProps> = ({ setValue, field, value: currentValue, type, options }) => {
const EditableValue = ({ setValue, field, value: currentValue, type, options }: EditableValueProps): JSX.Element => {
const [active, setActive] = useState<boolean>(false);
const submit = (value: string | number) => {

View File

@ -4,7 +4,7 @@ type Props = {
addRoom: (name: string) => Promise<void>;
}
const NewRoomForm: React.FC<Props> = (props) => {
const NewRoomForm = (props: Props): JSX.Element => {
const [name, setName] = useState<string>('');
const [processing, setProcessing] = useState<boolean>(false);
const [error, setError] = useState<string>('');

View File

@ -10,8 +10,7 @@ type Props = {
publishRecording: (recording: Recording, publish: boolean) => void;
}
const RecordingRow: React.FC<Props> = ({recording, isAdmin, deleteRecording, storeRecording, publishRecording}) => {
const RecordingRow = ({recording, isAdmin, deleteRecording, storeRecording, publishRecording}: Props): JSX.Element => {
function checkPublished(recording: Recording, onChange: (value: boolean) => void) {
return (
@ -26,7 +25,6 @@ const RecordingRow: React.FC<Props> = ({recording, isAdmin, deleteRecording, sto
);
}
return (
<tr key={recording.id}>
<td className="start icon-col">

View File

@ -20,7 +20,7 @@ type RecordingsNumberProps = {
setShowRecordings: (showRecordings: boolean) => void;
}
const RecordingsNumber: React.FC<RecordingsNumberProps> = ({ recordings, showRecordings, setShowRecordings }) => {
const RecordingsNumber = ({ recordings, showRecordings, setShowRecordings }: RecordingsNumberProps): JSX.Element => {
if (recordings === null) {
return <span className="icon icon-loading-small icon-visible"></span>;
}
@ -36,7 +36,7 @@ const RecordingsNumber: React.FC<RecordingsNumberProps> = ({ recordings, showRec
return <span>0</span>;
};
const RoomRow: React.FC<Props> = (props) => {
const RoomRow = (props: Props): JSX.Element => {
const [recordings, setRecordings] = useState<Recording[] | null>(null);
const [showRecordings, setShowRecordings] = useState<boolean>(false);
const room = props.room;
@ -74,7 +74,7 @@ const RoomRow: React.FC<Props> = (props) => {
props.deleteRoom(room.id);
}
},
true
true,
);
}
@ -92,7 +92,7 @@ const RoomRow: React.FC<Props> = (props) => {
OC.dialogs.alert(
t('bbb', 'URL to room could not be stored.'),
t('bbb', 'Error'),
() => undefined
() => undefined,
);
});
}, undefined, 'httpd/unix-directory');
@ -112,7 +112,7 @@ const RoomRow: React.FC<Props> = (props) => {
OC.dialogs.alert(
t('bbb', 'URL to presentation could not be stored.'),
t('bbb', 'Error'),
() => undefined
() => undefined,
);
});
}, undefined, 'httpd/unix-directory');
@ -151,7 +151,7 @@ const RoomRow: React.FC<Props> = (props) => {
});
}
},
true
true,
);
}

View File

@ -11,7 +11,7 @@ type Props = {
setShares: (shares: RoomShare[]) => void;
}
const ShareWith: React.FC<Props> = ({ room, permission, shares: allShares, setShares }) => {
const ShareWith = ({ room, permission, shares: allShares, setShares }: Props): JSX.Element => {
const isOwner = room.userId === OC.currentUser;
const shares = (allShares && permission === Permission.Moderator) ?

View File

@ -1,7 +1,5 @@
import * as React from 'react';
import {
Component, InputHTMLAttributes,
SyntheticEvent,
import React, {
useState, useEffect, InputHTMLAttributes, SyntheticEvent,
} from 'react';
export interface SubmitInputProps extends InputHTMLAttributes<HTMLInputElement> {
@ -12,37 +10,41 @@ export interface SubmitInputProps extends InputHTMLAttributes<HTMLInputElement>
focus?: boolean;
}
export interface SubmitInputState {
value: string;
}
export const SubmitInput = ({
type = 'text',
initialValue = '',
name,
onSubmitValue,
focus,
min,
max,
...rest
}: SubmitInputProps): JSX.Element => {
const [value, setValue] = useState<string>(initialValue);
export class SubmitInput extends Component<SubmitInputProps, SubmitInputState> {
state: SubmitInputState = {
value: '',
useEffect(() => {
setValue(initialValue ?? '');
}, [initialValue]);
const onSubmit = (e: SyntheticEvent) => {
e.preventDefault();
onSubmitValue(value);
};
constructor(props: SubmitInputProps) {
super(props);
this.state.value = props.initialValue ?? '';
}
private onSubmit = (event: SyntheticEvent<any>) => {
event.preventDefault();
this.props.onSubmitValue(this.state.value);
};
public render(): JSX.Element {
return <form onSubmit={this.onSubmit}>
<input value={this.state.value}
type={this.props.type}
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}
min={this.props.min}
max={this.props.max}
return (
<form onSubmit={onSubmit}>
<input
value={value}
type={type}
id={`bbb-${name}`}
name={name}
onChange={(ev) => setValue((ev.target as HTMLInputElement).value)}
onBlur={() => onSubmitValue(value)}
autoFocus={focus}
min={min}
max={max}
{...rest}
/>
</form>;
}
}
</form>
);
};

View File

@ -8,5 +8,8 @@ import ReactDom from 'react-dom';
window['React'] = React;
$(document).ready(() => {
ReactDom.render( <App/>, document.getElementById('bbb-root'));
const root = document.getElementById('bbb-root');
if (root) {
ReactDom.render( <App /> as any , root);
}
});

4
ts/Nextcloud.d.ts vendored
View File

@ -113,3 +113,7 @@ declare module 'NC' {
};
}
}
declare const OC: any;
declare const OCP: any;
declare const OCA: any;

View File

@ -4,16 +4,12 @@ import { api, Restriction, ShareType } from '../Common/Api';
import RestrictionRow from './RestrictionRow';
import ShareSelection from '../Common/ShareSelection';
type Props = {
}
const App: React.FC<Props> = () => {
const App = (): JSX.Element => {
const [areRestrictionsLoaded, setRestrictionsLoaded] = useState(false);
const [error, setError] = useState<string>('');
const [restrictions, setRestrictions] = useState<Restriction[]>([]);
const rows = restrictions.sort((a, b) => a.groupId.localeCompare(b.groupId)).map(restriction => <RestrictionRow key={restriction.id} restriction={restriction} updateRestriction={updateRestriction} deleteRestriction={deleteRestriction} />);
const rows = restrictions.sort((a: Restriction, b: Restriction) => a.groupId.localeCompare(b.groupId)).map(restriction => <RestrictionRow key={restriction.id} restriction={restriction} updateRestriction={updateRestriction} deleteRestriction={deleteRestriction} />);
useEffect(() => {
api.getRestrictions().then(restrictions => {
@ -35,7 +31,7 @@ const App: React.FC<Props> = () => {
function updateRestriction(restriction: Restriction) {
return api.updateRestriction(restriction).then(updatedRestriction => {
setRestrictions(restrictions.map(restriction => {
setRestrictions(restrictions.map((restriction: Restriction) => {
if (restriction.id === updatedRestriction.id || restriction.groupId === updatedRestriction.groupId) {
return updatedRestriction;
}
@ -86,7 +82,7 @@ const App: React.FC<Props> = () => {
placeholder={t('bbb', 'Group …')}
selectShare={(share) => addRestriction(share.value.shareWith)}
shareType={[ShareType.Group]}
excluded={{groupIds: restrictions.map(restriction => restriction.groupId)}} /> }
excluded={{groupIds: restrictions.map((restriction: Restriction) => restriction.groupId)}} /> }
{error && <><span className="icon icon-error icon-visible"></span> {error}</>}
</td>
<td colSpan={4} />

View File

@ -1,4 +1,4 @@
import React, { } from 'react';
import React from 'react';
import { Restriction } from '../Common/Api';
import EditableValue from '../Manager/EditableValue';
import EditableSelection from '../Common/EditableSelection';
@ -11,7 +11,7 @@ type Props = {
}
const RestrictionRoom: React.FC<Props> = (props) => {
const RestrictionRoom = (props: Props): JSX.Element => {
const restriction = props.restriction;
function updateRestriction(key: string, value: string | boolean | number | string[]) {
@ -32,7 +32,7 @@ const RestrictionRoom: React.FC<Props> = (props) => {
props.deleteRestriction(restriction.id);
}
},
true
true,
);
}

View File

@ -2,11 +2,14 @@
import App from './App';
import React from 'react';
import ReactDom from 'react-dom';
import { render } from 'react-dom';
// Enable React devtools
window['React'] = React;
$(document).ready(() => {
ReactDom.render( <App/>, document.getElementById('bbb-restrictions'));
const root = document.getElementById('bbb-restrictions');
if (root) {
render(<App /> as any, root);
}
});

View File

@ -1,8 +1,6 @@
import {api} from './Common/Api';
import { api } from './Common/Api';
import './Manager/App.scss';
declare const OCP: any;
$(() => {
function generateWarningElement(message: string) {
return $(`<div id="bbb-warning"><span class="icon icon-error-color icon-visible"></span> ${message}</div>`);
@ -23,14 +21,10 @@ $(() => {
}
function checkPasswordConfirmation() {
return new Promise<void>(resolve => {
if (OC.PasswordConfirmation && OC.PasswordConfirmation.requiresPasswordConfirmation()) {
OC.PasswordConfirmation.requirePasswordConfirmation(() => resolve());
return;
}
resolve();
return new Promise<void>((resolve: () => void) => {
OC.PasswordConfirmation?.requiresPasswordConfirmation()
? OC.PasswordConfirmation.requirePasswordConfirmation(() => resolve())
: resolve();
});
}
@ -49,7 +43,14 @@ $(() => {
const resultElement = $(this).find('.bbb-result').empty();
saveApiSettings(this['api.url'].value, this['api.secret'].value).then(() => {
const apiUrl = this['api.url'] as HTMLInputElement;
const apiSecret = this['api.secret'] as HTMLInputElement;
if (apiUrl === null || apiSecret == null) {
return;
}
saveApiSettings(apiUrl.value, apiSecret.value).then(() => {
const successElement = generateSuccessElement(t('bbb', 'Settings saved'));
setTimeout(() => {
@ -95,7 +96,11 @@ $(() => {
const resultElement = $(this).find('.bbb-result').empty();
saveAppSettings(this['app.shortener'].value).then(() => {
const shortenerInput = this['app.shortener'] as HTMLInputElement;
if (shortenerInput === null) {
return;
}
saveAppSettings(shortenerInput.value).then(() => {
const successElement = generateSuccessElement(t('bbb', 'Settings saved'));
setTimeout(() => {
@ -123,7 +128,7 @@ $(() => {
$<HTMLInputElement>('#bbb-shortener [name="app.shortener"]').on('keyup', (ev) => {
ev.preventDefault();
const {value} = ev.target;
const {value} = ev.target as HTMLInputElement;
if (!value || value.indexOf('https://') !== 0 || value.indexOf('{token}') < 0) {
$('#bbb-shortener-example').text(t('bbb', 'URL has to start with https:// and contain {token}. Additionally the {user} placeholder can be used.'));
@ -154,8 +159,10 @@ return 307;</pre></details>
$<HTMLInputElement>('.bbb-setting[type="checkbox"]').on('change', (ev) => {
ev.preventDefault();
console.log(`checkbox ${ev.target.name} changed to ${ev.target.checked}`);
const inputElement = ev.target as HTMLInputElement;
OCP.AppConfig.setValue('bbb', ev.target.name, ev.target.checked);
console.log(`checkbox ${inputElement.name} changed to ${inputElement.checked}`);
OCP.AppConfig.setValue('bbb', inputElement.name, inputElement.checked);
});
});

View File

@ -1,7 +1,6 @@
import axios from '@nextcloud/axios';
import { generateOcsUrl, generateUrl } from '@nextcloud/router';
import { showSuccess, showWarning, showError } from '@nextcloud/dialogs';
import '@nextcloud/dialogs/styles/toast';
import { api } from './Common/Api';
import './filelist.scss';
@ -80,7 +79,9 @@ async function openDialog(fileId: number, filename: string) {
const initContent = '<div id="bbb-file-action"><span className="icon icon-loading-small icon-visible"></span></div>';
const title = t('bbb', 'Send file to BBB');
await (OC.dialogs as ExtendedDialogs).message(initContent, title, 'none', -1, undefined, true, true);
const exDialogs = OC.dialogs as ExtendedDialogs;
await exDialogs.message(initContent, title, 'none', -1, undefined, true, true);
const rooms = await api.getRooms();

View File

@ -9,8 +9,8 @@ $(() => {
'bbb',
'This room is not open yet. We will try it again in %n second. Please wait.',
'This room is not open yet. We will try it again in %n seconds. Please wait.',
--countdown
)
--countdown,
),
);
if (countdown === 0) {

View File

@ -3,7 +3,7 @@
"lib": [
"dom",
"es2015.promise",
"es6"
"es2017"
],
"module": "ES6",
"moduleResolution": "node",

View File

@ -1,9 +1,30 @@
/* eslint-disable @typescript-eslint/no-var-requires */
process.env.npm_package_name = 'bbb';
const path = require('path');
const ESLintPlugin = require('eslint-webpack-plugin');
const webpackConfig = require('@nextcloud/webpack-vue-config')
const webpackRules = require('@nextcloud/webpack-vue-config/rules')
module.exports = {
entry: {
webpackRules.RULE_TSX = {
test: /\.tsx?$/,
use: [
{
loader: 'babel-loader',
options: {
babelrc: false,
},
},
'ts-loader',
],
};
webpackRules.RULE_RAW = {
test: /\.svg$/,
resourceQuery: /raw/,
type: 'asset/source'
};
webpackConfig.entry = {
admin: [
path.join(__dirname, 'ts', 'admin.ts'),
],
@ -22,55 +43,12 @@ module.exports = {
waiting: [
path.join(__dirname, 'ts', 'waiting.ts'),
],
},
output: {
path: path.resolve(__dirname, './js'),
publicPath: '/js/',
filename: '[name].js',
chunkFilename: 'chunks/[name]-[hash].js',
},
module: {
rules: [
{
test: /\.tsx?$/,
use: [
{
loader: 'babel-loader',
options: {
babelrc: false,
plugins: ['react-hot-loader/babel'],
},
},
'ts-loader',
],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.(png|jpg|gif|svg)$/,
type: 'asset',
generator: {
filename: 'static/[name][ext]?[hash]',
},
},
],
},
plugins: [
new ESLintPlugin(),
],
resolve: {
extensions: ['*', '.tsx', '.ts', '.js', '.scss'],
symlinks: false,
},
};
};
webpackConfig.module.rules = Object.values(webpackRules);
webpackConfig.plugins.push(new ESLintPlugin());
webpackConfig.resolve.extensions = [...webpackConfig.resolve.extensions, '.jsx', '.ts', '.tsx'];
module.exports = webpackConfig

6695
yarn.lock

File diff suppressed because it is too large Load Diff