Compare commits

...

36 Commits

Author SHA1 Message Date
Simon Vieille 854beb8078 release v1.19.0 2020-10-19 18:26:17 +02:00
Simon Vieille 709f938f48 update screenshots 2020-10-19 18:24:08 +02:00
Simon Vieille 80700cac32 add option: the menu is enabled by default for users 2020-10-19 13:44:07 +02:00
Simon Vieille a71a8c7208 add documentation 2020-10-16 13:42:23 +02:00
Simon Vieille cf80c3512c add licence 2020-10-16 11:46:25 +02:00
Simon Vieille a5d88317e6 update screenshots 2020-10-16 11:18:48 +02:00
Simon Vieille b2103a012c release v1.18.0 2020-10-16 11:17:44 +02:00
Simon Vieille 847b3696cb Merge branch 'feature/ref' into develop 2020-10-16 11:08:37 +02:00
Simon Vieille c67bfa0ce4 indentation 2020-10-16 11:07:45 +02:00
Simon Vieille 7edd6d6d84 refactor menus with separated components 2020-10-16 11:04:03 +02:00
Simon Vieille ae148aba02 Merge branch 'feature/issue44' into develop 2020-10-15 16:44:57 +02:00
Simon Vieille 575f963902 indentation 2020-10-15 15:47:28 +02:00
Simon Vieille ae74e745db add menu element: link to settings 2020-10-15 15:46:59 +02:00
Simon Vieille b749a9be2e add option: show link to settings 2020-10-15 15:46:43 +02:00
Simon Vieille d279fbd813 change typo 2020-10-14 16:23:09 +02:00
Simon Vieille 0d98a40536 release v1.17.0 2020-10-14 16:16:06 +02:00
Simon Vieille 5e894f9f08 add admin actions: remove cache, export configuration 2020-10-14 16:14:50 +02:00
Simon Vieille 731d4f826b release v1.16.4 2020-10-14 10:00:21 +02:00
Simon Vieille 4225b157e0 app:check compliance 2020-10-14 09:57:42 +02:00
Simon Vieille 0eefcd23cd app:check compliance (info.xml) 2020-10-14 09:33:19 +02:00
Simon Vieille ead4ce9a6f release v1.16.3 2020-10-13 14:33:07 +02:00
Simon Vieille baa2a0748e fix links that must be opened in new window 2020-10-13 14:32:15 +02:00
Simon Vieille 5b5daa92b9 remove useless call 2020-10-13 13:26:08 +02:00
Simon Vieille b19911b02f release v1.16.2 2020-10-13 11:09:56 +02:00
Simon Vieille a57017e80b fix issue with personal settings when global settings are forced 2020-10-13 11:09:14 +02:00
Simon Vieille dfe6ad7bd4 release v1.16.1 2020-10-12 09:11:28 +02:00
Simon Vieille 2653fbbfa7 add cache and loader for big menu 2020-10-11 17:06:34 +02:00
Simon Vieille 62e5860531 fix side menu covers apps left menu 2020-10-11 16:15:51 +02:00
Simon Vieille 46af933a4e fix side menu covers apps left menu 2020-10-09 09:53:37 +02:00
Simon Vieille 9de22bf5e9 update the discussion link 2020-10-06 13:33:02 +02:00
Simon Vieille b41d413059 update properties of controller 2020-09-29 11:25:31 +02:00
Simon Vieille cc0fac6486 release v1.16.0 2020-09-24 14:29:12 +02:00
Simon Vieille 4d1ec41a9b release v1.16.0 2020-09-24 14:27:35 +02:00
Simon Vieille 034b24369a Merge branch 'feature/issue38' into develop 2020-09-24 14:26:46 +02:00
Simon Vieille b5874792f1 update screenshots 2020-09-24 14:26:43 +02:00
Simon Vieille c7f9fd1b8b add an option to force settings to users 2020-09-24 14:26:25 +02:00
32 changed files with 956 additions and 256 deletions
+9 -2
View File
@@ -17,11 +17,18 @@ You like this app and you want to support me? [Buy me a coffee](https://www.buym
Installation and upgrade Installation and upgrade
------------------------ ------------------------
Custom menu is available from the app store. If you want to install it from source: go to https://gitnet.fr/deblan/side_menu/releases and download the last release (side_menu_vX.Y.Z.zip). Copy the content into `apps`. Custom menu is available from the app store.
```
$ cd /path/to/nextcloud
$ php occ app:install side_menu
```
If you want to install it from source, go to https://gitnet.fr/deblan/side_menu/releases and copy the link to the last release (side_menu_vX.Y.Z.tar.gz). Then:
``` ```
$ cd /path/to/nextcloud/apps $ cd /path/to/nextcloud/apps
$ unzip /path/to/side_menu_vX.Y.Z.zip # tar xvf /path/to/side_menu_vX.Y.Z.tar.gz $ curl -sS https://gitnet.fr/attachments/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | tar xvfz -
``` ```
Administrators can edit many settings using the administration page. Administrators can edit many settings using the administration page.
+10 -9
View File
@@ -1,9 +1,10 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<info> <info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
<id>side_menu</id> <id>side_menu</id>
<name>Custom menu</name> <name>Custom menu</name>
<summary>Modify the display of the menu.</summary> <summary>Modify the display of the menu.</summary>
<description><![CDATA[Side menu allows you to modify the position of the main menu by creating a panel on the left of the interface or with a big menu on the top. <description><![CDATA[Custom menu allows you to modify the position of the main menu by creating a panel on the left of the interface or with a big menu on the top.
You can also define apps that must be displayed in the top menu. Fully customisable. You can also define apps that must be displayed in the top menu. Fully customisable.
This application is rather suitable for instances that activate a lot of applications. This application is rather suitable for instances that activate a lot of applications.
@@ -14,18 +15,22 @@ Comptatible with AppOrder.
You can report a bug or request a feature by opening an issue. You can report a bug or request a feature by opening an issue.
You like this app and you want to support me? If you like this application and want to support the development:
* ☕ [Buy me a coffee](https://www.buymeacoffee.com/deblan) * ☕ [Buy me a coffee](https://www.buymeacoffee.com/deblan)
* 😃 [Leave a comment](https://apps.nextcloud.com/apps/side_menu#comments) * 😃 [Leave a comment](https://apps.nextcloud.com/apps/side_menu#comments)
]]></description> ]]></description>
<version>1.19.0</version>
<licence>agpl</licence> <licence>agpl</licence>
<author mail="contact@deblan.fr" homepage="https://www.deblan.io/">Simon Vieille</author> <author mail="contact@deblan.fr" homepage="https://www.deblan.io/">Simon Vieille</author>
<version>1.15.0</version>
<namespace>SideMenu</namespace> <namespace>SideMenu</namespace>
<documentation>
<admin>https://gitnet.fr/deblan/side_menu/src/branch/master/README.md</admin>
<developer>https://gitnet.fr/deblan/side_menu/src/branch/master/README.md</developer>
</documentation>
<category>customization</category> <category>customization</category>
<website>https://gitnet.fr/deblan/side_menu</website> <website>https://gitnet.fr/deblan/side_menu</website>
<discussion>https://deblan.cloud/index.php/call/fffxkc7h</discussion> <discussion>https://matrix.to/#/!GhfQePnOSPYpYEunWM:neutralnetwork.org</discussion>
<bugs>https://gitnet.fr/deblan/side_menu/issues</bugs> <bugs>https://gitnet.fr/deblan/side_menu/issues</bugs>
<repository type="git">https://gitnet.fr/deblan/side_menu</repository> <repository type="git">https://gitnet.fr/deblan/side_menu</repository>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc19_default_menu.png</screenshot> <screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc19_default_menu.png</screenshot>
@@ -34,10 +39,6 @@ You like this app and you want to support me?
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc18_menu_always_displayed.png</screenshot> <screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc18_menu_always_displayed.png</screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc20_big_menu_responsive.png</screenshot> <screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/nc20_big_menu_responsive.png</screenshot>
<screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/personal_settings.png</screenshot> <screenshot>https://gitnet.fr/deblan/side_menu/raw/branch/master/screenshots/personal_settings.png</screenshot>
<documentation>
<admin>https://gitnet.fr/deblan/side_menu/src/branch/master/README.md</admin>
<developer>https://gitnet.fr/deblan/side_menu/src/branch/master/README.md</developer>
</documentation>
<dependencies> <dependencies>
<nextcloud min-version="18" max-version="20"/> <nextcloud min-version="18" max-version="20"/>
</dependencies> </dependencies>
+7 -5
View File
@@ -19,10 +19,12 @@
return [ return [
'routes' => [ 'routes' => [
['name' => 'Css#stylesheet', 'url' => '/css/stylesheet', 'verb' => 'GET'], ['name' => 'Css#stylesheet', 'url' => '/css/stylesheet', 'verb' => 'GET'],
['name' => 'Js#script', 'url' => '/js/script', 'verb' => 'GET'], ['name' => 'Js#script', 'url' => '/js/script', 'verb' => 'GET'],
['name' => 'Js#config', 'url' => '/js/config', 'verb' => 'GET'], ['name' => 'Js#config', 'url' => '/js/config', 'verb' => 'GET'],
['name' => 'Nav#items', 'url' => '/nav/items', 'verb' => 'GET'], ['name' => 'Nav#items', 'url' => '/nav/items', 'verb' => 'GET'],
['name' => 'PersonalSetting#valueSet', 'url' => '/personalSetting/valueSet', 'verb' => 'POST'], ['name' => 'PersonalSetting#valueSet', 'url' => '/personalSetting/valueSet', 'verb' => 'POST'],
['name' => 'AdminSetting#removeCache', 'url' => '/admin/cache/remove', 'verb' => 'GET'],
['name' => 'AdminSetting#exportConfiguration', 'url' => '/admin/config/export', 'verb' => 'GET'],
], ],
]; ];
+38
View File
@@ -37,6 +37,34 @@
margin-left: 5px; margin-left: 5px;
} }
.side-menu-settings {
margin-right: 9px;
margin-top: 2px;
float: right;
line-height: 34px;
height: 28px;
display: none;
}
.side-menu-settings a {
color: var(--side-menu-text-color, #fff);
display: block;
padding: 4px 7px;
}
.side-menu-settings:hover a, .side-menu-settings a:active, .side-menu-settings a:focus {
background: var(--side-menu-current-app-background-color, #444);
}
.side-menu-settings img {
vertical-align: bottom;
margin-left: 3px;
}
#side-menu.open .side-menu-settings {
display: block;
}
.side-menu-opener { .side-menu-opener {
background: var(--side-menu-opener, url('../img/side-menu-opener.svg')); background: var(--side-menu-opener, url('../img/side-menu-opener.svg'));
height: 40px; height: 40px;
@@ -175,6 +203,16 @@
color: var(--side-menu-text-color, #fff); color: var(--side-menu-text-color, #fff);
} }
.side-menu-loader {
text-align: center;
}
.side-menu-loader svg {
width: 38px;
margin: auto;
stroke: var(--side-menu-text-color, #fff);
}
.side-menu-big .side-menu-app-icon { .side-menu-big .side-menu-app-icon {
vertical-align: middle; vertical-align: middle;
margin-top: -2px; margin-top: -2px;
+17
View File
@@ -0,0 +1,17 @@
<!-- By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL -->
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg" stroke="#fff">
<g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 694 B

+13 -3
View File
@@ -55,9 +55,19 @@ class Application extends App
public function isEnabled(): bool public function isEnabled(): bool
{ {
$enabled = true; $enabled = true;
$isForced = (bool) $this->config->getAppValue(self::APP_ID, 'force', '0');
if (null !== $this->user) { if (null !== $this->user && !$isForced) {
$enabled = (bool) $this->config->getUserValue($this->user->getUid(), self::APP_ID, 'enabled', '1'); $enabled = (bool) $this->config->getUserValue(
$this->user->getUid(),
self::APP_ID,
'enabled',
$this->config->getAppValue(
self::APP_ID,
'default-enabled',
'1'
)
);
} }
return $enabled; return $enabled;
@@ -71,7 +81,7 @@ class Application extends App
$container = $this->getContainer(); $container = $this->getContainer();
$container->registerService('AppRepository', function (ContainerInterface $c) { $container->registerService('AppRepository', function (ContainerInterface $c) {
return new AppRepository(new OC_App()); return new AppRepository();
}); });
$container->registerService('ConfigProxy', function (ContainerInterface $c) { $container->registerService('ConfigProxy', function (ContainerInterface $c) {
+92
View File
@@ -0,0 +1,92 @@
<?php
/**
* @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\SideMenu\Controller;
use OCA\SideMenu\AppInfo\Application;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\DataDownloadResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\AppFramework\Http\Response;
use OCP\IConfig;
use OCP\IRequest;
use OCP\IURLGenerator;
class AdminSettingController extends Controller
{
/**
* @var IConfig
*/
protected $config;
/**
* @var IURLGenerator
*/
protected $urlGenerator;
public function __construct($appName, IRequest $request, IConfig $config, IURLGenerator $urlGenerator)
{
parent::__construct($appName, $request);
$this->config = $config;
$this->urlGenerator = $urlGenerator;
}
/**
* @NoCSRFRequired
*
* @return RedirectResponse
*/
public function removeCache()
{
$this->config->setAppValue(Application::APP_ID, 'cache-categories', '[]');
return new RedirectResponse($this->urlGenerator->linkToRoute('settings.AdminSettings.index', [
'section' => Application::APP_ID,
]).'#more');
}
/**
* @NoCSRFRequired
*
* @return Response
*/
public function exportConfiguration()
{
$keys = $this->config->getAppKeys(Application::APP_ID);
$config = [];
$excludedKeys = [
'cache',
'cache-categories',
];
foreach ($keys as $key) {
if (in_array($key, $excludedKeys)) {
continue;
}
$config[$key] = $this->config->getAppValue(Application::APP_ID, $key);
}
return new DataDownloadResponse(
json_encode($config, JSON_PRETTY_PRINT),
'config.json',
'text/json'
);
}
}
+2 -2
View File
@@ -67,13 +67,13 @@ class CssController extends Controller
{ {
$backgroundColor = $this->config->getAppValue('background-color', '#333333'); $backgroundColor = $this->config->getAppValue('background-color', '#333333');
$backgroundColorTo = $this->config->getAppValue('background-color-to', $backgroundColor); $backgroundColorTo = $this->config->getAppValue('background-color-to', $backgroundColor);
$isForced = $this->config->getAppValueBool('force', '0');
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]'); $topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
if ($this->user) { if ($this->user) {
$userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]'); $userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]');
if (!empty($userTopMenuApps)) { if (!empty($userTopMenuApps) && !$isForced) {
$topMenuApps = $userTopMenuApps; $topMenuApps = $userTopMenuApps;
} }
} }
+40 -11
View File
@@ -28,6 +28,7 @@ use OCP\AppFramework\Http\JSONResponse;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUserSession; use OCP\IUserSession;
use OCP\L10N\IFactory;
class JsController extends Controller class JsController extends Controller
{ {
@@ -46,14 +47,25 @@ class JsController extends Controller
*/ */
protected $themingDefaults; protected $themingDefaults;
public function __construct(string $appName, IRequest $request, ConfigProxy $config, ThemingDefaults $themingDefaults) /**
{ * @var IFactory
*/
protected $l10nFactory;
public function __construct(
string $appName,
IRequest $request,
ConfigProxy $config,
ThemingDefaults $themingDefaults,
IFactory $l10nFactory
) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
$this->themingDefaults = $themingDefaults; $this->themingDefaults = $themingDefaults;
$this->user = OC::$server[IUserSession::class]->getUser(); $this->user = OC::$server[IUserSession::class]->getUser();
$this->config = $config; $this->config = $config;
$this->l10nFactory = $l10nFactory;
} }
/** /**
@@ -84,32 +96,48 @@ class JsController extends Controller
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]'); $topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
$targetBlankApps = $this->config->getAppValueArray('target-blank-apps', '[]'); $targetBlankApps = $this->config->getAppValueArray('target-blank-apps', '[]');
$useAvatar = $this->config->getAppValueBool('use-avatar', '0'); $useAvatar = $this->config->getAppValueBool('use-avatar', '0');
$isForced = $this->config->getAppValueBool('force', '0');
$avatar = null; $avatar = null;
$settings = null;
if ($this->user) { if ($this->user) {
$userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]'); $userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]');
if (!empty($userTopMenuApps)) { if (!empty($userTopMenuApps) && !$isForced) {
$topMenuApps = $userTopMenuApps; $topMenuApps = $userTopMenuApps;
} }
$userTargetBlankMode = $this->config->getUserValueInt($this->user, 'target-blank-mode', '1'); $userTargetBlankMode = $this->config->getUserValueInt($this->user, 'target-blank-mode', '1');
$userTargetBlankApps = $this->config->getUserValueArray($this->user, 'target-blank-apps', '[]'); $userTargetBlankApps = $this->config->getUserValueArray($this->user, 'target-blank-apps', '[]');
if (2 === $userTargetBlankMode) { if (2 === $userTargetBlankMode && !$isForced) {
$targetBlankApps = $userTargetBlankApps; $targetBlankApps = $userTargetBlankApps;
} }
$isAvatarSet = OC::$server->getAvatarManager()->getAvatar($this->user->getUid())->exists(); $isAvatarSet = OC::$server->getAvatarManager()->getAvatar($this->user->getUid())->exists();
if ($useAvatar && $isAvatarSet) { if ($useAvatar && $isAvatarSet) {
$avatar = OC::$server->getURLGenerator()->linkToRoute( $avatar = OC::$server->getURLGenerator()->linkToRoute('core.avatar.getAvatar', [
'core.avatar.getAvatar', [ 'userId' => $this->user->getUid(),
'userId' => $this->user->getUid(), 'size' => 128,
'size' => 128, 'v' => $this->config->getUserValueInt($this->user, 'avatar', 'version', 0),
'v' => $this->config->getUserValueInt($this->user, 'avatar', 'version', 0), ]);
] }
);
if ($this->config->getAppValueBool('show-settings', '0')) {
$settingsNav = OC::$server->getNavigationManager()->getAll('settings');
if (isset($settingsNav['settings'])) {
$settings = [
'href' => $settingsNav['settings']['href'],
'name' => $settingsNav['settings']['name'],
'avatar' => OC::$server->getURLGenerator()->linkToRoute('core.avatar.getAvatar', [
'userId' => $this->user->getUid(),
'size' => 32,
'v' => $this->config->getUserValueInt($this->user, 'avatar', 'version', 0),
]),
];
}
} }
} }
@@ -125,6 +153,7 @@ class JsController extends Controller
'avatar' => $avatar, 'avatar' => $avatar,
'top-menu-apps' => $topMenuApps, 'top-menu-apps' => $topMenuApps,
'target-blank-apps' => $targetBlankApps, 'target-blank-apps' => $targetBlankApps,
'settings' => $settings,
'logo' => $this->themingDefaults->getLogo(), 'logo' => $this->themingDefaults->getLogo(),
]; ];
} }
+22 -5
View File
@@ -21,10 +21,12 @@ namespace OCA\SideMenu\Controller;
use OC; use OC;
use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\App\AppStore\Fetcher\CategoryFetcher;
use OC\URLGenerator; use OC\URLGenerator;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\AppRepository; use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\ConfigProxy; use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\JSONResponse;
use OCP\IConfig;
use OCP\IL10N; use OCP\IL10N;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUserSession; use OCP\IUserSession;
@@ -37,6 +39,11 @@ class NavController extends Controller
*/ */
protected $config; protected $config;
/**
* @var IConfig
*/
protected $iConfig;
/** /**
* @var AppRepository * @var AppRepository
*/ */
@@ -60,16 +67,18 @@ class NavController extends Controller
public function __construct( public function __construct(
string $appName, string $appName,
IRequest $request, IRequest $request,
IConfig $iConfig,
ConfigProxy $config, ConfigProxy $config,
AppRepository $appRepository, AppRepository $appRepository,
CategoryFetcher $categoryFetcher, CategoryFetcher $categoryFetcher,
URLGenerator $router, URLGenerator $router,
IL10N $trans, IL10N $trans,
IFactory $l10nFactory) IFactory $l10nFactory
{ ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
$this->config = $config; $this->config = $config;
$this->iConfig = $iConfig;
$this->appRepository = $appRepository; $this->appRepository = $appRepository;
$this->categoryFetcher = $categoryFetcher; $this->categoryFetcher = $categoryFetcher;
$this->l10nFactory = $l10nFactory; $this->l10nFactory = $l10nFactory;
@@ -87,9 +96,9 @@ class NavController extends Controller
{ {
$apps = $this->appRepository->getVisibleApps(); $apps = $this->appRepository->getVisibleApps();
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2); $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
$categoriesLabels = $this->categoryFetcher->get();
$externalSitesInTopMenu = $this->config->getAppValueBool('external-sites-in-top-menu', '0'); $externalSitesInTopMenu = $this->config->getAppValueBool('external-sites-in-top-menu', '0');
$user = OC::$server[IUserSession::class]->getUser(); $user = OC::$server[IUserSession::class]->getUser();
$isForced = $this->config->getAppValueBool('force', '0');
$appsCategories = []; $appsCategories = [];
$categoriesAppsCount = []; $categoriesAppsCount = [];
$items = []; $items = [];
@@ -100,10 +109,18 @@ class NavController extends Controller
]); ]);
} }
$categoriesLabels = $this->config->getAppValueArray('cache-categories', '[]');
if (empty($categoriesLabels)) {
$categoriesLabels = $this->categoryFetcher->get();
$this->iConfig->setAppValue(Application::APP_ID, 'cache-categories', json_encode($categoriesLabels));
}
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]'); $topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
$userTopMenuApps = $this->config->getUserValueArray($user, 'top-menu-apps', '[]'); $userTopMenuApps = $this->config->getUserValueArray($user, 'top-menu-apps', '[]');
if (!empty($userTopMenuApps)) { if (!$isForced && !empty($userTopMenuApps)) {
$topMenuApps = $userTopMenuApps; $topMenuApps = $userTopMenuApps;
} }
@@ -135,7 +152,7 @@ class NavController extends Controller
foreach ($categories as $category) { foreach ($categories as $category) {
if (!isset($items[$category])) { if (!isset($items[$category])) {
$items[$category] = [ $items[$category] = [
'name' => $categoriesLabels[$category], 'name' => $categoriesLabels[$category] ?? $category,
'apps' => [], 'apps' => [],
]; ];
} }
+14 -3
View File
@@ -24,24 +24,31 @@ use OCP\IConfig;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUserSession; use OCP\IUserSession;
use OCA\SideMenu\AppInfo\Application; use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\ConfigProxy;
class PersonalSettingController extends Controller class PersonalSettingController extends Controller
{ {
/** /**
* @var \OCP\IConfig * @var IConfig
*/ */
protected $config; protected $config;
/**
* @var ConfigProxy
*/
protected $configProxy;
/** /**
* @var IUserSession * @var IUserSession
*/ */
private $userSession; protected $userSession;
public function __construct($appName, IRequest $request, IConfig $config, IUserSession $userSession) public function __construct($appName, IRequest $request, IConfig $config, ConfigProxy $configProxy, IUserSession $userSession)
{ {
parent::__construct($appName, $request); parent::__construct($appName, $request);
$this->config = $config; $this->config = $config;
$this->configProxy = $configProxy;
$this->userSession = $userSession; $this->userSession = $userSession;
} }
@@ -104,6 +111,10 @@ class PersonalSettingController extends Controller
} }
} }
if ($this->configProxy->getAppValueBool('force', '0')) {
$doSave = false;
}
if ($doSave) { if ($doSave) {
$this->config->setUserValue($user->getUid(), Application::APP_ID, $name, $value); $this->config->setUserValue($user->getUid(), Application::APP_ID, $name, $value);
+2 -3
View File
@@ -2,7 +2,6 @@
namespace OCA\SideMenu\Service; namespace OCA\SideMenu\Service;
use OC_App;
use OCP\L10N\IFactory; use OCP\L10N\IFactory;
/** /**
@@ -13,7 +12,7 @@ use OCP\L10N\IFactory;
class AppRepository class AppRepository
{ {
/** /**
* @var OC_App * @var \OC_App
*/ */
protected $ocApp; protected $ocApp;
@@ -22,7 +21,7 @@ class AppRepository
*/ */
protected $l10nFactory; protected $l10nFactory;
public function __construct(OC_App $ocApp, IFactory $l10nFactory) public function __construct(\OC_App $ocApp, IFactory $l10nFactory)
{ {
$this->ocApp = $ocApp; $this->ocApp = $ocApp;
$this->l10nFactory = $l10nFactory; $this->l10nFactory = $l10nFactory;
+29 -26
View File
@@ -18,13 +18,13 @@
namespace OCA\SideMenu\Settings; namespace OCA\SideMenu\Settings;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\AppRepository; use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCP\IConfig;
use OCP\IL10N; use OCP\IL10N;
use OCP\ILogger; use OCP\ILogger;
use OCP\Settings\ISettings; use OCP\Settings\ISettings;
use OCA\SideMenu\AppInfo\Application;
class Admin implements ISettings class Admin implements ISettings
{ {
@@ -39,7 +39,7 @@ class Admin implements ISettings
private $logger; private $logger;
/** /**
* @var IConfig * @var ConfigProxy
*/ */
private $config; private $config;
@@ -48,7 +48,7 @@ class Admin implements ISettings
*/ */
private $appRepository; private $appRepository;
public function __construct(IL10N $l, ILogger $logger, IConfig $config, AppRepository $appRepository) public function __construct(IL10N $l, ILogger $logger, ConfigProxy $config, AppRepository $appRepository)
{ {
$this->l = $l; $this->l = $l;
$this->logger = $logger; $this->logger = $logger;
@@ -61,32 +61,35 @@ class Admin implements ISettings
*/ */
public function getForm() public function getForm()
{ {
$backgroundColor = $this->config->getAppValue(Application::APP_ID, 'background-color', '#333333'); $backgroundColor = $this->config->getAppValue('background-color', '#333333');
$backgroundColorTo = $this->config->getAppValue(Application::APP_ID, 'background-color-to', $backgroundColor); $backgroundColorTo = $this->config->getAppValue('background-color-to', $backgroundColor);
$parameters = [ $parameters = [
'background-color' => $backgroundColor, 'background-color' => $backgroundColor,
'background-color-to' => $backgroundColorTo, 'background-color-to' => $backgroundColorTo,
'current-app-background-color' => $this->config->getAppValue(Application::APP_ID, 'current-app-background-color', '#444444'), 'current-app-background-color' => $this->config->getAppValue('current-app-background-color', '#444444'),
'loader-color' => $this->config->getAppValue(Application::APP_ID, 'loader-color', '#0e75ac'), 'loader-color' => $this->config->getAppValue('loader-color', '#0e75ac'),
'icon-invert-filter' => (int) $this->config->getAppValue(Application::APP_ID, 'icon-invert-filter', '0'), 'icon-invert-filter' => $this->config->getAppValueInt('icon-invert-filter', '0'),
'icon-opacity' => (int) $this->config->getAppValue(Application::APP_ID, 'icon-opacity', '100'), 'icon-opacity' => $this->config->getAppValueInt('icon-opacity', '100'),
'loader-enabled' => $this->config->getAppValue(Application::APP_ID, 'loader-enabled', '1'), 'loader-enabled' => $this->config->getAppValue('loader-enabled', '1'),
'text-color' => $this->config->getAppValue(Application::APP_ID, 'text-color', '#FFFFFF'), 'text-color' => $this->config->getAppValue('text-color', '#FFFFFF'),
'cache' => $this->config->getAppValue(Application::APP_ID, 'cache', '0'), 'cache' => $this->config->getAppValue('cache', '0'),
'opener' => $this->config->getAppValue(Application::APP_ID, 'opener', 'side-menu-opener'), 'opener' => $this->config->getAppValue('opener', 'side-menu-opener'),
'always-displayed' => $this->config->getAppValue(Application::APP_ID, 'always-displayed', '0'), 'always-displayed' => $this->config->getAppValue('always-displayed', '0'),
'big-menu' => $this->config->getAppValue(Application::APP_ID, 'big-menu', '0'), 'big-menu' => $this->config->getAppValue('big-menu', '0'),
'display-logo' => $this->config->getAppValue(Application::APP_ID, 'display-logo', '1'), 'display-logo' => $this->config->getAppValue('display-logo', '1'),
'use-avatar' => $this->config->getAppValue(Application::APP_ID, 'use-avatar', '0'), 'use-avatar' => $this->config->getAppValue('use-avatar', '0'),
'opener-position' => $this->config->getAppValue(Application::APP_ID, 'opener-position', 'before'), 'opener-position' => $this->config->getAppValue('opener-position', 'before'),
'opener-hover' => $this->config->getAppValue(Application::APP_ID, 'opener-hover', '0'), 'opener-hover' => $this->config->getAppValue('opener-hover', '0'),
'opener-only' => $this->config->getAppValue(Application::APP_ID, 'opener-only', '0'), 'opener-only' => $this->config->getAppValue('opener-only', '0'),
'hide-when-no-apps' => $this->config->getAppValue(Application::APP_ID, 'hide-when-no-apps', '0'), 'show-settings' => $this->config->getAppValue('show-settings', '0'),
'size-icon' => $this->config->getAppValue(Application::APP_ID, 'size-icon', 'normal'), 'hide-when-no-apps' => $this->config->getAppValue('hide-when-no-apps', '0'),
'size-text' => $this->config->getAppValue(Application::APP_ID, 'size-text', 'normal'), 'size-icon' => $this->config->getAppValue('size-icon', 'normal'),
'target-blank-apps' => (array) json_decode($this->config->getAppValue(Application::APP_ID, 'target-blank-apps', '[]'), true), 'size-text' => $this->config->getAppValue('size-text', 'normal'),
'top-menu-apps' => (array) json_decode($this->config->getAppValue(Application::APP_ID, 'top-menu-apps', '[]'), true), 'force' => $this->config->getAppValue('force', '0'),
'target-blank-apps' => $this->config->getAppValueArray('target-blank-apps', '[]'),
'top-menu-apps' => $this->config->getAppValueArray('top-menu-apps', '[]'),
'default-enabled' => $this->config->getAppValue('default-enabled', '1'),
'apps' => $this->appRepository->getVisibleApps(), 'apps' => $this->appRepository->getVisibleApps(),
]; ];
+17 -12
View File
@@ -18,14 +18,14 @@
namespace OCA\SideMenu\Settings; namespace OCA\SideMenu\Settings;
use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCP\IL10N; use OCP\IL10N;
use OCP\ILogger; use OCP\ILogger;
use OCP\Settings\ISettings;
use OCP\IConfig;
use OCP\IUserSession; use OCP\IUserSession;
use OCA\SideMenu\Service\AppRepository; use OCP\Settings\ISettings;
use OCA\SideMenu\AppInfo\Application;
class Personal implements ISettings class Personal implements ISettings
{ {
@@ -40,7 +40,7 @@ class Personal implements ISettings
private $logger; private $logger;
/** /**
* @var IConfig * @var ConfigProxy
*/ */
private $config; private $config;
@@ -54,7 +54,7 @@ class Personal implements ISettings
*/ */
private $appRepository; private $appRepository;
public function __construct(IL10N $l, ILogger $logger, IConfig $config, IUserSession $userSession, AppRepository $appRepository) public function __construct(IL10N $l, ILogger $logger, ConfigProxy $config, IUserSession $userSession, AppRepository $appRepository)
{ {
$this->l = $l; $this->l = $l;
$this->logger = $logger; $this->logger = $logger;
@@ -71,10 +71,15 @@ class Personal implements ISettings
$user = $this->userSession->getUser(); $user = $this->userSession->getUser();
$parameters = [ $parameters = [
'enabled' => $this->config->getUserValue($user->getUid(), Application::APP_ID, 'enabled', '1'), 'force' => $this->config->getAppValueBool('force', '0'),
'top-menu-apps' => (array) json_decode($this->config->getUserValue($user->getUid(), Application::APP_ID, 'top-menu-apps', '[]'), true), 'enabled' => $this->config->getUserValue(
'target-blank-mode' => $this->config->getUserValue($user->getUid(), Application::APP_ID, 'target-blank-mode', '1'), $user,
'target-blank-apps' => (array) json_decode($this->config->getUserValue($user->getUid(), Application::APP_ID, 'target-blank-apps', '[]'), true), 'enabled',
$this->config->getAppValue('default-enabled', '1')
),
'top-menu-apps' => $this->config->getUserValueArray($user, 'top-menu-apps', '[]'),
'target-blank-mode' => $this->config->getUserValue($user, 'target-blank-mode', '1'),
'target-blank-apps' => $this->config->getUserValueArray($user, 'target-blank-apps', '[]'),
'apps' => $this->appRepository->getVisibleApps(), 'apps' => $this->appRepository->getVisibleApps(),
]; ];
@@ -91,8 +96,8 @@ class Personal implements ISettings
/** /**
* @return int whether the form should be rather on the top or bottom of * @return int whether the form should be rather on the top or bottom of
* the admin section. The forms are arranged in ascending order of the * the admin section. The forms are arranged in ascending order of the
* priority values. It is required to return a value between 0 and 100. * priority values. It is required to return a value between 0 and 100.
* *
* E.g.: 70 * E.g.: 70
*/ */
Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 57 KiB

+25
View File
@@ -0,0 +1,25 @@
<!--
@license GNU AGPL version 3 or any later version
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<button class="side-menu-opener side-menu-closer"></button>
</template>
<script>
export default {
name: 'CloserButton',
}
</script>
+42
View File
@@ -0,0 +1,42 @@
<!--
@license GNU AGPL version 3 or any later version
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="side-menu-loader">
<svg width="38" height="38" viewBox="0 0 38 38" xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd">
<g transform="translate(1 1)" stroke-width="2">
<circle stroke-opacity=".5" cx="18" cy="18" r="18"/>
<path d="M36 18c0-9.94-8.06-18-18-18">
<animateTransform
attributeName="transform"
type="rotate"
from="0 18 18"
to="360 18 18"
dur="1s"
repeatCount="indefinite"/>
</path>
</g>
</g>
</svg>
</div>
</template>
<script>
export default {
name: 'Loader',
}
</script>
+37
View File
@@ -0,0 +1,37 @@
<!--
@license GNU AGPL version 3 or any later version
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div v-bind:class="classes">
<img v-bind:src="image">
</div>
</template>
<script>
export default {
name: 'Logo',
props: {
image: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
},
}
</script>
+25
View File
@@ -0,0 +1,25 @@
<!--
@license GNU AGPL version 3 or any later version
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<button class="side-menu-opener"></button>
</template>
<script>
export default {
name: 'OpenerButton',
}
</script>
+47
View File
@@ -0,0 +1,47 @@
<!--
@license GNU AGPL version 3 or any later version
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<div class="side-menu-settings">
<a v-bind:href="href">
{{ label }}
<span class="avatardiv avatardiv-shown">
<img v-bind:src="avatar" v-bind:alt="name" v-bind:title="name">
</span>
</a>
</div>
</template>
<script>
export default {
name: 'SettingsButton',
props: {
label: {
type: String,
required: true
},
href: {
type: String,
required: true
},
avatar: {
type: String,
required: true
},
},
}
</script>
+29 -13
View File
@@ -17,23 +17,27 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template> <template>
<div id="side-menu"> <div id="side-menu">
<div class="side-menu-header"> <div class="side-menu-header">
<button class="side-menu-opener"></button> <SettingsButton
v-if="settings"
v-bind:href="settings.href"
v-bind:label="settings.name"
v-bind:avatar="settings.avatar" />
<div v-if="!avatar && logo" v-bind:class="{'side-menu-logo': true, 'avatardiv': false}"> <OpenerButton />
<img v-bind:src="logo">
</div> <Logo v-if="!avatar && logo" v-bind:classes="{'side-menu-logo': true, 'avatardiv': false}" v-bind:image="logo" />
<div v-if="avatar" v-bind:class="{'side-menu-logo': true, 'avatardiv': true}"> <Logo v-if="avatar" v-bind:classes="{'side-menu-logo': true, 'avatardiv': true}" v-bind:image="avatar" />
<img v-bind:src="avatar">
</div>
</div> </div>
<ul class="side-menu-apps-list"> <ul class="side-menu-apps-list">
<li v-for="app in apps" v-bind:class="{'side-menu-app': true, 'active': app.active}"> <SideMenuApp
<a v-bind:href="app.href" :target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined" v-bind:title="app.name"> v-for="app in apps"
<span class="side-menu-app-icon" v-html="app.icon"></span> v-bind:classes="{'side-menu-app': true, 'active': app.active}"
<span class="side-menu-app-text" v-html="app.name"></span> v-bind:icon="app.icon"
</a> v-bind:label="app.name"
</li> v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined"
/>
</ul> </ul>
</div> </div>
</template> </template>
@@ -41,9 +45,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<script> <script>
import trim from 'trim' import trim from 'trim'
import axios from 'axios' import axios from 'axios'
import OpenerButton from './OpenerButton'
import SettingsButton from './SettingsButton'
import SideMenuApp from './SideMenuApp'
import Logo from './Logo'
export default { export default {
name: 'SideMenu', name: 'SideMenu',
components: {
SettingsButton,
OpenerButton,
SideMenuApp,
Logo,
},
data() { data() {
return { return {
apps: [], apps: [],
@@ -51,6 +65,7 @@ export default {
avatar: null, avatar: null,
forceLightIcon: false, forceLightIcon: false,
targetBlankApps: [], targetBlankApps: [],
settings: null,
} }
}, },
methods: { methods: {
@@ -120,6 +135,7 @@ export default {
that.forceLightIcon = config['force-light-icon'] that.forceLightIcon = config['force-light-icon']
that.avatar = config['avatar'] that.avatar = config['avatar']
that.logo = config['logo'] that.logo = config['logo']
that.settings = config['settings']
}); });
}, },
}, },
+52
View File
@@ -0,0 +1,52 @@
<!--
@license GNU AGPL version 3 or any later version
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<li v-bind:class="classes">
<a v-bind:href="href" :target="target" v-bind:title="label">
<span class="side-menu-app-icon" v-html="icon"></span>
<span class="side-menu-app-text" v-html="label"></span>
</a>
</li>
</template>
<script>
export default {
name: 'SideMenuApp',
props: {
label: {
type: String,
required: true
},
icon: {
type: String,
required: true
},
href: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
target: {
type: String,
required: false
},
},
}
</script>
+34 -8
View File
@@ -17,22 +17,34 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<template> <template>
<div id="side-menu" class="side-menu-big"> <div id="side-menu" class="side-menu-big">
<div class="side-menu-header"> <div class="side-menu-header">
<button class="side-menu-opener side-menu-closer"></button> <CloserButton />
<button class="side-menu-opener"></button>
<SettingsButton
v-if="settings"
v-bind:href="settings.href"
v-bind:label="settings.name"
v-bind:avatar="settings.avatar"
/>
<OpenerButton />
</div> </div>
<div class="side-menu-categories-wrapper"> <div class="side-menu-categories-wrapper">
<div class="side-menu-categories"> <div class="side-menu-categories">
<Loader v-if="!items.length" />
<div class="side-menu-category" v-for="category in items"> <div class="side-menu-category" v-for="category in items">
<h2 class="side-menu-category-title" v-if="category.name != ''" v-html="category.name"></h2> <h2 class="side-menu-category-title" v-if="category.name != ''" v-html="category.name"></h2>
<ul class="side-menu-apps-list"> <ul class="side-menu-apps-list">
<li v-bind:class="{'side-menu-app': true, 'active': activeApp === appId}" v-for="(app, appId) in category.apps"> <SideMenuBigApp
<a v-bind:href="app.href" :target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined" v-bind:title="app.name"> v-for="(app, appId) in category.apps"
<img class="side-menu-app-icon" v-bind:src="app.icon"></span> v-bind:classes="{'side-menu-app': true, 'active': activeApp === appId}"
<span class="side-menu-app-text" v-html="app.name"></span> v-bind:icon="app.icon"
</a> v-bind:label="app.name"
</li> v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(app.id) !== -1 ? '_blank' : undefined"
/>
</ul> </ul>
</div> </div>
</div> </div>
@@ -43,15 +55,28 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
<script> <script>
import trim from 'trim' import trim from 'trim'
import axios from 'axios' import axios from 'axios'
import OpenerButton from './OpenerButton'
import CloserButton from './CloserButton'
import SettingsButton from './SettingsButton'
import Loader from './Loader'
import SideMenuBigApp from './SideMenuBigApp'
export default { export default {
name: 'SideMenuBig', name: 'SideMenuBig',
components: {
SettingsButton,
OpenerButton,
CloserButton,
Loader,
SideMenuBigApp,
},
data() { data() {
return { return {
items: [], items: [],
activeApp: null, activeApp: null,
targetBlank: false, targetBlank: false,
targetBlankApps: [], targetBlankApps: [],
settings: null,
} }
}, },
methods: { methods: {
@@ -90,6 +115,7 @@ export default {
const config = response.data const config = response.data
that.targetBlankApps = config['target-blank-apps'] that.targetBlankApps = config['target-blank-apps']
that.settings = config['settings']
}); });
}, },
}, },
+52
View File
@@ -0,0 +1,52 @@
<!--
@license GNU AGPL version 3 or any later version
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<template>
<li v-bind:class="classes">
<a v-bind:href="href" :target="target" v-bind:title="label">
<img class="side-menu-app-icon" v-bind:src="icon"></span>
<span class="side-menu-app-text" v-html="label"></span>
</a>
</li>
</template>
<script>
export default {
name: 'SideMenuBigApp',
props: {
label: {
type: String,
required: true
},
icon: {
type: String,
required: true
},
href: {
type: String,
required: true
},
classes: {
type: Object,
required: true
},
target: {
type: String,
required: false
},
},
}
</script>
+7
View File
@@ -57,3 +57,10 @@
"Use my selection": "Verwende meine Auswahl" "Use my selection": "Verwende meine Auswahl"
"Show and hide the list of applications": "Ein- und Ausblenden der Anwendungsliste" "Show and hide the list of applications": "Ein- und Ausblenden der Anwendungsliste"
"Use the avatar instead of the logo": "Verwenden Sie den Avatar anstelle des Logos" "Use the avatar instead of the logo": "Verwenden Sie den Avatar anstelle des Logos"
"You do not have permission to change the settings.": "Sie haben keine Berechtigung zum Ändern der Einstellungen."
"Force this configuration to users": "Erzwingen Sie diese Konfiguration für Benutzer"
"Export the configuration": "Exportieren Sie die Konfiguration"
"Purge the cache": "Leeren Sie den Cache"
"Show the link to settings": "Zeigen Sie den Link zu den Einstellungen an"
"The menu is enabled by default for users": "Das Menü ist standardmäßig für Benutzer aktiviert"
"Except when the configuration is forced.": "Außer wenn die Konfiguration erzwungen wird."
+7
View File
@@ -57,3 +57,10 @@
"Use my selection": "Utiliser ma sélection" "Use my selection": "Utiliser ma sélection"
"Show and hide the list of applications": "Afficher et masquer la liste des applications" "Show and hide the list of applications": "Afficher et masquer la liste des applications"
"Use the avatar instead of the logo": "Utiliser l'avatar à la place du logo" "Use the avatar instead of the logo": "Utiliser l'avatar à la place du logo"
"You do not have permission to change the settings.": "Vous n'avez pas la permission de changer les paramètres."
"Force this configuration to users": "Forcer cette configuration aux utilisateurs"
"Export the configuration": "Exporter la configuration"
"Purge the cache": "Purger le cache"
"Show the link to settings": "Afficher le lien vers les paramètres"
"The menu is enabled by default for users": "Le menu est activé par défaut pour les utilisateurs"
"Except when the configuration is forced.": "Sauf lorsque la configuration est forcée."
+25 -1
View File
@@ -41,4 +41,28 @@ var alwaysDisplayed = function() {
} }
} }
window.setTimeout(alwaysDisplayed, 200) let content = document.getElementById('content')
if (content && content.classList.contains('app-settings')) {
let loaded = false
const config = {attributes: false, childList: true, subtree: true}
const observer = new MutationObserver(() => {
if (loaded) {
return;
}
const element = content.querySelector('#app-category-your-apps')
|| content.querySelector('#app-navigation ul')
if (element) {
loaded = true
alwaysDisplayed()
}
})
observer.observe(content, config)
} else {
window.setTimeout(alwaysDisplayed, 200)
}
+22
View File
@@ -108,6 +108,28 @@ var updateTopMenu = function() {
k++ k++
}) })
// Hack for https://github.com/nextcloud/server/blob/23b0b63c213f5b31eecae817ffd4a9e26f6624d0/core/src/components/MainMenu.js#L74-L96
menu.undelegate('li:not(#more-apps) > a', 'click')
menu.delegate('li:not(#more-apps) > a', 'click', function(e) {
var a = $(e.target)
if (!a.is('a')) {
a = a.closest('a')
}
if (a.attr('target') !== '_blank' && e.which === 1 && !e.ctrlKey && !e.metaKey && a.parent('#more-apps').length === 0) {
a.find('svg').remove()
a.find('div').remove()
a.prepend(jQuery('<div/>').addClass(
OCA.Theming && OCA.Theming.inverted
? 'icon-loading-small'
: 'icon-loading-small-dark'
))
window.location.href = a.attr('href')
}
})
jQuery('#apps li.app-external-site').each(function(i, app) { jQuery('#apps li.app-external-site').each(function(i, app) {
app = jQuery(app) app = jQuery(app)
var appId = app.attr('data-id') var appId = app.attr('data-id')
+73 -2
View File
@@ -16,9 +16,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
use OCP\IURLGenerator;
use OCP\IConfig;
use OCA\SideMenu\AppInfo\Application;
script('side_menu', 'admin'); script('side_menu', 'admin');
style('side_menu', 'admin'); style('side_menu', 'admin');
style('side_menu', 'support');
$urlGenerator = \OC::$server[IURLGenerator::class];
$cacheSize = floor(mb_strlen(\OC::$server[IConfig::class]->getAppValue(Application::APP_ID, 'cache-categories', ''), '8bit') / 1024);
$choicesYesNo = [ $choicesYesNo = [
'No' => '0', 'No' => '0',
@@ -31,6 +37,7 @@ $choicesSizes = [
'Normal' => 'normal', 'Normal' => 'normal',
'Big' => 'big', 'Big' => 'big',
]; ];
?> ?>
<div id="side-menu-section"> <div id="side-menu-section">
@@ -354,6 +361,22 @@ $choicesSizes = [
</select> </select>
</div> </div>
<div>
<label for="side-menu-show-settings">
<?php p($l->t('Show the link to settings')); ?>
</label>
</div>
<div>
<select id="side-menu-show-settings" name="show-settings" class="side-menu-setting">
<?php foreach ($choicesYesNo as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['show-settings']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div> <div>
<label> <label>
<?php p($l->t('Icons and texts')); ?> <?php p($l->t('Icons and texts')); ?>
@@ -473,11 +496,59 @@ $choicesSizes = [
</div> </div>
<div class="section"> <div class="section">
<div>
<label for="side-menu-default-enabled">
<?php p($l->t('The menu is enabled by default for users')); ?>
</label>
<p><em><?php p($l->t('Except when the configuration is forced.')); ?></em></p>
<select id="side-menu-default-enabled" name="default-enabled" class="side-menu-setting">
<?php foreach ($choicesYesNo as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['default-enabled']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label for="side-menu-opener-only">
<?php p($l->t('Force this configuration to users')); ?>
</label>
</div>
<div>
<select id="side-menu-force" name="force" class="side-menu-setting">
<?php foreach ($choicesYesNo as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['force']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<input type="hidden" id="side-menu-cache" name="cache" value="<?php print_unescaped($_['cache']); ?>" class="side-menu-setting"> <input type="hidden" id="side-menu-cache" name="cache" value="<?php print_unescaped($_['cache']); ?>" class="side-menu-setting">
<button id="side-menu-save" class="btn btn-primary"><?php p($l->t('Save')); ?></button> <button id="side-menu-save" class="btn btn-info">
<?php p($l->t('Save')); ?>
</button>
<span id="side-menu-message" class="msg"></span> <span id="side-menu-message" class="msg"></span>
</div>
<div class="section" id="more">
<a href="<?php echo $urlGenerator->linkToRoute('side_menu.AdminSetting.exportConfiguration') ?>" target="_blank">
<button class="btn btn-primary" >
<?php p($l->t('Export the configuration')); ?>
</button>
</a>
<a href="<?php echo $urlGenerator->linkToRoute('side_menu.AdminSetting.removeCache') ?>">
<button class="btn btn-primary" >
<?php p($l->t('Purge the cache')); ?> (<?php echo $cacheSize ?> Kb)
</button>
</a>
<div style="height: 30px"></div> <div style="height: 30px"></div>
+120 -104
View File
@@ -18,7 +18,6 @@
script('side_menu', 'admin'); script('side_menu', 'admin');
style('side_menu', 'admin'); style('side_menu', 'admin');
style('side_menu', 'support');
$choicesYesNo = [ $choicesYesNo = [
'No' => '0', 'No' => '0',
@@ -27,125 +26,142 @@ $choicesYesNo = [
?> ?>
<div id="side-menu-section"> <div id="side-menu-section">
<div class="section"> <?php if ($_['force']): ?>
<h2> <div class="section">
<?php p($l->t('Menu')); ?> <h2>
</h2> <?php p($l->t('Menu')); ?>
</h2>
<div> <p>
<label for="side-menu-enabled"> <em><?php echo $l->t('You do not have permission to change the settings.'); ?></em>
<?php p($l->t('Enable the custom menu')); ?> </p>
</label>
</div> </div>
<?php else: ?>
<div class="section">
<h2>
<?php p($l->t('Menu')); ?>
</h2>
<p>
<em><?php echo $l->t('Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'); ?></em>
</p>
<div>
<select id="side-menu-enabled" name="enabled" class="side-menu-setting" data-personal> <div>
<?php foreach ($choicesYesNo as $label => $value): ?> <label for="side-menu-enabled">
<option value="<?php echo $value ?>" <?php if ($value === $_['enabled']): ?>selected<?php endif; ?>> <?php p($l->t('Enable the custom menu')); ?>
<?php echo $l->t($label); ?> </label>
</option> </div>
<p>
<em><?php echo $l->t('Use the shortcut <span class="keyboard-key">Ctrl</span>+<span class="keyboard-key">o</span> to open and to hide the side menu. Use <span class="keyboard-key">tab</span> to navigate.'); ?></em>
</p>
<div>
<select id="side-menu-enabled" name="enabled" class="side-menu-setting" data-personal>
<?php foreach ($choicesYesNo as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['enabled']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label for="side-menu-target-blank">
<?php p($l->t('Open apps in new tab')); ?>
</label>
</div>
<div>
<?php $choices = [
'Use the global setting' => '1',
'Use my selection' => '2',
]; ?>
<select id="side-menu-loader-enabled" name="target-blank-mode" class="side-menu-setting" data-personal>
<?php foreach ($choices as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['target-blank-mode']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<p>
<a class="side-menu-toggler" data-target="#target-blank-apps" href="#_">
🖱️ <?php p($l->t('Show and hide the list of applications')); ?>
</a>
</p>
<div class="side-menu-setting" data-name="target-blank-apps" id="target-blank-apps" data-personal data-checkbox style="display: none">
<?php foreach ($_['apps'] as $app): ?>
<div>
<input
type="checkbox"
name="target-blank-apps[]"
value="<?php echo $app['id'] ?>"
id="target-blank-app-<?php echo $app['id'] ?>"
<?php if (in_array($app['id'], $_['target-blank-apps'])): ?>checked<?php endif; ?>
/>
<label for="target-blank-app-<?php echo $app['id'] ?>">
<?php echo p($l->t($app['name'])); ?>
</label>
</div>
<?php endforeach; ?> <?php endforeach; ?>
</select> </div>
</div> </div>
<div> <div class="section">
<label for="side-menu-target-blank"> <h2>
<?php p($l->t('Open apps in new tab')); ?> <?php p($l->t('Top menu')); ?>
</label> </h2>
</div> <div>
<label for="side-menu-top-menu-apps">
<?php p($l->t('Apps that not must be moved in the side menu')); ?>
</label>
</div>
<div> <p>
<?php $choices = [ <em>
'Use the global setting' => '1', <?php p($l->t('If there is no selection then the global configuration is applied.')); ?>
'Use my selection' => '2', </em>
]; ?> </p>
<select id="side-menu-loader-enabled" name="target-blank-mode" class="side-menu-setting" data-personal> <p>
<?php foreach ($choices as $label => $value): ?> <a class="side-menu-toggler" data-target="#top-menu-apps" href="#_">
<option value="<?php echo $value ?>" <?php if ($value === $_['target-blank-mode']): ?>selected<?php endif; ?>> 🖱️ <?php p($l->t('Show and hide the list of applications')); ?>
<?php echo $l->t($label); ?> </a>
</option> </p>
<div class="side-menu-setting" data-name="top-menu-apps" data-checkbox data-personal id="top-menu-apps" style="display: none">
<?php foreach ($_['apps'] as $app): ?>
<div>
<input
type="checkbox"
name="top-menu-apps[]"
value="<?php echo $app['id'] ?>"
id="top-menu-app-<?php echo $app['id'] ?>"
<?php if (in_array($app['id'], $_['top-menu-apps'])): ?>checked<?php endif; ?>
/>
<label for="top-menu-app-<?php echo $app['id'] ?>">
<?php echo $app['name'] ?>
</label>
</div>
<?php endforeach; ?> <?php endforeach; ?>
</select> </div>
</div> </div>
<?php endif ?>
<p>
<a class="side-menu-toggler" data-target="#target-blank-apps" href="#_">
🖱️ <?php p($l->t('Show and hide the list of applications')); ?>
</a>
</p>
<div class="side-menu-setting" data-name="target-blank-apps" id="target-blank-apps" data-personal data-checkbox style="display: none">
<?php foreach ($_['apps'] as $app): ?>
<div>
<input
type="checkbox"
name="target-blank-apps[]"
value="<?php echo $app['id'] ?>"
id="target-blank-app-<?php echo $app['id'] ?>"
<?php if (in_array($app['id'], $_['target-blank-apps'])): ?>checked<?php endif; ?>
/>
<label for="target-blank-app-<?php echo $app['id'] ?>">
<?php echo p($l->t($app['name'])); ?>
</label>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="section"> <div class="section">
<h2> <?php if (!$_['force']): ?>
<?php p($l->t('Top menu')); ?> <button id="side-menu-save" class="btn btn-primary"><?php p($l->t('Save')); ?></button>
</h2>
<div>
<label for="side-menu-top-menu-apps">
<?php p($l->t('Apps that not must be moved in the side menu')); ?>
</label>
</div>
<p> <span id="side-menu-message" class="msg"></span>
<em>
<?php p($l->t('If there is no selection then the global configuration is applied.')); ?>
</em>
</p>
<p> <div style="height: 30px"></div>
<a class="side-menu-toggler" data-target="#top-menu-apps" href="#_"> <?php endif ?>
🖱️ <?php p($l->t('Show and hide the list of applications')); ?>
</a>
</p>
<div class="side-menu-setting" data-name="top-menu-apps" data-checkbox data-personal id="top-menu-apps" style="display: none">
<?php foreach ($_['apps'] as $app): ?>
<div>
<input
type="checkbox"
name="top-menu-apps[]"
value="<?php echo $app['id'] ?>"
id="top-menu-app-<?php echo $app['id'] ?>"
<?php if (in_array($app['id'], $_['top-menu-apps'])): ?>checked<?php endif; ?>
/>
<label for="top-menu-app-<?php echo $app['id'] ?>">
<?php echo $app['name'] ?>
</label>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="section">
<button id="side-menu-save" class="btn btn-primary"><?php p($l->t('Save')); ?></button>
<span id="side-menu-message" class="msg"></span>
<div style="height: 30px"></div>
<div> <div>
<span for="side-menu-opener"> <span for="side-menu-opener">
+47 -47
View File
@@ -3,51 +3,51 @@ const { VueLoaderPlugin } = require('vue-loader')
const StyleLintPlugin = require('stylelint-webpack-plugin') const StyleLintPlugin = require('stylelint-webpack-plugin')
module.exports = { module.exports = {
entry: { entry: {
'admin': path.join(__dirname, 'src', 'admin.js'), 'admin': path.join(__dirname, 'src', 'admin.js'),
'sideMenu': path.join(__dirname, 'src', 'SideMenu.js'), 'sideMenu': path.join(__dirname, 'src', 'SideMenu.js'),
}, },
output: { output: {
path: path.resolve(__dirname, './js'), path: path.resolve(__dirname, './js'),
publicPath: '/js', publicPath: '/js',
filename: '[name].js?v=[hash]', filename: '[name].js?v=[hash]',
chunkFilename: 'chunks/[name]-[hash].js', chunkFilename: 'chunks/[name]-[hash].js',
}, },
module: { module: {
rules: [ rules: [
{ {
test: /\.css$/, test: /\.css$/,
use: ['vue-style-loader', 'css-loader'], use: ['vue-style-loader', 'css-loader'],
}, },
{ {
test: /\.scss$/, test: /\.scss$/,
use: ['vue-style-loader', 'css-loader', 'sass-loader'], use: ['vue-style-loader', 'css-loader', 'sass-loader'],
}, },
{ {
test: /\.vue$/, test: /\.vue$/,
loader: 'vue-loader', loader: 'vue-loader',
}, },
{ {
test: /\.js$/, test: /\.js$/,
loader: 'babel-loader', loader: 'babel-loader',
exclude: /node_modules/, exclude: /node_modules/,
}, },
{ {
test: /\.(png|jpg|gif|svg)$/, test: /\.(png|jpg|gif|svg)$/,
loader: 'url-loader', loader: 'url-loader',
options: { options: {
name: '[name].[ext]?[hash]', name: '[name].[ext]?[hash]',
limit: 8192, limit: 8192,
}, },
}, },
], ],
}, },
plugins: [ plugins: [
new VueLoaderPlugin(), new VueLoaderPlugin(),
new StyleLintPlugin(), new StyleLintPlugin(),
], ],
resolve: { resolve: {
extensions: ['*', '.js', '.vue'], extensions: ['*', '.js', '.vue'],
symlinks: false, symlinks: false,
}, },
} }