aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Molakvoæ <skjnldsv@protonmail.com>2022-12-14 16:54:35 +0100
committerJohn Molakvoæ <skjnldsv@protonmail.com>2023-01-04 16:45:52 +0100
commit5c987a0ff4530cd0951920fcbfaf97411aeec17a (patch)
treecec3ffdd3282cfe2a84f6f2d9251c72bc3922ed8
parent887c9e05de88f81ed6f0cb88bd185c05b1a22076 (diff)
downloadnextcloud-server-5c987a0ff4530cd0951920fcbfaf97411aeec17a.tar.gz
nextcloud-server-5c987a0ff4530cd0951920fcbfaf97411aeec17a.zip
Port settings to Modal
Signed-off-by: John Molakvoæ <skjnldsv@protonmail.com>
-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/js/app.js74
-rw-r--r--apps/files/js/filelist.js62
-rw-r--r--apps/files/js/filesummary.js10
-rw-r--r--apps/files/lib/AppInfo/Application.php4
-rw-r--r--apps/files/lib/Controller/ApiController.php67
-rw-r--r--apps/files/lib/Controller/ViewController.php52
-rw-r--r--apps/files/lib/Service/UserConfig.php142
-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/main.js18
-rw-r--r--apps/files/src/router/router.js4
-rw-r--r--apps/files/src/views/Navigation.vue61
-rw-r--r--apps/files/src/views/Settings.vue85
-rw-r--r--package-lock.json14
-rw-r--r--package.json2
18 files changed, 429 insertions, 237 deletions
diff --git a/apps/files/appinfo/routes.php b/apps/files/appinfo/routes.php
index 29859d78b0d..0fc494d1173 100644
--- a/apps/files/appinfo/routes.php
+++ b/apps/files/appinfo/routes.php
@@ -91,6 +91,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'
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/js/app.js b/apps/files/js/app.js
index c88ea42a487..a2f8fa58d30 100644
--- a/apps/files/js/app.js
+++ b/apps/files/js/app.js
@@ -56,11 +56,6 @@
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,10 +64,7 @@
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 fileActions = new OCA.Files.FileActions();
@@ -223,8 +215,8 @@
* Sets the currently active view
* @param viewId view id
*/
- setActiveView: function(viewId, options) {
- window._nc_event_bus.emit('files:view:changed', { id: viewId })
+ setActiveView: function(viewId) {
+ window._nc_event_bus.emit('files:navigation:changed', { id: viewId })
},
/**
@@ -254,57 +246,6 @@
$('#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));
-
- window._nc_event_bus.subscribe('files:view:changed', _.bind(this._onNavigationChanged, 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
- });
},
/**
@@ -379,7 +320,7 @@
if (lastId !== this.getActiveView()) {
this.getCurrentAppContainer().trigger(new $.Event('show'));
}
- this.getCurrentAppContainer().trigger(new $.Event('urlChanged', params));
+ // this.getCurrentAppContainer().trigger(new $.Event('urlChanged', params));
window._nc_event_bus.emit('files:navigation:changed')
},
@@ -408,13 +349,18 @@
}
var currentParams = OC.Util.History.parseUrlQuery();
if (currentParams.dir === params.dir && currentParams.view === params.view) {
- if (currentParams.fileid !== params.fileid) {
+ if (parseInt(currentParams.fileid) !== parseInt(params.fileid)) {
// if only fileid changed or was added, replace instead of push
+ console.debug('F2V 1', currentParams.fileid, params.fileid, params);
OC.Util.History.replaceState(this._makeUrlParams(params));
+ return
}
} else {
+ console.debug('F2V 2', params);
OC.Util.History.pushState(this._makeUrlParams(params));
+ return
}
+ console.debug('F2V 3', params, currentParams);
},
/**
diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js
index 89fc3f7e9c5..37aa9139850 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');
@@ -393,6 +395,7 @@
this.$fileList.on('change', 'td.selection>.selectCheckBox', _.bind(this._onClickFileCheckbox, this));
this.$fileList.on('mouseover', 'td.selection', _.bind(this._onMouseOverCheckbox, this));
+ console.debug('F2V', this.$el);
this.$el.on('show', _.bind(this._onShow, this));
this.$el.on('urlChanged', _.bind(this._onUrlChanged, this));
this.$el.find('.select-all').click(_.bind(this._onClickSelectAll, this));
@@ -754,23 +757,22 @@
* Event handler when leaving previously hidden state
*/
_onShow: function(e) {
+ console.debug('F2V', 'onShow', 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;
},
/**
* Event handler for when the URL changed
*/
_onUrlChanged: function(e) {
+ console.debug('F2V', 'onUrlChanged', e);
if (e && _.isString(e.dir)) {
var currentDir = this.getCurrentDirectory();
// this._currentDirectory is NULL when fileList is first initialised
@@ -1407,7 +1409,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 +2373,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 +3297,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 +3445,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/lib/AppInfo/Application.php b/apps/files/lib/AppInfo/Application.php
index f2104be4df8..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),
);
});
diff --git a/apps/files/lib/Controller/ApiController.php b/apps/files/lib/Controller/ApiController.php
index a87b4f9490a..e9d15018d03 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 bool $key
+ * @param string|bool $value
+ * @return JSONResponse
+ */
+ public function setConfig(string $key, string|bool $value): JSONResponse {
+ try {
+ $this->userConfig->setConfig($key, $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();
}
diff --git a/apps/files/lib/Controller/ViewController.php b/apps/files/lib/Controller/ViewController.php
index 63863b3e367..4e81b630bab 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;
}
/**
@@ -236,7 +224,6 @@ class ViewController extends Controller {
'folderPosition' => $sortingValue,
'name' => basename($favElement),
'icon' => 'folder',
- 'quickaccesselement' => 'true'
];
array_push($favoritesSublistArray, $element);
@@ -266,11 +253,10 @@ 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($userId)));
-
$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) {
@@ -328,7 +314,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..e3b863c7333
--- /dev/null
+++ b/apps/files/lib/Service/UserConfig.php
@@ -0,0 +1,142 @@
+<?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 = [
+ [
+ 'key' => 'crop_image_previews',
+ 'default' => true,
+ 'allowed' => [true, false],
+ ],
+ [
+ 'key' => 'show_hidden',
+ 'default' => false,
+ 'allowed' => [true, false],
+ ],
+ ];
+
+ private IConfig $config;
+ private IUser|null $user;
+
+ 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): string|bool {
+ foreach (self::ALLOWED_CONFIGS as $config) {
+ if ($config['key'] === $key) {
+ return $config['default'];
+ }
+ }
+ return '';
+ }
+
+ /**
+ * Set a user config
+ *
+ * @param string $key
+ * @param string $value
+ * @throws \Exception
+ * @throws \InvalidArgumentException
+ */
+ public function setConfig($key, $value) {
+ if (!$this->user) {
+ 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) {
+ throw new \Exception('No user logged in');
+ }
+
+ $userId = $this->user->getUID();
+ $userConfigs = array_map(function(string $key) use ($userId): string|bool {
+ $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/main.js b/apps/files/src/main.js
index 948e1b68aca..3099a4c619c 100644
--- a/apps/files/src/main.js
+++ b/apps/files/src/main.js
@@ -1,4 +1,3 @@
-import './files-app-settings.js'
import './templates.js'
import './legacy/filelistSearch.js'
import processLegacyFilesViews from './legacy/navigationMapper.js'
@@ -7,15 +6,24 @@ import Vue from 'vue'
import NavigationService from './services/Navigation.ts'
import NavigationView from './views/Navigation.vue'
-import router from './router/router.js'
+import SettingsService from './services/Settings.js'
+import SettingsModel from './models/Setting.js'
-// Init Files App Navigation Service
-const Navigation = new NavigationService()
+import router from './router/router.js'
-// Assign Navigation Service to the global OCP.Files
+// 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({
diff --git a/apps/files/src/router/router.js b/apps/files/src/router/router.js
index a2d063a9532..c955cbb2e3e 100644
--- a/apps/files/src/router/router.js
+++ b/apps/files/src/router/router.js
@@ -44,9 +44,5 @@ export default new Router({
name: 'filelist',
props: true,
},
- {
- path: '/not-found',
- name: 'notfound',
- },
],
})
diff --git a/apps/files/src/views/Navigation.vue b/apps/files/src/views/Navigation.vue
index 50f5e6f5d77..8595c447b57 100644
--- a/apps/files/src/views/Navigation.vue
+++ b/apps/files/src/views/Navigation.vue
@@ -36,6 +36,19 @@
:icon="child.iconClass"
:title="child.name" />
</NcAppNavigationItem>
+
+ <!-- Settings toggle -->
+ <template #footer>
+ <NcAppNavigationItem :pinned="true"
+ :title="t('files', 'Files settings')"
+ @click.prevent.stop="openSettings">
+ <Cog slot="icon" :size="20" />
+ </NcAppNavigationItem>
+ </template>
+
+ <!-- Settings modal-->
+ <SettingsModal :open="settingsOpened"
+ @close="onSettingsClose" />
</NcAppNavigation>
</template>
@@ -45,7 +58,9 @@ import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
import NcAppNavigation from '@nextcloud/vue/dist/Components/NcAppNavigation.js'
import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationItem.js'
+import Cog from 'vue-material-design-icons/Cog.vue'
+import SettingsModal from './Settings.vue'
import Navigation from '../services/Navigation.ts'
import logger from '../logger.js'
@@ -53,8 +68,10 @@ export default {
name: 'Navigation',
components: {
+ Cog,
NcAppNavigation,
NcAppNavigationItem,
+ SettingsModal,
},
props: {
@@ -67,7 +84,7 @@ export default {
data() {
return {
- key: 'value',
+ settingsOpened: false,
}
},
@@ -110,7 +127,7 @@ export default {
watch: {
currentView(view, oldView) {
- logger.debug('View changed', { view })
+ logger.debug('View changed', { id: view.id, view })
this.showView(view, oldView)
},
},
@@ -128,21 +145,57 @@ export default {
* @param {Navigation} oldView the old active view
*/
showView(view, oldView) {
+ // Closing any opened sidebar
+ 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')
})
- document.querySelector('#app-content #app-content-' + this.currentView.id + '.viewcontainer').classList.remove('hidden')
+ newAppContent.classList.remove('hidden')
+
+ // Legacy event
+ console.debug('F2V', jQuery(newAppContent))
+
+ // previousItemId: oldItemId,
+ // dir: itemDir,
+ // view: itemView
+ $(newAppContent).trigger(new $.Event('show', { itemId: view.id, dir: '/' }))
+ $(newAppContent).trigger(new $.Event('urlChanged', { itemId: view.id, dir: '/' }))
}
+
this.Navigation.setActive(view)
- emit('files:view:changed', view)
+ emit('files:navigation:changed', 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 })
},
+
+ /**
+ * Open the settings modal and update the settings API entries
+ */
+ openSettings() {
+ this.settingsOpened = true
+ OCA.Files.Settings.settings.forEach(setting => setting.open())
+ },
+
+ /**
+ * Close the settings modal and update the settings API entries
+ */
+ onSettingsClose() {
+ this.settingsOpened = false
+ OCA.Files.Settings.settings.forEach(setting => setting.close())
+ },
},
}
</script>
diff --git a/apps/files/src/views/Settings.vue b/apps/files/src/views/Settings.vue
index 5d2aff2f49a..e1c7d8b42bf 100644
--- a/apps/files/src/views/Settings.vue
+++ b/apps/files/src/views/Settings.vue
@@ -20,26 +20,99 @@
-
-->
<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 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 type="text" readonly="readonly" :value="webdavUrl" />
+ <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 NcInputField from '@nextcloud/vue/dist/Components/NcInputField'
+import Setting from '../components/Setting.vue'
+
+import { generateRemoteUrl, generateUrl } from '@nextcloud/router'
+import { getCurrentUser } from '@nextcloud/auth'
+import { loadState } from '@nextcloud/initial-state'
+import { emit } from '@nextcloud/event-bus'
+import axios from '@nextcloud/axios'
+
+const userConfig = loadState('files', 'config')
export default {
name: 'Settings',
components: {
+ NcAppSettingsDialog,
+ NcAppSettingsSection,
+ NcCheckboxRadioSwitch,
+ NcInputField,
Setting,
},
+
+ props: {
+ open: {
+ type: Boolean,
+ default: false,
+ },
+ },
+
data() {
return {
+
+ ...userConfig,
+
+ // Settings API
settings: OCA.Files.Settings.settings,
+
+ // Webdav infos
+ webdavUrl: generateRemoteUrl('dav/files/' + getCurrentUser()?.uid),
+ webdavDocs: 'https://docs.nextcloud.com/server/stable/go.php?to=user-webdav',
}
},
+
+ methods: {
+ onClose() {
+ this.$emit('close')
+ },
+
+ setConfig(key, value) {
+ emit('files:config:updated', { key, value })
+ axios.post(generateUrl('/apps/files/api/v1/config/' + key), {
+ value,
+ })
+ },
+ },
}
</script>
diff --git a/package-lock.json b/package-lock.json
index d35c251dff1..d6d26997736 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,7 +24,7 @@
"@nextcloud/l10n": "^1.6.0",
"@nextcloud/logger": "^2.4.0",
"@nextcloud/moment": "^1.2.1",
- "@nextcloud/password-confirmation": "^4.0.2",
+ "@nextcloud/password-confirmation": "^4.0.3",
"@nextcloud/paths": "^2.1.0",
"@nextcloud/router": "^2.0.0",
"@nextcloud/sharing": "^0.1.0",
@@ -4290,9 +4290,9 @@
}
},
"node_modules/@nextcloud/password-confirmation": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@nextcloud/password-confirmation/-/password-confirmation-4.0.2.tgz",
- "integrity": "sha512-5UwPka9hHOOaoevAE9PpPzZYepKJURuogggAp71BrGl8z1mvE8iMckRQ3B7TYwWX5p9pmMtdWtflWIsVA0uvhw==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@nextcloud/password-confirmation/-/password-confirmation-4.0.3.tgz",
+ "integrity": "sha512-kS7yREq3F4DiXpmbxVsm9Ezv58+1BT5PPrrZV+VjQtUY69Rjc0xP9X5fbZH+BBT9LXHPypN32qbBuvPpgVZZqA==",
"dependencies": {
"@nextcloud/axios": "^2.0.0",
"@nextcloud/l10n": "^1.6.0",
@@ -26826,9 +26826,9 @@
}
},
"@nextcloud/password-confirmation": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/@nextcloud/password-confirmation/-/password-confirmation-4.0.2.tgz",
- "integrity": "sha512-5UwPka9hHOOaoevAE9PpPzZYepKJURuogggAp71BrGl8z1mvE8iMckRQ3B7TYwWX5p9pmMtdWtflWIsVA0uvhw==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/@nextcloud/password-confirmation/-/password-confirmation-4.0.3.tgz",
+ "integrity": "sha512-kS7yREq3F4DiXpmbxVsm9Ezv58+1BT5PPrrZV+VjQtUY69Rjc0xP9X5fbZH+BBT9LXHPypN32qbBuvPpgVZZqA==",
"requires": {
"@nextcloud/axios": "^2.0.0",
"@nextcloud/l10n": "^1.6.0",
diff --git a/package.json b/package.json
index 6b0e5e852e9..de04893bb2d 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
"@nextcloud/l10n": "^1.6.0",
"@nextcloud/logger": "^2.4.0",
"@nextcloud/moment": "^1.2.1",
- "@nextcloud/password-confirmation": "^4.0.2",
+ "@nextcloud/password-confirmation": "^4.0.3",
"@nextcloud/paths": "^2.1.0",
"@nextcloud/router": "^2.0.0",
"@nextcloud/sharing": "^0.1.0",