summaryrefslogtreecommitdiffstats
path: root/apps
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2023-01-04 19:06:52 +0100
committerJohn Molakvoæ <skjnldsv@protonmail.com>2023-01-17 18:30:41 +0100
commitb9906fb21e9f1aadf14e473b8c26a2ec7537fa11 (patch)
tree3b068eb999c675760d715e78da2d5eee8668cb44 /apps
parent9af7ee8d11b43a7a3d14f7aa8390aff0a4174f55 (diff)
downloadnextcloud-server-b9906fb21e9f1aadf14e473b8c26a2ec7537fa11.tar.gz
nextcloud-server-b9906fb21e9f1aadf14e473b8c26a2ec7537fa11.zip
feat(files): Quota in navigation
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
Diffstat (limited to 'apps')
-rw-r--r--apps/files/appinfo/routes.php10
-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/lib/Controller/AjaxController.php57
-rw-r--r--apps/files/lib/Controller/ApiController.php14
-rw-r--r--apps/files/lib/Controller/ViewController.php24
-rw-r--r--apps/files/src/components/NavigationQuota.vue153
-rw-r--r--apps/files/src/views/Navigation.cy.ts109
-rw-r--r--apps/files/src/views/Navigation.vue21
-rw-r--r--apps/files/src/views/Sidebar.vue14
-rw-r--r--apps/files/templates/appnavigation.php44
-rw-r--r--apps/files/tests/Controller/ViewControllerTest.php12
12 files changed, 317 insertions, 143 deletions
diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php
index 9c45668333b..dabbeab978e 100644
--- a/apps/files/appinfo/routes.php
+++ b/apps/files/appinfo/routes.php
@@ -62,11 +62,6 @@ $application->registerRoutes(
'root' => '',
],
[
- 'name' => 'ajax#getStorageStats',
- 'url' => '/ajax/getstoragestats',
- 'verb' => 'GET',
- ],
- [
'name' => 'API#getThumbnail',
'url' => '/api/v1/thumbnail/{x}/{y}/{file}',
'verb' => 'GET',
@@ -84,6 +79,11 @@ $application->registerRoutes(
'verb' => 'GET'
],
[
+ 'name' => 'API#getStorageStats',
+ 'url' => '/api/v1/stats',
+ 'verb' => 'GET'
+ ],
+ [
'name' => 'API#setConfig',
'url' => '/api/v1/config/{key}',
'verb' => 'POST'
diff --git a/apps/files/composer/composer/autoload_classmap.php b/apps/files/composer/composer/autoload_classmap.php
index 0f6c2caf4f2..ef3480081e0 100644
--- a/apps/files/composer/composer/autoload_classmap.php
+++ b/apps/files/composer/composer/autoload_classmap.php
@@ -32,7 +32,6 @@ return array(
'OCA\\Files\\Command\\Scan' => $baseDir . '/../lib/Command/Scan.php',
'OCA\\Files\\Command\\ScanAppData' => $baseDir . '/../lib/Command/ScanAppData.php',
'OCA\\Files\\Command\\TransferOwnership' => $baseDir . '/../lib/Command/TransferOwnership.php',
- 'OCA\\Files\\Controller\\AjaxController' => $baseDir . '/../lib/Controller/AjaxController.php',
'OCA\\Files\\Controller\\ApiController' => $baseDir . '/../lib/Controller/ApiController.php',
'OCA\\Files\\Controller\\DirectEditingController' => $baseDir . '/../lib/Controller/DirectEditingController.php',
'OCA\\Files\\Controller\\DirectEditingViewController' => $baseDir . '/../lib/Controller/DirectEditingViewController.php',
diff --git a/apps/files/composer/composer/autoload_static.php b/apps/files/composer/composer/autoload_static.php
index 28e48b9919e..4f7872e39df 100644
--- a/apps/files/composer/composer/autoload_static.php
+++ b/apps/files/composer/composer/autoload_static.php
@@ -47,7 +47,6 @@ class ComposerStaticInitFiles
'OCA\\Files\\Command\\Scan' => __DIR__ . '/..' . '/../lib/Command/Scan.php',
'OCA\\Files\\Command\\ScanAppData' => __DIR__ . '/..' . '/../lib/Command/ScanAppData.php',
'OCA\\Files\\Command\\TransferOwnership' => __DIR__ . '/..' . '/../lib/Command/TransferOwnership.php',
- 'OCA\\Files\\Controller\\AjaxController' => __DIR__ . '/..' . '/../lib/Controller/AjaxController.php',
'OCA\\Files\\Controller\\ApiController' => __DIR__ . '/..' . '/../lib/Controller/ApiController.php',
'OCA\\Files\\Controller\\DirectEditingController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingController.php',
'OCA\\Files\\Controller\\DirectEditingViewController' => __DIR__ . '/..' . '/../lib/Controller/DirectEditingViewController.php',
diff --git a/apps/files/lib/Controller/AjaxController.php b/apps/files/lib/Controller/AjaxController.php
deleted file mode 100644
index cd26ab7a6f8..00000000000
--- a/apps/files/lib/Controller/AjaxController.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-/**
- * @copyright Copyright (c) 2018, Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @author Roeland Jago Douma <roeland@famdouma.nl>
- *
- * @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\Controller;
-
-use OCA\Files\Helper;
-use OCP\AppFramework\Controller;
-use OCP\AppFramework\Http\JSONResponse;
-use OCP\Files\NotFoundException;
-use OCP\IRequest;
-
-class AjaxController extends Controller {
- public function __construct(string $appName, IRequest $request) {
- parent::__construct($appName, $request);
- }
-
- /**
- * @NoAdminRequired
- */
- public function getStorageStats(string $dir = '/'): JSONResponse {
- try {
- return new JSONResponse([
- 'status' => 'success',
- 'data' => Helper::buildFileStorageStatistics($dir),
- ]);
- } catch (NotFoundException $e) {
- return new JSONResponse([
- 'status' => 'error',
- 'data' => [
- 'message' => 'Folder not found'
- ],
- ]);
- }
- }
-}
diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php
index f2329fc384b..604cf9a3c64 100644
--- a/apps/files/lib/Controller/ApiController.php
+++ b/apps/files/lib/Controller/ApiController.php
@@ -257,6 +257,20 @@ class ApiController extends Controller {
return new DataResponse(['files' => $files]);
}
+
+ /**
+ * Returns the current logged-in user's storage stats.
+ *
+ * @NoAdminRequired
+ *
+ * @param ?string $dir the directory to get the storage stats from
+ * @return JSONResponse
+ */
+ public function getStorageStats($dir = '/'): JSONResponse {
+ $storageInfo = \OC_Helper::getStorageInfo($dir ?: '/');
+ return new JSONResponse(['message' => 'ok', 'data' => $storageInfo]);
+ }
+
/**
* Change the default sort mode
*
diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php
index 0594b63f56b..b607764e602 100644
--- a/apps/files/lib/Controller/ViewController.php
+++ b/apps/files/lib/Controller/ViewController.php
@@ -136,11 +136,11 @@ class ViewController extends Controller {
* @return array
* @throws \OCP\Files\NotFoundException
*/
- protected function getStorageInfo() {
+ protected function getStorageInfo(string $dir = '/') {
\OC_Util::setupFS();
- $dirInfo = \OC\Files\Filesystem::getFileInfo('/', false);
+ $rootInfo = \OC\Files\Filesystem::getFileInfo('/', false);
- return \OC_Helper::getStorageInfo('/', $dirInfo);
+ return \OC_Helper::getStorageInfo($dir, $rootInfo ?: null);
}
/**
@@ -241,18 +241,16 @@ class ViewController extends Controller {
$nav->assign('navigationItems', $navItems);
- $nav->assign('usage', \OC_Helper::humanFileSize($storageInfo['used']));
- if ($storageInfo['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED) {
- $totalSpace = $this->l10n->t('Unlimited');
- } else {
- $totalSpace = \OC_Helper::humanFileSize($storageInfo['total']);
- }
- $nav->assign('total_space', $totalSpace);
- $nav->assign('quota', $storageInfo['quota']);
- $nav->assign('usage_relative', $storageInfo['relative']);
-
$contentItems = [];
+ try {
+ // If view is files, we use the directory, otherwise we use the root storage
+ $storageInfo = $this->getStorageInfo(($view === 'files' && $dir) ? $dir : '/');
+ } catch(\Exception $e) {
+ $storageInfo = $this->getStorageInfo();
+ }
+
+ $this->initialState->provideInitialState('storageStats', $storageInfo);
$this->initialState->provideInitialState('navigation', $navItems);
$this->initialState->provideInitialState('config', $this->userConfig->getConfigs());
diff --git a/apps/files/src/components/NavigationQuota.vue b/apps/files/src/components/NavigationQuota.vue
new file mode 100644
index 00000000000..bfcbaea3776
--- /dev/null
+++ b/apps/files/src/components/NavigationQuota.vue
@@ -0,0 +1,153 @@
+<template>
+ <NcAppNavigationItem v-if="storageStats"
+ :aria-label="t('files', 'Storage informations')"
+ :class="{ 'app-navigation-entry__settings-quota--not-unlimited': storageStats.quota >= 0}"
+ :loading="loadingStorageStats"
+ :name="storageStatsTitle"
+ :title="storageStatsTooltip"
+ class="app-navigation-entry__settings-quota"
+ data-cy-files-navigation-settings-quota
+ @click.stop.prevent="debounceUpdateStorageStats">
+ <ChartPie slot="icon" :size="20" />
+
+ <!-- Progress bar -->
+ <NcProgressBar v-if="storageStats.quota >= 0"
+ slot="extra"
+ :error="storageStats.relative > 80"
+ :value="Math.min(storageStats.relative, 100)" />
+ </NcAppNavigationItem>
+</template>
+
+<script>
+import { formatFileSize } from '@nextcloud/files'
+import { generateUrl } from '@nextcloud/router'
+import { loadState } from '@nextcloud/initial-state'
+import { showError } from '@nextcloud/dialogs'
+import { debounce, throttle } from 'throttle-debounce'
+import { translate } from '@nextcloud/l10n'
+import axios from '@nextcloud/axios'
+import ChartPie from 'vue-material-design-icons/ChartPie.vue'
+import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
+import NcProgressBar from '@nextcloud/vue/dist/Components/NcProgressBar.js'
+
+import logger from '../logger.js'
+import { subscribe } from '@nextcloud/event-bus'
+
+export default {
+ name: 'NavigationQuota',
+
+ components: {
+ ChartPie,
+ NcAppNavigationItem,
+ NcProgressBar,
+ },
+
+ data() {
+ return {
+ loadingStorageStats: false,
+ storageStats: loadState('files', 'storageStats', null),
+ }
+ },
+
+ computed: {
+ storageStatsTitle() {
+ const usedQuotaByte = formatFileSize(this.storageStats?.used)
+ const quotaByte = formatFileSize(this.storageStats?.quota)
+
+ // If no quota set
+ if (this.storageStats?.quota < 0) {
+ return this.t('files', '{usedQuotaByte} used', { usedQuotaByte })
+ }
+
+ return this.t('files', '{used} of {quota} used', {
+ used: usedQuotaByte,
+ quota: quotaByte,
+ })
+ },
+ storageStatsTooltip() {
+ if (!this.storageStats.relative) {
+ return ''
+ }
+
+ return this.t('files', '{relative}% used', this.storageStats)
+ },
+ },
+
+ beforeMount() {
+ /**
+ * Update storage stats every minute
+ * TODO: remove when all views are migrated to Vue
+ */
+ setInterval(this.throttleUpdateStorageStats, 60 * 1000)
+
+ subscribe('files:file:created', this.throttleUpdateStorageStats)
+ subscribe('files:file:deleted', this.throttleUpdateStorageStats)
+ subscribe('files:file:moved', this.throttleUpdateStorageStats)
+ subscribe('files:file:updated', this.throttleUpdateStorageStats)
+
+ subscribe('files:folder:created', this.throttleUpdateStorageStats)
+ subscribe('files:folder:deleted', this.throttleUpdateStorageStats)
+ subscribe('files:folder:moved', this.throttleUpdateStorageStats)
+ subscribe('files:folder:updated', this.throttleUpdateStorageStats)
+ },
+
+ methods: {
+ // From user input
+ debounceUpdateStorageStats: debounce(200, function(event) {
+ this.updateStorageStats(event)
+ }),
+ // From interval or event bus
+ throttleUpdateStorageStats: throttle(1000, function(event) {
+ this.updateStorageStats(event)
+ }),
+
+ /**
+ * Update the storage stats
+ * Throttled at max 1 refresh per minute
+ *
+ * @param {Event} [event = null] if user interaction
+ */
+ async updateStorageStats(event = null) {
+ if (this.loadingStorageStats) {
+ return
+ }
+
+ this.loadingStorageStats = true
+ try {
+ const response = await axios.get(generateUrl('/apps/files/api/v1/stats'))
+ if (!response?.data?.data) {
+ throw new Error('Invalid storage stats')
+ }
+ this.storageStats = response.data.data
+ } catch (error) {
+ logger.error('Could not refresh storage stats', { error })
+ // Only show to the user if it was manually triggered
+ if (event) {
+ showError(t('files', 'Could not refresh storage stats'))
+ }
+ } finally {
+ this.loadingStorageStats = false
+ }
+ },
+
+ t: translate,
+ },
+}
+</script>
+
+<style lang="scss" scoped>
+// User storage stats display
+.app-navigation-entry__settings-quota {
+ // Align title with progress and icon
+ &--not-unlimited::v-deep .app-navigation-entry__title {
+ margin-top: -4px;
+ }
+
+ progress {
+ position: absolute;
+ bottom: 10px;
+ margin-left: 44px;
+ width: calc(100% - 44px - 22px);
+ }
+}
+</style>
diff --git a/apps/files/src/views/Navigation.cy.ts b/apps/files/src/views/Navigation.cy.ts
index 65c5d8938a9..c8b0f07dea1 100644
--- a/apps/files/src/views/Navigation.cy.ts
+++ b/apps/files/src/views/Navigation.cy.ts
@@ -1,4 +1,5 @@
-/* eslint-disable import/first */
+import * as InitialState from '@nextcloud/initial-state'
+import * as L10n from '@nextcloud/l10n'
import FolderSvg from '@mdi/svg/svg/folder.svg'
import ShareSvg from '@mdi/svg/svg/share-variant.svg'
@@ -6,9 +7,18 @@ import NavigationService from '../services/Navigation'
import NavigationView from './Navigation.vue'
import router from '../router/router.js'
-const Navigation = new NavigationService()
-
describe('Navigation renders', () => {
+ const Navigation = new NavigationService()
+
+ before(() => {
+ cy.stub(InitialState, 'loadState')
+ .returns({
+ used: 1024 * 1024 * 1024,
+ quota: -1,
+ })
+
+ })
+
it('renders', () => {
cy.mount(NavigationView, {
propsData: {
@@ -17,11 +27,14 @@ describe('Navigation renders', () => {
})
cy.get('[data-cy-files-navigation]').should('be.visible')
+ cy.get('[data-cy-files-navigation-settings-quota]').should('be.visible')
cy.get('[data-cy-files-navigation-settings-button]').should('be.visible')
})
})
describe('Navigation API', () => {
+ const Navigation = new NavigationService()
+
it('Check API entries rendering', () => {
Navigation.register({
id: 'files',
@@ -114,3 +127,93 @@ describe('Navigation API', () => {
}).to.throw('Navigation id files is already registered')
})
})
+
+describe('Quota rendering', () => {
+ const Navigation = new NavigationService()
+
+ beforeEach(() => {
+ // TODO: remove when @nextcloud/l10n 2.0 is released
+ // https://github.com/nextcloud/nextcloud-l10n/pull/542
+ cy.stub(L10n, 'translate', (app, text, vars = {}, number) => {
+ cy.log({app, text, vars, number})
+ return text.replace(/%n/g, '' + number).replace(/{([^{}]*)}/g, (match, key) => {
+ return vars[key]
+ })
+ })
+ })
+
+ it('Unknown quota', () => {
+ cy.stub(InitialState, 'loadState')
+ .as('loadStateStats')
+ .returns(undefined)
+
+ cy.mount(NavigationView, {
+ propsData: {
+ Navigation,
+ },
+ })
+
+ cy.get('[data-cy-files-navigation-settings-quota]').should('not.exist')
+ })
+
+ it('Unlimited quota', () => {
+ cy.stub(InitialState, 'loadState')
+ .as('loadStateStats')
+ .returns({
+ used: 1024 * 1024 * 1024,
+ quota: -1,
+ })
+
+ cy.mount(NavigationView, {
+ propsData: {
+ Navigation,
+ },
+ })
+
+ cy.get('[data-cy-files-navigation-settings-quota]').should('be.visible')
+ cy.get('[data-cy-files-navigation-settings-quota]').should('contain.text', '1 GB used')
+ cy.get('[data-cy-files-navigation-settings-quota] progress').should('not.exist')
+ })
+
+ it('Non-reached quota', () => {
+ cy.stub(InitialState, 'loadState')
+ .as('loadStateStats')
+ .returns({
+ used: 1024 * 1024 * 1024,
+ quota: 5 * 1024 * 1024 * 1024,
+ relative: 20, // percent
+ })
+
+ cy.mount(NavigationView, {
+ propsData: {
+ Navigation,
+ },
+ })
+
+ cy.get('[data-cy-files-navigation-settings-quota]').should('be.visible')
+ cy.get('[data-cy-files-navigation-settings-quota]').should('contain.text', '1 GB of 5 GB used')
+ cy.get('[data-cy-files-navigation-settings-quota] progress').should('be.visible')
+ cy.get('[data-cy-files-navigation-settings-quota] progress').should('have.attr', 'value', '20')
+ })
+
+ it('Reached quota', () => {
+ cy.stub(InitialState, 'loadState')
+ .as('loadStateStats')
+ .returns({
+ used: 5 * 1024 * 1024 * 1024,
+ quota: 1024 * 1024 * 1024,
+ relative: 500, // percent
+ })
+
+ cy.mount(NavigationView, {
+ propsData: {
+ Navigation,
+ },
+ })
+
+ cy.get('[data-cy-files-navigation-settings-quota]').should('be.visible')
+ cy.get('[data-cy-files-navigation-settings-quota]').should('contain.text', '5 GB of 1 GB used')
+ cy.get('[data-cy-files-navigation-settings-quota] progress').should('be.visible')
+ cy.get('[data-cy-files-navigation-settings-quota] progress').should('have.attr', 'value', '100') // progress max is 100
+ })
+})
diff --git a/apps/files/src/views/Navigation.vue b/apps/files/src/views/Navigation.vue
index 074bee0b010..040e1482e32 100644
--- a/apps/files/src/views/Navigation.vue
+++ b/apps/files/src/views/Navigation.vue
@@ -42,10 +42,14 @@
</NcAppNavigationItem>
</template>
- <!-- Settings toggle -->
+ <!-- Non-scrollable navigation bottom elements -->
<template #footer>
<ul class="app-navigation-entry__settings">
- <NcAppNavigationItem :aria-label="t('files', 'Open the Files app settings')"
+ <!-- User storage usage statistics -->
+ <NavigationQuota />
+
+ <!-- Files settings modal toggle-->
+ <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">
@@ -64,6 +68,8 @@
<script>
import { emit, subscribe } from '@nextcloud/event-bus'
import { generateUrl } from '@nextcloud/router'
+import { translate } from '@nextcloud/l10n'
+
import axios from '@nextcloud/axios'
import Cog from 'vue-material-design-icons/Cog.vue'
import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
@@ -71,10 +77,9 @@ import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationI
import logger from '../logger.js'
import Navigation from '../services/Navigation.ts'
+import NavigationQuota from '../components/NavigationQuota.vue'
import SettingsModal from './Settings.vue'
-import { translate } from '@nextcloud/l10n'
-
export default {
name: 'Navigation',
@@ -83,6 +88,7 @@ export default {
NcAppNavigation,
NcAppNavigationItem,
SettingsModal,
+ NavigationQuota,
},
props: {
@@ -103,6 +109,8 @@ export default {
currentViewId() {
return this.$route?.params?.view || 'files'
},
+
+ /** @return {Navigation} */
currentView() {
return this.views.find(view => view.id === this.currentViewId)
},
@@ -111,6 +119,8 @@ export default {
views() {
return this.Navigation.views
},
+
+ /** @return {Navigation[]} */
parentViews() {
return this.views
// filter child views
@@ -120,6 +130,8 @@ export default {
return a.order - b.order
})
},
+
+ /** @return {Navigation[]} */
childViews() {
return this.views
// filter parent views
@@ -213,6 +225,7 @@ export default {
/**
* Generate the route to a view
+ *
* @param {Navigation} view the view to toggle
*/
generateToNavigation(view) {
diff --git a/apps/files/src/views/Sidebar.vue b/apps/files/src/views/Sidebar.vue
index 1fb60f7fc39..c97fb304c32 100644
--- a/apps/files/src/views/Sidebar.vue
+++ b/apps/files/src/views/Sidebar.vue
@@ -285,6 +285,13 @@ export default {
return OCA && 'SystemTags' in OCA
},
},
+ created() {
+ window.addEventListener('resize', this.handleWindowResize)
+ this.handleWindowResize()
+ },
+ beforeDestroy() {
+ window.removeEventListener('resize', this.handleWindowResize)
+ },
methods: {
/**
@@ -494,13 +501,6 @@ export default {
this.hasLowHeight = document.documentElement.clientHeight < 1024
},
},
- created() {
- window.addEventListener('resize', this.handleWindowResize)
- this.handleWindowResize()
- },
- beforeDestroy() {
- window.removeEventListener('resize', this.handleWindowResize)
- },
}
</script>
<style lang="scss" scoped>
diff --git a/apps/files/templates/appnavigation.php b/apps/files/templates/appnavigation.php
index 9da3f764a7f..f316ccbf773 100644
--- a/apps/files/templates/appnavigation.php
+++ b/apps/files/templates/appnavigation.php
@@ -9,51 +9,7 @@
$pinned = NavigationListElements($item, $l, $pinned);
}
?>
-
- <?php if ($_['quota'] === \OCP\Files\FileInfo::SPACE_UNLIMITED): ?>
- <li id="quota" class="pinned <?php p($pinned === 0 ? 'first-pinned ' : '') ?>">
- <a href="#" class="icon-quota svg quota-navigation-item">
- <p id="quotatext" class="quota-navigation-item__text"><?php p($l->t('%s used', [$_['usage']])); ?></p>
- </a>
- </li>
- <?php else: ?>
- <li id="quota" class="has-tooltip pinned <?php p($pinned === 0 ? 'first-pinned ' : '') ?>"
- title="<?php p($l->t('%s%%', [round($_['usage_relative'])])); ?>">
- <a href="#" class="icon-quota svg quota-navigation-item">
- <p id="quotatext" class="quota-navigation-item__text"><?php p($l->t('%1$s of %2$s used', [$_['usage'], $_['total_space']])); ?></p>
- <div class="quota-navigation-item__container">
- <progress value="<?php p($_['usage_relative']); ?>" max="100" class="<?= ($_['usage_relative'] > 80) ? 'warn' : '' ?>"></progress>
- </div>
- </a>
- </li>
- <?php endif; ?>
</ul>
- <div id="app-settings">
- <div id="app-settings-header">
- <button class="settings-button"
- data-apps-slide-toggle="#app-settings-content">
- <?php p($l->t('Files settings')); ?>
- </button>
- </div>
- <div id="app-settings-content">
- <div id="files-app-settings"></div>
- <div id="files-setting-showhidden">
- <input class="checkbox" id="showhiddenfilesToggle"
- checked="checked" type="checkbox">
- <label for="showhiddenfilesToggle"><?php p($l->t('Show hidden files')); ?></label>
- </div>
- <div id="files-setting-cropimagepreviews">
- <input class="checkbox" id="cropimagepreviewsToggle"
- checked="checked" type="checkbox">
- <label for="cropimagepreviewsToggle"><?php p($l->t('Crop image previews')); ?></label>
- </div>
- <label for="webdavurl"><?php p($l->t('WebDAV')); ?></label>
- <input id="webdavurl" type="text" readonly="readonly"
- value="<?php p($_['webdav_url']); ?>"/>
- <em><a href="<?php echo link_to_docs('user-webdav') ?>" target="_blank" rel="noreferrer noopener"><?php p($l->t('Use this address to access your Files via WebDAV')) ?> ↗</a></em>
- </div>
- </div>
-
</div>
diff --git a/apps/files/tests/Controller/ViewControllerTest.php b/apps/files/tests/Controller/ViewControllerTest.php
index 6ab09011e1f..08ec7d17a1d 100644
--- a/apps/files/tests/Controller/ViewControllerTest.php
+++ b/apps/files/tests/Controller/ViewControllerTest.php
@@ -139,7 +139,7 @@ class ViewControllerTest extends TestCase {
public function testIndexWithRegularBrowser() {
$this->viewController
- ->expects($this->once())
+ ->expects($this->any())
->method('getStorageInfo')
->willReturn([
'used' => 123,
@@ -160,17 +160,13 @@ class ViewControllerTest extends TestCase {
]);
$this->config
- ->expects($this->any())
- ->method('getAppValue')
- ->willReturnArgument(2);
+ ->expects($this->any())
+ ->method('getAppValue')
+ ->willReturnArgument(2);
$this->shareManager->method('shareApiAllowLinks')
->willReturn(true);
$nav = new Template('files', 'appnavigation');
- $nav->assign('usage_relative', 123);
- $nav->assign('usage', '123 B');
- $nav->assign('quota', 100);
- $nav->assign('total_space', '100 B');
$nav->assign('navigationItems', [
'files' => [
'id' => 'files',