Skip to content

Commit

Permalink
feat(files): Quota in navigation
Browse files Browse the repository at this point in the history
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
  • Loading branch information
skjnldsv committed Jan 12, 2023
1 parent d389b54 commit 03282dc
Show file tree
Hide file tree
Showing 30 changed files with 416 additions and 174 deletions.
9 changes: 8 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ module.exports = {
oc_userconfig: true,
dayNames: true,
firstDay: true,
'cypress/globals': true,
},
extends: ['@nextcloud'],
plugins: [
'cypress',
],
extends: [
'@nextcloud',
'plugin:cypress/recommended',
],
rules: {
'no-tabs': 'warn',
// TODO: make sure we fix this as this is bad vue coding style.
Expand Down
10 changes: 5 additions & 5 deletions apps/files/appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,6 @@
'verb' => 'GET',
'root' => '',
],
[
'name' => 'ajax#getStorageStats',
'url' => '/ajax/getstoragestats',
'verb' => 'GET',
],
[
'name' => 'API#getThumbnail',
'url' => '/api/v1/thumbnail/{x}/{y}/{file}',
Expand All @@ -83,6 +78,11 @@
'url' => '/api/v1/recent/',
'verb' => 'GET'
],
[
'name' => 'API#getStorageStats',
'url' => '/api/v1/stats',
'verb' => 'GET'
],
[
'name' => 'API#setConfig',
'url' => '/api/v1/config/{key}',
Expand Down
1 change: 0 additions & 1 deletion apps/files/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
'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',
Expand Down
1 change: 0 additions & 1 deletion apps/files/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
57 changes: 0 additions & 57 deletions apps/files/lib/Controller/AjaxController.php

This file was deleted.

14 changes: 14 additions & 0 deletions apps/files/lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,20 @@ public function getRecentFiles() {
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
*
Expand Down
24 changes: 11 additions & 13 deletions apps/files/lib/Controller/ViewController.php
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ protected function renderScript($appName, $scriptName) {
* @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);
}

/**
Expand Down Expand Up @@ -241,18 +241,16 @@ public function index($dir = '', $view = '', $fileid = null, $fileNotFound = fal

$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());

Expand Down
153 changes: 153 additions & 0 deletions apps/files/src/components/NavigationQuota.vue
Original file line number Diff line number Diff line change
@@ -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>
Loading

0 comments on commit 03282dc

Please sign in to comment.