diff options
Diffstat (limited to 'apps')
41 files changed, 1337 insertions, 622 deletions
diff --git a/apps/dav/src/views/Availability.vue b/apps/dav/src/views/Availability.vue index 4a66dc383c2..e0128a59e0a 100644 --- a/apps/dav/src/views/Availability.vue +++ b/apps/dav/src/views/Availability.vue @@ -52,7 +52,6 @@ import { enableUserStatusAutomation, disableUserStatusAutomation, } from '../service/PreferenceService' -import jstz from 'jstimezonedetect' import NcButton from '@nextcloud/vue/dist/Components/NcButton' import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch' import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection' @@ -69,8 +68,7 @@ export default { }, data() { // Try to determine the current timezone, and fall back to UTC otherwise - const defaultTimezone = jstz.determine() - const defaultTimezoneId = defaultTimezone ? defaultTimezone.name() : 'UTC' + const defaultTimezoneId = (new Intl.DateTimeFormat())?.resolvedOptions()?.timeZone ?? 'UTC' return { loading: true, diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php index 6c94490b085..60ba6afdf7a 100644 --- a/apps/files/appinfo/routes.php +++ b/apps/files/appinfo/routes.php @@ -39,6 +39,13 @@ namespace OCA\Files\AppInfo; use OCA\Files\Controller\OpenLocalEditorController; +// Legacy routes above +/** @var $this \OC\Route\Router */ +$this->create('files_ajax_download', 'apps/files/ajax/download.php') + ->actionInclude('files/ajax/download.php'); +$this->create('files_ajax_list', 'apps/files/ajax/list.php') + ->actionInclude('files/ajax/list.php'); + /** @var Application $application */ $application = \OC::$server->query(Application::class); $application->registerRoutes( @@ -46,12 +53,33 @@ $application->registerRoutes( [ 'routes' => [ [ + 'name' => 'view#index', + 'url' => '/', + 'verb' => 'GET', + ], + [ + 'name' => 'view#index', + 'url' => '/{view}', + 'verb' => 'GET', + 'postfix' => 'view', + ], + [ + 'name' => 'view#index', + 'url' => '/{view}/{fileid}', + 'verb' => 'GET', + 'postfix' => 'fileid', + ], + [ 'name' => 'View#showFile', 'url' => '/f/{fileid}', 'verb' => 'GET', 'root' => '', ], - + [ + 'name' => 'ajax#getStorageStats', + 'url' => '/ajax/getstoragestats', + 'verb' => 'GET', + ], [ 'name' => 'API#getThumbnail', 'url' => '/api/v1/thumbnail/{x}/{y}/{file}', @@ -70,6 +98,16 @@ $application->registerRoutes( 'verb' => 'GET' ], [ + 'name' => 'API#setConfig', + 'url' => '/api/v1/config/{key}', + 'verb' => 'POST' + ], + [ + 'name' => 'API#getConfigs', + 'url' => '/api/v1/configs', + 'verb' => 'GET' + ], + [ 'name' => 'API#updateFileSorting', 'url' => '/api/v1/sorting', 'verb' => 'POST' @@ -95,16 +133,6 @@ $application->registerRoutes( 'verb' => 'GET' ], [ - 'name' => 'view#index', - 'url' => '/', - 'verb' => 'GET', - ], - [ - 'name' => 'ajax#getStorageStats', - 'url' => '/ajax/getstoragestats', - 'verb' => 'GET', - ], - [ 'name' => 'API#toggleShowFolder', 'url' => '/api/v1/toggleShowFolder/{key}', 'verb' => 'POST' @@ -186,10 +214,3 @@ $application->registerRoutes( ], ] ); - -/** @var $this \OC\Route\Router */ - -$this->create('files_ajax_download', 'apps/files/ajax/download.php') - ->actionInclude('files/ajax/download.php'); -$this->create('files_ajax_list', 'apps/files/ajax/list.php') - ->actionInclude('files/ajax/list.php'); diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php index 2327cf44138..0f6c2caf4f2 100644 --- a/apps/files/composer/composer/autoload_classmap.php +++ b/apps/files/composer/composer/autoload_classmap.php @@ -58,5 +58,6 @@ return array( 'OCA\\Files\\Service\\DirectEditingService' => $baseDir . '/../lib/Service/DirectEditingService.php', 'OCA\\Files\\Service\\OwnershipTransferService' => $baseDir . '/../lib/Service/OwnershipTransferService.php', 'OCA\\Files\\Service\\TagService' => $baseDir . '/../lib/Service/TagService.php', + 'OCA\\Files\\Service\\UserConfig' => $baseDir . '/../lib/Service/UserConfig.php', 'OCA\\Files\\Settings\\PersonalSettings' => $baseDir . '/../lib/Settings/PersonalSettings.php', ); diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php index fe23d4ed7b0..28e48b9919e 100644 --- a/apps/files/composer/composer/autoload_static.php +++ b/apps/files/composer/composer/autoload_static.php @@ -73,6 +73,7 @@ class ComposerStaticInitFiles 'OCA\\Files\\Service\\DirectEditingService' => __DIR__ . '/..' . '/../lib/Service/DirectEditingService.php', 'OCA\\Files\\Service\\OwnershipTransferService' => __DIR__ . '/..' . '/../lib/Service/OwnershipTransferService.php', 'OCA\\Files\\Service\\TagService' => __DIR__ . '/..' . '/../lib/Service/TagService.php', + 'OCA\\Files\\Service\\UserConfig' => __DIR__ . '/..' . '/../lib/Service/UserConfig.php', 'OCA\\Files\\Settings\\PersonalSettings' => __DIR__ . '/..' . '/../lib/Settings/PersonalSettings.php', ); diff --git a/apps/files/css/files.css b/apps/files/css/files.css index 8696d5f7707..52871928750 100644 --- a/apps/files/css/files.css +++ b/apps/files/css/files.css @@ -480,7 +480,9 @@ table td.filename .thumbnail { display: inline-block; width: 32px; height: 32px; - background-size: 32px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; margin-left: 9px; margin-top: 9px; border-radius: var(--border-radius); diff --git a/apps/files/css/files.css.map b/apps/files/css/files.css.map index ea917aa36b4..7139f1cee08 100644 --- a/apps/files/css/files.css.map +++ b/apps/files/css/files.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../../core/css/variables.scss","files.scss","../../../core/css/functions.scss"],"names":[],"mappings":";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA;AAAA;AAAA;AA4BA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ADxCA;AACA;EACC;EACA;EACA;EACA;;;AAED;EAAoD;EAAU;;;AAC9D;EAAqB;;;AACrB;AAAA;EAEC;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;;AACA;EACC;;;AAIF;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;AAiBA;AAAA;AAAA;;AAfA;EACC;;AAGD;EACC;EACA;EAEA;EAEA;EACA;EACA;;AAMD;EACC;EACA;;AAEA;AAAA;EAEC;;AAEA;AAAA;EACC;;AAKF;EACC;;;AAKH;EACC;;;AAGD;AACA;EACC;EACA;;;AAGD;EAGC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;AACA;ACvEC;EAEA;;;ADwED;AC1EC;EAEA;;;AD2ED;AC7EC;EAEA;;;AD8ED;AAAA;AAAA;AAAA;AChFC;EAEA;;;ADoFD;ACtFC;EAEA;;;ADuFD;ACzFC;EAEA;;;AD0FD;AC5FC;EAEA;;;AD6FD;AC/FC;EAEA;;;ADgGD;AClGC;EAEA;;;ADmGD;ACrGC;EAEA;;;ADuGD;EACC;;;AAED;AACA;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAED;AAAA;AAAA;AAAA;AAAA;EAKC;;;AAED;AAAA;AAAA;AAAA;AAAA;EAKC;;;AAGD;EAAU;;;AAEV;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;;;AAED;AAAA;AAAA;AAAA;EAIC;EACA;;;AAGD;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;AACA;EACC;;;AAGF;EACC;EACA;;;AAED;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;;;AAED;AAAA;AAAA;AAAA;EAIC;;;AAED;EACC;;;AAED;AAAA;EAEC;;;AAGD;AAAA;EAEC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAED;EACC;;;AAED;AAAA;EAEC;EACA;EACA;AACA;EACA;;;AAGD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQC;;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACC;;;AAIF;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAED;EACC;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAED;AAAA;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;AACC;EACA;EACA;EACA;EACA;;;AAGA;EACC;;AAED;EACC;;;AAGF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAID;EACC;;;AAGD;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAGD;EAA6H;;;AAC7H;EAAwE;EAAY;;;AAEpF;EACC;EACA;EACA;EACA;;;AAGD;AAEC;EACC;;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKH;AAAA;EAEC;;;AAGD;EACC;EACA;EACA;EACA;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;AAEA;EACC;EACA;EAEA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACC;EACA;;;AAGD;AACA;AAAA;AAAA;AAAA;EAIC;;;AAGD;AACA;EACC;;;AAGD;AAGC;AAAA;EACC;;AAGD;AAAA;EACC;EACA;EACA;EACA;EACA;;;AAIF;AAAA;EAEC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EAA2C;EAAwC;EAAsC;;;AAG1H;AAAA;EAEC;EACA;EACA;;;AAGD;EACC;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EAAsC;;;AAEtC;AACA;EACC;;;AAGD;EACC;;;AAGD;AACA;AAAA;EAEC;;;AAGD;AACA;EACC;EACA;;;AAGD;EACC;;;AAGD;AAAA;AAAA;AAIC;EACC;;AAGD;EACC;;AAGD;EACC;;;AAIF;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACI;EACA;EACA;;;AAEJ;EACI;;;AAEJ;EACC;EACA;EACA;;;AAGD;EACC;;;AAED;EACC;EACA;EACA;;;AAGD;EACC;;;AAIA;EACC;EACA;EACA;EACA;;AACA;EACC;;AACA;AACC;AACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;;AAGF;EACC;EACA;EACA;EACA;;AAGA;EACC;;AAID;AAAA;EAEC;;AAED;EACC;;AACA;EACC;;AAIH;EACC;;AAED;EACC;EACA;;AAGF;EACC;;AAED;EACC;;;AAKF;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;AAAA;AAAA;AAAA;EAKC;;;AAGD;EACC;;;AAGD;AAAA;EAEC;;;AAGD;EACC;;;AAGD;EACC;AACA;EAEA;;;AAED;EACC;AACA;EACA;;;AAED;AAAA;AAAA;AAGA;EACC;;;AAED;AAAA;AAAA;AAAA;EAIC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;EACC;;;AAGD;EACC;EACA;;;AAED;EACC;EACA;EACA;AAEA;EACA;;;AAED;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;AAEA;;AACA;EACC;;;AAKF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKE;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAED;EACC;;;AAMJ;AAEA;EACC;;;AAGD;AAAA;AAAA;AAAA;EAIC;EACA;EACA;;;AAMA;EACC;;AAED;EACC;;;AAIF;AAAA;AAAA;EAGC;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAGD;EACC;;;AAGD;EACC;;AAEA;EACC;;;AAIF;AAAA;EAEC;;;AAED;EACC;;AACA;EACC;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;AAGD;EACC;EACA;EACA;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;AAKA;EACC;;AAIF;EACC;EACA;;;AAIF;AACA;AAIC;AAaA;AAqOA;;AAhPC;EACC;EACA;EACA;;AACA;EACC;EACA;;AAMH;EACC;EACA;EACA;EACA;EACA;;AAGA;EACC;EACA;EACA;EACA;;AAEA;AAAA;EAKC;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAGC;;AAKH;EACC;EACA;AAoJA;AA8BA;;AA/KC;EACC;EACA;EACA;EACA,OAvDQ;EAwDR,QAxDQ;EAyDR,SAxDO;EAyDP;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;AAAA;AAAA;;AAGA;EACC,SA1EK;EA2EL;EACA;EACA;;AAKH;EACC;EACA;EACA;EACA;EAEA;EACA;EAEA;;AAGD;EACC;EACA;EAIA;EAKA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;AAoBA;;AAlBA;EACC;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;;AAID;EACC;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC,SApJK;EAqJL;EACA;EACA;EACA;EACA;;AAGA;EACC;;AAQH;EACC;;AAEA;EACC;EACA;;AAIF;EACC;;AAGD;EACC;;AAIF;EACC;EACA;;AAEA;EACC;EACA;EACA;;AAMH;EAEC;;AAGD;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA,SAxNO;;AAyNP;EACC;EACA,OA3NM;EA4NN,QA5NM;;AAkOT;EACC;EACA;EACA;AAEA;;AACA;EACC;EACA;;AAMJ;EACC;;AAID;EACC;;AAEA;EACC;EACA;EAEA;;AAEA;EACC;;AAEA;EAEC;;AAGD;EACI;;;AAOR;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAGC;;AAGD;EAEC;;;AAIF;AAAA;AAAA;AAAA;AAAA;AAKA;EACC;EACA;;;AAGD;AACA;AAaC;;AAZA;AACC;AAKA;;AAJA;EACC;;AAID;EACC;;AAKF;EACC;EACA;EACA;;;AAIF;AACA;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;;AAGD;EACC;EACA;EACA","file":"files.css"}
\ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../../core/css/variables.scss","files.scss","../../../core/css/functions.scss"],"names":[],"mappings":";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA;AAAA;AAAA;AA4BA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ADxCA;AACA;EACC;EACA;EACA;EACA;;;AAED;EAAoD;EAAU;;;AAC9D;EAAqB;;;AACrB;AAAA;EAEC;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;;AACA;EACC;;;AAIF;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;AAiBA;AAAA;AAAA;;AAfA;EACC;;AAGD;EACC;EACA;EAEA;EAEA;EACA;EACA;;AAMD;EACC;EACA;;AAEA;AAAA;EAEC;;AAEA;AAAA;EACC;;AAKF;EACC;;;AAKH;EACC;;;AAGD;AACA;EACC;EACA;;;AAGD;EAGC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;AACA;ACvEC;EAEA;;;ADwED;AC1EC;EAEA;;;AD2ED;AC7EC;EAEA;;;AD8ED;AAAA;AAAA;AAAA;AChFC;EAEA;;;ADoFD;ACtFC;EAEA;;;ADuFD;ACzFC;EAEA;;;AD0FD;AC5FC;EAEA;;;AD6FD;AC/FC;EAEA;;;ADgGD;AClGC;EAEA;;;ADmGD;ACrGC;EAEA;;;ADuGD;EACC;;;AAED;AACA;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAED;AAAA;AAAA;AAAA;AAAA;EAKC;;;AAED;AAAA;AAAA;AAAA;AAAA;EAKC;;;AAGD;EAAU;;;AAEV;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;;;AAED;AAAA;AAAA;AAAA;EAIC;EACA;;;AAGD;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;AACA;EACC;;;AAGF;EACC;EACA;;;AAED;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;;;AAED;AAAA;AAAA;AAAA;EAIC;;;AAED;EACC;;;AAED;AAAA;EAEC;;;AAGD;AAAA;EAEC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAED;EACC;;;AAED;AAAA;EAEC;EACA;EACA;AACA;EACA;;;AAGD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQC;;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACC;;;AAIF;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAED;EACC;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAED;AAAA;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;AACC;EACA;EACA;EACA;EACA;;;AAGA;EACC;;AAED;EACC;;;AAGF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAID;EACC;;;AAGD;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAGD;EAA6H;;;AAC7H;EAAwE;EAAY;;;AAEpF;EACC;EACA;EACA;EACA;;;AAGD;AAEC;EACC;;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKH;AAAA;EAEC;;;AAGD;EACC;EACA;EACA;EACA;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;AAEA;EACC;EACA;EAEA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACC;EACA;;;AAGD;AACA;AAAA;AAAA;AAAA;EAIC;;;AAGD;AACA;EACC;;;AAGD;AAGC;AAAA;EACC;;AAGD;AAAA;EACC;EACA;EACA;EACA;EACA;;;AAIF;AAAA;EAEC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EAA2C;EAAwC;EAAsC;;;AAG1H;AAAA;EAEC;EACA;EACA;;;AAGD;EACC;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EAAsC;;;AAEtC;AACA;EACC;;;AAGD;EACC;;;AAGD;AACA;AAAA;EAEC;;;AAGD;AACA;EACC;EACA;;;AAGD;EACC;;;AAGD;AAAA;AAAA;AAIC;EACC;;AAGD;EACC;;AAGD;EACC;;;AAIF;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACI;EACA;EACA;;;AAEJ;EACI;;;AAEJ;EACC;EACA;EACA;;;AAGD;EACC;;;AAED;EACC;EACA;EACA;;;AAGD;EACC;;;AAIA;EACC;EACA;EACA;EACA;;AACA;EACC;;AACA;AACC;AACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;;AAGF;EACC;EACA;EACA;EACA;;AAGA;EACC;;AAID;AAAA;EAEC;;AAED;EACC;;AACA;EACC;;AAIH;EACC;;AAED;EACC;EACA;;AAGF;EACC;;AAED;EACC;;;AAKF;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;AAAA;AAAA;AAAA;EAKC;;;AAGD;EACC;;;AAGD;AAAA;EAEC;;;AAGD;EACC;;;AAGD;EACC;AACA;EAEA;;;AAED;EACC;AACA;EACA;;;AAED;AAAA;AAAA;AAGA;EACC;;;AAED;AAAA;AAAA;AAAA;EAIC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;EACC;;;AAGD;EACC;EACA;;;AAED;EACC;EACA;EACA;AAEA;EACA;;;AAED;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;AAEA;;AACA;EACC;;;AAKF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKE;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAED;EACC;;;AAMJ;AAEA;EACC;;;AAGD;AAAA;AAAA;AAAA;EAIC;EACA;EACA;;;AAMA;EACC;;AAED;EACC;;;AAIF;AAAA;AAAA;EAGC;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAGD;EACC;;;AAGD;EACC;;AAEA;EACC;;;AAIF;AAAA;EAEC;;;AAED;EACC;;AACA;EACC;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;AAGD;EACC;EACA;EACA;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;AAKA;EACC;;AAIF;EACC;EACA;;;AAIF;AACA;AAIC;AAaA;AAqOA;;AAhPC;EACC;EACA;EACA;;AACA;EACC;EACA;;AAMH;EACC;EACA;EACA;EACA;EACA;;AAGA;EACC;EACA;EACA;EACA;;AAEA;AAAA;EAKC;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAGC;;AAKH;EACC;EACA;AAoJA;AA8BA;;AA/KC;EACC;EACA;EACA;EACA,OAvDQ;EAwDR,QAxDQ;EAyDR,SAxDO;EAyDP;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;AAAA;AAAA;;AAGA;EACC,SA1EK;EA2EL;EACA;EACA;;AAKH;EACC;EACA;EACA;EACA;EAEA;EACA;EAEA;;AAGD;EACC;EACA;EAIA;EAKA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;AAoBA;;AAlBA;EACC;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;;AAID;EACC;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC,SApJK;EAqJL;EACA;EACA;EACA;EACA;;AAGA;EACC;;AAQH;EACC;;AAEA;EACC;EACA;;AAIF;EACC;;AAGD;EACC;;AAIF;EACC;EACA;;AAEA;EACC;EACA;EACA;;AAMH;EAEC;;AAGD;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA,SAxNO;;AAyNP;EACC;EACA,OA3NM;EA4NN,QA5NM;;AAkOT;EACC;EACA;EACA;AAEA;;AACA;EACC;EACA;;AAMJ;EACC;;AAID;EACC;;AAEA;EACC;EACA;EAEA;;AAEA;EACC;;AAEA;EAEC;;AAGD;EACI;;;AAOR;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAGC;;AAGD;EAEC;;;AAIF;AAAA;AAAA;AAAA;AAAA;AAKA;EACC;EACA;;;AAGD;AACA;AAaC;;AAZA;AACC;AAKA;;AAJA;EACC;;AAID;EACC;;AAKF;EACC;EACA;EACA;;;AAIF;AACA;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;;AAGD;EACC;EACA;EACA","file":"files.css"}
\ No newline at end of file diff --git a/apps/files/css/files.scss b/apps/files/css/files.scss index 612ac975aeb..22e0770d8b2 100644 --- a/apps/files/css/files.scss +++ b/apps/files/css/files.scss @@ -380,7 +380,9 @@ table td.filename .thumbnail { display: inline-block; width: 32px; height: 32px; - background-size: 32px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; margin-left: 9px; margin-top: 9px; border-radius: var(--border-radius); diff --git a/apps/files/css/merged.css b/apps/files/css/merged.css index 38ac0c66b13..c2fe7917e19 100644 --- a/apps/files/css/merged.css +++ b/apps/files/css/merged.css @@ -480,7 +480,9 @@ table td.filename .thumbnail { display: inline-block; width: 32px; height: 32px; - background-size: 32px; + background-size: contain; + background-position: center; + background-repeat: no-repeat; margin-left: 9px; margin-top: 9px; border-radius: var(--border-radius); diff --git a/apps/files/css/merged.css.map b/apps/files/css/merged.css.map index c4fe79c3b39..574393f4cd0 100644 --- a/apps/files/css/merged.css.map +++ b/apps/files/css/merged.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["../../../core/css/variables.scss","files.scss","../../../core/css/functions.scss","upload.scss","mobile.scss","detailsView.scss","../../../core/css/whatsnew.scss"],"names":[],"mappings":";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA;AAAA;AAAA;AA4BA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ADxCA;AACA;EACC;EACA;EACA;EACA;;;AAED;EAAoD;EAAU;;;AAC9D;EAAqB;;;AACrB;AAAA;EAEC;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;;AACA;EACC;;;AAIF;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;AAiBA;AAAA;AAAA;;AAfA;EACC;;AAGD;EACC;EACA;EAEA;EAEA;EACA;EACA;;AAMD;EACC;EACA;;AAEA;AAAA;EAEC;;AAEA;AAAA;EACC;;AAKF;EACC;;;AAKH;EACC;;;AAGD;AACA;EACC;EACA;;;AAGD;EAGC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;AACA;ACvEC;EAEA;;;ADwED;AC1EC;EAEA;;;AD2ED;AC7EC;EAEA;;;AD8ED;AAAA;AAAA;AAAA;AChFC;EAEA;;;ADoFD;ACtFC;EAEA;;;ADuFD;ACzFC;EAEA;;;AD0FD;AC5FC;EAEA;;;AD6FD;AC/FC;EAEA;;;ADgGD;AClGC;EAEA;;;ADmGD;ACrGC;EAEA;;;ADuGD;EACC;;;AAED;AACA;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAED;AAAA;AAAA;AAAA;AAAA;EAKC;;;AAED;AAAA;AAAA;AAAA;AAAA;EAKC;;;AAGD;EAAU;;;AAEV;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;;;AAED;AAAA;AAAA;AAAA;EAIC;EACA;;;AAGD;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;AACA;EACC;;;AAGF;EACC;EACA;;;AAED;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;;;AAED;AAAA;AAAA;AAAA;EAIC;;;AAED;EACC;;;AAED;AAAA;EAEC;;;AAGD;AAAA;EAEC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAED;EACC;;;AAED;AAAA;EAEC;EACA;EACA;AACA;EACA;;;AAGD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQC;;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACC;;;AAIF;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAED;EACC;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAED;AAAA;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;AACC;EACA;EACA;EACA;EACA;;;AAGA;EACC;;AAED;EACC;;;AAGF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAID;EACC;;;AAGD;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAGD;EAA6H;;;AAC7H;EAAwE;EAAY;;;AAEpF;EACC;EACA;EACA;EACA;;;AAGD;AAEC;EACC;;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKH;AAAA;EAEC;;;AAGD;EACC;EACA;EACA;EACA;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;AAEA;EACC;EACA;EAEA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACC;EACA;;;AAGD;AACA;AAAA;AAAA;AAAA;EAIC;;;AAGD;AACA;EACC;;;AAGD;AAGC;AAAA;EACC;;AAGD;AAAA;EACC;EACA;EACA;EACA;EACA;;;AAIF;AAAA;EAEC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EAA2C;EAAwC;EAAsC;;;AAG1H;AAAA;EAEC;EACA;EACA;;;AAGD;EACC;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EAAsC;;;AAEtC;AACA;EACC;;;AAGD;EACC;;;AAGD;AACA;AAAA;EAEC;;;AAGD;AACA;EACC;EACA;;;AAGD;EACC;;;AAGD;AAAA;AAAA;AAIC;EACC;;AAGD;EACC;;AAGD;EACC;;;AAIF;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACI;EACA;EACA;;;AAEJ;EACI;;;AAEJ;EACC;EACA;EACA;;;AAGD;EACC;;;AAED;EACC;EACA;EACA;;;AAGD;EACC;;;AAIA;EACC;EACA;EACA;EACA;;AACA;EACC;;AACA;AACC;AACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;;AAGF;EACC;EACA;EACA;EACA;;AAGA;EACC;;AAID;AAAA;EAEC;;AAED;EACC;;AACA;EACC;;AAIH;EACC;;AAED;EACC;EACA;;AAGF;EACC;;AAED;EACC;;;AAKF;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;AAAA;AAAA;AAAA;EAKC;;;AAGD;EACC;;;AAGD;AAAA;EAEC;;;AAGD;EACC;;;AAGD;EACC;AACA;EAEA;;;AAED;EACC;AACA;EACA;;;AAED;AAAA;AAAA;AAGA;EACC;;;AAED;AAAA;AAAA;AAAA;EAIC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;EACC;;;AAGD;EACC;EACA;;;AAED;EACC;EACA;EACA;AAEA;EACA;;;AAED;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;AAEA;;AACA;EACC;;;AAKF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKE;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAED;EACC;;;AAMJ;AAEA;EACC;;;AAGD;AAAA;AAAA;AAAA;EAIC;EACA;EACA;;;AAMA;EACC;;AAED;EACC;;;AAIF;AAAA;AAAA;EAGC;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAGD;EACC;;;AAGD;EACC;;AAEA;EACC;;;AAIF;AAAA;EAEC;;;AAED;EACC;;AACA;EACC;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;AAGD;EACC;EACA;EACA;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;AAKA;EACC;;AAIF;EACC;EACA;;;AAIF;AACA;AAIC;AAaA;AAqOA;;AAhPC;EACC;EACA;EACA;;AACA;EACC;EACA;;AAMH;EACC;EACA;EACA;EACA;EACA;;AAGA;EACC;EACA;EACA;EACA;;AAEA;AAAA;EAKC;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAGC;;AAKH;EACC;EACA;AAoJA;AA8BA;;AA/KC;EACC;EACA;EACA;EACA,OAvDQ;EAwDR,QAxDQ;EAyDR,SAxDO;EAyDP;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;AAAA;AAAA;;AAGA;EACC,SA1EK;EA2EL;EACA;EACA;;AAKH;EACC;EACA;EACA;EACA;EAEA;EACA;EAEA;;AAGD;EACC;EACA;EAIA;EAKA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;AAoBA;;AAlBA;EACC;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;;AAID;EACC;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC,SApJK;EAqJL;EACA;EACA;EACA;EACA;;AAGA;EACC;;AAQH;EACC;;AAEA;EACC;EACA;;AAIF;EACC;;AAGD;EACC;;AAIF;EACC;EACA;;AAEA;EACC;EACA;EACA;;AAMH;EAEC;;AAGD;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA,SAxNO;;AAyNP;EACC;EACA,OA3NM;EA4NN,QA5NM;;AAkOT;EACC;EACA;EACA;AAEA;;AACA;EACC;EACA;;AAMJ;EACC;;AAID;EACC;;AAEA;EACC;EACA;EAEA;;AAEA;EACC;;AAEA;EAEC;;AAGD;EACI;;;AAOR;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAGC;;AAGD;EAEC;;;AAIF;AAAA;AAAA;AAAA;AAAA;AAKA;EACC;EACA;;;AAGD;AACA;AAaC;;AAZA;AACC;AAKA;;AAJA;EACC;;AAID;EACC;;AAKF;EACC;EACA;EACA;;;AAIF;AACA;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;;AAGD;EACC;EACA;EACA;;;AEvyCF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EAAsB;;;AACtB;EAAoB;EAAgB;EAAY;EAAU;EAAW;EAAgB;;;AAErF;EACC;;;AAGD;EACC;EACA;EACA;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;;AAGF;EACC;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;;;AAED;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;AAAA;AAAA;EAGC;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;AAAA;EAEC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAGD;EACC;EACA;;AAEA;EACC;;;AAIF;EACC;EACA;EACA;EACA;;;AAGD;EACE;IAAK;;EACL;IAAO;;;AAET;EACE;IAAK;;EACL;IAAO;;;AAET;EACE;IAAK;;EACL;IAAO;;;AAET;EACE;IAAK;;EACL;IAAO;;;AHjNT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AIEA;AAAA;AAAA;AAIA;EAEA;IACC;;EAGD;AAAA;AAAA;AAAA;IAIC;;AAGD;EACA;IACC;;AAGD;EACA;IACC;;EAGD;IACC;;EAGD;IACC;;AAED;AACA;EACA;IACC;IACA;IACA;IACA;IACA;IACA;;AAID;EACA;IACC;;AAED;EACA;IACC;;EAED;IACC;;AAGD;EACA;IACC;;;AAID;AACC;EACA;IACC;;EAED;IACC;;AAGD;EACA;IACC;;AAGD;EACA;IACC;;;AClFF;EACC;EACA;;;AAGD;EACC;;;AAID;EACC;EACA;;;AAGD;EACC;EACA;EACA;;AAEA;EAEC;;;AAGF;EACC;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AC/HD;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE","file":"merged.css"}
\ No newline at end of file +{"version":3,"sourceRoot":"","sources":["../../../core/css/variables.scss","files.scss","../../../core/css/functions.scss","upload.scss","mobile.scss","detailsView.scss","../../../core/css/whatsnew.scss"],"names":[],"mappings":";AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA;AAAA;AAAA;AA4BA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ADxCA;AACA;EACC;EACA;EACA;EACA;;;AAED;EAAoD;EAAU;;;AAC9D;EAAqB;;;AACrB;AAAA;EAEC;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;;AACA;EACC;;;AAIF;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAGD;AACA;EACC;EACA;EACA;EACA;EACA;AAiBA;AAAA;AAAA;;AAfA;EACC;;AAGD;EACC;EACA;EAEA;EAEA;EACA;EACA;;AAMD;EACC;EACA;;AAEA;AAAA;EAEC;;AAEA;AAAA;EACC;;AAKF;EACC;;;AAKH;EACC;;;AAGD;AACA;EACC;EACA;;;AAGD;EAGC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;AACA;ACvEC;EAEA;;;ADwED;AC1EC;EAEA;;;AD2ED;AC7EC;EAEA;;;AD8ED;AAAA;AAAA;AAAA;AChFC;EAEA;;;ADoFD;ACtFC;EAEA;;;ADuFD;ACzFC;EAEA;;;AD0FD;AC5FC;EAEA;;;AD6FD;AC/FC;EAEA;;;ADgGD;AClGC;EAEA;;;ADmGD;ACrGC;EAEA;;;ADuGD;EACC;;;AAED;AACA;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;;;AAED;AAAA;AAAA;AAAA;AAAA;EAKC;;;AAED;AAAA;AAAA;AAAA;AAAA;EAKC;;;AAGD;EAAU;;;AAEV;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;;;AAED;AAAA;AAAA;AAAA;EAIC;EACA;;;AAGD;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;AACA;EACC;;;AAGF;EACC;EACA;;;AAED;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;;;AAED;AAAA;AAAA;AAAA;EAIC;;;AAED;EACC;;;AAED;AAAA;EAEC;;;AAGD;AAAA;EAEC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAGD;EACC;EACA;;;AAGD;EACC;;;AAED;EACC;;;AAED;AAAA;EAEC;EACA;EACA;AACA;EACA;;;AAGD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAQC;;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EACC;;;AAIF;EACC;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;;;AAED;EACC;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAED;AAAA;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;AACC;EACA;EACA;EACA;EACA;;;AAGA;EACC;;AAED;EACC;;;AAGF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAID;EACC;;;AAGD;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;;;AAGD;EAA6H;;;AAC7H;EAAwE;EAAY;;;AAEpF;EACC;EACA;EACA;EACA;;;AAGD;AAEC;EACC;;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAKH;AAAA;EAEC;;;AAGD;EACC;EACA;EACA;EACA;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;AAEA;EACC;EACA;EAEA;EACA;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACC;EACA;;;AAGD;AACA;AAAA;AAAA;AAAA;EAIC;;;AAGD;AACA;EACC;;;AAGD;AAGC;AAAA;EACC;;AAGD;AAAA;EACC;EACA;EACA;EACA;EACA;;;AAIF;AAAA;EAEC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EAA2C;EAAwC;EAAsC;;;AAG1H;AAAA;EAEC;EACA;EACA;;;AAGD;EACC;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EAAsC;;;AAEtC;AACA;EACC;;;AAGD;EACC;;;AAGD;AACA;AAAA;EAEC;;;AAGD;AACA;EACC;EACA;;;AAGD;EACC;;;AAGD;AAAA;AAAA;AAIC;EACC;;AAGD;EACC;;AAGD;EACC;;;AAIF;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;AACA;EACI;EACA;EACA;;;AAEJ;EACI;;;AAEJ;EACC;EACA;EACA;;;AAGD;EACC;;;AAED;EACC;EACA;EACA;;;AAGD;EACC;;;AAIA;EACC;EACA;EACA;EACA;;AACA;EACC;;AACA;AACC;AACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;;AAGF;EACC;EACA;EACA;EACA;;AAGA;EACC;;AAID;AAAA;EAEC;;AAED;EACC;;AACA;EACC;;AAIH;EACC;;AAED;EACC;EACA;;AAGF;EACC;;AAED;EACC;;;AAKF;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;AAAA;AAAA;AAAA;EAKC;;;AAGD;EACC;;;AAGD;AAAA;EAEC;;;AAGD;EACC;;;AAGD;EACC;AACA;EAEA;;;AAED;EACC;AACA;EACA;;;AAED;AAAA;AAAA;AAGA;EACC;;;AAED;AAAA;AAAA;AAAA;EAIC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAED;EACC;;;AAGD;EACC;EACA;;;AAED;EACC;EACA;EACA;AAEA;EACA;;;AAED;EACC;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;AAEA;;AACA;EACC;;;AAKF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAKE;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAED;EACC;;;AAMJ;AAEA;EACC;;;AAGD;AAAA;AAAA;AAAA;EAIC;EACA;EACA;;;AAMA;EACC;;AAED;EACC;;;AAIF;AAAA;AAAA;EAGC;;;AAGD;AAAA;AAAA;EAGC;EACA;;;AAGD;EACC;;;AAGD;EACC;;AAEA;EACC;;;AAIF;AAAA;EAEC;;;AAED;EACC;;AACA;EACC;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;AAGD;EACC;EACA;EACA;;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;AAKA;EACC;;AAIF;EACC;EACA;;;AAIF;AACA;AAIC;AAaA;AAqOA;;AAhPC;EACC;EACA;EACA;;AACA;EACC;EACA;;AAMH;EACC;EACA;EACA;EACA;EACA;;AAGA;EACC;EACA;EACA;EACA;;AAEA;AAAA;EAKC;;AAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;EAGC;;AAKH;EACC;EACA;AAoJA;AA8BA;;AA/KC;EACC;EACA;EACA;EACA,OAvDQ;EAwDR,QAxDQ;EAyDR,SAxDO;EAyDP;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;AAEA;AAAA;AAAA;;AAGA;EACC,SA1EK;EA2EL;EACA;EACA;;AAKH;EACC;EACA;EACA;EACA;EAEA;EACA;EAEA;;AAGD;EACC;EACA;EAIA;EAKA;;AAEA;EACC;EACA;EACA;EACA;EACA;EACA;AAoBA;;AAlBA;EACC;EACA;EACA;EACA;EACA;;AAED;EACC;EACA;EACA;;AAED;EACC;EACA;EACA;;AAID;EACC;;AAIF;EACC;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC,SApJK;EAqJL;EACA;EACA;EACA;EACA;;AAGA;EACC;;AAQH;EACC;;AAEA;EACC;EACA;;AAIF;EACC;;AAGD;EACC;;AAIF;EACC;EACA;;AAEA;EACC;EACA;EACA;;AAMH;EAEC;;AAGD;EAEC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;EACA;EACA,SAxNO;;AAyNP;EACC;EACA,OA3NM;EA4NN,QA5NM;;AAkOT;EACC;EACA;EACA;AAEA;;AACA;EACC;EACA;;AAMJ;EACC;;AAID;EACC;;AAEA;EACC;EACA;EAEA;;AAEA;EACC;;AAEA;EAEC;;AAGD;EACI;;;AAOR;AACA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EAGC;;AAGD;EAEC;;;AAIF;AAAA;AAAA;AAAA;AAAA;AAKA;EACC;EACA;;;AAGD;AACA;AAaC;;AAZA;AACC;AAKA;;AAJA;EACC;;AAID;EACC;;AAKF;EACC;EACA;EACA;;;AAIF;AACA;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;EACA;;AAGD;EACC;EACA;EACA;;;AEzyCF;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAED;EAAsB;;;AACtB;EAAoB;EAAgB;EAAY;EAAU;EAAW;EAAgB;;;AAErF;EACC;;;AAGD;EACC;EACA;EACA;EACA;;;AAED;EACC;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACC;;;AAGF;EACC;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;;;AAED;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAGD;EACC;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;AAAA;AAAA;EAGC;EACA;EACA;EACA;EACA;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;;;AAED;EACC;EACA;EACA;;;AAED;EACC;EACA;EACA;EACA;EACA;EACA;;;AAGD;AAAA;EAEC;EACA;;;AAED;EACC;EACA;;;AAED;EACC;;;AAED;EACC;;;AAED;EACC;EACA;EACA;;;AAED;EACC;;;AAGD;EACC;EACA;;AAEA;EACC;;;AAIF;EACC;EACA;EACA;EACA;;;AAGD;EACE;IAAK;;EACL;IAAO;;;AAET;EACE;IAAK;;EACL;IAAO;;;AAET;EACE;IAAK;;EACL;IAAO;;;AAET;EACE;IAAK;;EACL;IAAO;;;AHjNT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AIEA;AAAA;AAAA;AAIA;EAEA;IACC;;EAGD;AAAA;AAAA;AAAA;IAIC;;AAGD;EACA;IACC;;AAGD;EACA;IACC;;EAGD;IACC;;EAGD;IACC;;AAED;AACA;EACA;IACC;IACA;IACA;IACA;IACA;IACA;;AAID;EACA;IACC;;AAED;EACA;IACC;;EAED;IACC;;AAGD;EACA;IACC;;;AAID;AACC;EACA;IACC;;EAED;IACC;;AAGD;EACA;IACC;;AAGD;EACA;IACC;;;AClFF;EACC;EACA;;;AAGD;EACC;;;AAID;EACC;EACA;;;AAGD;EACC;EACA;EACA;;AAEA;EAEC;;;AAGF;EACC;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;EACA;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;;;AAGD;EACC;;;AAGD;EACC;;;AAGD;EACC;EACA;EACA;EACA;EACA;EACA;EACA;;;AC/HD;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA;EACE;EACA;EACA;EACA;;;AAGF;EACE;;;AAGF;EACE;EACA;;;AAGF;EACE;EACA;;;AAGF;EACE","file":"merged.css"}
\ No newline at end of file diff --git a/apps/files/js/app.js b/apps/files/js/app.js index 8053f89268c..f0c3ac5c212 100644 --- a/apps/files/js/app.js +++ b/apps/files/js/app.js @@ -27,9 +27,9 @@ */ OCA.Files.App = { /** - * Navigation control + * Navigation instance * - * @member {OCA.Files.Navigation} + * @member {OCP.Files.Navigation} */ navigation: null, @@ -51,16 +51,11 @@ * Initializes the files app */ initialize: function() { - this.navigation = new OCA.Files.Navigation($('#app-navigation')); + this.navigation = OCP.Files.Navigation; this.$showHiddenFiles = $('input#showhiddenfilesToggle'); var showHidden = $('#showHiddenFiles').val() === "1"; this.$showHiddenFiles.prop('checked', showHidden); - // crop image previews - this.$cropImagePreviews = $('input#cropimagepreviewsToggle'); - var cropImagePreviews = $('#cropImagePreviews').val() === "1"; - this.$cropImagePreviews.prop('checked', cropImagePreviews); - // Toggle for grid view this.$showGridView = $('input#showgridview'); this.$showGridView.on('change', _.bind(this._onGridviewChange, this)); @@ -69,12 +64,9 @@ OC.Notification.show(t('files', 'File could not be found'), {type: 'error'}); } - this._filesConfig = new OC.Backbone.Model({ - showhidden: showHidden, - cropimagepreviews: cropImagePreviews, - }); + this._filesConfig = OCP.InitialState.loadState('files', 'config', {}) - var urlParams = OC.Util.History.parseUrlQuery(); + var { fileid, scrollto, openfile } = OC.Util.History.parseUrlQuery(); var fileActions = new OCA.Files.FileActions(); // default actions fileActions.registerDefaultActions(); @@ -94,8 +86,8 @@ folderDropOptions: folderDropOptions, fileActions: fileActions, allowLegacyActions: true, - scrollTo: urlParams.scrollto, - openFile: urlParams.openfile, + scrollTo: scrollto, + openFile: openfile, filesClient: OC.Files.getClient(), multiSelectMenu: [ { @@ -144,7 +136,7 @@ this._setupEvents(); // trigger URL change event handlers - this._onPopState(urlParams); + this._onPopState({ ...OC.Util.History.parseUrlQuery(), view: this.navigation?.active?.id }); this._debouncedPersistShowHiddenFilesState = _.debounce(this._persistShowHiddenFilesState, 1200); this._debouncedPersistCropImagePreviewsState = _.debounce(this._persistCropImagePreviewsState, 1200); @@ -159,7 +151,6 @@ * Destroy the app */ destroy: function() { - this.navigation = null; this.fileList.destroy(); this.fileList = null; this.files = null; @@ -216,15 +207,17 @@ * @return app container */ getCurrentAppContainer: function() { - return this.navigation.getActiveContainer(); + var viewId = this.getActiveView(); + return $('#app-content-' + viewId); }, /** * Sets the currently active view * @param viewId view id */ - setActiveView: function(viewId, options) { - this.navigation.setActiveItem(viewId, options); + setActiveView: function(viewId) { + // The Navigation API will handle the final event + window._nc_event_bus.emit('files:legacy-navigation:changed', { id: viewId }) }, /** @@ -232,7 +225,8 @@ * @return view id */ getActiveView: function() { - return this.navigation.getActiveItem(); + return this.navigation.active + && this.navigation.active.id; }, /** @@ -253,71 +247,29 @@ $('#app-content').delegate('>div', 'changeDirectory', _.bind(this._onDirectoryChanged, this)); $('#app-content').delegate('>div', 'afterChangeDirectory', _.bind(this._onAfterDirectoryChanged, this)); $('#app-content').delegate('>div', 'changeViewerMode', _.bind(this._onChangeViewerMode, this)); - - $('#app-navigation').on('itemChanged', _.bind(this._onNavigationChanged, this)); - this.$showHiddenFiles.on('change', _.bind(this._onShowHiddenFilesChange, this)); - this.$cropImagePreviews.on('change', _.bind(this._onCropImagePreviewsChange, this)); - }, - - /** - * Toggle showing hidden files according to the settings checkbox - * - * @returns {undefined} - */ - _onShowHiddenFilesChange: function() { - var show = this.$showHiddenFiles.is(':checked'); - this._filesConfig.set('showhidden', show); - this._debouncedPersistShowHiddenFilesState(); - }, - - /** - * Persist show hidden preference on the server - * - * @returns {undefined} - */ - _persistShowHiddenFilesState: function() { - var show = this._filesConfig.get('showhidden'); - $.post(OC.generateUrl('/apps/files/api/v1/showhidden'), { - show: show - }); - }, - - /** - * Toggle cropping image previews according to the settings checkbox - * - * @returns void - */ - _onCropImagePreviewsChange: function() { - var crop = this.$cropImagePreviews.is(':checked'); - this._filesConfig.set('cropimagepreviews', crop); - this._debouncedPersistCropImagePreviewsState(); - }, - - /** - * Persist crop image previews preference on the server - * - * @returns void - */ - _persistCropImagePreviewsState: function() { - var crop = this._filesConfig.get('cropimagepreviews'); - $.post(OC.generateUrl('/apps/files/api/v1/cropimagepreviews'), { - crop: crop - }); }, /** * Event handler for when the current navigation item has changed */ - _onNavigationChanged: function(e) { + _onNavigationChanged: function(view) { var params; - if (e && e.itemId) { - params = { - view: typeof e.view === 'string' && e.view !== '' ? e.view : e.itemId, - dir: e.dir ? e.dir : '/' - }; + if (view && (view.itemId || view.id)) { + if (view.id) { + params = { + view: view.id, + dir: '/', + } + } else { + // Legacy handling + params = { + view: typeof view.view === 'string' && view.view !== '' ? view.view : view.itemId, + dir: view.dir ? view.dir : '/' + } + } this._changeUrl(params.view, params.dir); OCA.Files.Sidebar.close(); - this.navigation.getActiveContainer().trigger(new $.Event('urlChanged', params)); + this.getCurrentAppContainer().trigger(new $.Event('urlChanged', params)); window._nc_event_bus.emit('files:navigation:changed') } }, @@ -327,7 +279,7 @@ */ _onDirectoryChanged: function(e) { if (e.dir && !e.changedThroughUrl) { - this._changeUrl(this.navigation.getActiveItem(), e.dir, e.fileId); + this._changeUrl(this.getActiveView(), e.dir, e.fileId); } }, @@ -336,7 +288,7 @@ */ _onAfterDirectoryChanged: function(e) { if (e.dir && e.fileId) { - this._changeUrl(this.navigation.getActiveItem(), e.dir, e.fileId); + this._changeUrl(this.getActiveView(), e.dir, e.fileId); } }, @@ -361,16 +313,20 @@ dir: '/', view: 'files' }, params); - var lastId = this.navigation.getActiveItem(); - if (!this.navigation.itemExists(params.view)) { + + var lastId = this.navigation.active; + if (!this.navigation.views.find(view => view.id === params.view)) { params.view = 'files'; } - this.navigation.setActiveItem(params.view, {silent: true}); - if (lastId !== this.navigation.getActiveItem()) { - this.navigation.getActiveContainer().trigger(new $.Event('show')); + + this.setActiveView(params.view, {silent: true}); + if (lastId !== this.getActiveView()) { + this.getCurrentAppContainer().trigger(new $.Event('show', params)); } - this.navigation.getActiveContainer().trigger(new $.Event('urlChanged', params)); + + this.getCurrentAppContainer().trigger(new $.Event('urlChanged', params)); window._nc_event_bus.emit('files:navigation:changed') + }, /** @@ -390,7 +346,7 @@ * Change the URL to point to the given dir and view */ _changeUrl: function(view, dir, fileId) { - var params = {dir: dir}; + var params = { dir: dir }; if (view !== 'files') { params.view = view; } else if (fileId) { @@ -401,9 +357,11 @@ if (currentParams.fileid !== params.fileid) { // if only fileid changed or was added, replace instead of push OC.Util.History.replaceState(this._makeUrlParams(params)); + return } } else { OC.Util.History.pushState(this._makeUrlParams(params)); + return } }, diff --git a/apps/files/js/favoritesfilelist.js b/apps/files/js/favoritesfilelist.js index 7ea41da8143..2c6b3c63e15 100644 --- a/apps/files/js/favoritesfilelist.js +++ b/apps/files/js/favoritesfilelist.js @@ -67,7 +67,7 @@ window.addEventListener('DOMContentLoaded', function() { reload: function() { this.showMask(); - if (this._reloadCall) { + if (this._reloadCall?.abort) { this._reloadCall.abort(); } diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 89fc3f7e9c5..a74cc79a83f 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -173,7 +173,8 @@ _filter: '', /** - * @type Backbone.Model + * @type UserConfig + * @see /apps/files/lib/Service/UserConfig.php */ _filesConfig: undefined, @@ -252,10 +253,7 @@ } else if (!_.isUndefined(OCA.Files) && !_.isUndefined(OCA.Files.App)) { this._filesConfig = OCA.Files.App.getFilesConfig(); } else { - this._filesConfig = new OC.Backbone.Model({ - 'showhidden': false, - 'cropimagepreviews': true - }); + this._filesConfig = OCP.InitialState.loadState('files', 'config', {}) } if (options.dragOptions) { @@ -281,26 +279,30 @@ this.$header = $el.find('.filelist-header'); this.$footer = $el.find('.filelist-footer'); - if (!_.isUndefined(this._filesConfig)) { - this._filesConfig.on('change:showhidden', function() { - var showHidden = this.get('showhidden'); - self.$el.toggleClass('hide-hidden-files', !showHidden); + // Legacy mapper for new vue components + window._nc_event_bus.subscribe('files:config:updated', ({ key, value }) => { + // Replace existing config with new one + Object.assign(this._filesConfig, { [key]: value }) + + if (key === 'show_hidden') { + self.$el.toggleClass('hide-hidden-files', !value); self.updateSelectionSummary(); - if (!showHidden) { - // hiding files could make the page too small, need to try rendering next page + // hiding files could make the page too small, need to try rendering next page + if (!value) { self._onScroll(); } - }); - - this._filesConfig.on('change:cropimagepreviews', function() { + } + if (key === 'crop_image_previews') { self.reload(); - }); + } + }) - this.$el.toggleClass('hide-hidden-files', !this._filesConfig.get('showhidden')); + var config = OCP.InitialState.loadState('files', 'config', {}) + if (config.show_hidden === false) { + this.$el.addClass('hide-hidden-files'); } - if (_.isUndefined(options.detailsViewEnabled) || options.detailsViewEnabled) { this._detailsView = new OCA.Files.DetailsView(); this._detailsView.$el.addClass('disappear'); @@ -413,7 +415,7 @@ this._setCurrentDir(options.dir || '/', false); } - if(options.openFile) { + if (options.openFile) { // Wait for some initialisation process to be over before triggering the default action. _.defer(() => { try { @@ -755,16 +757,13 @@ */ _onShow: function(e) { OCA.Files.App && OCA.Files.App.updateCurrentFileList(this); - if (this.shown) { - if (e.itemId === this.id) { - this._setCurrentDir('/', false); - } - // Only reload if we don't navigate to a different directory - if (typeof e.dir === 'undefined' || e.dir === this.getCurrentDirectory()) { - this.reload(); - } + if (e.itemId === this.id) { + this._setCurrentDir('/', false); + } + // Only reload if we don't navigate to a different directory + if (typeof e.dir === 'undefined' || e.dir === this.getCurrentDirectory()) { + this.reload(); } - this.shown = true; }, /** @@ -1407,7 +1406,7 @@ fileData, newTrs = [], isAllSelected = this.isAllSelected(), - showHidden = this._filesConfig.get('showhidden'); + showHidden = this._filesConfig.show_hidden; if (index >= this.files.length) { return false; @@ -2371,7 +2370,7 @@ * Images are cropped to a square by default. Append a=1 to the URL * if the user wants to see images with original aspect ratio. */ - urlSpec.a = this._filesConfig.get('cropimagepreviews') ? 0 : 1; + urlSpec.a = this._filesConfig.crop_image_previews ? 0 : 1; if (typeof urlSpec.fileId !== 'undefined') { delete urlSpec.file; @@ -3295,7 +3294,7 @@ this.$el.find('tfoot').append($tr); - return new OCA.Files.FileSummary($tr, {config: this._filesConfig}); + return new OCA.Files.FileSummary($tr, { config: this._filesConfig }); }, updateEmptyContent: function() { var permissions = this.getDirectoryPermissions(); @@ -3443,7 +3442,7 @@ var summary = this._selectionSummary.summary; var selection; - var showHidden = !!this._filesConfig.get('showhidden'); + var showHidden = !!this._filesConfig.show_hidden; if (summary.totalFiles === 0 && summary.totalDirs === 0) { this.$el.find('.column-name a.name>span:first').text(t('files','Name')); this.$el.find('.column-size a>span:first').text(t('files','Size')); diff --git a/apps/files/js/filesummary.js b/apps/files/js/filesummary.js index 54d038d86c6..00f13249ff3 100644 --- a/apps/files/js/filesummary.js +++ b/apps/files/js/filesummary.js @@ -36,10 +36,12 @@ this.$el = $tr; var filesConfig = options.config; if (filesConfig) { - this._showHidden = !!filesConfig.get('showhidden'); - filesConfig.on('change:showhidden', function() { - self._showHidden = !!this.get('showhidden'); - self.update(); + this._showHidden = !!filesConfig.show_hidden; + window._nc_event_bus.subscribe('files:config:updated', ({ key, value }) => { + if (key === 'show_hidden') { + self._showHidden = !!value; + self.update(); + } }); } this.clear(); diff --git a/apps/files/js/merged-index.json b/apps/files/js/merged-index.json index 478db35f6fb..01a46958d8b 100644 --- a/apps/files/js/merged-index.json +++ b/apps/files/js/merged-index.json @@ -19,7 +19,6 @@ "jquery.fileupload.js", "keyboardshortcuts.js", "mainfileinfodetailview.js", - "navigation.js", "newfilemenu.js", "operationprogressbar.js", "recentfilelist.js", diff --git a/apps/files/js/recentfilelist.js b/apps/files/js/recentfilelist.js index 3b7cd035f2a..c4de59b8465 100644 --- a/apps/files/js/recentfilelist.js +++ b/apps/files/js/recentfilelist.js @@ -72,7 +72,7 @@ window.addEventListener('DOMContentLoaded', function () { reload: function () { this.showMask(); - if (this._reloadCall) { + if (this._reloadCall?.abort) { this._reloadCall.abort(); } diff --git a/apps/files/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php index 2662f2d6e9b..01fe46bb877 100644 --- a/apps/files/lib/AppInfo/Application.php +++ b/apps/files/lib/AppInfo/Application.php @@ -47,6 +47,7 @@ use OCA\Files\Listener\LoadSidebarListener; use OCA\Files\Notification\Notifier; use OCA\Files\Search\FilesSearchProvider; use OCA\Files\Service\TagService; +use OCA\Files\Service\UserConfig; use OCP\Activity\IManager as IActivityManager; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; @@ -88,7 +89,8 @@ class Application extends App implements IBootstrap { $c->get(IPreview::class), $c->get(IShareManager::class), $c->get(IConfig::class), - $server->getUserFolder() + $server->getUserFolder(), + $c->get(UserConfig::class), ); }); @@ -172,7 +174,6 @@ class Application extends App implements IBootstrap { 'script' => 'simplelist.php', 'order' => 5, 'name' => $l10n->t('Favorites'), - 'expandedState' => 'show_Quick_Access' ]; }); } diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php index e29e81d6296..f2329fc384b 100644 --- a/apps/files/lib/Controller/ApiController.php +++ b/apps/files/lib/Controller/ApiController.php @@ -39,6 +39,7 @@ namespace OCA\Files\Controller; use OC\Files\Node\Node; use OCA\Files\Service\TagService; +use OCA\Files\Service\UserConfig; use OCP\AppFramework\Controller; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; @@ -61,18 +62,13 @@ use OCP\Share\IShare; * @package OCA\Files\Controller */ class ApiController extends Controller { - /** @var TagService */ - private $tagService; - /** @var IManager * */ - private $shareManager; - /** @var IPreview */ - private $previewManager; - /** @var IUserSession */ - private $userSession; - /** @var IConfig */ - private $config; - /** @var Folder */ - private $userFolder; + private TagService $tagService; + private IManager $shareManager; + private IPreview $previewManager; + private IUserSession $userSession; + private IConfig $config; + private Folder $userFolder; + private UserConfig $userConfig; /** * @param string $appName @@ -91,7 +87,8 @@ class ApiController extends Controller { IPreview $previewManager, IManager $shareManager, IConfig $config, - Folder $userFolder) { + Folder $userFolder, + UserConfig $userConfig) { parent::__construct($appName, $request); $this->userSession = $userSession; $this->tagService = $tagService; @@ -99,6 +96,7 @@ class ApiController extends Controller { $this->shareManager = $shareManager; $this->config = $config; $this->userFolder = $userFolder; + $this->userConfig = $userConfig; } /** @@ -283,16 +281,47 @@ class ApiController extends Controller { } /** + * Toggle default files user config + * + * @NoAdminRequired + * + * @param string $key + * @param string|bool $value + * @return JSONResponse + */ + public function setConfig(string $key, $value): JSONResponse { + try { + $this->userConfig->setConfig($key, (string)$value); + } catch (\InvalidArgumentException $e) { + return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_BAD_REQUEST); + } + + return new JSONResponse(['message' => 'ok', 'data' => ['key' => $key, 'value' => $value]]); + } + + + /** + * Get the user config + * + * @NoAdminRequired + * + * @return JSONResponse + */ + public function getConfigs(): JSONResponse { + return new JSONResponse(['message' => 'ok', 'data' => $this->userConfig->getConfigs()]); + } + + /** * Toggle default for showing/hiding hidden files * * @NoAdminRequired * - * @param bool $show + * @param bool $value * @return Response * @throws \OCP\PreConditionNotMetException */ - public function showHiddenFiles(bool $show): Response { - $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', $show ? '1' : '0'); + public function showHiddenFiles(bool $value): Response { + $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', $value ? '1' : '0'); return new Response(); } @@ -301,12 +330,12 @@ class ApiController extends Controller { * * @NoAdminRequired * - * @param bool $crop + * @param bool $value * @return Response * @throws \OCP\PreConditionNotMetException */ - public function cropImagePreviews(bool $crop): Response { - $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'crop_image_previews', $crop ? '1' : '0'); + public function cropImagePreviews(bool $value): Response { + $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', 'crop_image_previews', $value ? '1' : '0'); return new Response(); } @@ -346,18 +375,18 @@ class ApiController extends Controller { * @throws \OCP\PreConditionNotMetException */ public function toggleShowFolder(int $show, string $key): Response { - // ensure the edited key exists - $navItems = \OCA\Files\App::getNavigationManager()->getAll(); - foreach ($navItems as $item) { - // check if data is valid - if (($show === 0 || $show === 1) && isset($item['expandedState']) && $key === $item['expandedState']) { - $this->config->setUserValue($this->userSession->getUser()->getUID(), 'files', $key, (string)$show); - return new Response(); - } + if ($show !== 0 && $show !== 1) { + return new DataResponse([ + 'message' => 'Invalid show value. Only 0 and 1 are allowed.' + ], Http::STATUS_BAD_REQUEST); } - $response = new Response(); - $response->setStatus(Http::STATUS_FORBIDDEN); - return $response; + + $userId = $this->userSession->getUser()->getUID(); + + // Set the new value and return it + // Using a prefix prevents the user from setting arbitrary keys + $this->config->setUserValue($userId, 'files', 'show_' . $key, (string)$show); + return new JSONResponse([$key => $show]); } /** diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php index 1da9814d7e8..0594b63f56b 100644 --- a/apps/files/lib/Controller/ViewController.php +++ b/apps/files/lib/Controller/ViewController.php @@ -36,8 +36,10 @@ namespace OCA\Files\Controller; use OCA\Files\Activity\Helper; +use OCA\Files\AppInfo\Application; use OCA\Files\Event\LoadAdditionalScriptsEvent; use OCA\Files\Event\LoadSidebar; +use OCA\Files\Service\UserConfig; use OCA\Viewer\Event\LoadViewer; use OCP\App\IAppManager; use OCP\AppFramework\Controller; @@ -65,32 +67,18 @@ use OCP\Share\IManager; * @package OCA\Files\Controller */ class ViewController extends Controller { - /** @var string */ - protected $appName; - /** @var IRequest */ - protected $request; - /** @var IURLGenerator */ - protected $urlGenerator; - /** @var IL10N */ - protected $l10n; - /** @var IConfig */ - protected $config; - /** @var IEventDispatcher */ - protected $eventDispatcher; - /** @var IUserSession */ - protected $userSession; - /** @var IAppManager */ - protected $appManager; - /** @var IRootFolder */ - protected $rootFolder; - /** @var Helper */ - protected $activityHelper; - /** @var IInitialState */ - private $initialState; - /** @var ITemplateManager */ - private $templateManager; - /** @var IManager */ - private $shareManager; + private IURLGenerator $urlGenerator; + private IL10N $l10n; + private IConfig $config; + private IEventDispatcher $eventDispatcher; + private IUserSession $userSession; + private IAppManager $appManager; + private IRootFolder $rootFolder; + private Helper $activityHelper; + private IInitialState $initialState; + private ITemplateManager $templateManager; + private IManager $shareManager; + private UserConfig $userConfig; public function __construct(string $appName, IRequest $request, @@ -104,11 +92,10 @@ class ViewController extends Controller { Helper $activityHelper, IInitialState $initialState, ITemplateManager $templateManager, - IManager $shareManager + IManager $shareManager, + UserConfig $userConfig ) { parent::__construct($appName, $request); - $this->appName = $appName; - $this->request = $request; $this->urlGenerator = $urlGenerator; $this->l10n = $l10n; $this->config = $config; @@ -120,6 +107,7 @@ class ViewController extends Controller { $this->initialState = $initialState; $this->templateManager = $templateManager; $this->shareManager = $shareManager; + $this->userConfig = $userConfig; } /** @@ -186,6 +174,7 @@ class ViewController extends Controller { * @throws NotFoundException */ public function index($dir = '', $view = '', $fileid = null, $fileNotFound = false, $openfile = null) { + if ($fileid !== null && $dir === '') { try { return $this->redirectToFile($fileid); @@ -205,11 +194,11 @@ class ViewController extends Controller { // FIXME: Make non static $storageInfo = $this->getStorageInfo(); - $user = $this->userSession->getUser()->getUID(); + $userId = $this->userSession->getUser()->getUID(); // Get all the user favorites to create a submenu try { - $favElements = $this->activityHelper->getFavoriteFilePaths($this->userSession->getUser()->getUID()); + $favElements = $this->activityHelper->getFavoriteFilePaths($userId); } catch (\RuntimeException $e) { $favElements['folders'] = []; } @@ -222,20 +211,17 @@ class ViewController extends Controller { $favoritesSublistArray = []; $navBarPositionPosition = 6; - $currentCount = 0; foreach ($favElements['folders'] as $favElement) { - $link = $this->urlGenerator->linkToRoute('files.view.index', ['dir' => $favElement, 'view' => 'files']); - $sortingValue = ++$currentCount; $element = [ 'id' => str_replace('/', '-', $favElement), - 'view' => 'files', - 'href' => $link, 'dir' => $favElement, 'order' => $navBarPositionPosition, - 'folderPosition' => $sortingValue, 'name' => basename($favElement), - 'icon' => 'files', - 'quickaccesselement' => 'true' + 'icon' => 'folder', + 'params' => [ + 'view' => 'files', + 'dir' => $favElement, + ], ]; array_push($favoritesSublistArray, $element); @@ -248,11 +234,9 @@ class ViewController extends Controller { $navItems['favorites']['sublist'] = $favoritesSublistArray; $navItems['favorites']['classes'] = $collapseClasses; - // parse every menu and add the expandedState user value + // parse every menu and add the expanded user value foreach ($navItems as $key => $item) { - if (isset($item['expandedState'])) { - $navItems[$key]['defaultExpandedState'] = $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', $item['expandedState'], '0') === '1'; - } + $navItems[$key]['expanded'] = $this->config->getUserValue($userId, 'files', 'show_' . $item['id'], '0') === '1'; } $nav->assign('navigationItems', $navItems); @@ -267,10 +251,11 @@ class ViewController extends Controller { $nav->assign('quota', $storageInfo['quota']); $nav->assign('usage_relative', $storageInfo['relative']); - $nav->assign('webdav_url', \OCP\Util::linkToRemote('dav/files/' . rawurlencode($user))); - $contentItems = []; + $this->initialState->provideInitialState('navigation', $navItems); + $this->initialState->provideInitialState('config', $this->userConfig->getConfigs()); + // render the container content for every navigation item foreach ($navItems as $item) { $content = ''; @@ -314,12 +299,12 @@ class ViewController extends Controller { $params['ownerDisplayName'] = $storageInfo['ownerDisplayName'] ?? ''; $params['isPublic'] = false; $params['allowShareWithLink'] = $this->shareManager->shareApiAllowLinks() ? 'yes' : 'no'; - $params['defaultFileSorting'] = $this->config->getUserValue($user, 'files', 'file_sorting', 'name'); - $params['defaultFileSortingDirection'] = $this->config->getUserValue($user, 'files', 'file_sorting_direction', 'asc'); - $params['showgridview'] = $this->config->getUserValue($user, 'files', 'show_grid', false); - $showHidden = (bool) $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'show_hidden', false); + $params['defaultFileSorting'] = $this->config->getUserValue($userId, 'files', 'file_sorting', 'name'); + $params['defaultFileSortingDirection'] = $this->config->getUserValue($userId, 'files', 'file_sorting_direction', 'asc'); + $params['showgridview'] = $this->config->getUserValue($userId, 'files', 'show_grid', false); + $showHidden = (bool) $this->config->getUserValue($userId, 'files', 'show_hidden', false); $params['showHiddenFiles'] = $showHidden ? 1 : 0; - $cropImagePreviews = (bool) $this->config->getUserValue($this->userSession->getUser()->getUID(), 'files', 'crop_image_previews', true); + $cropImagePreviews = (bool) $this->config->getUserValue($userId, 'files', 'crop_image_previews', true); $params['cropImagePreviews'] = $cropImagePreviews ? 1 : 0; $params['fileNotFound'] = $fileNotFound ? 1 : 0; $params['appNavigation'] = $nav; @@ -327,7 +312,7 @@ class ViewController extends Controller { $params['hiddenFields'] = $event->getHiddenFields(); $response = new TemplateResponse( - $this->appName, + Application::APP_ID, 'index', $params ); diff --git a/apps/files/lib/Service/UserConfig.php b/apps/files/lib/Service/UserConfig.php new file mode 100644 index 00000000000..e405b02c07a --- /dev/null +++ b/apps/files/lib/Service/UserConfig.php @@ -0,0 +1,144 @@ +<?php +/** + * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +namespace OCA\Files\Service; + +use OCA\Files\AppInfo\Application; +use OCP\IConfig; +use OCP\IUser; +use OCP\IUserSession; + +class UserConfig { + const ALLOWED_CONFIGS = [ + [ + // Whether to crop the files previews or not in the files list + 'key' => 'crop_image_previews', + 'default' => true, + 'allowed' => [true, false], + ], + [ + // Whether to show the hidden files or not in the files list + 'key' => 'show_hidden', + 'default' => false, + 'allowed' => [true, false], + ], + ]; + + protected IConfig $config; + protected ?IUser $user = null; + + public function __construct(IConfig $config, IUserSession $userSession) { + $this->config = $config; + $this->user = $userSession->getUser(); + } + + /** + * Get the list of all allowed user config keys + * @return string[] + */ + public function getAllowedConfigKeys(): array { + return array_map(function($config) { + return $config['key']; + }, self::ALLOWED_CONFIGS); + } + + /** + * Get the list of allowed config values for a given key + * + * @param string $key a valid config key + * @return array + */ + private function getAllowedConfigValues(string $key): array { + foreach (self::ALLOWED_CONFIGS as $config) { + if ($config['key'] === $key) { + return $config['allowed']; + } + } + return []; + } + + /** + * Get the default config value for a given key + * + * @param string $key a valid config key + * @return string|bool + */ + private function getDefaultConfigValue(string $key) { + foreach (self::ALLOWED_CONFIGS as $config) { + if ($config['key'] === $key) { + return $config['default']; + } + } + return ''; + } + + /** + * Set a user config + * + * @param string $key + * @param string|bool $value + * @throws \Exception + * @throws \InvalidArgumentException + */ + public function setConfig(string $key, $value): void { + if ($this->user === null) { + throw new \Exception('No user logged in'); + } + + if (!in_array($key, $this->getAllowedConfigKeys())) { + throw new \InvalidArgumentException('Unknown config key'); + } + + if (!in_array($value, $this->getAllowedConfigValues($key))) { + throw new \InvalidArgumentException('Invalid config value'); + } + + if (is_bool($value)) { + $value = $value ? '1' : '0'; + } + + $this->config->setUserValue($this->user->getUID(), Application::APP_ID, $key, $value); + } + + /** + * Get the current user configs array + * + * @return array + */ + public function getConfigs(): array { + if ($this->user === null) { + throw new \Exception('No user logged in'); + } + + $userId = $this->user->getUID(); + $userConfigs = array_map(function(string $key) use ($userId) { + $value = $this->config->getUserValue($userId, Application::APP_ID, $key, $this->getDefaultConfigValue($key)); + // If the default is expected to be a boolean, we need to cast the value + if (is_bool($this->getDefaultConfigValue($key))) { + return $value === '1'; + } + return $value; + }, $this->getAllowedConfigKeys()); + + return array_combine($this->getAllowedConfigKeys(), $userConfigs); + } +} diff --git a/apps/files/src/components/Setting.vue b/apps/files/src/components/Setting.vue index b50a938cb52..c55a2841517 100644 --- a/apps/files/src/components/Setting.vue +++ b/apps/files/src/components/Setting.vue @@ -37,5 +37,3 @@ export default { }, } </script> -<style> -</style> diff --git a/apps/files/src/files-app-settings.js b/apps/files/src/files-app-settings.js deleted file mode 100644 index 491ea127ccd..00000000000 --- a/apps/files/src/files-app-settings.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev> - * @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com> - * - * @author Gary Kim <gary@garykim.dev> - * @author John Molakvoæ <skjnldsv@protonmail.com> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - */ - -import Vue from 'vue' -import Settings from './services/Settings' -import SettingsView from './views/Settings' -import Setting from './models/Setting' - -Vue.prototype.t = t - -// Init Files App Settings Service -if (!window.OCA.Files) { - window.OCA.Files = {} -} -Object.assign(window.OCA.Files, { Settings: new Settings() }) -Object.assign(window.OCA.Files.Settings, { Setting }) - -window.addEventListener('DOMContentLoaded', function() { - if (window.TESTING) { - return - } - // Init Vue app - // eslint-disable-next-line - new Vue({ - el: '#files-app-settings', - render: h => h(SettingsView), - }) - - const appSettingsHeader = document.getElementById('app-settings-header') - if (appSettingsHeader) { - appSettingsHeader.addEventListener('click', e => { - const opened = e.currentTarget.children[0].classList.contains('opened') - OCA.Files.Settings.settings.forEach(e => opened ? e.close() : e.open()) - }) - } -}) diff --git a/apps/files/src/legacy/navigationMapper.js b/apps/files/src/legacy/navigationMapper.js new file mode 100644 index 00000000000..764a7cb6cd9 --- /dev/null +++ b/apps/files/src/legacy/navigationMapper.js @@ -0,0 +1,55 @@ +/** + * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ + +import { loadState } from '@nextcloud/initial-state' +import logger from '../logger.js' + +/** + * Fetch and register the legacy files views + */ +export default function() { + const legacyViews = Object.values(loadState('files', 'navigation', {})) + + if (legacyViews.length > 0) { + logger.debug('Legacy files views detected. Processing...', legacyViews) + legacyViews.forEach(view => { + registerLegacyView(view) + if (view.sublist) { + view.sublist.forEach(subview => registerLegacyView({ ...subview, parent: view.id })) + } + }) + } +} + +const registerLegacyView = function({ id, name, order, icon, parent, classes = '', expanded, params }) { + OCP.Files.Navigation.register({ + id, + name, + order, + params, + parent, + expanded: expanded === true, + iconClass: icon ? `icon-${icon}` : 'nav-icon-' + id, + legacy: true, + sticky: classes.includes('pinned'), + }) +} diff --git a/apps/files/src/logger.js b/apps/files/src/logger.js index 0005ee13cb4..39283bd331d 100644 --- a/apps/files/src/logger.js +++ b/apps/files/src/logger.js @@ -1,8 +1,7 @@ /** - * @copyright 2019 Christoph Wurst <christoph@winzerhof-wurst.at> + * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com> * - * @author Christoph Wurst <christoph@winzerhof-wurst.at> - * @author Roeland Jago Douma <roeland@famdouma.nl> + * @author John Molakvoæ <skjnldsv@protonmail.com> * * @license AGPL-3.0-or-later * @@ -20,20 +19,9 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * */ - -import { getCurrentUser } from '@nextcloud/auth' import { getLoggerBuilder } from '@nextcloud/logger' -const getLogger = user => { - if (user === null) { - return getLoggerBuilder() - .setApp('files') - .build() - } - return getLoggerBuilder() - .setApp('files') - .setUid(user.uid) - .build() -} - -export default getLogger(getCurrentUser()) +export default getLoggerBuilder() + .setApp('files') + .detectUser() + .build() diff --git a/apps/files/src/main.js b/apps/files/src/main.js index a979822bdc4..3099a4c619c 100644 --- a/apps/files/src/main.js +++ b/apps/files/src/main.js @@ -1,3 +1,39 @@ -import './files-app-settings' -import './templates' -import './legacy/filelistSearch' +import './templates.js' +import './legacy/filelistSearch.js' +import processLegacyFilesViews from './legacy/navigationMapper.js' + +import Vue from 'vue' +import NavigationService from './services/Navigation.ts' +import NavigationView from './views/Navigation.vue' + +import SettingsService from './services/Settings.js' +import SettingsModel from './models/Setting.js' + +import router from './router/router.js' + +// Init private and public Files namespace +window.OCA.Files = window.OCA.Files ?? {} +window.OCP.Files = window.OCP.Files ?? {} + +// Init Navigation Service +const Navigation = new NavigationService() +Object.assign(window.OCP.Files, { Navigation }) + +// Init Files App Settings Service +const Settings = new SettingsService() +Object.assign(window.OCA.Files, { Settings }) +Object.assign(window.OCA.Files.Settings, { Setting: SettingsModel }) + +// Init Navigation View +const View = Vue.extend(NavigationView) +const FilesNavigationRoot = new View({ + name: 'FilesNavigationRoot', + propsData: { + Navigation, + }, + router, +}) +FilesNavigationRoot.$mount('#app-navigation-files') + +// Init legacy files views +processLegacyFilesViews() diff --git a/apps/files/src/router/router.js b/apps/files/src/router/router.js new file mode 100644 index 00000000000..cf5e5ec5ea8 --- /dev/null +++ b/apps/files/src/router/router.js @@ -0,0 +1,57 @@ +/** + * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +import Vue from 'vue' +import Router from 'vue-router' +import { generateUrl } from '@nextcloud/router' +import { stringify } from 'query-string' + +Vue.use(Router) + +const router = new Router({ + mode: 'history', + + // if index.php is in the url AND we got this far, then it's working: + // let's keep using index.php in the url + base: generateUrl('/apps/files', ''), + linkActiveClass: 'active', + + routes: [ + { + path: '/', + // Pretending we're using the default view + alias: '/files', + }, + { + path: '/:view/:fileid?', + name: 'filelist', + props: true, + }, + ], + + // Custom stringifyQuery to prevent encoding of slashes in the url + stringifyQuery(query) { + const result = stringify(query).replace(/%2F/gmi, '/') + return result ? ('?' + result) : '' + }, +}) + +export default router diff --git a/apps/files/src/services/Navigation.ts b/apps/files/src/services/Navigation.ts new file mode 100644 index 00000000000..e3286c79a88 --- /dev/null +++ b/apps/files/src/services/Navigation.ts @@ -0,0 +1,217 @@ +/** + * @copyright Copyright (c) 2022 John Molakvoæ <skjnldsv@protonmail.com> + * + * @author John Molakvoæ <skjnldsv@protonmail.com> + * + * @license AGPL-3.0-or-later + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + */ +import type Node from '@nextcloud/files/dist/files/node' +import isSvg from 'is-svg' + +import logger from '../logger' + +export interface Column { + /** Unique column ID */ + id: string + /** Translated column title */ + title: string + /** Property key from Node main or additional attributes. + Will be used if no custom sort function is provided. + Sorting will be done by localCompare */ + property: string + /** Special function used to sort Nodes between them */ + sortFunction?: (nodeA: Node, nodeB: Node) => number; + /** Custom summary of the column to display at the end of the list. + Will not be displayed if nothing is provided */ + summary?: (node: Node[]) => string +} + +export interface Navigation { + /** Unique view ID */ + id: string + /** Translated view name */ + name: string + /** Method return the content of the provided path */ + getFiles: (path: string) => Node[] + /** The view icon as an inline svg */ + icon: string + /** The view order */ + order: number + /** This view column(s). Name and actions are + by default always included */ + columns?: Column[] + /** The empty view element to render your empty content into */ + emptyView?: (div: HTMLDivElement) => void + /** The parent unique ID */ + parent?: string + /** This view is sticky (sent at the bottom) */ + sticky?: boolean + /** This view has children and is expanded or not */ + expanded?: boolean + + /** + * This view is sticky a legacy view. + * Here until all the views are migrated to Vue. + * @deprecated It will be removed in a near future + */ + legacy?: boolean + /** + * An icon class. + * @deprecated It will be removed in a near future + */ + iconClass?: string +} + +export default class { + + private _views: Navigation[] = [] + private _currentView: Navigation | null = null + + constructor() { + logger.debug('Navigation service initialized') + } + + register(view: Navigation) { + try { + isValidNavigation(view) + isUniqueNavigation(view, this._views) + } catch (e) { + if (e instanceof Error) { + logger.error(e.message, { view }) + } + throw e + } + + if (view.legacy) { + logger.warn('Legacy view detected, please migrate to Vue') + } + + if (view.iconClass) { + view.legacy = true + } + + this._views.push(view) + } + + get views(): Navigation[] { + return this._views + } + + setActive(view: Navigation | null) { + this._currentView = view + } + + get active(): Navigation | null { + return this._currentView + } + +} + +/** + * Make sure the given view is unique + * and not already registered. + */ +const isUniqueNavigation = function(view: Navigation, views: Navigation[]): boolean { + if (views.find(search => search.id === view.id)) { + throw new Error(`Navigation id ${view.id} is already registered`) + } + return true +} + +/** + * Typescript cannot validate an interface. + * Please keep in sync with the Navigation interface requirements. + */ +const isValidNavigation = function(view: Navigation): boolean { + if (!view.id || typeof view.id !== 'string') { + throw new Error('Navigation id is required and must be a string') + } + + if (!view.name || typeof view.name !== 'string') { + throw new Error('Navigation name is required and must be a string') + } + + /** + * Legacy handle their content and icon differently + * TODO: remove when support for legacy views is removed + */ + if (!view.legacy) { + if (!view.getFiles || typeof view.getFiles !== 'function') { + throw new Error('Navigation getFiles is required and must be a function') + } + + if (!view.icon || typeof view.icon !== 'string' || !isSvg(view.icon)) { + throw new Error('Navigation icon is required and must be a valid svg string') + } + } + + if (!('order' in view) || typeof view.order !== 'number') { + throw new Error('Navigation order is required and must be a number') + } + + // Optional properties + if (view.columns) { + view.columns.forEach(isValidColumn) + } + + if (view.emptyView && typeof view.emptyView !== 'function') { + throw new Error('Navigation emptyView must be a function') + } + + if (view.parent && typeof view.parent !== 'string') { + throw new Error('Navigation parent must be a string') + } + + if ('sticky' in view && typeof view.sticky !== 'boolean') { + throw new Error('Navigation sticky must be a boolean') + } + + if ('expanded' in view && typeof view.expanded !== 'boolean') { + throw new Error('Navigation expanded must be a boolean') + } + + return true +} + +/** + * Typescript cannot validate an interface. + * Please keep in sync with the Column interface requirements. + */ +const isValidColumn = function(column: Column): boolean { + if (!column.id || typeof column.id !== 'string') { + throw new Error('Column id is required') + } + + if (!column.title || typeof column.title !== 'string') { + throw new Error('Column title is required') + } + + if (!column.property || typeof column.property !== 'string') { + throw new Error('Column property is required') + } + + // Optional properties + if (column.sortFunction && typeof column.sortFunction !== 'function') { + throw new Error('Column sortFunction must be a function') + } + + if (column.summary && typeof column.summary !== 'function') { + throw new Error('Column summary must be a function') + } + + return true +} diff --git a/apps/files/src/views/Navigation.cy.ts b/apps/files/src/views/Navigation.cy.ts new file mode 100644 index 00000000000..65c5d8938a9 --- /dev/null +++ b/apps/files/src/views/Navigation.cy.ts @@ -0,0 +1,116 @@ +/* eslint-disable import/first */ +import FolderSvg from '@mdi/svg/svg/folder.svg' +import ShareSvg from '@mdi/svg/svg/share-variant.svg' + +import NavigationService from '../services/Navigation' +import NavigationView from './Navigation.vue' +import router from '../router/router.js' + +const Navigation = new NavigationService() + +describe('Navigation renders', () => { + it('renders', () => { + cy.mount(NavigationView, { + propsData: { + Navigation, + }, + }) + + cy.get('[data-cy-files-navigation]').should('be.visible') + cy.get('[data-cy-files-navigation-settings-button]').should('be.visible') + }) +}) + +describe('Navigation API', () => { + it('Check API entries rendering', () => { + Navigation.register({ + id: 'files', + name: 'Files', + getFiles: () => [], + icon: FolderSvg, + order: 1, + }) + + cy.mount(NavigationView, { + propsData: { + Navigation, + }, + router, + }) + + cy.get('[data-cy-files-navigation]').should('be.visible') + cy.get('[data-cy-files-navigation-item]').should('have.length', 1) + cy.get('[data-cy-files-navigation-item="files"]').should('be.visible') + cy.get('[data-cy-files-navigation-item="files"]').should('contain.text', 'Files') + }) + + it('Adds a new entry and render', () => { + Navigation.register({ + id: 'sharing', + name: 'Sharing', + getFiles: () => [], + icon: ShareSvg, + order: 2, + }) + + cy.mount(NavigationView, { + propsData: { + Navigation, + }, + router, + }) + + cy.get('[data-cy-files-navigation]').should('be.visible') + cy.get('[data-cy-files-navigation-item]').should('have.length', 2) + cy.get('[data-cy-files-navigation-item="sharing"]').should('be.visible') + cy.get('[data-cy-files-navigation-item="sharing"]').should('contain.text', 'Sharing') + }) + + it('Adds a new children, render and open menu', () => { + Navigation.register({ + id: 'sharingin', + name: 'Shared with me', + getFiles: () => [], + parent: 'sharing', + icon: ShareSvg, + order: 1, + }) + + cy.mount(NavigationView, { + propsData: { + Navigation, + }, + router, + }) + + cy.get('[data-cy-files-navigation]').should('be.visible') + cy.get('[data-cy-files-navigation-item]').should('have.length', 3) + + // Intercept collapse preference request + cy.intercept('POST', '*/apps/files/api/v1/toggleShowFolder/*', { + statusCode: 200, + }).as('toggleShowFolder') + + // Toggle the sharing entry children + cy.get('[data-cy-files-navigation-item="sharing"] button.icon-collapse').should('exist') + cy.get('[data-cy-files-navigation-item="sharing"] button.icon-collapse').click({ force: true }) + cy.wait('@toggleShowFolder') + + // Validate children + cy.get('[data-cy-files-navigation-item="sharingin"]').should('be.visible') + cy.get('[data-cy-files-navigation-item="sharingin"]').should('contain.text', 'Shared with me') + + }) + + it('Throws when adding a duplicate entry', () => { + expect(() => { + Navigation.register({ + id: 'files', + name: 'Files', + getFiles: () => [], + icon: FolderSvg, + order: 1, + }) + }).to.throw('Navigation id files is already registered') + }) +}) diff --git a/apps/files/src/views/Navigation.vue b/apps/files/src/views/Navigation.vue new file mode 100644 index 00000000000..05fc7cdacd2 --- /dev/null +++ b/apps/files/src/views/Navigation.vue @@ -0,0 +1,264 @@ +<!-- + - @copyright Copyright (c) 2019 Gary Kim <gary@garykim.dev> + - + - @author Gary Kim <gary@garykim.dev> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + - + --> +<template> + <NcAppNavigation data-cy-files-navigation> + <template #list> + <NcAppNavigationItem v-for="view in parentViews" + :key="view.id" + :allow-collapse="true" + :data-cy-files-navigation-item="view.id" + :icon="view.iconClass" + :open="view.expanded" + :pinned="view.sticky" + :title="view.name" + :to="generateToNavigation(view)" + @update:open="onToggleExpand(view)"> + <NcAppNavigationItem v-for="child in childViews[view.id]" + :key="child.id" + :data-cy-files-navigation-item="child.id" + :exact="true" + :icon="child.iconClass" + :title="child.name" + :to="generateToNavigation(child)" /> + </NcAppNavigationItem> + </template> + + <!-- Settings toggle --> + <template #footer> + <ul class="app-navigation-entry__settings"> + <NcAppNavigationItem :aria-label="t('files', 'Open the files app settings')" + :title="t('files', 'Files settings')" + data-cy-files-navigation-settings-button + @click.prevent.stop="openSettings"> + <Cog slot="icon" :size="20" /> + </NcAppNavigationItem> + </ul> + </template> + + <!-- Settings modal--> + <SettingsModal :open="settingsOpened" + data-cy-files-navigation-settings + @close="onSettingsClose" /> + </NcAppNavigation> +</template> + +<script> +import { emit, subscribe } from '@nextcloud/event-bus' +import { generateUrl } from '@nextcloud/router' +import axios from '@nextcloud/axios' +import Cog from 'vue-material-design-icons/Cog.vue' +import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js' +import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js' + +import logger from '../logger.js' +import Navigation from '../services/Navigation.ts' +import SettingsModal from './Settings.vue' + +import { translate } from '@nextcloud/l10n' + +export default { + name: 'Navigation', + + components: { + Cog, + NcAppNavigation, + NcAppNavigationItem, + SettingsModal, + }, + + props: { + // eslint-disable-next-line vue/prop-name-casing + Navigation: { + type: Navigation, + required: true, + }, + }, + + data() { + return { + settingsOpened: false, + } + }, + + computed: { + currentViewId() { + return this.$route?.params?.view || 'files' + }, + currentView() { + return this.views.find(view => view.id === this.currentViewId) + }, + + /** @return {Navigation[]} */ + views() { + return this.Navigation.views + }, + parentViews() { + return this.views + // filter child views + .filter(view => !view.parent) + // sort views by order + .sort((a, b) => { + return a.order - b.order + }) + }, + childViews() { + return this.views + // filter parent views + .filter(view => !!view.parent) + // create a map of parents and their children + .reduce((list, view) => { + list[view.parent] = [...(list[view.parent] || []), view] + // Sort children by order + list[view.parent].sort((a, b) => { + return a.order - b.order + }) + return list + }, {}) + }, + }, + + watch: { + currentView(view, oldView) { + logger.debug('View changed', { id: view.id, view }) + this.showView(view, oldView) + }, + }, + + beforeMount() { + if (this.currentView) { + logger.debug('Navigation mounted. Showing requested view', { view: this.currentView }) + this.showView(this.currentView) + } + + subscribe('files:legacy-navigation:changed', this.onLegacyNavigationChanged) + }, + + methods: { + /** + * @param {Navigation} view the new active view + * @param {Navigation} oldView the old active view + */ + showView(view, oldView) { + // Closing any opened sidebar + window?.OCA?.Files?.Sidebar?.close?.() + + if (view.legacy) { + const newAppContent = document.querySelector('#app-content #app-content-' + this.currentView.id + '.viewcontainer') + document.querySelectorAll('#app-content .viewcontainer').forEach(el => { + el.classList.add('hidden') + }) + newAppContent.classList.remove('hidden') + + // Triggering legacy navigation events + const { dir = '/' } = OC.Util.History.parseUrlQuery() + const params = { itemId: view.id, dir } + + logger.debug('Triggering legacy navigation event', params) + window.jQuery(newAppContent).trigger(new window.jQuery.Event('show', params)) + window.jQuery(newAppContent).trigger(new window.jQuery.Event('urlChanged', params)) + + } + + this.Navigation.setActive(view) + emit('files:navigation:changed', view) + }, + + /** + * Coming from the legacy files app. + * TODO: remove when all views are migrated. + * + * @param {Navigation} view the new active view + */ + onLegacyNavigationChanged({ id } = { id: 'files' }) { + const view = this.Navigation.views.find(view => view.id === id) + if (view && view.legacy && view.id !== this.currentView.id) { + // Force update the current route as the request comes + // from the legacy files app router + this.$router.replace({ ...this.$route, params: { view: view.id } }) + this.Navigation.setActive(view) + this.showView(view) + } + }, + + /** + * Expand/collapse a a view with children and permanently + * save this setting in the server. + * + * @param {Navigation} view the view to toggle + */ + onToggleExpand(view) { + // Invert state + view.expanded = !view.expanded + axios.post(generateUrl(`/apps/files/api/v1/toggleShowFolder/${view.id}`), { show: view.expanded }) + }, + + /** + * Generate the route to a view + * @param {Navigation} view the view to toggle + */ + generateToNavigation(view) { + if (view.params) { + const { dir, fileid } = view.params + return { name: 'filelist', params: view.params, query: { dir, fileid } } + } + return { name: 'filelist', params: { view: view.id } } + }, + + /** + * Open the settings modal + */ + openSettings() { + this.settingsOpened = true + }, + + /** + * Close the settings modal + */ + onSettingsClose() { + this.settingsOpened = false + }, + + t: translate, + }, +} +</script> + +<style scoped lang="scss"> +// TODO: remove when https://github.com/nextcloud/nextcloud-vue/pull/3539 is in +.app-navigation::v-deep .app-navigation-entry-icon { + background-repeat: no-repeat; + background-position: center; +} + +.app-navigation > ul.app-navigation__list { + // Use flex gap value for more elegant spacing + padding-bottom: var(--default-grid-baseline, 4px); +} + +.app-navigation-entry__settings { + height: auto !important; + overflow: hidden !important; + padding-top: 0 !important; + // Prevent shrinking or growing + flex: 0 0 auto; +} +</style> diff --git a/apps/files/src/views/Settings.vue b/apps/files/src/views/Settings.vue index 5d2aff2f49a..9a63fea4924 100644 --- a/apps/files/src/views/Settings.vue +++ b/apps/files/src/views/Settings.vue @@ -20,26 +20,150 @@ - --> <template> - <div id="files-app-extra-settings"> - <template v-for="setting in settings"> - <Setting :key="setting.name" :el="setting.el" /> - </template> - </div> + <NcAppSettingsDialog :open="open" + :show-navigation="true" + :title="t('files', 'Files settings')" + @update:open="onClose"> + <!-- Settings API--> + <NcAppSettingsSection id="settings" :title="t('files', 'Files settings')"> + <NcCheckboxRadioSwitch :checked.sync="show_hidden" + @update:checked="setConfig('show_hidden', $event)"> + {{ t('files', 'Show hidden files') }} + </NcCheckboxRadioSwitch> + <NcCheckboxRadioSwitch :checked.sync="crop_image_previews" + @update:checked="setConfig('crop_image_previews', $event)"> + {{ t('files', 'Crop image previews') }} + </NcCheckboxRadioSwitch> + </NcAppSettingsSection> + + <!-- Settings API--> + <NcAppSettingsSection v-if="settings.length !== 0" + id="more-settings" + :title="t('files', 'Additional settings')"> + <template v-for="setting in settings"> + <Setting :key="setting.name" :el="setting.el" /> + </template> + </NcAppSettingsSection> + + <!-- Webdav URL--> + <NcAppSettingsSection id="webdav" :title="t('files', 'Webdav')"> + <NcInputField id="webdav-url-input" + :show-trailing-button="true" + :success="webdavUrlCopied" + :trailing-button-label="t('files', 'Copy to clipboard')" + :value="webdavUrl" + readonly="readonly" + type="url" + @focus="$event.target.select()" + @trailing-button-click="copyCloudId"> + <template #trailing-button-icon> + <Clipboard :size="20" /> + </template> + </NcInputField> + <em> + <a :href="webdavDocs" target="_blank" rel="noreferrer noopener"> + {{ t('files', 'Use this address to access your Files via WebDAV') }} ↗ + </a> + </em> + </NcAppSettingsSection> + </NcAppSettingsDialog> </template> <script> -import Setting from '../components/Setting' +import NcAppSettingsDialog from '@nextcloud/vue/dist/Components/NcAppSettingsDialog.js' +import NcAppSettingsSection from '@nextcloud/vue/dist/Components/NcAppSettingsSection.js' +import NcCheckboxRadioSwitch from '@nextcloud/vue/dist/Components/NcCheckboxRadioSwitch.js' +import Clipboard from 'vue-material-design-icons/Clipboard.vue' +import NcInputField from '@nextcloud/vue/dist/Components/NcInputField' +import Setting from '../components/Setting.vue' + +import { emit } from '@nextcloud/event-bus' +import { generateRemoteUrl, generateUrl } from '@nextcloud/router' +import { getCurrentUser } from '@nextcloud/auth' +import { loadState } from '@nextcloud/initial-state' +import { showError, showSuccess } from '@nextcloud/dialogs' +import { translate } from '@nextcloud/l10n' +import axios from '@nextcloud/axios' + +const userConfig = loadState('files', 'config', { + show_hidden: false, + crop_image_previews: true, +}) export default { name: 'Settings', components: { + Clipboard, + NcAppSettingsDialog, + NcAppSettingsSection, + NcCheckboxRadioSwitch, + NcInputField, Setting, }, + + props: { + open: { + type: Boolean, + default: false, + }, + }, + data() { return { - settings: OCA.Files.Settings.settings, + + ...userConfig, + + // Settings API + settings: window.OCA?.Files?.Settings?.settings || [], + + // Webdav infos + webdavUrl: generateRemoteUrl('dav/files/' + encodeURIComponent(getCurrentUser()?.uid)), + webdavDocs: 'https://docs.nextcloud.com/server/stable/go.php?to=user-webdav', + webdavUrlCopied: false, } }, + + beforeMount() { + // Update the settings API entries state + this.settings.forEach(setting => setting.open()) + }, + + beforeDestroy() { + // Update the settings API entries state + this.settings.forEach(setting => setting.close()) + }, + + methods: { + onClose() { + this.$emit('close') + }, + + setConfig(key, value) { + emit('files:config:updated', { key, value }) + axios.post(generateUrl('/apps/files/api/v1/config/' + key), { + value, + }) + }, + + async copyCloudId() { + document.querySelector('input#webdav-url-input').select() + + if (!navigator.clipboard) { + // Clipboard API not available + showError(t('files', 'Clipboard is not available')) + return + } + + await navigator.clipboard.writeText(this.webdavUrl) + this.webdavUrlCopied = true + showSuccess(t('files', 'Webdav URL copied to clipboard')) + setTimeout(() => { + this.webdavUrlCopied = false + }, 5000) + }, + + t: translate, + }, } </script> diff --git a/apps/files/src/views/Sidebar.vue b/apps/files/src/views/Sidebar.vue index 4c29da59708..1fb60f7fc39 100644 --- a/apps/files/src/views/Sidebar.vue +++ b/apps/files/src/views/Sidebar.vue @@ -117,6 +117,7 @@ export default { fileInfo: null, starLoading: false, isFullScreen: false, + hasLowHeight: false, } }, @@ -231,7 +232,7 @@ export default { 'app-sidebar--has-preview': this.fileInfo.hasPreview && !this.isFullScreen, 'app-sidebar--full': this.isFullScreen, }, - compact: !this.fileInfo.hasPreview || this.isFullScreen, + compact: this.hasLowHeight || !this.fileInfo.hasPreview || this.isFullScreen, loading: this.loading, starred: this.fileInfo.isFavourited, subtitle: this.subtitle, @@ -489,6 +490,16 @@ export default { handleClosed() { emit('files:sidebar:closed') }, + handleWindowResize() { + this.hasLowHeight = document.documentElement.clientHeight < 1024 + }, + }, + created() { + window.addEventListener('resize', this.handleWindowResize) + this.handleWindowResize() + }, + beforeDestroy() { + window.removeEventListener('resize', this.handleWindowResize) }, } </script> diff --git a/apps/files/templates/appnavigation.php b/apps/files/templates/appnavigation.php index 5684c3d0d32..9da3f764a7f 100644 --- a/apps/files/templates/appnavigation.php +++ b/apps/files/templates/appnavigation.php @@ -1,4 +1,5 @@ -<div id="app-navigation" role="navigation"> +<div id="app-navigation-files" role="navigation"></div> +<div class="hidden"> <ul class="with-icon" tabindex="0"> <?php diff --git a/apps/files/templates/index.php b/apps/files/templates/index.php index 016e6f32c70..80eca84ed65 100644 --- a/apps/files/templates/index.php +++ b/apps/files/templates/index.php @@ -8,6 +8,10 @@ <label id="view-toggle" for="showgridview" tabindex="0" class="button <?php p($_['showgridview'] ? 'icon-toggle-filelist' : 'icon-toggle-pictures') ?>" title="<?php p($_['showgridview'] ? $l->t('Show list view') : $l->t('Show grid view'))?>"></label> + <!-- New files vue container --> + <div id="app-content-vue" class="hidden"></div> + + <!-- Legacy views --> <?php foreach ($_['appContents'] as $content) { ?> <div id="app-content-<?php p($content['id']) ?>" class="hidden viewcontainer"> <?php print_unescaped($content['content']) ?> diff --git a/apps/files/tests/Controller/ApiControllerTest.php b/apps/files/tests/Controller/ApiControllerTest.php index 64c70fb2de6..6df3f46c5a9 100644 --- a/apps/files/tests/Controller/ApiControllerTest.php +++ b/apps/files/tests/Controller/ApiControllerTest.php @@ -28,6 +28,7 @@ namespace OCA\Files\Controller; use OCA\Files\Service\TagService; +use OCA\Files\Service\UserConfig; use OCP\AppFramework\Http; use OCP\AppFramework\Http\DataResponse; use OCP\Files\File; @@ -67,6 +68,8 @@ class ApiControllerTest extends TestCase { private $config; /** @var Folder|\PHPUnit\Framework\MockObject\MockObject */ private $userFolder; + /** @var UserConfig|\PHPUnit\Framework\MockObject\MockObject */ + private $userConfig; protected function setUp(): void { parent::setUp(); @@ -95,6 +98,7 @@ class ApiControllerTest extends TestCase { $this->userFolder = $this->getMockBuilder(Folder::class) ->disableOriginalConstructor() ->getMock(); + $this->userConfig = $this->createMock(UserConfig::class); $this->apiController = new ApiController( $this->appName, @@ -104,7 +108,8 @@ class ApiControllerTest extends TestCase { $this->preview, $this->shareManager, $this->config, - $this->userFolder + $this->userFolder, + $this->userConfig ); } diff --git a/apps/files/tests/Controller/ViewControllerTest.php b/apps/files/tests/Controller/ViewControllerTest.php index 38f3670d4ca..6ab09011e1f 100644 --- a/apps/files/tests/Controller/ViewControllerTest.php +++ b/apps/files/tests/Controller/ViewControllerTest.php @@ -34,6 +34,7 @@ namespace OCA\Files\Tests\Controller; use OCA\Files\Activity\Helper; use OCA\Files\Controller\ViewController; +use OCA\Files\Service\UserConfig; use OCP\App\IAppManager; use OCP\AppFramework\Http; use OCP\AppFramework\Services\IInitialState; @@ -87,6 +88,8 @@ class ViewControllerTest extends TestCase { private $templateManager; /** @var IManager|\PHPUnit\Framework\MockObject\MockObject */ private $shareManager; + /** @var UserConfig|\PHPUnit\Framework\MockObject\MockObject */ + private $userConfig; protected function setUp(): void { parent::setUp(); @@ -109,6 +112,7 @@ class ViewControllerTest extends TestCase { $this->initialState = $this->createMock(IInitialState::class); $this->templateManager = $this->createMock(ITemplateManager::class); $this->shareManager = $this->createMock(IManager::class); + $this->userConfig = $this->createMock(UserConfig::class); $this->viewController = $this->getMockBuilder('\OCA\Files\Controller\ViewController') ->setConstructorArgs([ 'files', @@ -124,6 +128,7 @@ class ViewControllerTest extends TestCase { $this->initialState, $this->templateManager, $this->shareManager, + $this->userConfig, ]) ->setMethods([ 'getStorageInfo', @@ -166,7 +171,6 @@ class ViewControllerTest extends TestCase { $nav->assign('usage', '123 B'); $nav->assign('quota', 100); $nav->assign('total_space', '100 B'); - $nav->assign('webdav_url', 'http://localhost/remote.php/dav/files/testuser1/'); $nav->assign('navigationItems', [ 'files' => [ 'id' => 'files', @@ -178,6 +182,7 @@ class ViewControllerTest extends TestCase { 'icon' => '', 'type' => 'link', 'classes' => '', + 'expanded' => false, 'unread' => 0, ], 'recent' => [ @@ -190,6 +195,7 @@ class ViewControllerTest extends TestCase { 'icon' => '', 'type' => 'link', 'classes' => '', + 'expanded' => false, 'unread' => 0, ], 'favorites' => [ @@ -205,51 +211,50 @@ class ViewControllerTest extends TestCase { 'sublist' => [ [ 'id' => '-test1', - 'view' => 'files', - 'href' => '', 'dir' => '/test1', 'order' => 6, - 'folderPosition' => 1, 'name' => 'test1', - 'icon' => 'files', - 'quickaccesselement' => 'true', + 'icon' => 'folder', + 'params' => [ + 'view' => 'files', + 'dir' => '/test1', + ], ], [ 'name' => 'test2', 'id' => '-test2-', - 'view' => 'files', - 'href' => '', 'dir' => '/test2/', 'order' => 7, - 'folderPosition' => 2, - 'icon' => 'files', - 'quickaccesselement' => 'true', + 'icon' => 'folder', + 'params' => [ + 'view' => 'files', + 'dir' => '/test2/', + ], ], [ 'name' => 'sub4', 'id' => '-test3-sub4', - 'view' => 'files', - 'href' => '', 'dir' => '/test3/sub4', 'order' => 8, - 'folderPosition' => 3, - 'icon' => 'files', - 'quickaccesselement' => 'true', + 'icon' => 'folder', + 'params' => [ + 'view' => 'files', + 'dir' => '/test3/sub4', + ], ], [ 'name' => 'sub6', 'id' => '-test5-sub6-', - 'view' => 'files', - 'href' => '', 'dir' => '/test5/sub6/', 'order' => 9, - 'folderPosition' => 4, - 'icon' => 'files', - 'quickaccesselement' => 'true', + 'icon' => 'folder', + 'params' => [ + 'view' => 'files', + 'dir' => '/test5/sub6/', + ], ], ], - 'defaultExpandedState' => false, - 'expandedState' => 'show_Quick_Access', + 'expanded' => false, 'unread' => 0, ], 'systemtagsfilter' => [ @@ -262,6 +267,7 @@ class ViewControllerTest extends TestCase { 'icon' => '', 'type' => 'link', 'classes' => '', + 'expanded' => false, 'unread' => 0, ], 'trashbin' => [ @@ -274,6 +280,7 @@ class ViewControllerTest extends TestCase { 'icon' => '', 'type' => 'link', 'classes' => 'pinned', + 'expanded' => false, 'unread' => 0, ], 'shareoverview' => [ @@ -323,8 +330,7 @@ class ViewControllerTest extends TestCase { 'active' => false, 'icon' => '', 'type' => 'link', - 'expandedState' => 'show_sharing_menu', - 'defaultExpandedState' => false, + 'expanded' => false, 'unread' => 0, ] ]); @@ -407,7 +413,7 @@ class ViewControllerTest extends TestCase { ], ], 'hiddenFields' => [], - 'showgridview' => false + 'showgridview' => null ] ); $policy = new Http\ContentSecurityPolicy(); diff --git a/apps/files/tests/js/appSpec.js b/apps/files/tests/js/appSpec.js deleted file mode 100644 index d5c793c4d24..00000000000 --- a/apps/files/tests/js/appSpec.js +++ /dev/null @@ -1,244 +0,0 @@ -/** -* @copyright 2014 Vincent Petry <pvince81@owncloud.com> - * - * @author John Molakvoæ <skjnldsv@protonmail.com> - * @author Vincent Petry <vincent@nextcloud.com> - * - * @license AGPL-3.0-or-later - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - */ - -describe('OCA.Files.App tests', function() { - var App = OCA.Files.App; - var pushStateStub; - var replaceStateStub; - var parseUrlQueryStub; - - beforeEach(function() { - $('#testArea').append( - '<div id="content" class="app-files">' + - '<div id="app-navigation">' + - '<ul><li data-id="files"><a>Files</a></li>' + - '<li data-id="other"><a>Other</a></li>' + - '</div>' + - '<div id="app-content">' + - '<div id="app-content-files" class="hidden">' + - '</div>' + - '<div id="app-content-other" class="hidden">' + - '</div>' + - '</div>' + - '</div>' + - '</div>' - ); - - OCA.Files.fileActions = new OCA.Files.FileActions(); - - pushStateStub = sinon.stub(OC.Util.History, 'pushState'); - replaceStateStub = sinon.stub(OC.Util.History, 'replaceState'); - parseUrlQueryStub = sinon.stub(OC.Util.History, 'parseUrlQuery'); - parseUrlQueryStub.returns({}); - - App.initialize(); - }); - afterEach(function() { - App.destroy(); - - pushStateStub.restore(); - replaceStateStub.restore(); - parseUrlQueryStub.restore(); - }); - - describe('initialization', function() { - it('initializes the default file list with the default file actions', function() { - expect(App.fileList).toBeDefined(); - expect(App.fileList.fileActions.actions.all).toBeDefined(); - expect(App.fileList.$el.is('#app-content-files')).toEqual(true); - }); - }); - - describe('URL handling', function() { - it('pushes the state to the URL when current app changed directory', function() { - $('#app-content-files').trigger(new $.Event('changeDirectory', {dir: 'sub dir'})); - expect(pushStateStub.calledOnce).toEqual(true); - var params = OC.parseQueryString(pushStateStub.getCall(0).args[0]); - expect(params.dir).toEqual('sub dir'); - expect(params.view).not.toBeDefined(); - - $('li[data-id=other]>a').click(); - pushStateStub.reset(); - - $('#app-content-other').trigger(new $.Event('changeDirectory', {dir: 'sub dir'})); - expect(pushStateStub.calledOnce).toEqual(true); - params = OC.parseQueryString(pushStateStub.getCall(0).args[0]); - expect(params.dir).toEqual('sub dir'); - expect(params.view).toEqual('other'); - }); - it('replaces the state to the URL when fileid is known', function() { - $('#app-content-files').trigger(new $.Event('changeDirectory', {dir: 'sub dir'})); - expect(pushStateStub.calledOnce).toEqual(true); - var params = OC.parseQueryString(pushStateStub.getCall(0).args[0]); - expect(params.dir).toEqual('sub dir'); - expect(params.view).not.toBeDefined(); - expect(replaceStateStub.notCalled).toEqual(true); - - parseUrlQueryStub.returns({dir: 'sub dir'}); - - $('#app-content-files').trigger(new $.Event('afterChangeDirectory', {dir: 'sub dir', fileId: 123})); - - expect(pushStateStub.calledOnce).toEqual(true); - expect(replaceStateStub.calledOnce).toEqual(true); - params = OC.parseQueryString(replaceStateStub.getCall(0).args[0]); - expect(params.dir).toEqual('sub dir'); - expect(params.view).not.toBeDefined(); - expect(params.fileid).toEqual('123'); - }); - describe('onpopstate', function() { - it('sends "urlChanged" event to current app', function() { - var handler = sinon.stub(); - $('#app-content-files').on('urlChanged', handler); - App._onPopState({view: 'files', dir: '/somedir'}); - expect(handler.calledOnce).toEqual(true); - expect(handler.getCall(0).args[0].view).toEqual('files'); - expect(handler.getCall(0).args[0].dir).toEqual('/somedir'); - }); - it('sends "show" event to current app and sets navigation', function() { - var showHandlerFiles = sinon.stub(); - var showHandlerOther = sinon.stub(); - var hideHandlerFiles = sinon.stub(); - var hideHandlerOther = sinon.stub(); - $('#app-content-files').on('show', showHandlerFiles); - $('#app-content-files').on('hide', hideHandlerFiles); - $('#app-content-other').on('show', showHandlerOther); - $('#app-content-other').on('hide', hideHandlerOther); - App._onPopState({view: 'other', dir: '/somedir'}); - expect(showHandlerFiles.notCalled).toEqual(true); - expect(hideHandlerFiles.calledOnce).toEqual(true); - expect(showHandlerOther.calledOnce).toEqual(true); - expect(hideHandlerOther.notCalled).toEqual(true); - - showHandlerFiles.reset(); - showHandlerOther.reset(); - hideHandlerFiles.reset(); - hideHandlerOther.reset(); - - App._onPopState({view: 'files', dir: '/somedir'}); - expect(showHandlerFiles.calledOnce).toEqual(true); - expect(hideHandlerFiles.notCalled).toEqual(true); - expect(showHandlerOther.notCalled).toEqual(true); - expect(hideHandlerOther.calledOnce).toEqual(true); - - expect(App.navigation.getActiveItem()).toEqual('files'); - expect($('#app-content-files').hasClass('hidden')).toEqual(false); - expect($('#app-content-other').hasClass('hidden')).toEqual(true); - }); - it('does not send "show" or "hide" event to current app when already visible', function() { - var showHandler = sinon.stub(); - var hideHandler = sinon.stub(); - $('#app-content-files').on('show', showHandler); - $('#app-content-files').on('hide', hideHandler); - App._onPopState({view: 'files', dir: '/somedir'}); - expect(showHandler.notCalled).toEqual(true); - expect(hideHandler.notCalled).toEqual(true); - }); - it('state defaults to files app with root dir', function() { - var handler = sinon.stub(); - parseUrlQueryStub.returns({}); - $('#app-content-files').on('urlChanged', handler); - App._onPopState(); - expect(handler.calledOnce).toEqual(true); - expect(handler.getCall(0).args[0].view).toEqual('files'); - expect(handler.getCall(0).args[0].dir).toEqual('/'); - }); - it('activates files app if invalid view is passed', function() { - App._onPopState({view: 'invalid', dir: '/somedir'}); - - expect(App.navigation.getActiveItem()).toEqual('files'); - expect($('#app-content-files').hasClass('hidden')).toEqual(false); - }); - }); - describe('navigation', function() { - it('switches the navigation item and panel visibility when onpopstate', function() { - App._onPopState({view: 'other', dir: '/somedir'}); - expect(App.navigation.getActiveItem()).toEqual('other'); - expect($('#app-content-files').hasClass('hidden')).toEqual(true); - expect($('#app-content-other').hasClass('hidden')).toEqual(false); - expect($('li[data-id=files] > a').hasClass('active')).toEqual(false); - expect($('li[data-id=other] > a').hasClass('active')).toEqual(true); - - App._onPopState({view: 'files', dir: '/somedir'}); - - expect(App.navigation.getActiveItem()).toEqual('files'); - expect($('#app-content-files').hasClass('hidden')).toEqual(false); - expect($('#app-content-other').hasClass('hidden')).toEqual(true); - expect($('li[data-id=files] > a').hasClass('active')).toEqual(true); - expect($('li[data-id=other] > a').hasClass('active')).toEqual(false); - }); - it('clicking on navigation switches the panel visibility', function() { - $('li[data-id=other] > a').click(); - expect(App.navigation.getActiveItem()).toEqual('other'); - expect($('#app-content-files').hasClass('hidden')).toEqual(true); - expect($('#app-content-other').hasClass('hidden')).toEqual(false); - expect($('li[data-id=files] > a').hasClass('active')).toEqual(false); - expect($('li[data-id=other] > a').hasClass('active')).toEqual(true); - - $('li[data-id=files] > a').click(); - expect(App.navigation.getActiveItem()).toEqual('files'); - expect($('#app-content-files').hasClass('hidden')).toEqual(false); - expect($('#app-content-other').hasClass('hidden')).toEqual(true); - expect($('li[data-id=files] > a').hasClass('active')).toEqual(true); - expect($('li[data-id=other] > a').hasClass('active')).toEqual(false); - }); - it('clicking on navigation sends "show" and "urlChanged" event', function() { - var handler = sinon.stub(); - var showHandler = sinon.stub(); - $('#app-content-other').on('urlChanged', handler); - $('#app-content-other').on('show', showHandler); - $('li[data-id=other] > a').click(); - expect(handler.calledOnce).toEqual(true); - expect(handler.getCall(0).args[0].view).toEqual('other'); - expect(handler.getCall(0).args[0].dir).toEqual('/'); - expect(showHandler.calledOnce).toEqual(true); - }); - it('clicking on activate navigation only sends "urlChanged" event', function() { - var handler = sinon.stub(); - var showHandler = sinon.stub(); - $('#app-content-files').on('urlChanged', handler); - $('#app-content-files').on('show', showHandler); - $('li[data-id=files] > a').click(); - expect(handler.calledOnce).toEqual(true); - expect(handler.getCall(0).args[0].view).toEqual('files'); - expect(handler.getCall(0).args[0].dir).toEqual('/'); - expect(showHandler.notCalled).toEqual(true); - }); - }); - describe('viewer mode', function() { - it('toggles the sidebar when viewer mode is enabled', function() { - $('#app-content-files').trigger( - new $.Event('changeViewerMode', {viewerModeEnabled: true} - )); - expect($('#app-navigation').hasClass('hidden')).toEqual(true); - expect($('.app-files').hasClass('viewer-mode no-sidebar')).toEqual(true); - - $('#app-content-files').trigger( - new $.Event('changeViewerMode', {viewerModeEnabled: false} - )); - - expect($('#app-navigation').hasClass('hidden')).toEqual(false); - expect($('.app-files').hasClass('viewer-mode no-sidebar')).toEqual(false); - }); - }); - }); -}); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index a302121ae0d..cd3510c2faa 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -515,9 +515,9 @@ describe('OCA.Files.FileList tests', function() { }); it('toggles the list\'s class when toggling hidden files', function() { expect(fileList.$el.hasClass('hide-hidden-files')).toEqual(false); - filesConfig.set('showhidden', false); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: false }); expect(fileList.$el.hasClass('hide-hidden-files')).toEqual(true); - filesConfig.set('showhidden', true); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: true }) expect(fileList.$el.hasClass('hide-hidden-files')).toEqual(false); }); }); @@ -1371,7 +1371,7 @@ describe('OCA.Files.FileList tests', function() { expect($('.files-fileList tr').length).toEqual(20); }); it('renders the full first page despite hidden rows', function() { - filesConfig.set('showhidden', false); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: false }); var files = _.map(generateFiles(0, 23), function(data) { return _.extend(data, { name: '.' + data.name @@ -1385,7 +1385,7 @@ describe('OCA.Files.FileList tests', function() { expect($('.files-fileList tr').length).toEqual(25); }); it('renders the full first page despite hidden rows', function() { - filesConfig.set('showhidden', true); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: true }); var files = _.map(generateFiles(0, 23), function(data) { return _.extend(data, { name: '.' + data.name @@ -1817,18 +1817,6 @@ describe('OCA.Files.FileList tests', function() { $('#app-content-files').trigger(new $.Event('urlChanged', {view: 'files', dir: '/somedir'})); expect(fileList.getCurrentDirectory()).toEqual('/somedir'); }); - it('reloads the list when leaving hidden state', function() { - var reloadStub = sinon.stub(fileList, 'reload'); - - // First show should not trigger - $('#app-content-files').trigger(new $.Event('show')); - expect(reloadStub.calledOnce).toEqual(false); - - // Second show should! - $('#app-content-files').trigger(new $.Event('show')); - expect(reloadStub.calledOnce).toEqual(true); - reloadStub.restore(); - }); it('refreshes breadcrumb after update', function() { var setDirSpy = sinon.spy(fileList.breadcrumb, 'setDirectory'); fileList.changeDirectory('/anothersubdir'); @@ -2014,7 +2002,7 @@ describe('OCA.Files.FileList tests', function() { expect($('.select-all').prop('checked')).toEqual(false); }); it('Selecting all files also selects hidden files when invisible', function() { - filesConfig.set('showhidden', false); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: false }); var $tr = fileList.add(new FileInfo({ name: '.hidden', type: 'dir', @@ -2103,7 +2091,7 @@ describe('OCA.Files.FileList tests', function() { expect($summary.text()).toEqual('Name'); }); it('Displays the number of hidden files in selection summary if hidden files are invisible', function() { - filesConfig.set('showhidden', false); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: false }); var $tr = fileList.add(new FileInfo({ name: '.hidden', type: 'dir', @@ -2115,7 +2103,7 @@ describe('OCA.Files.FileList tests', function() { expect($summary.text()).toEqual('2 folders and 3 files (including 1 hidden)'); }); it('Does not displays the number of hidden files in selection summary if hidden files are visible', function() { - filesConfig.set('showhidden', true); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: true }); var $tr = fileList.add(new FileInfo({ name: '.hidden', type: 'dir', @@ -2127,7 +2115,7 @@ describe('OCA.Files.FileList tests', function() { expect($summary.text()).toEqual('2 folders and 3 files'); }); it('Toggling hidden file visibility updates selection summary', function() { - filesConfig.set('showhidden', false); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: false }); var $tr = fileList.add(new FileInfo({ name: '.hidden', type: 'dir', @@ -2137,7 +2125,7 @@ describe('OCA.Files.FileList tests', function() { $('.select-all').click(); var $summary = $('.column-name a.name>span:first'); expect($summary.text()).toEqual('2 folders and 3 files (including 1 hidden)'); - filesConfig.set('showhidden', true); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: true }); expect($summary.text()).toEqual('2 folders and 3 files'); }); it('Select/deselect files shows/hides file actions', function() { diff --git a/apps/files/tests/js/filesummarySpec.js b/apps/files/tests/js/filesummarySpec.js index 8692b8b14aa..8bc7bd8f995 100644 --- a/apps/files/tests/js/filesummarySpec.js +++ b/apps/files/tests/js/filesummarySpec.js @@ -204,7 +204,8 @@ describe('OCA.Files.FileSummary tests', function() { }); it('renders hidden count section when hidden files are hidden', function() { - config.set('showhidden', false); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: false }); + summary.add({name: 'abc', type: 'file', size: 256000}); summary.add({name: 'def', type: 'dir', size: 100}); summary.add({name: '.hidden', type: 'dir', size: 512000}); @@ -217,7 +218,8 @@ describe('OCA.Files.FileSummary tests', function() { expect($container.find('.filesize').text()).toEqual('750 KB'); }); it('does not render hidden count section when hidden files exist but are visible', function() { - config.set('showhidden', true); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: true }); + summary.add({name: 'abc', type: 'file', size: 256000}); summary.add({name: 'def', type: 'dir', size: 100}); summary.add({name: '.hidden', type: 'dir', size: 512000}); @@ -229,7 +231,8 @@ describe('OCA.Files.FileSummary tests', function() { expect($container.find('.filesize').text()).toEqual('750 KB'); }); it('does not render hidden count section when no hidden files exist', function() { - config.set('showhidden', false); + window._nc_event_bus.emit('files:config:updated', { key: 'show_hidden', value: false }); + summary.add({name: 'abc', type: 'file', size: 256000}); summary.add({name: 'def', type: 'dir', size: 100}); summary.update(); diff --git a/apps/files_external/js/mountsfilelist.js b/apps/files_external/js/mountsfilelist.js index 58cd356ccff..3b88ec070db 100644 --- a/apps/files_external/js/mountsfilelist.js +++ b/apps/files_external/js/mountsfilelist.js @@ -84,7 +84,7 @@ reload: function() { this.showMask(); - if (this._reloadCall) { + if (this._reloadCall?.abort) { this._reloadCall.abort(); } diff --git a/apps/files_sharing/js/sharedfilelist.js b/apps/files_sharing/js/sharedfilelist.js index 3a8786bf2a9..65eb1b78f5f 100644 --- a/apps/files_sharing/js/sharedfilelist.js +++ b/apps/files_sharing/js/sharedfilelist.js @@ -179,7 +179,7 @@ reload: function() { this.showMask() - if (this._reloadCall) { + if (this._reloadCall?.abort) { this._reloadCall.abort() } diff --git a/apps/files_sharing/lib/AppInfo/Application.php b/apps/files_sharing/lib/AppInfo/Application.php index 960d376c6d3..eff4a3ac5b7 100644 --- a/apps/files_sharing/lib/AppInfo/Application.php +++ b/apps/files_sharing/lib/AppInfo/Application.php @@ -284,7 +284,6 @@ class Application extends App implements IBootstrap { 'name' => $l->t('Shares'), 'classes' => 'collapsible', 'sublist' => $sharingSublistArray, - 'expandedState' => 'show_sharing_menu' ]; }); } diff --git a/apps/files_trashbin/src/filelist.js b/apps/files_trashbin/src/filelist.js index 7fb55d4b932..8920dcbf8b9 100644 --- a/apps/files_trashbin/src/filelist.js +++ b/apps/files_trashbin/src/filelist.js @@ -292,7 +292,7 @@ this._selectionSummary.clear() this.$el.find('.select-all').prop('checked', false) this.showMask() - if (this._reloadCall) { + if (this._reloadCall?.abort) { this._reloadCall.abort() } this._reloadCall = this.client.getFolderContents( |