From 5c987a0ff4530cd0951920fcbfaf97411aeec17a Mon Sep 17 00:00:00 2001 From: =?utf8?q?John=20Molakvo=C3=A6?= Date: Wed, 14 Dec 2022 16:54:35 +0100 Subject: [PATCH] Port settings to Modal MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: John Molakvoæ --- apps/files/appinfo/routes.php | 10 ++ .../composer/composer/autoload_classmap.php | 1 + .../composer/composer/autoload_static.php | 1 + apps/files/js/app.js | 74 ++------- apps/files/js/filelist.js | 62 ++++---- apps/files/js/filesummary.js | 10 +- apps/files/lib/AppInfo/Application.php | 4 +- apps/files/lib/Controller/ApiController.php | 67 ++++++--- apps/files/lib/Controller/ViewController.php | 52 +++---- apps/files/lib/Service/UserConfig.php | 142 ++++++++++++++++++ apps/files/src/components/Setting.vue | 2 - apps/files/src/files-app-settings.js | 57 ------- apps/files/src/main.js | 18 ++- apps/files/src/router/router.js | 4 - apps/files/src/views/Navigation.vue | 61 +++++++- apps/files/src/views/Settings.vue | 85 ++++++++++- package-lock.json | 14 +- package.json | 2 +- 18 files changed, 429 insertions(+), 237 deletions(-) create mode 100644 apps/files/lib/Service/UserConfig.php delete mode 100644 apps/files/src/files-app-settings.js 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 @@ -90,6 +90,16 @@ $application->registerRoutes( 'url' => '/api/v1/recent/', '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', 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; } /** @@ -282,17 +280,48 @@ class ApiController extends Controller { return new Response(); } + /** + * 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 @@ + + * + * @author John Molakvoæ + * + * @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 . + * + */ +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 { }, } - 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 - * @copyright Copyright (c) 2019 John Molakvoæ - * - * @author Gary Kim - * @author John Molakvoæ - * - * @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 . - * - */ - -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" /> + + + + + + @@ -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()) + }, }, } 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 @@ - --> 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", -- 2.39.5