summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
Diffstat (limited to 'apps')
-rw-r--r--apps/dav/src/views/Availability.vue4
-rw-r--r--apps/files/appinfo/routes.php57
-rw-r--r--apps/files/composer/composer/autoload_classmap.php1
-rw-r--r--apps/files/composer/composer/autoload_static.php1
-rw-r--r--apps/files/css/files.css4
-rw-r--r--apps/files/css/files.css.map2
-rw-r--r--apps/files/css/files.scss4
-rw-r--r--apps/files/css/merged.css4
-rw-r--r--apps/files/css/merged.css.map2
-rw-r--r--apps/files/js/app.js132
-rw-r--r--apps/files/js/favoritesfilelist.js2
-rw-r--r--apps/files/js/filelist.js61
-rw-r--r--apps/files/js/filesummary.js10
-rw-r--r--apps/files/js/merged-index.json1
-rw-r--r--apps/files/js/recentfilelist.js2
-rw-r--r--apps/files/lib/AppInfo/Application.php5
-rw-r--r--apps/files/lib/Controller/ApiController.php89
-rw-r--r--apps/files/lib/Controller/ViewController.php87
-rw-r--r--apps/files/lib/Service/UserConfig.php144
-rw-r--r--apps/files/src/components/Setting.vue2
-rw-r--r--apps/files/src/files-app-settings.js57
-rw-r--r--apps/files/src/legacy/navigationMapper.js55
-rw-r--r--apps/files/src/logger.js24
-rw-r--r--apps/files/src/main.js42
-rw-r--r--apps/files/src/router/router.js57
-rw-r--r--apps/files/src/services/Navigation.ts217
-rw-r--r--apps/files/src/views/Navigation.cy.ts116
-rw-r--r--apps/files/src/views/Navigation.vue264
-rw-r--r--apps/files/src/views/Settings.vue138
-rw-r--r--apps/files/src/views/Sidebar.vue13
-rw-r--r--apps/files/templates/appnavigation.php3
-rw-r--r--apps/files/templates/index.php4
-rw-r--r--apps/files/tests/Controller/ApiControllerTest.php7
-rw-r--r--apps/files/tests/Controller/ViewControllerTest.php58
-rw-r--r--apps/files/tests/js/appSpec.js244
-rw-r--r--apps/files/tests/js/filelistSpec.js30
-rw-r--r--apps/files/tests/js/filesummarySpec.js9
-rw-r--r--apps/files_external/js/mountsfilelist.js2
-rw-r--r--apps/files_sharing/js/sharedfilelist.js2
-rw-r--r--apps/files_sharing/lib/AppInfo/Application.php1
-rw-r--r--apps/files_trashbin/src/filelist.js2
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(