Merge pull request 'develop' (#1) from deblan/side_menu:develop into develop

Reviewed-on: Pavelb/side_menu#1
This commit is contained in:
Pavelb 2022-08-13 14:38:27 +02:00
commit a2ab8fa907
51 changed files with 2278 additions and 2324 deletions

41
.woodpecker.yml Normal file
View file

@ -0,0 +1,41 @@
pipeline:
dependencies:
image: deblan/devenv
commands:
- npm install
when:
event: [tag, push, pull_request]
build:
image: deblan/devenv
commands:
- make npm-build
when:
event: [push, pull_request]
package:
image: deblan/devenv
volumes:
- /var/www/html/artifacts:/var/www/html/artifacts
secrets: [app_certificate]
commands:
- mkdir -p "$HOME/.nextcloud/certificates"
- echo "$APP_CERTIFICATE" > "$HOME/.nextcloud/certificates/side_menu.key"
- export VERSION=$(grep "<version>" appinfo/info.xml | grep -o "[0-9]*\.[0-9]*\.[0-9]*" --color=never)
- export RELEASE_DIRECTORY="/var/www/html/artifacts/deblan/side_menu"
- make release
when:
event: [tag]
release:
image: plugins/gitea-release
volumes:
- /var/www/html/artifacts:/var/www/html/artifacts
settings:
api_key:
from_secret: gitnet_api_key
base_url: https://gitnet.fr
note: ${CI_COMMIT_MESSAGE}
files: /var/www/html/artifacts/deblan/side_menu/${CI_COMMIT_TAG/v//}/*
when:
event: [tag]

View file

@ -1,5 +1,108 @@
## [Unreleased] ## [Unreleased]
## 2.4.2
### Fixed
* fix typo
### Changed
* change ci/cd
## 2.4.1
### Fixed
* fix user setting save
## 2.4.0
### Added
* remove focus on opener after click
* add button to set default colors
* add menu hover effect
* add translations
### Fixed
* fix deprecated app.php file
* fix menu with categories header
* fix minor issues
### Changed
* change saving progression
### Removed
* Nextcloud 19 is not supported anymore
* PHP 7.3 is not supported anymore
## 2.3.5
### Fixed
* fix white square (#99)
## 2.3.4
### Fixed
* fix blank line when settings are open (#96)
## 2.3.3
### Added
* hide the scrollbar when mouse is out (menu always displayed)
### Fixed
* fix SQL Exception InvalidFieldNameException (#93)
## 2.3.2
### Fixed
- fix hidden menu
## 2.3.1
### Fixed
- fix #88: does not work with default menu
## 2.3.0
### Added
- fix #82: add an option to keep visible an app in both menus
- fix #83: add custom categories
- add auto-reload when settings are saved
## 2.2.0
### Added
- fix #84: update icons
- fix #85: use Nextcloud colors by default
### Fixed
- fix categories order in large menu
## 2.1.0
### Added
- add compatibility with Nextcloud 23
## 2.0.1
### Fixed
- fix #78: Top menu is broken - invisible apps are shown
- fix #77: Update personal settings - HTTP error 412 (Precondition Failed)
- fix js error on the personal settings page (undefined sortable)
## 2.0.0
### Fixed
- fix #66: removing usage of setInterval
- fix #73: icon background
### Changed
- fix #67: replace jQuery with Vanilla JS
### Removed
- Nextcloud 18 is not supported anymore
## 1.28.0
### Added
- fix #63: add a new side menu with categories
## 1.27.2
### Fixed
- fix #62: hide app notification icon
## 1.27.1
### Fixed
- fix German translation render
## 1.27.0
### Added
- hide personal settings access when settings are forced by the administrator
### Fixed
- improve German translations
## 1.26.0
### Added
- add Czech translation
## 1.25.2 ## 1.25.2
### Fixed ### Fixed
- fix CHANGELOG - fix CHANGELOG

File diff suppressed because one or more lines are too long

View file

@ -11,10 +11,14 @@ release: npm-build translations
exit 1 exit 1
fi fi
test -d releases/$$VERSION && rm -fr releases/$$VERSION if [ -z "$$RELEASE_DIRECTORY" ]; then
mkdir -p releases/$$VERSION/side_menu RELEASE_DIRECTORY=releases
cp -r README.md CHANGELOG.md appinfo css lib img l10n js src templates screenshots releases/$$VERSION/side_menu fi
cd releases/$$VERSION
test -d $$RELEASE_DIRECTORY/$$VERSION && rm -fr $$RELEASE_DIRECTORY/$$VERSION
mkdir -p $$RELEASE_DIRECTORY/$$VERSION/side_menu
cp -r README.md CHANGELOG.md appinfo css lib img l10n js src templates screenshots vendor $$RELEASE_DIRECTORY/$$VERSION/side_menu
cd $$RELEASE_DIRECTORY/$$VERSION
zip -r side_menu_v$$VERSION.zip side_menu zip -r side_menu_v$$VERSION.zip side_menu
tar cvzf side_menu_v$$VERSION.tar.gz side_menu tar cvzf side_menu_v$$VERSION.tar.gz side_menu
rm -fr side_menu rm -fr side_menu

View file

@ -2,7 +2,7 @@
=============================== ===============================
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. 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 add and sort custom categories, define apps that must be displayed in the top menu, etc. 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.
@ -10,14 +10,17 @@ You can customize colors depending of the theme (Dark theme and Breeze Dark). Co
* [Installation and upgrade](#installation-and-upgrade) * [Installation and upgrade](#installation-and-upgrade)
* [How to contribute?](#how-to-contribute) * [How to contribute?](#how-to-contribute)
* [Support](#support)
* [Screenshots](https://gitnet.fr/deblan/side_menu/src/branch/master/screenshots/) * [Screenshots](https://gitnet.fr/deblan/side_menu/src/branch/master/screenshots/)
You like this app and you want to support me? ☕ [Buy me a coffee](https://www.buymeacoffee.com/deblan) or [Donate with liberapay](https://liberapay.com/deblan) You like this app and you want to support me? ☕ [Buy me a coffee](https://www.buymeacoffee.com/deblan) or [Donate with liberapay](https://liberapay.com/deblan)
[![Build Status](https://ci.gitnet.fr/api/badges/deblan/side_menu/status.svg)](https://ci.gitnet.fr/deblan/side_menu)
Requirements Requirements
------------ ------------
* PHP >= 7.3 * PHP >= 7.4
* App `theming` enabled * App `theming` enabled
Installation and upgrade Installation and upgrade
@ -58,3 +61,8 @@ If you are a developer:
Build javascripts using `make npm-build` (or `make npm-watch` to build them in real time). Build javascripts using `make npm-build` (or `make npm-watch` to build them in real time).
Then commit and create a pull request. Then commit and create a pull request.
Support
-------
You can join the official room on Matrix: [#custommenu:neutralnetwork.org](https://matrix.to/#/#custommenu:neutralnetwork.org).

View file

@ -1,10 +0,0 @@
<?php
use OCA\SideMenu\AppInfo\Application;
$app = new Application();
if ($app->isEnabled()) {
$app->registerAssets();
$app->registerServices();
}

View file

@ -17,7 +17,7 @@ You can report a bug or request a feature by opening an issue.
Requirements: Requirements:
* PHP >= 7.3 * PHP >= 7.4
* App `theming` enabled * App `theming` enabled
If you like this application and if you want to support the development: If you like this application and if you want to support the development:
@ -26,7 +26,7 @@ If you like this application and if you want to support the development:
* [Donate with liberapay](https://liberapay.com/deblan) * [Donate with liberapay](https://liberapay.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.25.2</version> <version>2.4.2</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>
<namespace>SideMenu</namespace> <namespace>SideMenu</namespace>
@ -46,8 +46,8 @@ If you like this application and if you want to support the development:
<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>
<dependencies> <dependencies>
<nextcloud min-version="18" max-version="22"/> <nextcloud min-version="20" max-version="24"/>
<php min-version="7.3"/> <php min-version="7.4"/>
</dependencies> </dependencies>
<settings> <settings>
<admin>OCA\SideMenu\Settings\Admin</admin> <admin>OCA\SideMenu\Settings\Admin</admin>

View file

@ -20,7 +20,7 @@
margin: 10px 0 10px 0; margin: 10px 0 10px 0;
} }
#side-menu-section input[type="checkbox"] { #-dropside-menu-section input[type="checkbox"] {
vertical-align: middle; vertical-align: middle;
} }
@ -81,6 +81,12 @@
cursor: pointer; cursor: pointer;
} }
.side-menu-setting-list-drop {
background: yellow;
border-color: yellow;
height: 34px;
}
.side-menu-setting.arrow { .side-menu-setting.arrow {
color: #ccc; color: #ccc;
padding-right: 5px; padding-right: 5px;
@ -91,6 +97,10 @@
margin-top: -1px; margin-top: -1px;
} }
#apps-categories-custom-list select {
width: 100%;
}
.side-menu-setting-table { .side-menu-setting-table {
display: table; display: table;
@ -109,7 +119,7 @@
.side-menu-setting-form { .side-menu-setting-form {
display: table-cell; display: table-cell;
width: 300px; min-width: 300px;
} }
.side-menu-setting-label-short { .side-menu-setting-label-short {
@ -119,3 +129,18 @@
.side-menu-setting-form-long { .side-menu-setting-form-long {
width: 400px; width: 400px;
} }
#side-menu-save-progress {
display: inline-block;
width: 0;
height: 15px;
background: #fff;
}
.btn-reset {
display: inline-block;
cursor: pointer;
position: absolute;
margin-top: 17px;
margin-left: 5px;
}

View file

@ -29,12 +29,16 @@
display: none; display: none;
} }
#side-menu a {
transition: 0.2s;
}
#side-menu.open { #side-menu.open {
display: block; display: block;
} }
#header .side-menu-opener { #header .side-menu-opener {
margin-left: 5px; margin-left: 0px;
} }
.side-menu-settings { .side-menu-settings {
@ -59,6 +63,8 @@
.side-menu-settings img { .side-menu-settings img {
vertical-align: bottom; vertical-align: bottom;
margin-left: 3px; margin-left: 3px;
width: 32px;
height: 32px;
} }
#side-menu.open .side-menu-settings { #side-menu.open .side-menu-settings {
@ -67,10 +73,19 @@
.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; background-color: transparent !important;
width: 40px; height: 40px !important;
border-radius: 0; width: 40px !important;
border: 0; border-radius: 0 !important;
border: 0 !important;
padding-right: 12px !important;
padding-left: 12px !important;
margin-left: 5px !important;
margin-left: 3px !important;
}
.side-menu-opener:active, .side-menu-opener:focus {
background-color: var(--side-menu-current-app-background-color, #444) !important;
} }
.side-menu-closer { .side-menu-closer {
@ -105,6 +120,10 @@
margin-top: -3px; margin-top: -3px;
} }
.side-menu-app-icon .app-icon-notification {
display: none;
}
.side-menu-app a { .side-menu-app a {
line-height: 30px; line-height: 30px;
color: var(--side-menu-text-color, #fff); color: var(--side-menu-text-color, #fff);
@ -135,10 +154,13 @@
max-width: 250px; max-width: 250px;
position: fixed; position: fixed;
padding-top: 2px; padding-top: 2px;
padding-left: 5px;
top: 0; top: 0;
} }
#side-menu.side-menu-with-categories .side-menu-header {
max-width: 295px;
}
#side-menu.hide-opener .side-menu-logo { #side-menu.hide-opener .side-menu-logo {
margin-top: 20px; margin-top: 20px;
} }
@ -158,23 +180,23 @@
transition-property: width; transition-property: width;
} }
#side-menu.side-menu-big { #side-menu.side-menu-big, #side-menu.side-menu-with-categories {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
} }
.side-menu-big .side-menu-header { .side-menu-big .side-menu-header, .side-menu-with-categories .side-menu-header {
height: auto; height: auto;
} }
.side-menu-big .side-menu-apps-list { .side-menu-big .side-menu-apps-list, .side-menu-with-categories .side-menu-apps-list {
height: auto; height: auto;
position: static; position: static;
max-width: 100vw; max-width: 100vw;
overflow: auto; overflow: auto;
} }
.side-menu-big .side-menu-app a { .side-menu-big .side-menu-app a, .side-menu-with-categories .side-menu-app a {
padding: 7px 0 7px 7px; padding: 7px 0 7px 7px;
} }
@ -213,7 +235,7 @@
stroke: var(--side-menu-text-color, #fff); stroke: var(--side-menu-text-color, #fff);
} }
.side-menu-big .side-menu-app-icon { .side-menu-with-categories .side-menu-app-icon, .side-menu-big .side-menu-app-icon {
vertical-align: middle; vertical-align: middle;
margin-top: -2px; margin-top: -2px;
} }
@ -235,6 +257,11 @@
.side-menu-always-displayed .side-menu-apps-list { .side-menu-always-displayed .side-menu-apps-list {
height: calc(100vh - 49px); height: calc(100vh - 49px);
top: 49px; top: 49px;
overflow: hidden;
}
.side-menu-always-displayed .side-menu-apps-list:hover {
overflow: auto;
} }
.side-menu-always-displayed #side-menu, .side-menu-always-displayed #side-menu,
@ -267,6 +294,24 @@
transform: translateX(calc(-100% + 50px)) !important; transform: translateX(calc(-100% + 50px)) !important;
} }
#side-menu.side-menu-with-categories {
max-width: 290px;
height: 100vh;
}
.side-menu-with-categories .side-menu-categories {
display: block;
padding: 0;
}
.side-menu-with-categories .side-menu-category {
padding: 10px 0;
}
.side-menu-always-displayed #body-settings, #body-settings.body-settings-side-menu {
overflow-x: visible;
}
@media screen and (max-width: 1024px) { @media screen and (max-width: 1024px) {
#side-menu.side-menu-big { #side-menu.side-menu-big {
max-width: 290px; max-width: 290px;

View file

@ -0,0 +1,224 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="571.907"
height="156.921"
viewBox="0 0 151.317 41.519"
version="1.1"
id="svg901"
sodipodi:docname="layout-side-menu-with-categories.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata907">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs905" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1036"
id="namedview903"
showgrid="false"
inkscape:zoom="1.6086532"
inkscape:cx="455.61054"
inkscape:cy="16.694081"
inkscape:window-x="0"
inkscape:window-y="21"
inkscape:window-maximized="0"
inkscape:current-layer="svg901"
showguides="false" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#fff"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v41.16H.18zM62.735.18h58.532v41.16H62.735z"
id="path853" />
<g
fill="#b3b3b3"
stroke="#ccc"
fill-rule="evenodd"
stroke-width="1.465"
stroke-linecap="round"
id="g857">
<path
d="M84.066 21.35l5.055-5.056M82.925 29.835l18.152-18.151M95.893 25.225l3.585-3.586"
id="path855" />
</g>
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M62.735.18h58.532v41.16H62.735z"
id="path861" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#f9f9f9"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v5.063H.18z"
id="path877" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v41.16H.18z"
id="path879" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#666"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M1.357 1.034h3.496V4.39H1.357z"
id="path881" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#e6e6e6"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M6.068 1.034h6.102V4.39H6.068z"
id="path883" />
<g
fill="#b3b3b3"
stroke="#ccc"
fill-rule="evenodd"
stroke-width="1.465"
stroke-linecap="round"
id="g887">
<path
d="M21.51 21.35l5.056-5.056M20.37 29.835L38.52 11.684M33.337 25.225l3.586-3.586"
id="path885" />
</g>
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#fff"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.28v41.16h-25.28z"
id="path889" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#f9f9f9"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.257v5.063h-25.257z"
id="path891" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.257v41.16h-25.257z"
id="path893" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#4d4d4d"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h20.829v41.16h-20.83z"
id="path895" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#666"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M127.034 1.034h3.496V4.39h-3.496z"
id="path897" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#e6e6e6"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M127.569 10.155h1.893v1.81h-1.893zM130.498 10.486h12.463v1.148h-12.463zM127.569 13.696h1.893v1.81h-1.893zM130.498 14.027h10.592v1.148h-10.592zM127.569 17.104h1.893v1.81h-1.893zM130.498 17.435h12.997v1.148h-12.997zM127.569 24.708h1.893v1.81h-1.893zM130.498 25.04h7.585v1.147h-7.585zM127.569 28.116h1.893v1.81h-1.893zM130.498 28.447h11.46v1.148h-11.46zM127.569 31.657h1.893v1.81h-1.893zM130.498 31.988h9.322v1.148h-9.322zM127.569 35.065h1.893v1.81h-1.893zM130.498 35.396h11.126v1.148h-11.126zM127.616 6.848h11.518v1.148h-11.518zM127.569 21.311h9.344v1.148h-9.344z"
id="path899" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#4d4d4d;stroke:#b3b3b3;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 62.728587,0.17999881 h 20.829 V 41.34 h -20.83 z"
id="path895-5" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#666666;stroke:#cccccc;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 63.905587,1.0339988 h 3.496 V 4.389999 h -3.496 z"
id="path897-3" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#e6e6e6;stroke:#cccccc;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 64.440587,10.154999 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 12.463 v 1.148 h -12.463 z m -2.929,3.21 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 10.592 v 1.148 h -10.592 z m -2.929,3.077 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 12.997 v 1.148 h -12.997 z m -2.929,7.273 h 1.893 V 26.518 h -1.893 z m 2.929,0.332 h 7.585 v 1.147 h -7.585 z m -2.929,3.076001 h 1.893 v 1.81 h -1.893 z m 2.929,0.330999 h 11.46 v 1.147999 h -11.46 z m -2.929,3.21 h 1.893 v 1.809999 h -1.893 z m 2.929,0.330999 h 9.322 v 1.148001 h -9.322 z m -2.929,3.077001 h 1.893 v 1.809999 h -1.893 z m 2.929,0.331001 h 11.126 v 1.147999 h -11.126 z m -2.882,-28.5480012 h 11.518 v 1.1480001 h -11.518 z m -0.047,14.4630002 h 9.344 v 1.148 h -9.344 z"
id="path899-5" />
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -0,0 +1,223 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="571.907"
height="156.921"
viewBox="0 0 151.317 41.519"
version="1.1"
id="svg901"
sodipodi:docname="layout-side-menu-with-categories.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata907">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs905" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1036"
id="namedview903"
showgrid="false"
inkscape:zoom="1.6086532"
inkscape:cx="455.61054"
inkscape:cy="16.694081"
inkscape:window-x="0"
inkscape:window-y="21"
inkscape:window-maximized="0"
inkscape:current-layer="svg901" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#fff"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v41.16H.18zM62.735.18h58.532v41.16H62.735z"
id="path853" />
<g
fill="#b3b3b3"
stroke="#ccc"
fill-rule="evenodd"
stroke-width="1.465"
stroke-linecap="round"
id="g857">
<path
d="M84.066 21.35l5.055-5.056M82.925 29.835l18.152-18.151M95.893 25.225l3.585-3.586"
id="path855" />
</g>
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M62.735.18h58.532v41.16H62.735z"
id="path861" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#f9f9f9"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v5.063H.18z"
id="path877" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M.18.18h58.532v41.16H.18z"
id="path879" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#666"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M1.357 1.034h3.496V4.39H1.357z"
id="path881" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#e6e6e6"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M6.068 1.034h6.102V4.39H6.068z"
id="path883" />
<g
fill="#b3b3b3"
stroke="#ccc"
fill-rule="evenodd"
stroke-width="1.465"
stroke-linecap="round"
id="g887">
<path
d="M21.51 21.35l5.056-5.056M20.37 29.835L38.52 11.684M33.337 25.225l3.586-3.586"
id="path885" />
</g>
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#fff"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.28v41.16h-25.28z"
id="path889" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#f9f9f9"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.257v5.063h-25.257z"
id="path891" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="none"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h25.257v41.16h-25.257z"
id="path893" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#4d4d4d"
stroke="#b3b3b3"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M125.857.18h20.829v41.16h-20.83z"
id="path895" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#666"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M127.034 1.034h3.496V4.39h-3.496z"
id="path897" />
<path
style="marker:none"
color="#000"
overflow="visible"
fill="#e6e6e6"
stroke="#ccc"
stroke-width=".359"
stroke-linecap="square"
paint-order="fill markers stroke"
d="M127.569 10.155h1.893v1.81h-1.893zM130.498 10.486h12.463v1.148h-12.463zM127.569 13.696h1.893v1.81h-1.893zM130.498 14.027h10.592v1.148h-10.592zM127.569 17.104h1.893v1.81h-1.893zM130.498 17.435h12.997v1.148h-12.997zM127.569 24.708h1.893v1.81h-1.893zM130.498 25.04h7.585v1.147h-7.585zM127.569 28.116h1.893v1.81h-1.893zM130.498 28.447h11.46v1.148h-11.46zM127.569 31.657h1.893v1.81h-1.893zM130.498 31.988h9.322v1.148h-9.322zM127.569 35.065h1.893v1.81h-1.893zM130.498 35.396h11.126v1.148h-11.126zM127.616 6.848h11.518v1.148h-11.518zM127.569 21.311h9.344v1.148h-9.344z"
id="path899" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#4d4d4d;stroke:#b3b3b3;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 62.728587,0.17999881 h 20.829 V 41.34 h -20.83 z"
id="path895-5" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#666666;stroke:#cccccc;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 63.905587,1.0339988 h 3.496 V 4.389999 h -3.496 z"
id="path897-3" />
<path
inkscape:connector-curvature="0"
style="color:#000000;overflow:visible;fill:#e6e6e6;stroke:#cccccc;stroke-width:0.359;stroke-linecap:square;marker:none;paint-order:fill markers stroke"
overflow="visible"
d="m 64.440587,10.154999 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 12.463 v 1.148 h -12.463 z m -2.929,3.21 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 10.592 v 1.148 h -10.592 z m -2.929,3.077 h 1.893 v 1.81 h -1.893 z m 2.929,0.331 h 12.997 v 1.148 h -12.997 z m -2.929,7.273 h 1.893 V 26.518 h -1.893 z m 2.929,0.332 h 7.585 v 1.147 h -7.585 z m -2.929,3.076001 h 1.893 v 1.81 h -1.893 z m 2.929,0.330999 h 11.46 v 1.147999 h -11.46 z m -2.929,3.21 h 1.893 v 1.809999 h -1.893 z m 2.929,0.330999 h 9.322 v 1.148001 h -9.322 z m -2.929,3.077001 h 1.893 v 1.809999 h -1.893 z m 2.929,0.331001 h 11.126 v 1.147999 h -11.126 z m -2.882,-28.5480012 h 11.518 v 1.1480001 h -11.518 z m -0.047,14.4630002 h 9.344 v 1.148 h -9.344 z"
id="path899-5" />
</svg>

After

Width:  |  Height:  |  Size: 7.3 KiB

View file

@ -1,6 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"> <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><g fill="#fff" color="#000" transform="translate(-.067 .224)"><rect width="5.027" height=".777" x="-2.307" y="6.984" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none" transform="rotate(-45)"/><rect width="5.027" height=".777" x="4.859" y="-.595" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none" transform="rotate(45)"/></g></svg>
<g color="#000" fill="#fff">
<rect transform="rotate(135)" ry=".545" rx=".545" y="-8.209" x="-3.608" height="1.451" width="7.216" style="marker:none" overflow="visible" paint-order="stroke markers fill"/>
<rect transform="rotate(-135)" ry=".545" rx=".545" y="-.726" x="-11.091" height="1.451" width="7.216" style="marker:none" overflow="visible" paint-order="stroke markers fill"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 498 B

After

Width:  |  Height:  |  Size: 495 B

View file

@ -1,173 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><g fill-opacity=".898" color="#000" transform="matrix(.90559 0 0 .86896 .772 -247.893)"><rect width="1.451" height="1.451" x="4.266" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="1.451" height="1.451" x="4.266" y="290.631" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13 2.157)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="1.451" height="1.451" x="4.266" y="292.808" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13 4.334)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g></g></svg>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg935"
sodipodi:docname="side-menu-opener-dark.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata941">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs939" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview937"
showgrid="false"
inkscape:zoom="8.34386"
inkscape:cx="83.025045"
inkscape:cy="14.304895"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg935" />
<g
transform="translate(.3 -286.074)"
fill="#fff"
color="#000"
fill-opacity=".855"
id="g933"
style="fill:#000000;fill-opacity:0.89855075">
<rect
ry="0"
rx="0"
y="288.474"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#000000;fill-opacity:0.89855075"
overflow="visible"
paint-order="stroke markers fill"
id="rect909" />
<g
transform="translate(-.13)"
id="g915"
style="fill:#000000;fill-opacity:0.89855075">
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect911" />
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect913" />
</g>
<rect
ry="0"
rx="0"
y="290.631"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#000000;fill-opacity:0.89855075"
overflow="visible"
paint-order="stroke markers fill"
id="rect917" />
<g
transform="translate(-.13 2.157)"
id="g923"
style="fill:#000000;fill-opacity:0.89855075">
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect919" />
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect921" />
</g>
<rect
ry="0"
rx="0"
y="292.808"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#000000;fill-opacity:0.89855075"
overflow="visible"
paint-order="stroke markers fill"
id="rect925" />
<g
transform="translate(-.13 4.334)"
id="g931"
style="fill:#000000;fill-opacity:0.89855075">
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect927" />
<rect
style="marker:none;fill:#000000;fill-opacity:0.89855075"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect929" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -1,102 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><rect width="7.62" height="7.62" x="1.482" y="1.482" fill="none" stroke="#000" stroke-linecap="square" stroke-width=".777" color="#000" overflow="visible" paint-order="markers fill stroke" rx="0" ry="0" style="marker:none"/><rect width="5.027" height=".777" x="2.778" y="3.148" color="#000" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.027" height=".777" x="2.778" y="6.658" color="#000" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.027" height=".777" x="2.778" y="4.936" color="#000" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></svg>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg1090"
sodipodi:docname="side-menu-opener-hamburger-2-dark.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata1096">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1094" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview1092"
showgrid="false"
inkscape:zoom="8.34386"
inkscape:cx="84.523455"
inkscape:cy="12.270318"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg1090" />
<rect
overflow="visible"
style="marker:none;fill:#000000;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="2.787"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1082" />
<rect
overflow="visible"
style="marker:none;fill:#000000;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="4.655"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1084" />
<rect
overflow="visible"
style="marker:none;fill:#000000;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="6.54"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1086" />
<path
style="marker:none;fill:#000000;fill-opacity:1"
d="M1.243.71A.469.469 0 0 0 .881.88a.469.469 0 0 0-.171.362V9.34c0 .146.067.275.171.362a.469.469 0 0 0 .362.171H9.34a.469.469 0 0 0 .362-.171.469.469 0 0 0 .171-.362V1.243a.469.469 0 0 0-.171-.362A.469.469 0 0 0 9.34.71zm.723 1.256h6.65v6.65h-6.65z"
color="#000"
overflow="visible"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="path1088" />
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 786 B

View file

@ -1,101 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><rect width="7.62" height="7.62" x="1.482" y="1.482" fill="none" stroke="#fff" stroke-linecap="square" stroke-width=".777" color="#000" overflow="visible" paint-order="markers fill stroke" rx="0" ry="0" style="marker:none"/><g fill="#fff" color="#000" transform="translate(0 .034)"><rect width="5.027" height=".777" x="2.778" y="3.114" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.027" height=".777" x="2.778" y="6.624" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.027" height=".777" x="2.778" y="4.903" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g></svg>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg1443"
sodipodi:docname="side-menu-opener-hamburger-2.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata1449">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1447" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview1445"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="90.508475"
inkscape:cy="4.5762712"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg1443" />
<rect
overflow="visible"
style="marker:none;fill:#ffffff;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="2.787"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1435" />
<rect
overflow="visible"
style="marker:none;fill:#ffffff;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="4.655"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1437" />
<rect
overflow="visible"
style="marker:none;fill:#ffffff;fill-opacity:1"
width="4.806"
height="1.256"
x="2.888"
y="6.54"
rx="0"
ry=".472"
color="#000"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="rect1439" />
<path
style="marker:none;fill:#ffffff;fill-opacity:1"
d="M1.243.71A.469.469 0 0 0 .881.88a.469.469 0 0 0-.171.362V9.34c0 .146.067.275.171.362a.469.469 0 0 0 .362.171H9.34a.469.469 0 0 0 .362-.171.469.469 0 0 0 .171-.362V1.243a.469.469 0 0 0-.171-.362A.469.469 0 0 0 9.34.71zm.723 1.256h6.65v6.65h-6.65z"
color="#000"
overflow="visible"
fill="#fff"
fill-opacity=".855"
paint-order="stroke markers fill"
id="path1441" />
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 809 B

View file

@ -1,92 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><g color="#000" transform="matrix(.85624 0 0 .9944 .747 .03)"><g transform="matrix(1.0055 0 0 1 -.013 0)"><rect width="5.839" height=".782" x="2.372" y="2.764" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.839" height=".782" x="2.372" y="7.037" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="5.871" height=".782" x="2.372" y="4.901" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g></svg>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg1168"
sodipodi:docname="side-menu-opener-hamburger-dark.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata1174">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1172" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview1170"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="112.71186"
inkscape:cy="0.16949153"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg1168" />
<g
color="#000"
fill="#fff"
fill-opacity=".855"
transform="translate(0 -286.417)"
id="g1166"
style="fill:#000000;fill-opacity:0.99637681">
<rect
ry=".545"
rx="0"
y="288.816"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#000000;fill-opacity:0.99637681"
overflow="visible"
paint-order="stroke markers fill"
id="rect1160" />
<rect
ry=".545"
rx="0"
y="290.973"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#000000;fill-opacity:0.99637681"
overflow="visible"
paint-order="stroke markers fill"
id="rect1162" />
<rect
ry=".545"
rx="0"
y="293.15"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#000000;fill-opacity:0.99637681"
overflow="visible"
paint-order="stroke markers fill"
id="rect1164" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 637 B

View file

@ -1,91 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><g fill="#fff" color="#000" transform="matrix(.85624 0 0 .9944 .747 .03)"><g transform="matrix(1.0055 0 0 1 -.013 0)"><rect width="5.839" height=".782" x="2.372" y="2.764" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="5.839" height=".782" x="2.372" y="7.037" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="5.871" height=".782" x="2.372" y="4.901" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g></svg>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg1382"
sodipodi:docname="side-menu-opener-hamburger.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata1388">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1386" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview1384"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="108.13559"
inkscape:cy="3.8983051"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg1382" />
<g
color="#000"
fill="#fff"
fill-opacity=".855"
transform="translate(0 -286.417)"
id="g1380"
style="fill:#ffffff;fill-opacity:1">
<rect
ry=".545"
rx="0"
y="288.816"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#ffffff;fill-opacity:1"
overflow="visible"
paint-order="stroke markers fill"
id="rect1374" />
<rect
ry=".545"
rx="0"
y="290.973"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#ffffff;fill-opacity:1"
overflow="visible"
paint-order="stroke markers fill"
id="rect1376" />
<rect
ry=".545"
rx="0"
y="293.15"
x="2.516"
height="1.451"
width="5.551"
style="marker:none;fill:#ffffff;fill-opacity:1"
overflow="visible"
paint-order="stroke markers fill"
id="rect1378" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 649 B

View file

@ -1,172 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 10.583 10.583"><g fill="#fff" fill-opacity=".946" color="#000" transform="matrix(.90559 0 0 .86896 .772 -247.893)"><rect width="1.451" height="1.451" x="4.266" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="1.451" height="1.451" x="4.266" y="290.631" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13 2.157)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g><rect width="1.451" height="1.451" x="4.266" y="292.808" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><g transform="translate(-.13 4.334)"><rect width="1.451" height="1.451" x="6.445" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/><rect width="1.451" height="1.451" x="2.345" y="288.474" overflow="visible" paint-order="stroke markers fill" rx="0" ry="0" style="marker:none"/></g></g></svg>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="40"
height="40"
viewBox="0 0 10.583 10.583"
version="1.1"
id="svg1321"
sodipodi:docname="side-menu-opener.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<metadata
id="metadata1327">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs1325" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1918"
inkscape:window-height="1008"
id="namedview1323"
showgrid="false"
inkscape:zoom="5.9"
inkscape:cx="78.983051"
inkscape:cy="5.5932203"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="0"
inkscape:current-layer="svg1321" />
<g
transform="translate(.3 -286.074)"
fill="#fff"
color="#000"
fill-opacity=".855"
id="g1319"
style="fill:#ffffff;fill-opacity:0.94565219">
<rect
ry="0"
rx="0"
y="288.474"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
overflow="visible"
paint-order="stroke markers fill"
id="rect1295" />
<g
transform="translate(-.13)"
id="g1301"
style="fill:#ffffff;fill-opacity:0.94565219">
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1297" />
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1299" />
</g>
<rect
ry="0"
rx="0"
y="290.631"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
overflow="visible"
paint-order="stroke markers fill"
id="rect1303" />
<g
transform="translate(-.13 2.157)"
id="g1309"
style="fill:#ffffff;fill-opacity:0.94565219">
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1305" />
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1307" />
</g>
<rect
ry="0"
rx="0"
y="292.808"
x="4.266"
height="1.451"
width="1.451"
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
overflow="visible"
paint-order="stroke markers fill"
id="rect1311" />
<g
transform="translate(-.13 4.334)"
id="g1317"
style="fill:#ffffff;fill-opacity:0.94565219">
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="6.445"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1313" />
<rect
style="marker:none;fill:#ffffff;fill-opacity:0.94565219"
width="1.451"
height="1.451"
x="2.345"
y="288.474"
rx="0"
ry="0"
overflow="visible"
paint-order="stroke markers fill"
id="rect1315" />
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -9,6 +9,9 @@ use OCA\SideMenu\Service\AppRepository;
use OCA\SideMenu\Service\CategoryRepository; use OCA\SideMenu\Service\CategoryRepository;
use OCA\SideMenu\Service\ConfigProxy; use OCA\SideMenu\Service\ConfigProxy;
use OCP\AppFramework\App; use OCP\AppFramework\App;
use OCP\AppFramework\Bootstrap\IBootContext;
use OCP\AppFramework\Bootstrap\IBootstrap;
use OCP\AppFramework\Bootstrap\IRegistrationContext;
use OCP\IUserSession; use OCP\IUserSession;
use OCP\Util; use OCP\Util;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
@ -18,7 +21,7 @@ use Psr\Container\ContainerInterface;
* *
* @author Simon Vieille <simon@deblan.fr> * @author Simon Vieille <simon@deblan.fr>
*/ */
class Application extends App class Application extends App implements IBootstrap
{ {
public const APP_ID = 'side_menu'; public const APP_ID = 'side_menu';
@ -44,16 +47,9 @@ class Application extends App
public function __construct(array $urlParams = []) public function __construct(array $urlParams = [])
{ {
parent::__construct(self::APP_ID, $urlParams); parent::__construct(self::APP_ID, $urlParams);
$this->config = OC::$server->getConfig();
$this->cspnm = OC::$server->getContentSecurityPolicyNonceManager();
$this->user = OC::$server[IUserSession::class]->getUser();
} }
/** protected function isEnabled(): bool
* Checks if this app is enabled.
*/
public function isEnabled(): bool
{ {
$enabled = true; $enabled = true;
$isForced = (bool) $this->config->getAppValue(self::APP_ID, 'force', '0'); $isForced = (bool) $this->config->getAppValue(self::APP_ID, 'force', '0');
@ -74,64 +70,65 @@ class Application extends App
return $enabled; return $enabled;
} }
/** protected function addAssets()
* Registes services.
*/
public function registerServices()
{
$container = $this->getContainer();
$container->registerService('AppRepository', function (ContainerInterface $c) {
return new AppRepository();
});
$container->registerService('CategoryRepository', function (ContainerInterface $c) {
return new CategoryRepository();
});
$container->registerService('ConfigProxy', function (ContainerInterface $c) {
return new ConfigProxy();
});
}
/**
* Registers assets.
*/
public function registerAssets()
{ {
Util::addScript(self::APP_ID, 'sideMenu'); Util::addScript(self::APP_ID, 'sideMenu');
Util::addStyle(self::APP_ID, 'sideMenu'); Util::addStyle(self::APP_ID, 'sideMenu');
$stylesheet = OC::$server->getURLGenerator()->linkToRoute( $assets = [
'side_menu.Css.stylesheet', 'stylesheet' => [
[ 'route' => 'side_menu.Css.stylesheet',
'v' => $this->config->getAppValue(self::APP_ID, 'cache', '0'), 'type' => 'link',
] 'route_attr' => 'href',
); 'attr' => [
'rel' => 'stylesheet',
$script = OC::$server->getURLGenerator()->linkToRoute( ],
'side_menu.Js.script',
[
'v' => $this->config->getAppValue(self::APP_ID, 'cache', '0'),
]
);
Util::addHeader(
'link',
[
'href' => $stylesheet,
'rel' => 'stylesheet',
], ],
'' 'script' => [
); 'route' => 'side_menu.Js.script',
'type' => 'script',
Util::addHeader( 'route_attr' => 'src',
'script', 'attr' => [
[ 'nonce' => $this->cspnm->getNonce(),
'src' => $script, ],
'nonce' => $this->cspnm->getNonce(),
], ],
'' ];
);
$cache = $this->config->getAppValue(self::APP_ID, 'cache', '0');
foreach ($assets as $key => $value) {
$route = OC::$server->getURLGenerator()->linkToRoute($value['route'], ['v' => $cache]);
$value['attr'][$value['route_attr']] = $route;
Util::addHeader($value['type'], $value['attr'], '');
}
}
public function register(IRegistrationContext $context): void
{
$context->registerService('AppRepository', function (ContainerInterface $c) {
return new AppRepository();
});
$context->registerService('CategoryRepository', function (ContainerInterface $c) {
return new CategoryRepository();
});
$context->registerService('ConfigProxy', function (ContainerInterface $c) {
return new ConfigProxy();
});
}
public function boot(IBootContext $context): void
{
$this->config = OC::$server->getConfig();
$this->cspnm = OC::$server->getContentSecurityPolicyNonceManager();
$this->user = OC::$server[IUserSession::class]->getUser();
if (!$this->isEnabled()) {
return;
}
$this->addAssets();
} }
} }

View file

@ -27,6 +27,8 @@ use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse; use OCP\AppFramework\Http\TemplateResponse;
use OCP\IRequest; use OCP\IRequest;
use OCP\IUserSession; use OCP\IUserSession;
use OCA\Theming\ThemingDefaults;
use OCA\SideMenu\Service\Color;
class CssController extends Controller class CssController extends Controller
{ {
@ -40,12 +42,30 @@ class CssController extends Controller
*/ */
protected $user; protected $user;
public function __construct(string $appName, IRequest $request, ConfigProxy $config) /**
* @var ThemingDefaults
*/
protected $theming;
/**
* @var Color
*/
protected $color;
public function __construct(
string $appName,
IRequest $request,
ConfigProxy $config,
ThemingDefaults $theming,
Color $color
)
{ {
parent::__construct($appName, $request); parent::__construct($appName, $request);
$this->user = OC::$server[IUserSession::class]->getUser(); $this->user = OC::$server[IUserSession::class]->getUser();
$this->config = $config; $this->config = $config;
$this->theming = $theming;
$this->color = $color;
} }
/** /**
@ -67,6 +87,7 @@ class CssController extends Controller
{ {
$isForced = $this->config->getAppValueBool('force', '0'); $isForced = $this->config->getAppValueBool('force', '0');
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]'); $topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
$topSideMenuApps = $this->config->getAppValueArray('top-side-menu-apps', '[]');
$isAccessibilityAppEnabled = $this->config->getAppValueBool('enabled', '0', 'accessibility'); $isAccessibilityAppEnabled = $this->config->getAppValueBool('enabled', '0', 'accessibility');
$isBreezeDarkAppEnabled = $this->config->getAppValueBool('enabled', '0', 'breezedark'); $isBreezeDarkAppEnabled = $this->config->getAppValueBool('enabled', '0', 'breezedark');
@ -74,11 +95,16 @@ class CssController extends Controller
if ($this->user) { if ($this->user) {
$userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]'); $userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]');
$userTopSideMenuApps = $this->config->getUserValueArray($this->user, 'top-side-menu-apps', '[]');
if (!empty($userTopMenuApps) && !$isForced) { if (!empty($userTopMenuApps) && !$isForced) {
$topMenuApps = $userTopMenuApps; $topMenuApps = $userTopMenuApps;
} }
if (!empty($userTopSideMenuApps) && !$isForced) {
$topSideMenuApps = $userTopSideMenuApps;
}
$isDarkThemeUserEnabled = $this->config->getUserValue($this->user, 'theme', '', 'accessibility') === 'dark'; $isDarkThemeUserEnabled = $this->config->getUserValue($this->user, 'theme', '', 'accessibility') === 'dark';
$isBreezeDarkUserEnabled = $this->config->getUserValue($this->user, 'theme_enabled', '', 'breezedark'); $isBreezeDarkUserEnabled = $this->config->getUserValue($this->user, 'theme_enabled', '', 'breezedark');
@ -90,23 +116,29 @@ class CssController extends Controller
$isDarkMode = ($isAccessibilityAppEnabled && $isDarkThemeUserEnabled) || ($isBreezeDarkAppEnabled && $isBreezeDarkUserEnabled); $isDarkMode = ($isAccessibilityAppEnabled && $isDarkThemeUserEnabled) || ($isBreezeDarkAppEnabled && $isBreezeDarkUserEnabled);
$primaryColor = $this->theming->getColorPrimary();
$lightenPrimaryColor = $this->color->adjustBrightness($primaryColor, 0.2);
$darkenPrimaryColor = $this->color->adjustBrightness($primaryColor, -0.2);
$darkenPrimaryColor2 = $this->color->adjustBrightness($primaryColor, -0.3);
$textColor = $this->theming->getTextColorPrimary();
if ($isDarkMode) { if ($isDarkMode) {
$backgroundColor = $this->config->getAppValue('dark-mode-background-color', '#333333'); $backgroundColor = $this->config->getAppValue('dark-mode-background-color', $darkenPrimaryColor);
$backgroundColorTo = $this->config->getAppValue('dark-mode-background-color-to', $backgroundColor); $backgroundColorTo = $this->config->getAppValue('dark-mode-background-color-to', $darkenPrimaryColor);
$currentAppBackgroundColor = $this->config->getAppValue('dark-mode-current-app-background-color', '#444444'); $currentAppBackgroundColor = $this->config->getAppValue('dark-mode-current-app-background-color', $darkenPrimaryColor2);
$loaderColor = $this->config->getAppValue('dark-mode-loader-color', '#cccccc'); $loaderColor = $this->config->getAppValue('dark-mode-loader-color', $lightenPrimaryColor);
$textColor = $this->config->getAppValue('dark-mode-text-color', '#FFFFFF'); $textColor = $this->config->getAppValue('dark-mode-text-color', $textColor);
$iconInvertFilter = abs($this->config->getAppValueInt('dark-mode-icon-invert-filter', '0')).'%'; $iconInvertFilter = abs($this->config->getAppValueInt('dark-mode-icon-invert-filter', '0')).'%';
$iconOpacity = abs($this->config->getAppValueInt('dark-mode-icon-opacity', '100') / 100); $iconOpacity = abs($this->config->getAppValueInt('dark-mode-icon-opacity', '100') / 100);
$opener = $this->config->getAppValue('dark-mode-opener', 'side-menu-opener'); $opener = $this->config->getAppValue('dark-mode-opener', 'side-menu-opener');
$backgroundOpacity = dechex($this->config->getAppValueInt('dark-mode-background-color-opacity', '100') * 255 / 100); $backgroundOpacity = dechex($this->config->getAppValueInt('dark-mode-background-color-opacity', '100') * 255 / 100);
} else { } else {
$backgroundColor = $this->config->getAppValue('background-color', '#333333'); $backgroundColor = $this->config->getAppValue('background-color', $darkenPrimaryColor);
$backgroundColorTo = $this->config->getAppValue('background-color-to', $backgroundColor); $backgroundColorTo = $this->config->getAppValue('background-color-to', $darkenPrimaryColor);
$currentAppBackgroundColor = $this->config->getAppValue('current-app-background-color', '#444444'); $currentAppBackgroundColor = $this->config->getAppValue('current-app-background-color', $darkenPrimaryColor2);
$loaderColor = $this->config->getAppValue('loader-color', '#0e75ac'); $loaderColor = $this->config->getAppValue('loader-color', $lightenPrimaryColor);
$textColor = $this->config->getAppValue('text-color', '#FFFFFF'); $textColor = $this->config->getAppValue('text-color', $textColor);
$iconInvertFilter = abs($this->config->getAppValueInt('icon-invert-filter', '0')).'%'; $iconInvertFilter = abs($this->config->getAppValueInt('icon-invert-filter', '0')).'%';
$iconOpacity = abs($this->config->getAppValueInt('icon-opacity', '100') / 100); $iconOpacity = abs($this->config->getAppValueInt('icon-opacity', '100') / 100);
$opener = $this->config->getAppValue('opener', 'side-menu-opener'); $opener = $this->config->getAppValue('opener', 'side-menu-opener');
@ -136,6 +168,7 @@ class CssController extends Controller
'always-displayed' => $this->config->getAppValueBool('always-displayed', '0'), 'always-displayed' => $this->config->getAppValueBool('always-displayed', '0'),
'big-menu' => $this->config->getAppValueBool('big-menu', '0'), 'big-menu' => $this->config->getAppValueBool('big-menu', '0'),
'top-menu-apps' => $topMenuApps, 'top-menu-apps' => $topMenuApps,
'top-side-menu-apps' => $topSideMenuApps,
]; ];
} }
} }

View file

@ -94,6 +94,7 @@ class JsController extends Controller
protected function getConfig(): array protected function getConfig(): array
{ {
$topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]'); $topMenuApps = $this->config->getAppValueArray('top-menu-apps', '[]');
$topSideMenuApps = $this->config->getAppValueArray('top-side-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'); $isForced = $this->config->getAppValueBool('force', '0');
@ -103,11 +104,16 @@ class JsController extends Controller
if ($this->user) { if ($this->user) {
$userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]'); $userTopMenuApps = $this->config->getUserValueArray($this->user, 'top-menu-apps', '[]');
$userTopSideMenuApps = $this->config->getUserValueArray($this->user, 'top-side-menu-apps', '[]');
if (!empty($userTopMenuApps) && !$isForced) { if (!empty($userTopMenuApps) && !$isForced) {
$topMenuApps = $userTopMenuApps; $topMenuApps = $userTopMenuApps;
} }
if (!empty($userTopSideMenuApps) && !$isForced) {
$topSideMenuApps = $userTopSideMenuApps;
}
$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', '[]');
@ -152,10 +158,12 @@ class JsController extends Controller
'hide-when-no-apps' => $this->config->getAppValueBool('hide-when-no-apps', '0'), 'hide-when-no-apps' => $this->config->getAppValueBool('hide-when-no-apps', '0'),
'loader-enabled' => $this->config->getAppValueBool('loader-enabled', '1'), 'loader-enabled' => $this->config->getAppValueBool('loader-enabled', '1'),
'always-displayed' => $this->config->getAppValueBool('always-displayed', '0'), 'always-displayed' => $this->config->getAppValueBool('always-displayed', '0'),
'side-with-categories' => $this->config->getAppValueBool('side-with-categories', '0'),
'big-menu' => $this->config->getAppValueBool('big-menu', '0'), 'big-menu' => $this->config->getAppValueBool('big-menu', '0'),
'big-menu-hidden-apps' => $this->config->getAppValueArray('big-menu-hidden-apps', '[]'), 'big-menu-hidden-apps' => $this->config->getAppValueArray('big-menu-hidden-apps', '[]'),
'avatar' => $avatar, 'avatar' => $avatar,
'top-menu-apps' => $topMenuApps, 'top-menu-apps' => $topMenuApps,
'top-side-menu-apps' => $topSideMenuApps,
'target-blank-apps' => $targetBlankApps, 'target-blank-apps' => $targetBlankApps,
'settings' => $settings, 'settings' => $settings,
'logo' => $this->themingDefaults->getLogo(), 'logo' => $this->themingDefaults->getLogo(),

View file

@ -187,9 +187,18 @@ class NavController extends Controller
usort($items, function ($a, $b) use ($categoriesLabels) { usort($items, function ($a, $b) use ($categoriesLabels) {
foreach ($categoriesLabels as $key => $value) { foreach ($categoriesLabels as $key => $value) {
if ($a['categoryId'] === 'other') {
return -1;
}
if ($b['categoryId'] === 'other') {
return 1;
}
if ($a['categoryId'] === $key) { if ($a['categoryId'] === $key) {
return -1; return -1;
} }
if ($b['categoryId'] === $key) { if ($b['categoryId'] === $key) {
return 1; return 1;
} }

View file

@ -54,6 +54,7 @@ class PersonalSettingController extends Controller
/** /**
* @NoAdminRequired * @NoAdminRequired
* @NoCSRFRequired
* *
* @param mixed $name * @param mixed $name
* @param mixed $value * @param mixed $value
@ -96,7 +97,7 @@ class PersonalSettingController extends Controller
} }
} }
if ('top-menu-apps' === $name) { if (in_array($name, ['top-menu-apps', 'top-side-menu-apps'])) {
$doSave = true; $doSave = true;
$data = json_decode($value, true); $data = json_decode($value, true);

View file

@ -21,10 +21,27 @@ class AppRepository
*/ */
protected $l10nFactory; protected $l10nFactory;
public function __construct(\OC_App $ocApp, IFactory $l10nFactory) /**
* @var ConfigProxy
*/
protected $config;
/**
* @var CategoryRepository
*/
protected $categoryRepository;
public function __construct(
\OC_App $ocApp,
IFactory $l10nFactory,
ConfigProxy $config,
CategoryRepository $categoryRepository
)
{ {
$this->ocApp = $ocApp; $this->ocApp = $ocApp;
$this->l10nFactory = $l10nFactory; $this->l10nFactory = $l10nFactory;
$this->config = $config;
$this->categoryRepository = $categoryRepository;
} }
/** /**
@ -35,6 +52,9 @@ class AppRepository
public function getVisibleApps() public function getVisibleApps()
{ {
$navigation = $this->ocApp->getNavigation(); $navigation = $this->ocApp->getNavigation();
$appCategoriesCustom = $this->config->getAppValueArray('apps-categories-custom', '[]');
$categoriesCustom = $this->config->getAppValueArray('categories-custom', '[]');
$categories = $this->categoryRepository->getOrderedCategories();
$apps = $this->ocApp->listAllApps(); $apps = $this->ocApp->listAllApps();
$visibleApps = []; $visibleApps = [];
@ -74,6 +94,12 @@ class AppRepository
} }
} }
foreach ($visibleApps as $id => $app) {
if (isset($appCategoriesCustom[$id], $categories[$appCategoriesCustom[$id]])) {
$visibleApps[$id]['category'] = [$appCategoriesCustom[$id]];
}
}
usort($visibleApps, function ($a, $b) { usort($visibleApps, function ($a, $b) {
return ($a['name'] < $b['name']) ? -1 : 1; return ($a['name'] < $b['name']) ? -1 : 1;
}); });

View file

@ -5,6 +5,7 @@ namespace OCA\SideMenu\Service;
use OC\App\AppStore\Fetcher\CategoryFetcher; use OC\App\AppStore\Fetcher\CategoryFetcher;
use OCA\SideMenu\AppInfo\Application; use OCA\SideMenu\AppInfo\Application;
use OCP\IConfig; use OCP\IConfig;
use OCP\IUserSession;
use OCP\L10N\IFactory; use OCP\L10N\IFactory;
/** /**
@ -34,16 +35,23 @@ class CategoryRepository
*/ */
protected $iConfig; protected $iConfig;
/**
* @var IUserSession
*/
protected $userSession;
public function __construct( public function __construct(
CategoryFetcher $categoryFetcher, CategoryFetcher $categoryFetcher,
ConfigProxy $config, ConfigProxy $config,
IConfig $iConfig, IConfig $iConfig,
IFactory $l10nFactory IFactory $l10nFactory,
IUserSession $userSession
) { ) {
$this->categoryFetcher = $categoryFetcher; $this->categoryFetcher = $categoryFetcher;
$this->l10nFactory = $l10nFactory; $this->l10nFactory = $l10nFactory;
$this->config = $config; $this->config = $config;
$this->iConfig = $iConfig; $this->iConfig = $iConfig;
$this->userSession = $userSession;
} }
/** /**
@ -56,8 +64,8 @@ class CategoryRepository
$currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2); $currentLanguage = substr($this->l10nFactory->findLanguage(), 0, 2);
$type = $this->config->getAppValue('categories-order-type', 'default'); $type = $this->config->getAppValue('categories-order-type', 'default');
$order = $this->config->getAppValueArray('categories-order', '[]'); $order = $this->config->getAppValueArray('categories-order', '[]');
$categoriesLabels = $this->config->getAppValueArray('cache-categories', '[]'); $categoriesLabels = $this->config->getAppValueArray('cache-categories', '[]');
$customCategories = $this->config->getAppValueArray('categories-custom', '[]');
if (empty($categoriesLabels)) { if (empty($categoriesLabels)) {
$categoriesLabels = $this->categoryFetcher->get(); $categoriesLabels = $this->categoryFetcher->get();
@ -74,6 +82,18 @@ class CategoryRepository
$categoriesLabels['external_links'] = $this->l10nFactory->get('external')->t('External sites'); $categoriesLabels['external_links'] = $this->l10nFactory->get('external')->t('External sites');
$categoriesLabels['other'] = ''; $categoriesLabels['other'] = '';
$user = $this->userSession->getUser();
if ($user) {
$lang = $this->iConfig->getUserValue($user->getUid(), 'core', 'lang');
} else {
$lang = 'en';
}
foreach ($customCategories as $category) {
$categoriesLabels[$category['id']] = $category[$lang] ?? $category['en'];
}
asort($categoriesLabels); asort($categoriesLabels);
if ('custom' === $type) { if ('custom' === $type) {

34
lib/Service/Color.php Normal file
View file

@ -0,0 +1,34 @@
<?php
namespace OCA\SideMenu\Service;
/**
* class Color.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class Color
{
/**
* @thanks https://stackoverflow.com/posts/54393956/revision
*/
public function adjustBrightness(string $hexCode, float $adjustPercent): string
{
$hexCode = ltrim($hexCode, '#');
if (3 == strlen($hexCode)) {
$hexCode = $hexCode[0].$hexCode[0].$hexCode[1].$hexCode[1].$hexCode[2].$hexCode[2];
}
$hexCode = array_map('hexdec', str_split($hexCode, 2));
foreach ($hexCode as &$color) {
$adjustableLimit = $adjustPercent < 0 ? $color : 255 - $color;
$adjustAmount = ceil($adjustableLimit * $adjustPercent);
$color = str_pad(dechex($color + $adjustAmount), 2, '0', STR_PAD_LEFT);
}
return '#'.implode($hexCode);
}
}

View file

@ -0,0 +1,48 @@
<?php
namespace OCA\SideMenu\Service;
use OCP\IDBConnection;
/**
* class LangRepository.
*
* @author Simon Vieille <simon@deblan.fr>
*/
class LangRepository
{
/**
* @var IDBConnection
*/
protected $db;
public function __construct(IDBConnection $db)
{
$this->db = $db;
}
public function getUsedLangs(): array
{
$qb = $this->db->getQueryBuilder();
$qb->select($qb->createFunction('DISTINCT configvalue'))
->where('configkey=:configkey and appid=:appid and configvalue<>:configvalue')
->setParameters([
'configkey' => 'lang',
'appid' => 'core',
'configvalue' => 'en',
])
->from('preferences')
;
$stmt = $qb->execute();
$langs = ['en'];
foreach ($stmt->fetchAll() as $result) {
$langs[] = $result['configvalue'];
}
return $langs;
}
}

View file

@ -26,6 +26,9 @@ use OCP\AppFramework\Http\TemplateResponse;
use OCP\IL10N; use OCP\IL10N;
use OCP\ILogger; use OCP\ILogger;
use OCP\Settings\ISettings; use OCP\Settings\ISettings;
use OCA\Theming\ThemingDefaults;
use OCA\SideMenu\Service\Color;
use OCA\SideMenu\Service\LangRepository;
class Admin implements ISettings class Admin implements ISettings
{ {
@ -54,18 +57,39 @@ class Admin implements ISettings
*/ */
private $categoryRepository; private $categoryRepository;
/**
* @var ThemingDefaults
*/
protected $theming;
/**
* @var Color
*/
protected $color;
/**
* @var LangRepository
*/
protected $langRepository;
public function __construct( public function __construct(
IL10N $l, IL10N $l,
ILogger $logger, ILogger $logger,
ConfigProxy $config, ConfigProxy $config,
AppRepository $appRepository, AppRepository $appRepository,
CategoryRepository $categoryRepository CategoryRepository $categoryRepository,
ThemingDefaults $theming,
Color $color,
LangRepository $langRepository
) { ) {
$this->l = $l; $this->l = $l;
$this->logger = $logger; $this->logger = $logger;
$this->config = $config; $this->config = $config;
$this->appRepository = $appRepository; $this->appRepository = $appRepository;
$this->categoryRepository = $categoryRepository; $this->categoryRepository = $categoryRepository;
$this->theming = $theming;
$this->color = $color;
$this->langRepository = $langRepository;
} }
/** /**
@ -73,35 +97,54 @@ class Admin implements ISettings
*/ */
public function getForm() public function getForm()
{ {
$backgroundColor = $this->config->getAppValue('background-color', '#333333'); $primaryColor = $this->theming->getColorPrimary();
$backgroundColorTo = $this->config->getAppValue('background-color-to', $backgroundColor); $lightenPrimaryColor = $this->color->adjustBrightness($primaryColor, 0.2);
$darkenPrimaryColor = $this->color->adjustBrightness($primaryColor, -0.2);
$darkenPrimaryColor2 = $this->color->adjustBrightness($primaryColor, -0.3);
$textColor = $this->theming->getTextColorPrimary();
$darkModeBackgroundColor = $this->config->getAppValue('dark-mode-background-color', '#333333'); $backgroundColor = $this->config->getAppValue('background-color', $darkenPrimaryColor);
$darkModeBackgroundColorTo = $this->config->getAppValue('dark-mode-background-color-to', $darkModeBackgroundColor); $backgroundColorTo = $this->config->getAppValue('background-color-to', $darkenPrimaryColor);
$darkModeBackgroundColor = $this->config->getAppValue('dark-mode-background-color', $darkenPrimaryColor);
$darkModeBackgroundColorTo = $this->config->getAppValue('dark-mode-background-color-to', $darkenPrimaryColor);
$parameters = [ $parameters = [
'defaults' => [
'background-color' => $darkenPrimaryColor,
'background-color-to' => $darkenPrimaryColor,
'current-app-background-color' => $darkenPrimaryColor2,
'text-color' => $textColor,
'loader-color' => $lightenPrimaryColor,
'dark-mode-background-color' => $darkenPrimaryColor,
'dark-mode-background-color-to' => $darkenPrimaryColor,
'dark-mode-current-app-background-color' => $darkenPrimaryColor2,
'dark-mode-text-color' => $textColor,
'dark-mode-loader-color' => $textColor,
],
'background-color' => $backgroundColor, 'background-color' => $backgroundColor,
'background-color-to' => $backgroundColorTo, 'background-color-to' => $backgroundColorTo,
'background-color-opacity' => $this->config->getAppValueInt('background-color-opacity', '100'), 'background-color-opacity' => $this->config->getAppValueInt('background-color-opacity', '100'),
'current-app-background-color' => $this->config->getAppValue('current-app-background-color', '#444444'), 'current-app-background-color' => $this->config->getAppValue('current-app-background-color', $darkenPrimaryColor2),
'loader-color' => $this->config->getAppValue('loader-color', '#0e75ac'), 'loader-color' => $this->config->getAppValue('loader-color', $lightenPrimaryColor),
'icon-invert-filter' => $this->config->getAppValueInt('icon-invert-filter', '0'), 'icon-invert-filter' => $this->config->getAppValueInt('icon-invert-filter', '0'),
'icon-opacity' => $this->config->getAppValueInt('icon-opacity', '100'), 'icon-opacity' => $this->config->getAppValueInt('icon-opacity', '100'),
'text-color' => $this->config->getAppValue('text-color', '#FFFFFF'), 'text-color' => $this->config->getAppValue('text-color', $textColor),
'dark-mode-background-color' => $darkModeBackgroundColor, 'dark-mode-background-color' => $darkModeBackgroundColor,
'dark-mode-background-color-to' => $darkModeBackgroundColorTo, 'dark-mode-background-color-to' => $darkModeBackgroundColorTo,
'dark-mode-background-color-opacity' => $this->config->getAppValueInt('dark-mode-background-color-opacity', '100'), 'dark-mode-background-color-opacity' => $this->config->getAppValueInt('dark-mode-background-color-opacity', '100'),
'dark-mode-current-app-background-color' => $this->config->getAppValue('dark-mode-current-app-background-color', '#444444'), 'dark-mode-current-app-background-color' => $this->config->getAppValue('dark-mode-current-app-background-color', $darkenPrimaryColor2),
'dark-mode-loader-color' => $this->config->getAppValue('dark-mode-loader-color', '#cccccc'), 'dark-mode-loader-color' => $this->config->getAppValue('dark-mode-loader-color', $textColor),
'dark-mode-icon-invert-filter' => $this->config->getAppValueInt('dark-mode-icon-invert-filter', '0'), 'dark-mode-icon-invert-filter' => $this->config->getAppValueInt('dark-mode-icon-invert-filter', '0'),
'dark-mode-icon-opacity' => $this->config->getAppValueInt('dark-mode-icon-opacity', '100'), 'dark-mode-icon-opacity' => $this->config->getAppValueInt('dark-mode-icon-opacity', '100'),
'dark-mode-text-color' => $this->config->getAppValue('dark-mode-text-color', '#FFFFFF'), 'dark-mode-text-color' => $this->config->getAppValue('dark-mode-text-color', $textColor),
'dark-mode-opener' => $this->config->getAppValue('dark-mode-opener', 'side-menu-opener'), 'dark-mode-opener' => $this->config->getAppValue('dark-mode-opener', 'side-menu-opener'),
'opener' => $this->config->getAppValue('opener', 'side-menu-opener'), 'opener' => $this->config->getAppValue('opener', 'side-menu-opener'),
'loader-enabled' => $this->config->getAppValue('loader-enabled', '1'), 'loader-enabled' => $this->config->getAppValue('loader-enabled', '1'),
'cache' => $this->config->getAppValue('cache', '0'), 'cache' => $this->config->getAppValue('cache', '0'),
'always-displayed' => $this->config->getAppValue('always-displayed', '0'), 'always-displayed' => $this->config->getAppValue('always-displayed', '0'),
'big-menu' => $this->config->getAppValue('big-menu', '0'), 'big-menu' => $this->config->getAppValue('big-menu', '0'),
'side-with-categories' => $this->config->getAppValue('side-with-categories', '0'),
'big-menu-hidden-apps' => $this->config->getAppValueArray('big-menu-hidden-apps', '[]'), 'big-menu-hidden-apps' => $this->config->getAppValueArray('big-menu-hidden-apps', '[]'),
'display-logo' => $this->config->getAppValue('display-logo', '1'), 'display-logo' => $this->config->getAppValue('display-logo', '1'),
'add-logo-link' => $this->config->getAppValue('add-logo-link', '1'), 'add-logo-link' => $this->config->getAppValue('add-logo-link', '1'),
@ -116,11 +159,15 @@ class Admin implements ISettings
'force' => $this->config->getAppValue('force', '0'), 'force' => $this->config->getAppValue('force', '0'),
'target-blank-apps' => $this->config->getAppValueArray('target-blank-apps', '[]'), 'target-blank-apps' => $this->config->getAppValueArray('target-blank-apps', '[]'),
'top-menu-apps' => $this->config->getAppValueArray('top-menu-apps', '[]'), 'top-menu-apps' => $this->config->getAppValueArray('top-menu-apps', '[]'),
'top-side-menu-apps' => $this->config->getAppValueArray('top-side-menu-apps', '[]'),
'default-enabled' => $this->config->getAppValue('default-enabled', '1'), 'default-enabled' => $this->config->getAppValue('default-enabled', '1'),
'apps' => $this->appRepository->getVisibleApps(),
'apps-categories-custom' => $this->config->getAppValueArray('apps-categories-custom', '[]'),
'categories-order-type' => $this->config->getAppValue('categories-order-type', 'default'), 'categories-order-type' => $this->config->getAppValue('categories-order-type', 'default'),
'categories-order' => $this->config->getAppValueArray('categories-order', '[]'), 'categories-order' => $this->config->getAppValueArray('categories-order', '[]'),
'apps' => $this->appRepository->getVisibleApps(), 'categories-custom' => $this->config->getAppValueArray('categories-custom', '[]'),
'categories' => $this->categoryRepository->getOrderedCategories(), 'categories' => $this->categoryRepository->getOrderedCategories(),
'langs' => $this->langRepository->getUsedLangs(),
]; ];
return new TemplateResponse(Application::APP_ID, 'settings/admin-form', $parameters, ''); return new TemplateResponse(Application::APP_ID, 'settings/admin-form', $parameters, '');

View file

@ -78,6 +78,7 @@ class Personal implements ISettings
$this->config->getAppValue('default-enabled', '1') $this->config->getAppValue('default-enabled', '1')
), ),
'top-menu-apps' => $this->config->getUserValueArray($user, 'top-menu-apps', '[]'), 'top-menu-apps' => $this->config->getUserValueArray($user, 'top-menu-apps', '[]'),
'top-side-menu-apps' => $this->config->getUserValueArray($user, 'top-side-menu-apps', '[]'),
'target-blank-mode' => $this->config->getUserValue($user, 'target-blank-mode', '1'), 'target-blank-mode' => $this->config->getUserValue($user, 'target-blank-mode', '1'),
'target-blank-apps' => $this->config->getUserValueArray($user, 'target-blank-apps', '[]'), 'target-blank-apps' => $this->config->getUserValueArray($user, 'target-blank-apps', '[]'),
'apps' => $this->appRepository->getVisibleApps(), 'apps' => $this->appRepository->getVisibleApps(),

View file

@ -19,6 +19,7 @@
namespace OCA\SideMenu\Settings; namespace OCA\SideMenu\Settings;
use OCA\SideMenu\AppInfo\Application; use OCA\SideMenu\AppInfo\Application;
use OCA\SideMenu\Service\ConfigProxy;
use OCP\IL10N; use OCP\IL10N;
use OCP\IURLGenerator; use OCP\IURLGenerator;
use OCP\Settings\IIconSection; use OCP\Settings\IIconSection;
@ -35,10 +36,16 @@ class PersonalSection implements IIconSection
*/ */
private $url; private $url;
public function __construct(IURLGenerator $url, IL10N $l) /**
* @var ConfigProxy
*/
private $configProxy;
public function __construct(IURLGenerator $url, IL10N $l, ConfigProxy $configProxy)
{ {
$this->url = $url; $this->url = $url;
$this->l = $l; $this->l = $l;
$this->configProxy = $configProxy;
} }
/** /**
@ -49,6 +56,10 @@ class PersonalSection implements IIconSection
*/ */
public function getID() public function getID()
{ {
if ($this->configProxy->getAppValueBool('force', '0')) {
return '';
}
return Application::APP_ID; return Application::APP_ID;
} }
@ -60,6 +71,10 @@ class PersonalSection implements IIconSection
*/ */
public function getName() public function getName()
{ {
if ($this->configProxy->getAppValueBool('force', '0')) {
return '';
}
return $this->l->t(Application::APP_NAME); return $this->l->t(Application::APP_NAME);
} }
@ -72,6 +87,10 @@ class PersonalSection implements IIconSection
*/ */
public function getPriority() public function getPriority()
{ {
if ($this->configProxy->getAppValueBool('force', '0')) {
return null;
}
return 70; return 70;
} }

View file

@ -11,9 +11,9 @@
"stylelint:fix": "stylelint src --fix" "stylelint:fix": "stylelint src --fix"
}, },
"dependencies": { "dependencies": {
"@nextcloud/axios": "^1.3.2", "@nextcloud/axios": "^1.8.0",
"@nextcloud/vue": "^1.4.0", "@nextcloud/vue": "^1.5.0",
"axios": "^0.19.2", "axios": "^0.24.0",
"trim": "0.0.1", "trim": "0.0.1",
"vue": "^2.6.11" "vue": "^2.6.11"
}, },
@ -21,7 +21,7 @@
"extends @nextcloud/browserslist-config" "extends @nextcloud/browserslist-config"
], ],
"engines": { "engines": {
"node": ">=10.0.0" "node": ">=16.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.9.0", "@babel/core": "^7.9.0",
@ -43,9 +43,9 @@
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^5.2.3", "eslint-plugin-vue": "^5.2.3",
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"node-sass": "^4.13.1",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"stylelint": "^8.4.0", "stylelint": "^8.4.0",
"sass": "^1.49.9",
"stylelint-config-recommended-scss": "^3.3.0", "stylelint-config-recommended-scss": "^3.3.0",
"stylelint-scss": "^3.16.0", "stylelint-scss": "^3.16.0",
"stylelint-webpack-plugin": "^0.10.5", "stylelint-webpack-plugin": "^0.10.5",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 KiB

After

Width:  |  Height:  |  Size: 321 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View file

@ -0,0 +1,182 @@
<!--
@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>
<ul class="side-menu-setting-list">
<li v-for="item in values" class="side-menu-setting-list-item" v-on:click="showEditForm(item)">
<span v-html="item.en"></span>
</li>
</ul>
<Actions>
<ActionButton @click="showAddForm" icon="icon-add"></ActionButton>
</Actions>
<Modal v-if="addForm" @close="hideAddForm">
<div class="modal__content">
<div v-for="lang in langs">
<span class="lang" v-html="lang"></span>
<input type="text" v-model="newValue[lang]" required>
</div>
<Actions>
<ActionButton @click="saveAdd" icon="icon-checkmark"></ActionButton>
</Actions>
</div>
</Modal>
<Modal v-if="editForm" @close="hideEditForm">
<div class="modal__content">
<div v-for="lang in langs">
<span class="lang" v-html="lang"></span>
<input type="text" v-model="editValue[lang]" required>
</div>
<div class="pull-right">
<Actions>
<ActionButton @click="removeEdit" icon="icon-delete"></ActionButton>
</Actions>
</div>
<Actions>
<ActionButton @click="saveEdit" icon="icon-checkmark"></ActionButton>
</Actions>
</div>
</Modal>
</div>
</template>
<style scoped>
.modal__content {
width: 200px;
padding: 10px;
}
.modal__content .lang {
width: 60px;
display: inline-block;
padding: 4px;
box-sizing: border-box;
}
.modal__content input[type=text] {
width: calc(100% - 85px);
display: inline-block;
margin: 0;
}
.pull-right {
float: right;
}
</style>
<script>
import Modal from '@nextcloud/vue/dist/Components/Modal'
import Actions from '@nextcloud/vue/dist/Components/Actions'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
export default {
name: 'AdminCategoriesCustom',
components: {
Modal,
Actions,
ActionButton,
},
data() {
return {
input: null,
values: [],
langs: [],
addForm: false,
editForm: false,
newValue: {},
editValue: {},
}
},
methods: {
init() {
this.values = JSON.parse(this.input.value)
this.langs = JSON.parse(this.input.getAttribute('data-langs'))
},
update() {
this.input.value = JSON.stringify(this.values)
},
showAddForm() {
this.newValue = {id: 'cat' + Math.random().toString().replace('0.', '')}
this.addForm = true
},
showEditForm(value) {
this.editValue = {id: value.id}
for (let i of this.langs) {
this.editValue[i] = typeof value[i] !== 'undefined' ? value[i] : ''
}
this.editForm = true
},
saveAdd() {
for (let i of this.langs) {
if (!this.newValue[i] || /^\s*$/.test(this.newValue[i])) {
return
}
}
this.values.push(this.newValue)
this.update()
this.hideAddForm()
this.newValue = {}
},
saveEdit() {
for (let i of this.langs) {
if (!this.editValue[i] || /^\s*$/.test(this.editValue[i])) {
return
}
}
for (let i in this.values) {
if (this.values[i].id === this.editValue.id) {
this.values[i] = this.editValue
}
}
this.update()
this.hideEditForm()
},
removeEdit() {
for (let i in this.values) {
if (this.values[i].id === this.editValue.id) {
this.values.splice(i, 1);
}
}
this.update()
this.hideEditForm()
},
hideAddForm() {
this.addForm = false
},
hideEditForm() {
this.editForm = false
},
},
mounted() {
this.input = document.querySelector('input[name="categories-custom"]')
this.init()
}
}
</script>

View file

@ -18,11 +18,9 @@
import Vue from 'vue' import Vue from 'vue'
import SideMenu from './SideMenu.vue' import SideMenu from './SideMenu.vue'
import SideMenuBig from './SideMenuBig.vue' import SideMenuBig from './SideMenuBig.vue'
import SideMenuWithCategories from './SideMenuWithCategories.vue'
// Vue.prototype.t = t
Vue.prototype.OC = OC Vue.prototype.OC = OC
// Vue.prototype.OC = OCP
const mountSideMenuComponent = () => { const mountSideMenuComponent = () => {
const sideMenuContainer = document.querySelector('#side-menu') const sideMenuContainer = document.querySelector('#side-menu')
@ -32,6 +30,8 @@ const mountSideMenuComponent = () => {
if (sideMenuContainer.getAttribute('data-bigmenu')) { if (sideMenuContainer.getAttribute('data-bigmenu')) {
component = SideMenuBig component = SideMenuBig
} else if(sideMenuContainer.getAttribute('data-sidewithcategories')) {
component = SideMenuWithCategories
} else { } else {
component = SideMenu component = SideMenu
} }
@ -41,7 +41,7 @@ const mountSideMenuComponent = () => {
sideMenu.$mount('#side-menu') sideMenu.$mount('#side-menu')
$('body').trigger('side-menu.ready') document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.ready'))
} else { } else {
window.setTimeout(mountSideMenuComponent, 50) window.setTimeout(mountSideMenuComponent, 50)
} }

View file

@ -100,7 +100,7 @@ export default {
var dataId = parent.getAttribute('data-id') var dataId = parent.getAttribute('data-id')
dataId = dataId !== null ? dataId : '' dataId = dataId !== null ? dataId : ''
if (!parent.classList.contains('app-hidden') && !menuIsHidden) { if (!parent.classList.contains('app-top-side-menu') && !parent.classList.contains('app-hidden') && !menuIsHidden) {
continue continue
} }
@ -122,13 +122,15 @@ export default {
name: trim(element.querySelector('span').innerHTML), name: trim(element.querySelector('span').innerHTML),
icon: svg, icon: svg,
active: element.classList.contains('active') active: element.classList.contains('active')
}); })
} }
} }
(function(apps) { (function(apps) {
window.setTimeout(function() { window.setTimeout(function() {
jQuery('body').trigger('side-menu.apps', [apps]) document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
}, 1000) }, 1000)
})(this.apps) })(this.apps)
}, },
@ -147,7 +149,7 @@ export default {
that.logo = config['logo'] that.logo = config['logo']
that.logoLink = config['logo-link'] that.logoLink = config['logo-link']
that.settings = config['settings'] that.settings = config['settings']
}); })
}, },
}, },
mounted() { mounted() {

View file

@ -97,8 +97,10 @@ export default {
} }
} }
jQuery('body').trigger('side-menu.apps', [apps]) document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
}); detail: {apps: apps},
}))
})
}, },
retrieveActiveApp() { retrieveActiveApp() {
@ -116,7 +118,7 @@ export default {
that.targetBlankApps = config['target-blank-apps'] that.targetBlankApps = config['target-blank-apps']
that.settings = config['settings'] that.settings = config['settings']
}); })
}, },
}, },
mounted() { mounted() {

View file

@ -0,0 +1,128 @@
<!--
@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 id="side-menu" class="side-menu-with-categories">
<div class="side-menu-header">
<SettingsButton
v-if="settings"
v-bind:href="settings.href"
v-bind:label="settings.name"
v-bind:avatar="settings.avatar"
/>
<OpenerButton />
</div>
<div class="side-menu-categories-wrapper">
<div class="side-menu-categories">
<Loader v-if="!items.length" />
<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>
<ul class="side-menu-apps-list">
<SideMenuBigApp
v-for="(app, appId) in category.apps"
v-bind:classes="{'side-menu-app': true, 'active': activeApp === appId}"
v-bind:icon="app.icon"
v-bind:label="app.name"
v-bind:href="app.href"
v-bind:target="targetBlankApps.indexOf(appId) !== -1 ? '_blank' : undefined"
/>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import trim from 'trim'
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 {
name: 'SideMenuWithCategories',
components: {
SettingsButton,
OpenerButton,
CloserButton,
Loader,
SideMenuBigApp,
},
data() {
return {
items: [],
activeApp: null,
targetBlank: false,
targetBlankApps: [],
settings: null,
}
},
methods: {
retrieveApps() {
this.apps = []
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/nav/items'))
.then(function(response) {
that.items = response.data.items
let apps = []
for (let category of that.items) {
for (let a in category.apps) {
apps.push(category.apps[a])
}
}
document.querySelector('body').dispatchEvent(new CustomEvent('side-menu.apps', {
detail: {apps: apps},
}))
})
},
retrieveActiveApp() {
let activeAppLink = document.querySelector('#appmenu a.active')
this.activeApp = activeAppLink ? activeAppLink.parentNode.getAttribute('data-id') : null
},
retrieveConfig() {
let that = this
axios
.get(OC.generateUrl('/apps/side_menu/js/config'))
.then(function(response) {
const config = response.data
that.targetBlankApps = config['target-blank-apps']
that.settings = config['settings']
})
},
},
mounted() {
this.retrieveConfig()
this.retrieveApps()
this.retrieveActiveApp()
}
}
</script>

View file

@ -15,15 +15,32 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import AdminCategoriesCustom from './AdminCategoriesCustom.vue'
import Vue from 'vue'
Vue.prototype.OC = window.OC
Vue.prototype.OCA = window.OCA
let elements = [] let elements = []
const selector = '#side-menu-message'; const selector = '#side-menu-message'
const userConfig = (name, value, callbacks) => { const userConfig = (name, value, callbacks) => {
const url = OC.generateUrl('/apps/side_menu/personalSetting/valueSet') const url = OC.generateUrl('/apps/side_menu/personalSetting/valueSet')
const formData = []
$.post(url, {name: name, value: value}, callbacks.success) formData.push('name=' + encodeURIComponent(name))
.fail(callbacks.error) formData.push('value=' + encodeURIComponent(value))
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: formData.join('&')
})
.then(callbacks.success)
.catch(callbacks.error)
} }
const appConfig = (name, value, callbacks) => { const appConfig = (name, value, callbacks) => {
@ -31,23 +48,29 @@ const appConfig = (name, value, callbacks) => {
} }
const saveSettings = (key) => { const saveSettings = (key) => {
const element = elements.get(key) const element = elements[key]
if (!element) {
return
}
let value let value
let name let name
if (jQuery(element).is('[data-checkbox]')) { if (element.hasAttribute('data-checkbox')) {
name = jQuery(element).attr('data-name') name = element.getAttribute('data-name')
const inputs = jQuery('input[name="' + name + '[]"]:checked')
value = [] value = []
inputs.each((i, v) => { const inputs = document.querySelectorAll('input[name="' + name + '[]"]:checked')
value.push(v.value)
}) for (let input of inputs) {
value.push(input.value)
}
value = JSON.stringify(value) value = JSON.stringify(value)
} else { } else {
name = jQuery(element).attr('name') name = element.getAttribute('name')
value = jQuery(element).val() value = element.value
} }
const size = elements.length const size = elements.length
@ -56,17 +79,21 @@ const saveSettings = (key) => {
++value ++value
} }
const progress = document.querySelector('#side-menu-save-progress')
progress.style.width = '40px';
progress.style.marginLeft = '5px';
const callbacks = { const callbacks = {
success: () => { success: () => {
OC.msg.finishedSuccess( const percent = parseInt((key + 1) * 100 / size);
selector,
t('side_menu', (key + 1) + '/' + size) progress.setAttribute('value', percent)
)
if (key < size - 1) { if (key < size - 1) {
saveSettings(++key) saveSettings(key + 1)
} else { } else {
OC.msg.finishedSuccess(selector, t('side_menu', 'Saved')) location.reload()
} }
}, },
error: () => { error: () => {
@ -74,7 +101,7 @@ const saveSettings = (key) => {
} }
} }
if (jQuery(element).is('[data-personal]')) { if (element.hasAttribute('data-personal')) {
userConfig(name, value, callbacks) userConfig(name, value, callbacks)
} else { } else {
appConfig(name, value, callbacks) appConfig(name, value, callbacks)
@ -82,83 +109,152 @@ const saveSettings = (key) => {
} }
const elementToggler = (element) => { const elementToggler = (element) => {
jQuery(element).toggle() let display = 'none'
if (window.getComputedStyle(element).display === 'none') {
display = 'block'
}
element.style.display = display
} }
jQuery(document).ready(() => { const updateAppsCategoriesCustom = () => {
elements = jQuery('.side-menu-setting') let values = {}
jQuery('#side-menu-save').on('click', (event) => { for (let item of document.querySelectorAll('.apps-categories-custom')) {
let app = item.getAttribute('data-app')
let value = item.value
if (value) {
values[app] = value
}
}
document.querySelector('#apps-categories-custom').value = JSON.stringify(values)
}
document.addEventListener('DOMContentLoaded', () => {
$('*[data-toggle="tooltip"]').tooltip();
if (document.querySelector('#side-menu-categories-custom')) {
const View = Vue.extend(AdminCategoriesCustom)
const adminCategoriesCustom = new View({})
adminCategoriesCustom.$mount('#side-menu-categories-custom')
}
elements = document.querySelectorAll('.side-menu-setting')
document.querySelector('#side-menu-save').addEventListener('click', (event) => {
event.preventDefault() event.preventDefault()
OC.msg.startSaving(selector) OC.msg.startSaving(selector)
saveSettings(0) saveSettings(0)
});
jQuery('.side-menu-display').on('click', (event) => {
var target = jQuery(event.target)
jQuery('.side-menu-display').removeClass('is-active')
target.addClass('is-active')
jQuery('#side-menu-always-displayed').val(target.attr('data-alwaysdiplayed'))
jQuery('#side-menu-big-menu').val(target.attr('data-bigmenu'))
}) })
jQuery('.side-menu-setting-live').on('change', (event) => { const resets = document.querySelectorAll('.btn-reset')
var target = jQuery(event.target)
var name = target.attr('name')
var value = target.val()
if ('background-color-opacity' === name) { for (let btn of resets) {
return $('#side-menu-background-color, #side-menu-background-color-to').trigger('change'); btn.addEventListener('click', (event) => {
} else if ('dark-mode-background-color-opacity' === name) { const target = event.target
return $('#side-menu-dark-mode-background-color, #side-menu-dark-mode-background-color-to').trigger('change'); const values = JSON.parse(target.getAttribute('data-reset'))
}
if (name === 'opener') { for (let i in values) {
var url = OC.generateUrl(`/apps/side_menu/img/${value}.svg`).replace('/index.php', '') document.querySelector(`#${i}`).value = values[i]
}
})
}
value = `url(${url})`; const displays = document.querySelectorAll('.side-menu-display')
}
if (name === 'icon-invert-filter' || name === 'icon-opacity') { for (let display of displays) {
value/=100; display.addEventListener('click', (event) => {
} const target = event.target
if (['dark-mode-background-color', 'dark-mode-background-color-to'].indexOf(name) > -1) { for (let d of displays) {
var opacity = parseInt($('#side-menu-dark-mode-background-color-opacity').val() * 255 / 100); d.classList.toggle('is-active', d === display)
}
value = [value, opacity.toString(16)].join(''); document.querySelector('#side-menu-always-displayed').value = target.getAttribute('data-alwaysdiplayed')
} else if (['background-color', 'background-color-to'].indexOf(name) > -1) { document.querySelector('#side-menu-big-menu').value = target.getAttribute('data-bigmenu')
var opacity = parseInt($('#side-menu-background-color-opacity').val() * 255 / 100); document.querySelector('#side-menu-side-with-categories').value = target.getAttribute('data-sidewithcategories')
})
}
value = [value, opacity.toString(16)].join(''); for (let item of document.querySelectorAll('.apps-categories-custom')) {
} item.addEventListener('change', (event) => {
updateAppsCategoriesCustom()
})
}
document.documentElement.style.setProperty('--side-menu-' + name, value) for (let item of document.querySelectorAll('.side-menu-setting-live')) {
item.addEventListener('change', (event) => {
const target = event.target
const name = target.getAttribute('name')
let value = target.value
let id = null
if (name === 'background-color-opacity') {
id = '#side-menu-background-color, #side-menu-background-color-to'
} else if (name === 'dark-mode-background-color-opacity') {
id = '#side-menu-dark-mode-background-color, #side-menu-dark-mode-background-color-to'
}
if (id) {
document.querySelector(id).dispatchEvent(new CustomEvent('change'))
return
}
if (name === 'opener') {
const url = OC.generateUrl(`/apps/side_menu/img/${value}.svg`).replace('/index.php', '')
value = `url(${url})`
}
if (name === 'icon-invert-filter' || name === 'icon-opacity') {
value/=100
}
if (['dark-mode-background-color', 'dark-mode-background-color-to'].indexOf(name) > -1) {
const opacity = parseInt(document.querySelector('#side-menu-dark-mode-background-color-opacity').value * 255 / 100)
value = [value, opacity.toString(16)].join('')
} else if (['background-color', 'background-color-to'].indexOf(name) > -1) {
const opacity = parseInt(document.querySelector('#side-menu-background-color-opacity').value * 255 / 100)
value = [value, opacity.toString(16)].join('')
}
document.documentElement.style.setProperty('--side-menu-' + name, value)
})
}
for (let toggler of document.querySelectorAll('.side-menu-toggler')) {
toggler.addEventListener('click', (event) => {
const target = event.target
const element = document.querySelector(target.getAttribute('data-target'))
elementToggler(element)
})
}
sortable('#categories-list .side-menu-setting-list', {
placeholderClass: 'side-menu-setting-list-drop'
}) })
jQuery('.side-menu-toggler').on('click', (event) => { try {
var target = jQuery(event.target) sortable('#categories-list .side-menu-setting-list')[0].addEventListener('sortstop', (e) => {
var element = target.attr('data-target')
elementToggler(element)
})
jQuery("#categories-list .side-menu-setting-list").sortable({
forcePlaceholderSize: true,
placeholder: 'placeholder',
stop: function (event, ui) {
let value = [] let value = []
jQuery('#categories-list .side-menu-setting-list-item').each(function() { for (let item of document.querySelectorAll('#categories-list .side-menu-setting-list-item')) {
value.push(jQuery(this).attr('data-id')) console.log(item.getAttribute('data-id'))
}); value.push(item.getAttribute('data-id'))
}
value = JSON.stringify(value) document.querySelector('input[name="categories-order"]').value = JSON.stringify(value)
})
jQuery('input[name="categories-order"]').val(value) } catch (e) {
} }
}); })
});

View file

@ -40,7 +40,7 @@
"Panel": "Panel" "Panel": "Panel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Otevřít nabídku při najetím ukazatelem na tlačítko nabídky (automaticky vypnuto pro dotykové obrazovky)." "Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Otevřít nabídku při najetím ukazatelem na tlačítko nabídky (automaticky vypnuto pro dotykové obrazovky)."
"Display the big menu": "Zobrazit velkou nabídku" "Display the big menu": "Zobrazit velkou nabídku"
"The big menu is not compatible with AppOrder.": "Velká nabídka není kompatibilní s jinou aplikací (doplňkem) „Pořadí aplikací“." "This menu is not compatible with AppOrder.": "Nabídka není kompatibilní s jinou aplikací (doplňkem) „Pořadí aplikací“."
"Display the logo": "Zobrazit logo" "Display the logo": "Zobrazit logo"
"This feature is not compatible with the <code>big menu</code> display.": "Tato funkce není kompatibilní se zobrazením <code>velké nabídky</code>." "This feature is not compatible with the <code>big menu</code> display.": "Tato funkce není kompatibilní se zobrazením <code>velké nabídky</code>."
"Icons and texts": "Ikony a texty" "Icons and texts": "Ikony a texty"
@ -76,3 +76,18 @@
"Show and hide the list of categories": "Zobrazit/skrýt seznam kategorií" "Show and hide the list of categories": "Zobrazit/skrýt seznam kategorií"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "Tyto parametry jsou použity v případě, že je zapnutý (Breeze) tmavý motiv vzhledu." "This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "Tyto parametry jsou použity v případě, že je zapnutý (Breeze) tmavý motiv vzhledu."
"Dark mode colors": "Barvy tmavého režimu" "Dark mode colors": "Barvy tmavého režimu"
"With categories": "S kategoriemi"
"Custom categories": "Vlastní kategorie"
"Customize application categories": "Personnaliser les catégories des applications"
"Customize application categories": "Přizpůsobte kategorie aplikací"
"Apps only visible in the top menu": "Aplikace jsou viditelné pouze v horní nabídce "
"Apps visible in the top and side menus": "Aplikace viditelné v horní a boční nabídce"
"Reset to default": "Vrátit zpět na výchozí hodnoty"
"Hidden icon": "Skrytá ikona"
"Small icon": "Malá ikona"
"Normal icon": "Normální ikona"
"Big icon": "Velká ikona"
"Hidden text": "Skrytý text"
"Small text": "Malý text"
"Normal text": "Normální text"
"Big text": "Velký text"

View file

@ -1,31 +1,31 @@
"Custom menu": "Benutzerdefiniertes Menü" "Custom menu": "Benutzerdefiniertes Menü"
"Enable the custom menu": "Aktiviere das Benutzerdefiniertes Menü" "Enable the custom menu": "Benutzerdefiniertes Menü aktivieren"
"No": "Nein" "No": "Nein"
"Yes": "Ja" "Yes": "Ja"
"Menu": "Menü" "Menu": "Menü"
'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.': 'Verwende die Tastenkombination <span class="keyboard-key">Strg</span>+<span class="keyboard-key">o</span>, um das Seitenmenü ein- und auszublenden. Verwende <span class="keyboard-key">tab</span> zum Navigieren.' '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.': 'Verwende die Tastenkombination <span class="keyboard-key">Strg</span>+<span class="keyboard-key">o</span>, um das Seitenmenü ein- und auszublenden. Verwende <span class="keyboard-key">tab</span> zum Navigieren.'
"Top menu": "Hauptmenü" "Top menu": "Obere Navigationsleiste"
"Apps that not must be moved in the side menu": "Apps, die nicht ins Seitenmenü verschoben werden müssen" "Apps that not must be moved in the side menu": "Anwendungen, die nicht ins Seitenmenü verschoben werden sollen"
"If there is no selection then the global configuration is applied.": "Wenn keine Auswahl vorhanden ist, wird die globale Konfiguration angewendet." "If there is no selection then the global configuration is applied.": "Wenn keine Auswahl vorhanden ist, wird die globale Konfiguration angewendet."
"Experimental": "Experimentell" "Experimental": "Experimentell"
"Save": "Speichern" "Save": "Speichern"
"You like this app and you want to support me?": "Du magst diese App und möchtest mich unterstützen?" "You like this app and you want to support me?": "Du magst diese Anwendung und möchtest mich unterstützen?"
"Buy me a coffee ☕": "Gib mir einen Kaffee ☕" "Buy me a coffee ☕": "Gib mir einen Kaffee aus ☕"
"Hidden": "Versteckt" "Hidden": "Ausblenden"
"Small": "Klein" "Small": "Klein"
"Normal": "Normal" "Normal": "Normal"
"Big": "Groß" "Big": "Groß"
"Colors": "Farben" "Colors": "Farben"
"Background color": "Hintergrundfarbe" "Background color": "Hintergrundfarbe"
"Background color of current app": "Hintergrundfarbe der aktuellen App" "Background color of current app": "Hintergrundfarbe der aktuellen Anwendung"
"Text color": "Textfarbe" "Text color": "Textfarbe"
"Loader": "Ladestandanzeige" "Loader": "Ladestandanzeige"
"Icon": "Symbol" "Icon": "Symbol"
"Same color": "Selbe Farbe" "Same color": "Selbe Farbe"
"Opposite color": "Gegenfarbe" "Opposite color": "Gegenfarbe"
"Transparent": "Transparent" "Transparent": "Transparent"
"Opaque": "Undurchsichtig" "Opaque": "Nicht transparent"
"Opener": "Öffner" "Opener": "Menü-Symbol"
"Default": "Standard" "Default": "Standard"
"Default (dark)": "Standard (dunkel)" "Default (dark)": "Standard (dunkel)"
"Hamburger": "Hamburger" "Hamburger": "Hamburger"
@ -35,44 +35,58 @@
"Before the logo": "Vor dem Logo" "Before the logo": "Vor dem Logo"
"After the logo": "Nach dem Logo" "After the logo": "Nach dem Logo"
"Position": "Position" "Position": "Position"
"Show only the opener (hidden logo)": "Nur den Öffner anzeigen (verstecktes Logo)" "Show only the opener (hidden logo)": "Nur das Menü-Symbol anzeigen (Logo wird ausgeblendet)"
"Do not display the side menu and the opener if there is no application (eg: public pages).": "Zeige das Seitenmenü und den Öffner nicht an, wenn keine Anwendung vorhanden ist (z.B. bei öffentlichen Seiten)." "Do not display the side menu and the opener if there is no application (eg: public pages).": "Zeige das Seitenmenü und das Menü-Symbol nicht an, wenn keine Anwendung vorhanden ist (z.B. bei öffentlichen Seiten)."
"Panel": "Panel" "Panel": "Panel"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Öffne das Menü, wenn die Maus über den Öffner bewegt wird (auf Touchscreens automatisch deaktiviert)." "Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Öffne das Menü, wenn die Maus über das Menü-Symbol bewegt wird (auf Touchscreens automatisch deaktiviert)."
"Display the big menu": "Großes Menü anzeigen" "Display the big menu": "Großes Menü anzeigen"
"The big menu is not compatible with AppOrder.": "Das große Menü ist nicht mit AppOrder kompatibel." "This menu is not compatible with AppOrder.": "Dieses Menü ist nicht mit <code>AppOrder</code> kompatibel."
"Display the logo": "Logo anzeigen" "Display the logo": "Logo anzeigen"
"This feature is not compatible with the <code>big menu</code> display.": "Diese Funktion ist nicht mit <code>großes Menü</code> kompatibel." "This feature is not compatible with the <code>big menu</code> display.": "Diese Funktion ist nicht mit dem <code>großen Menü</code> kompatibel."
"Icons and texts": "Symbole und Texte" "Icons and texts": "Symbole und Texte"
"Loader enabled": "Loader aktiviert" "Loader enabled": "Ladestandanzeige aktiviert"
"Tips": "Tipps" "Tips": "Tipps"
"Always displayed": "Wird immer angezeigt" "Always displayed": "Immer anzeigen"
"The logo will be hidden when the menu is always displayed.": "Das Logo wird ausgeblendet, wenn das Menü immer angezeigt wird." "The logo will be hidden when the menu is always displayed.": "Das Logo wird ausgeblendet, wenn das Menü immer angezeigt wird."
"This is the automatic behavior when the menu is always displayed.": "Dies ist das automatische Verhalten, wenn das Menü immer angezeigt wird." "This is the automatic behavior when the menu is always displayed.": "Dies ist das automatische Verhalten, wenn das Menü immer angezeigt wird."
"Not compatible with touch screens.": "Nicht kompatibel mit Touchscreens." "Not compatible with touch screens.": "Nicht kompatibel mit Touchscreens."
"Big menu": "Großes Menü" "Big menu": "Großes Menü"
"Live preview": "Live Vorschau" "Live preview": "Live-Vorschau"
"Open apps in new tab": "Öffne Apps in einem neuen Tab" "Open apps in new tab": "Öffne Anwendungen in einem neuen Tab"
"Use the global setting": "Verwende die globale Einstellung" "Use the global setting": "Verwende die globale Einstellung"
"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": "Avatar anstelle des Logos anzeigen"
"You do not have permission to change the settings.": "Sie haben keine Berechtigung zum Ändern der Einstellungen." "You do not have permission to change the settings.": "Du hast keine Berechtigung, die Einstellungen dieser Anwendung zu ändern."
"Force this configuration to users": "Erzwingen Sie diese Konfiguration für Benutzer" "Force this configuration to users": "Konfiguration für alle Benutzer erzwingen"
"Export the configuration": "Exportieren Sie die Konfiguration" "Export the configuration": "Konfiguration exportieren"
"Purge the cache": "Leeren Sie den Cache" "Purge the cache": "Cache leeren"
"Show the link to settings": "Zeigen Sie den Link zu den Einstellungen an" "Show the link to settings": "Link zu den Einstellungen anzeigen"
"The menu is enabled by default for users": "Das Menü ist standardmäßig für Benutzer aktiviert" "The menu is enabled by default for users": "Das Menü ist standardmäßig für alle Benutzer aktiviert"
"Except when the configuration is forced.": "Außer wenn die Konfiguration erzwungen wird." "Except when the configuration is forced.": "Gilt nicht, wenn die Konfiguration erzwungen wird."
"Apps that should not be displayed in the menu": "Apps, die nicht im Menü angezeigt werden sollen" "Apps that should not be displayed in the menu": "Anwendungen, die nicht im Menü angezeigt werden sollen"
"This feature is only compatible with the <code>big menu</code> display.": "Kompatibel mit der Anzeige <code> Großes Menü </ code>." "This feature is only compatible with the <code>big menu</code> display.": "Kompatibel mit dem <code>großen Menü</code>."
"The logo is a link to the default app": "Das Logo ist ein Link zur Standard-App" "The logo is a link to the default app": "Das Logo ist ein Link zur Standard-App"
"Others": "Andere" "Others": "Andere"
"Categories": "Kategorien" "Categories": "Kategorien"
"Customize sorting": "Sortierung anpassen" "Customize sorting": "Sortierung anpassen"
"Order by": "Sortieren nach" "Order by": "Sortieren nach"
"Name": "Name" "Name": "Name"
"Customed": "Kundenspezifisch" "Customed": "Benutzerdefiniert"
"Show and hide the list of categories": "Liste der Kategorien ein- und ausblenden" "Show and hide the list of categories": "Liste der Kategorien ein- und ausblenden"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "Diese Parameter werden verwendet, wenn Dark Theme oder Breeze Dark Theme aktiviert sind." "This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "Diese Optionen werden auf <code>Dark Theme</code> oder <code>Breeze Dark Theme</code> angewendet."
"Dark mode colors": "Dunkle Modusfarben" "Dark mode colors": "Farben für den dunklen Modus"
"With categories": "Mit Kategorien"
"Custom categories": "Benutzerdefinierte Kategorien"
"Customize application categories": "Anwendungskategorien anpassen"
"Apps only visible in the top menu": "Apps nur im oberen Menü sichtbar "
"Apps visible in the top and side menus": "Apps im oberen und seitlichen Menü sichtbar"
"Reset to default": "Auf Standard zurücksetzen"
"Hidden icon": "Verstecktes Symbol"
"Small icon": "Kleines Symbol"
"Normal icon": "Normales Symbol"
"Big icon": "Große Ikone"
"Hidden text": "Versteckter Text"
"Small text": "Kleiner Text"
"Normal text": "Normaler Text"
"Big text": "Großer Text"

View file

@ -15,6 +15,14 @@
"Small": "Petit" "Small": "Petit"
"Normal": "Normal" "Normal": "Normal"
"Big": "Gros" "Big": "Gros"
"Hidden icon": "Icône masqué"
"Small icon": "Petit icône"
"Normal icon": "Icône normal"
"Big icon": "Gros icône"
"Hidden text": "Text masqué"
"Small text": "Texte petit"
"Normal text": "Texte normal"
"Big text": "Gros texte"
"Colors": "Couleurs" "Colors": "Couleurs"
"Background color": "Couleur de fond" "Background color": "Couleur de fond"
"Background color of current app": "Couleur de fond de l'application en cours" "Background color of current app": "Couleur de fond de l'application en cours"
@ -40,7 +48,7 @@
"Panel": "Panneau" "Panel": "Panneau"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Ouvrir le menu au passage de la souris (automatiquement désactivé sur les écrans tactiles)" "Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "Ouvrir le menu au passage de la souris (automatiquement désactivé sur les écrans tactiles)"
"Display the big menu": "Afficher le menu large" "Display the big menu": "Afficher le menu large"
"The big menu is not compatible with AppOrder.": "Le menu large n'est pas compatible avec l'application AppOrder" "This menu is not compatible with AppOrder.": "Ce menu n'est pas compatible avec l'application AppOrder"
"Display the logo": "Afficher le logo" "Display the logo": "Afficher le logo"
"This feature is not compatible with the <code>big menu</code> display.": "Cette fonctionnalité n'est pas compatible avec l'affichage du menu large." "This feature is not compatible with the <code>big menu</code> display.": "Cette fonctionnalité n'est pas compatible avec l'affichage du menu large."
"Icons and texts": "Icônes et textes" "Icons and texts": "Icônes et textes"
@ -76,3 +84,9 @@
"Show and hide the list of categories": "Afficher et masquer la liste des catégories" "Show and hide the list of categories": "Afficher et masquer la liste des catégories"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "Ces paramètres sont utilisés lorsque le thème sombre ou le thème Breeze Dark sont activés." "This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "Ces paramètres sont utilisés lorsque le thème sombre ou le thème Breeze Dark sont activés."
"Dark mode colors": "Couleurs du mode sombre" "Dark mode colors": "Couleurs du mode sombre"
"With categories": "Avec les catégories"
"Custom categories": "Catégories personnalisées"
"Customize application categories": "Personnaliser les catégories des applications"
"Apps only visible in the top menu": "Applications visibles uniquement dans le menu supérieur"
"Apps visible in the top and side menus": "Applications visibles dans le menus supérieur et latéral"
"Reset to default": "Restaurer les valeurs par défaut"

View file

@ -40,7 +40,7 @@
"Panel": "面板" "Panel": "面板"
"Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "鼠标悬停时打开菜单 (触摸屏时将自动禁用)" "Open the menu when the mouse is hover the opener (automatically disabled on touch screens)": "鼠标悬停时打开菜单 (触摸屏时将自动禁用)"
"Display the big menu": "显示大型菜单" "Display the big menu": "显示大型菜单"
"The big menu is not compatible with AppOrder.": "型菜单与应用顺序不兼容" "This menu is not compatible with AppOrder.": "型菜单与应用顺序不兼容"
"Display the logo": "显示logo" "Display the logo": "显示logo"
"This feature is not compatible with the <code>big menu<\/code> display.": "此功能与显示<code>大型菜单<\/code>不兼容。" "This feature is not compatible with the <code>big menu<\/code> display.": "此功能与显示<code>大型菜单<\/code>不兼容。"
"Icons and texts": "图标与文字" "Icons and texts": "图标与文字"
@ -76,3 +76,17 @@
"Show and hide the list of categories": "显示或隐藏类别列表" "Show and hide the list of categories": "显示或隐藏类别列表"
"This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "此参数将应用于暗黑主题激活时。" "This parameters are used when Dark theme or Breeze Dark Theme are enabled.": "此参数将应用于暗黑主题激活时。"
"Dark mode colors": "暗黑模式颜色" "Dark mode colors": "暗黑模式颜色"
"With categories": "有类别"
"Custom categories": "自定义类别"
"Customize application categories": "自定义应用程序类别"
"Apps only visible in the top menu": "应用程序仅在顶部菜单中可见"
"Apps visible in the top and side menus": "顶部和侧边菜单中可见的应用程序"
"Reset to default": "重置为默认设置"
"Hidden icon": "隐藏图标"
"Small icon": "小图标"
"Normal icon": "正常图标"
"Big icon": "大图标"
"Hidden text": "隐藏文字"
"Small text": "小文本"
"Normal text": "普通文本"
"Big text": "大文本"

View file

@ -8,7 +8,7 @@
<?php endforeach; ?> <?php endforeach; ?>
} }
<?php if (empty($_['top-menu-apps'])): ?> <?php if (empty($_['top-menu-apps']) && empty($_['top-side-menu-apps'])): ?>
#appmenu { #appmenu {
display: none; display: none;
} }

View file

@ -1,21 +1,19 @@
var alwaysDisplayed = function() { const alwaysDisplayed = function() {
var elements = document.querySelectorAll('*'); const elements = querySelectorAll('*')
var fixedElements = [] const fixedElements = []
for (var i in elements) {
var element = elements[i]
for (var element of elements) {
if (typeof element !== 'object') { if (typeof element !== 'object') {
continue continue
} }
var position = window.getComputedStyle(element, null).getPropertyValue('position'); const position = window.getComputedStyle(element, null).getPropertyValue('position')
if (position !== 'fixed') { if (position !== 'fixed') {
continue continue
} }
var id = element.getAttribute('id') const id = element.getAttribute('id')
if (id === 'header' || id === 'side-menu' || id === 'side-menu-loader') { if (id === 'header' || id === 'side-menu' || id === 'side-menu-loader') {
continue continue
@ -25,7 +23,21 @@ var alwaysDisplayed = function() {
continue continue
} }
if (jQuery(element).parents('#side-menu').length) { let elementIsInSideMenu = false
let parent = element.parentNode
while (parent && !elementIsInSideMenu) {
try {
if (parent.getAttribute('id') === 'side-menu') {
elementIsInSideMenu = true
}
} catch (e) {
}
parent = parent.parentNode
}
if (elementIsInSideMenu) {
continue continue
} }
@ -33,19 +45,19 @@ var alwaysDisplayed = function() {
} }
for (var i in fixedElements) { for (var i in fixedElements) {
var element = fixedElements[i] const element = fixedElements[i]
var computedStyle = window.getComputedStyle(element, null) const computedStyle = window.getComputedStyle(element, null)
var left = computedStyle.getPropertyValue('left') const left = computedStyle.getPropertyValue('left')
var right = computedStyle.getPropertyValue('right') const right = computedStyle.getPropertyValue('right')
if (right !== '0px') { if (right !== '0px') {
var intValue = parseInt(left.replace('px', '')) const intValue = parseInt(left.replace('px', '')) + 50
element.style.setProperty('transform', 'translateX(' + (intValue + 50) + 'px)') element.style.setProperty('transform', 'translateX(' + intValue.toString() + 'px)')
} }
} }
} }
let content = document.getElementById('content') const content = querySelector('#content')
if (content && content.classList.contains('app-settings')) { if (content && content.classList.contains('app-settings')) {
let loaded = false let loaded = false
@ -56,7 +68,7 @@ if (content && content.classList.contains('app-settings')) {
} }
const observer = new MutationObserver(() => { const observer = new MutationObserver(() => {
if (loaded) { if (loaded) {
return; return
} }
const element = content.querySelector('#app-category-your-apps') || content.querySelector('#app-navigation ul') const element = content.querySelector('#app-category-your-apps') || content.querySelector('#app-navigation ul')

View file

@ -1,15 +1,14 @@
var pageLoader = jQuery('<div id="side-menu-loader">') let pageLoader = createElement('div', {id: 'side-menu-loader'})
var pageLoaderBar = jQuery('<div id="side-menu-loader-bar">') let pageLoaderBar = createElement('div', {id: 'side-menu-loader-bar'})
body.append(pageLoader) pageLoader.appendChild(pageLoaderBar)
pageLoader.append(pageLoaderBar) querySelector('body').appendChild(pageLoader)
var pageLoaderValue = 0 let pageLoaderValue = 0
$(window).on('beforeunload', function() {
setInterval(function() {
pageLoaderBar.width(pageLoaderValue.toString() + '%')
window.addEventListener('beforeunload', () => {
setInterval(() => {
pageLoaderBar.style.width = pageLoaderValue.toString() + '%'
pageLoaderValue = Math.min(pageLoaderValue + .2, 100) pageLoaderValue = Math.min(pageLoaderValue + .2, 100)
}, 25) }, 25)
}) })

View file

@ -1,60 +1,91 @@
var menuCache = null let menuCache = null
var updateTopMenu = function() { const breakpointMobileWidth = 1024
var breakpointMobileWidth = 1024 const usePercentualAppMenuLimit = 0.8
var menu = jQuery('#appmenu') const minAppsDesktop = 8
var apps = menu.find('li')
var minAppsDesktop = 8
var usePercentualAppMenuLimit = 0.8
var isMobile = jQuery(window).width() < breakpointMobileWidth
var lastShownApp = null
var appShown = []
var moreApps = jQuery('#more-apps')
var navigation = jQuery('#navigation')
var navigationApps = jQuery('#apps ul')
var appCount = null
var currentMenuCache = menu.html() + menu.next().html() const handleMenuClick = (e, icon) => {
let element = e.target
if (currentMenuCache === menuCache) { while (element.tagName !== 'LI') {
element = element.parentNode
}
const a = querySelector('a', element)
if (a.getAttribute('target') !== '_blank' && e.which === 1 && !e.ctrlKey && !e.metaKey) {
for (let tag of ['svg', 'div']) {
let el = querySelector(tag, element)
if (el) {
el.remove()
}
}
const loader = createElement('div', {'class': icon})
a.insertBefore(loader, querySelector('span', a))
}
}
const updateTopMenu = function() {
const isMobile = window.innerWidth < breakpointMobileWidth
const menu = querySelector('#appmenu')
const moreApps = querySelector('#more-apps')
const navigation = querySelector('#navigation')
const navigationApps = querySelector('#apps ul')
let apps = querySelectorAll('li', menu)
let lastShownApp = null
let appShown = []
if ((menu.innerHTML + menu.nextSibling.innerHTML) === menuCache) {
return return
} }
navigationApps.html('') navigationAppsHtml = ''
apps.each(function(i, app) { for (let app of apps) {
var dataId = app.getAttribute('data-id') const dataId = app.getAttribute('data-id')
if (dataId === null) { if (dataId === null) {
return continue
} }
if (topMenuApps.indexOf(dataId) === -1) { if (topMenuApps.indexOf(dataId) === -1 && topSideMenuApps.indexOf(dataId) === -1) {
app.classList.add('hidden') app.classList.add('hidden')
app.classList.add('app-hidden') app.classList.add('app-hidden')
} else { } else {
app.classList.remove('hidden') app.classList.remove('hidden')
app.classList.add('app-external-site') app.classList.add('app-external-site')
if (topSideMenuApps.indexOf(dataId) !== -1) {
app.classList.add('app-top-side-menu')
}
appShown.push(app) appShown.push(app)
navigationApps.append(app.outerHTML)
navigationAppsHtml = navigationAppsHtml + app.outerHTML
} }
if (targetBlankApps.indexOf(dataId) !== -1) { if (targetBlankApps.indexOf(dataId) !== -1) {
jQuery(app).children('a').attr('target', '_blank'); querySelector('a', app).setAttribute('target', '_blank')
} }
})
var rightHeaderWidth = jQuery('.header-right').outerWidth()
var headerWidth = jQuery('header').outerWidth()
var availableWidth = headerWidth - jQuery('#nextcloud').outerWidth()
- jQuery('#header .side-menu-opener').outerWidth()
- (rightHeaderWidth > 230 ? rightHeaderWidth : 230)
if (!isMobile) {
availableWidth = availableWidth * usePercentualAppMenuLimit
} }
appCount = Math.floor(availableWidth / jQuery('#appmenu li').width()) navigationApps.innerHTML = navigationAppsHtml
const rightHeaderWidth = querySelector('.header-right').offsetWidth
const headerWidth = querySelector('header').offsetWidth
let availableWidth = headerWidth
availableWidth -= nextcloud.offsetWidth
availableWidth -= querySelector('#header .side-menu-opener').offsetWidth
availableWidth -= rightHeaderWidth > 230 ? rightHeaderWidth : 230
availableWidth *= isMobile ? usePercentualAppMenuLimit : 1
let appCount = Math.floor(availableWidth / querySelector('#appmenu li:not(.hidden)').offsetWidth)
if (isMobile && appCount > minAppsDesktop) { if (isMobile && appCount > minAppsDesktop) {
appCount = minAppsDesktop appCount = minAppsDesktop
@ -62,111 +93,124 @@ var updateTopMenu = function() {
appCount = minAppsDesktop appCount = minAppsDesktop
} }
if (appCount === 0) { menu.style.opacity = 1
menu.addClass('hidden')
}
menu.removeClass('hidden')
menu.css('opacity', 1)
if (appShown.length - 1 - appCount >= 1) { if (appShown.length - 1 - appCount >= 1) {
appCount-- appCount--
} }
moreApps.find('a').removeClass('active') for (let item of querySelectorAll('a', moreApps)) {
item.classList.remove('active')
}
var k = 0 let k = 0
var notInHeader = 0 let notInHeader = 0
var name
jQuery(appShown).each(function(i, app) { for (let app of appShown) {
app = jQuery(app) const name = app.getAttribute('data-id')
name = app.data('id') const li = querySelector('#apps li[data-id=' + name + '].app-external-site')
if (k < appCount && appCount > 0) { if (k < appCount && appCount > 0) {
app.removeClass('hidden') app.classList.remove('hidden')
lastShownApp = app li.classList.add('in-header')
jQuery('#apps li[data-id=' + name + '].app-external-site').addClass('in-header') lastShownApp = app
} else { } else {
app.addClass('hidden') app.classList.add('hidden')
li.classList.remove('in-header')
notInHeader++ notInHeader++
jQuery('#apps li[data-id=' + name + '].app-external-site').removeClass('in-header') const a = querySelector('a', app)
if (appCount > 0 && app.children('a').hasClass('active')) { if (appCount > 0 && a.classList.contains('active')) {
lastShownApp.addClass('hidden') lastShownApp.classList.add('hidden')
app.removeClass('hidden') app.classList.remove('hidden')
notInHeader++ notInHeader++
jQuery('#apps li[data-id=' + name + '].app-external-site') li.classList.add('in-header')
.removeClass('in-header')
.addClass('in-header')
} }
} }
k++ k++
}) }
// Hack for https://github.com/nextcloud/server/blob/23b0b63c213f5b31eecae817ffd4a9e26f6624d0/core/src/components/MainMenu.js#L74-L96 // Hack for:
menu.undelegate('li:not(#more-apps) > a', 'click') // - https://github.com/nextcloud/server/blob/master/core/src/components/MainMenu.js#L97-L119
menu.delegate('li:not(#more-apps) > a', 'click', function(e) { // - https://github.com/nextcloud/server/blob/master/core/src/components/MainMenu.js#L97-L119
var a = $(e.target) jQuery(menu).undelegate('li:not(#more-apps) > a', 'click')
jQuery(navigation).undelegate('a', 'click')
if (!a.is('a')) { const confs = [
a = a.closest('a') {
items: querySelectorAll('#navigation li'),
icon: 'icon-loading-small'
},
{
items: querySelectorAll('li:not(#more-apps)', menu),
icon: OCA.Theming && OCA.Theming.inverted ? 'icon-loading-small' : 'icon-loading-small-dark'
},
]
for (let conf of confs) {
for (let item of conf.items) {
item.addEventListener('click', (e) => {
handleMenuClick(e, conf.icon)
})
} }
}
if (a.attr('target') !== '_blank' && e.which === 1 && !e.ctrlKey && !e.metaKey && a.parent('#more-apps').length === 0) { for (let app of querySelectorAll('#apps li.app-external-site')) {
a.find('svg').remove() const appId = app.getAttribute('data-id')
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') if (app.classList.contains('in-header')) {
} for (let defs of querySelectorAll('svg defs', app)) {
}) defs.remove()
}
jQuery('#apps li.app-external-site').each(function(i, app) {
app = jQuery(app)
var appId = app.attr('data-id')
if (app.hasClass('in-header')) {
app.find('svg').find('defs').remove()
} else { } else {
var svg = app.find('svg'); const svg = querySelector('svg', app)
if (svg.find('defs').length > 0) { if (querySelectorAll('svg defs', app).length > 0) {
return; continue
} }
var defs = ` const defs = `
<defs> <defs>
<filter id="invertMenuMore-${appId}"> <filter id="invertMenuMore-${appId}">
<feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix> <feColorMatrix in="SourceGraphic" type="matrix" values="-1 0 0 0 1 0 -1 0 0 1 0 0 -1 0 1 0 0 0 1 0"></feColorMatrix>
</filter> </filter>
</defs>` </defs>`
svg.prepend(defs) svg.innerHTML = defs + svg.innerHTML
svg.find('image').attr('filter', `url(#invertMenuMore-${appId})`)
var html = svg.get(0).innerHTML.replace(/fecolormatrix/g, 'feColorMatrix'); for (let image of querySelectorAll('image', svg)) {
image.setAttribute('filter', `url(#invertMenuMore-${appId})`)
}
svg.html(html) svg.innerHTML = svg.innerHTML.replace(/fecolormatrix/g, 'feColorMatrix')
} }
})
if (notInHeader === 0) {
moreApps.hide()
navigation.hide()
} else {
moreApps.show()
} }
menuCache = menu.html() + menu.next().html() if (notInHeader === 0) {
moreApps.style.display = 'none'
navigation.style.display = 'none'
} else {
moreApps.style.display = 'flex'
}
menuCache = menu.innerHTML + menu.nextSibling.innerHTML
} }
setInterval(updateTopMenu, 50) for (let i = 0; i < 4000; i+= 100) {
setTimeout(updateTopMenu, i)
}
let resizeTimeout = null;
window.addEventListener('resize', () => {
if (resizeTimeout !== null) {
clearTimeout(resizeTimeout)
}
resizeTimeout = setTimeout(updateTopMenu, 100)
})

View file

@ -1,59 +1,110 @@
(function() { <?php
var sideMenuContainer = jQuery('<div id="side-menu-container">')
var sideMenuOpener = jQuery('<button class="side-menu-opener"></button>')
var sideMenu = jQuery('<div id="side-menu">')
var body = jQuery('body')
var html = jQuery('html')
var isTouchDevice = window.matchMedia("(pointer: coarse)").matches
<?php if ($_['big-menu']): ?> $display = 'default';
sideMenu.attr('data-bigmenu', '1')
if ($_['always-displayed']) {
$display = 'always-displayed';
} elseif ($_['big-menu']) {
$display = 'big-menu';
} elseif ($_['side-with-categories']) {
$display = 'side-with-categories';
}
?>
(function() {
const querySelector = function(selector, element) {
if (element) {
return element.querySelector(selector)
}
return document.querySelector(selector)
}
const querySelectorAll = function(selector, element) {
if (element) {
return element.querySelectorAll(selector)
}
return document.querySelectorAll(selector)
}
const createElement = function(tagName, attributes) {
const element = document.createElement(tagName)
if (typeof attributes === 'object') {
for (let i in attributes) {
element.setAttribute(i, attributes[i])
}
}
return element
}
const sideMenuContainer = createElement('div', {id: 'side-menu-container'})
const sideMenuOpener = createElement('button', {'class': 'side-menu-opener'})
const sideMenu = createElement('div', {id: 'side-menu'})
const body = querySelector('body')
const html = querySelector('html')
const nextcloud = querySelector('#nextcloud')
const isTouchDevice = window.matchMedia("(pointer: coarse)").matches
const targetBlankApps = <?php echo json_encode($_['target-blank-apps']) ?>
<?php if ($display === 'big-menu'): ?>
sideMenu.setAttribute('data-bigmenu', '1')
<?php elseif ($display === 'side-with-categories'): ?>
sideMenu.setAttribute('data-sidewithcategories', '1')
<?php endif; ?> <?php endif; ?>
var targetBlankApps = <?php echo json_encode($_['target-blank-apps']) ?>; querySelector('body').addEventListener('side-menu.apps', (e) => {
const apps = e.detail.apps;
body.on('side-menu.apps', function(e, apps) {
<?php if ($_['hide-when-no-apps']): ?> <?php if ($_['hide-when-no-apps']): ?>
sideMenu = jQuery('#side-menu') const sideMenu = querySelector('#side-menu')
if (apps.length === 0) { if (apps.length === 0) {
sideMenu.removeClass('open') sideMenu.classList.remove('open')
sideMenu.addClass('hide') sideMenu.classList.add('hide')
sideMenuOpener.addClass('hide') sideMenuOpener.classList.add('hide')
} else { } else {
sideMenu.removeClass('hide') sideMenu.classList.remove('hide')
sideMenuOpener.removeClass('hide') sideMenuOpener.classList.remove('hide')
} }
<?php if ($_['always-displayed'] && !$_['big-menu']): ?> <?php if ($display === 'always-displayed'): ?>
if (apps.length === 0) { if (apps.length === 0) {
html.removeClass('side-menu-always-displayed'); html.classList.remove('side-menu-always-displayed')
} else { } else {
html.addClass('side-menu-always-displayed'); html.classList.add('side-menu-always-displayed')
} }
<?php endif; ?> <?php endif; ?>
<?php else: ?> <?php else: ?>
<?php if ($_['always-displayed'] && !$_['big-menu']): ?> <?php if ($display === 'always-displayed'): ?>
if (apps.length === 0) { if (apps.length === 0) {
html.removeClass('side-menu-always-displayed'); html.classList.remove('side-menu-always-displayed')
} else { } else {
html.addClass('side-menu-always-displayed'); html.classList.add('side-menu-always-displayed')
} }
<?php endif; ?> <?php endif; ?>
<?php endif; ?> <?php endif; ?>
}) })
body.on('side-menu.ready', function() { body.addEventListener('side-menu.ready', () => {
sideMenu = jQuery('#side-menu') const sideMenu = querySelector('#side-menu')
const headerMenuOpener = querySelector('#header .side-menu-opener')
const sideMenuOpener = querySelectorAll('#side-menu .side-menu-opener')
var headerMenuOpener = jQuery('#header .side-menu-opener') sideMenuFocus = () => {
var sideMenuOpener = jQuery('#side-menu .side-menu-opener') let a = querySelector('.side-menu-app.active a', sideMenu)
sideMenuFocus = function() { if (!a) {
var a = sideMenu.find('.side-menu-app.active a') return
}
if (a.length === 0) { if (a.length === 0) {
a = sideMenu.find('.side-menu-app:first-child a') a = querySelector('.side-menu-app:first-child a', sideMenu)
} }
if (a.length > 0) { if (a.length > 0) {
@ -61,84 +112,105 @@
} }
} }
<?php if ($_['opener-hover'] || ($_['always-displayed'] && !$_['big-menu'])): ?> <?php if ($_['opener-hover']): ?>
var sideMenuMouseLeave = function() { const sideMenuMouseLeave = () => {
sideMenu sideMenu.classList.remove('open')
.removeClass('open') sideMenu.removeEventListener('mouseleave', sideMenuMouseLeave)
.off('mouseleave', sideMenuMouseLeave)
} }
var sideMenuMouseEnter = function() { const sideMenuMouseEnter = () => {
sideMenu.on('mouseleave', sideMenuMouseLeave) sideMenu.addEventListener('mouseleave', sideMenuMouseLeave)
} }
var sideMenuOpenerMouseEnter = function() { const sideMenuOpenerMouseEnter = () => {
sideMenu sideMenu.classList.add('open')
.addClass('open') sideMenu.addEventListener('mouseenter', sideMenuMouseEnter)
.on('mouseenter', sideMenuMouseEnter)
sideMenuFocus() sideMenuFocus()
} }
if (!isTouchDevice) { if (!isTouchDevice) {
<?php if ($_['opener-hover']): ?> <?php if ($_['opener-hover']): ?>
headerMenuOpener.on('mouseenter', sideMenuOpenerMouseEnter) headerMenuOpener.addEventListener('mouseenter', sideMenuOpenerMouseEnter)
sideMenu.addClass('hide-opener') sideMenu.classList.add('hide-opener')
<?php endif ?> <?php endif ?>
sideMenu.on('mouseleave', sideMenuMouseLeave) sideMenu.addEventListener('mouseleave', sideMenuMouseLeave)
sideMenu.on('mouseenter', sideMenuOpenerMouseEnter) sideMenu.addEventListener('mouseenter', sideMenuOpenerMouseEnter)
} }
<?php endif; ?> <?php endif; ?>
headerMenuOpener.on('click', function() { headerMenuOpener.addEventListener('click', () => {
sideMenu.addClass('open') sideMenu.classList.add('open')
sideMenu.find('.side-menu-app.active a').focus()
const a = querySelector('.side-menu-app.active a', sideMenu)
if (a !== null) {
a.focus()
}
headerMenuOpener.blur()
}) })
<?php if ($_['always-displayed'] && !$_['big-menu']): ?> for (let opener of sideMenuOpener) {
sideMenuOpener.on('click', function() { opener.addEventListener('click', () => {
sideMenu.toggleClass('open') <?php if ($display === 'always-displayed'): ?>
sideMenu.classList.toggle('open')
<?php else: ?>
sideMenu.classList.remove('open')
<?php endif; ?>
}) })
<?php else: ?> }
sideMenuOpener.on('click', function() {
sideMenu.removeClass('open')
})
<?php endif; ?>
jQuery(document).keydown(function(e) { document.addEventListener('keydown', (e) => {
var key = e.key || e.keyCode var key = e.key || e.keyCode
if ((key === 'o' || key === 79) && e.ctrlKey === true) { if ((key === 'o' || key === 79) && e.ctrlKey === true) {
e.preventDefault() e.preventDefault()
sideMenu.toggleClass('open') sideMenu.classList.toggle('open')
sideMenuFocus() sideMenuFocus()
} }
}) })
const sideMenuObserver = new MutationObserver((e) => {
if (body.getAttribute('id') !== 'body-settings') {
return
}
body.classList.toggle('body-settings-side-menu', sideMenu.classList.contains('open'))
})
sideMenuObserver.observe(sideMenu, {
attributes: true,
attributeFilter: ['class'],
childList: false,
characterData: false
})
}) })
body.append(sideMenuContainer) body.appendChild(sideMenuContainer)
sideMenuContainer.append(sideMenu) sideMenuContainer.appendChild(sideMenu)
<?php if ($_['loader-enabled'] === true): ?> <?php if ($_['loader-enabled'] === true): ?>
<?php require_once __DIR__.'/_loaderEnabled.js'; ?> <?php require_once __DIR__.'/_loaderEnabled.js'; ?>
<?php endif; ?> <?php endif; ?>
<?php if ($_['opener-position'] === 'before'): ?> <?php if ($_['opener-position'] === 'before'): ?>
sideMenuOpener.insertBefore('#nextcloud') nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud)
<?php else: ?> <?php else: ?>
sideMenuOpener.insertAfter('#nextcloud') nextcloud.parentNode.insertBefore(sideMenuOpener, nextcloud.nextSibling)
<?php endif; ?> <?php endif; ?>
<?php if (!empty($_['top-menu-apps'])): ?> <?php if (!empty($_['top-menu-apps']) || !empty($_['top-side-menu-apps'])): ?>
var topMenuApps = <?php echo json_encode($_['top-menu-apps']); ?>; const topMenuApps = <?php echo json_encode($_['top-menu-apps']), "\n"; ?>
const topSideMenuApps = <?php echo json_encode($_['top-side-menu-apps']); ?>
<?php require_once __DIR__.'/_topMenuApps.js'; ?> <?php require_once __DIR__.'/_topMenuApps.js'; ?>
<?php endif; ?> <?php endif; ?>
<?php if ($_['always-displayed'] && !$_['big-menu']): ?> <?php if ($display === 'always-displayed'): ?>
<?php require_once __DIR__.'/_alwaysDisplayed.js'; ?> <?php require_once __DIR__.'/_alwaysDisplayed.js'; ?>
<?php endif; ?> <?php endif; ?>
})(); })();

View file

@ -20,6 +20,7 @@ use OCP\IURLGenerator;
use OCP\IConfig; use OCP\IConfig;
use OCA\SideMenu\AppInfo\Application; use OCA\SideMenu\AppInfo\Application;
vendor_script('side_menu', 'html5sortable.min');
script('side_menu', 'admin'); script('side_menu', 'admin');
style('side_menu', 'admin'); style('side_menu', 'admin');
@ -39,7 +40,6 @@ $choicesSizes = [
]; ];
?> ?>
<div id="side-menu-section"> <div id="side-menu-section">
<div class="section"> <div class="section">
<h2> <h2>
@ -67,6 +67,10 @@ $choicesSizes = [
class="side-menu-setting side-menu-setting-live" class="side-menu-setting side-menu-setting-live"
value="<?php print_unescaped($_['background-color-to']); ?>"> value="<?php print_unescaped($_['background-color-to']); ?>">
<div class="theme-undo icon icon-history btn-reset" data-toggle="tooltip" data-original-title="<?php echo p($l->t('Reset to default')); ?>" data-reset="<?php echo htmlentities(json_encode([
'side-menu-background-color' => $_['defaults']['background-color'],
'side-menu-background-color-to' => $_['defaults']['background-color-to'],
])) ?>"></div>
<div> <div>
<em> <em>
<?php p($l->t('Transparent')); ?> <?php p($l->t('Transparent')); ?>
@ -102,6 +106,10 @@ $choicesSizes = [
type="color" type="color"
class="side-menu-setting side-menu-setting-live" class="side-menu-setting side-menu-setting-live"
value="<?php print_unescaped($_['current-app-background-color']); ?>"> value="<?php print_unescaped($_['current-app-background-color']); ?>">
<div class="theme-undo icon icon-history btn-reset" data-toggle="tooltip" data-original-title="<?php echo p($l->t('Reset to default')); ?>" data-reset="<?php echo htmlentities(json_encode([
'side-menu-current-app-background-color' => $_['defaults']['current-app-background-color'],
])) ?>"></div>
</div> </div>
</div> </div>
</div> </div>
@ -118,6 +126,10 @@ $choicesSizes = [
type="color" type="color"
class="side-menu-setting side-menu-setting-live" class="side-menu-setting side-menu-setting-live"
value="<?php print_unescaped($_['text-color']); ?>"> value="<?php print_unescaped($_['text-color']); ?>">
<div class="theme-undo icon icon-history btn-reset" data-toggle="tooltip" data-original-title="<?php echo p($l->t('Reset to default')); ?>" data-reset="<?php echo htmlentities(json_encode([
'side-menu-text-color' => $_['defaults']['text-color'],
])) ?>"></div>
</div> </div>
</div> </div>
</div> </div>
@ -134,6 +146,10 @@ $choicesSizes = [
type="color" type="color"
class="side-menu-setting" class="side-menu-setting"
value="<?php print_unescaped($_['loader-color']); ?>"> value="<?php print_unescaped($_['loader-color']); ?>">
<div class="theme-undo icon icon-history btn-reset" data-toggle="tooltip" data-original-title="<?php echo p($l->t('Reset to default')); ?>" data-reset="<?php echo htmlentities(json_encode([
'side-menu-loader-color' => $_['defaults']['loader-color'],
])) ?>"></div>
</div> </div>
</div> </div>
</div> </div>
@ -217,7 +233,7 @@ $choicesSizes = [
</h2> </h2>
<p> <p>
<?php p($l->t('This parameters are used when Dark theme or Breeze Dark Theme are enabled.')); ?> <?php echo $l->t('This parameters are used when Dark theme or Breeze Dark Theme are enabled.'); ?>
</p> </p>
<div class="side-menu-setting-table"> <div class="side-menu-setting-table">
@ -227,16 +243,23 @@ $choicesSizes = [
</div> </div>
<div class="side-menu-setting-form side-menu-setting-form-long"> <div class="side-menu-setting-form side-menu-setting-form-long">
<input <input
id="side-menu-dark-mode-background-color"
name="dark-mode-background-color" name="dark-mode-background-color"
type="color" type="color"
class="side-menu-setting" class="side-menu-setting"
value="<?php print_unescaped($_['dark-mode-background-color']); ?>"> value="<?php print_unescaped($_['dark-mode-background-color']); ?>">
<input <input
id="side-menu-dark-mode-background-color-to"
name="dark-mode-background-color-to" name="dark-mode-background-color-to"
type="color" type="color"
class="side-menu-setting" class="side-menu-setting"
value="<?php print_unescaped($_['dark-mode-background-color-to']); ?>"> value="<?php print_unescaped($_['dark-mode-background-color-to']); ?>">
<div class="theme-undo icon icon-history btn-reset" data-toggle="tooltip" data-original-title="<?php echo p($l->t('Reset to default')); ?>" data-reset="<?php echo htmlentities(json_encode([
'side-menu-dark-mode-background-color' => $_['defaults']['dark-mode-background-color'],
'side-menu-dark-mode-background-color-to' => $_['defaults']['dark-mode-background-color-to'],
])) ?>"></div>
<div> <div>
<em> <em>
<?php p($l->t('Transparent')); ?> <?php p($l->t('Transparent')); ?>
@ -266,10 +289,15 @@ $choicesSizes = [
</div> </div>
<div class="side-menu-setting-form side-menu-setting-form-long"> <div class="side-menu-setting-form side-menu-setting-form-long">
<input <input
id="side-menu-dark-mode-current-app-background-color"
name="dark-mode-current-app-background-color" name="dark-mode-current-app-background-color"
type="color" type="color"
class="side-menu-setting" class="side-menu-setting"
value="<?php print_unescaped($_['dark-mode-current-app-background-color']); ?>"> value="<?php print_unescaped($_['dark-mode-current-app-background-color']); ?>">
<div class="theme-undo icon icon-history btn-reset" data-toggle="tooltip" data-original-title="<?php echo p($l->t('Reset to default')); ?>" data-reset="<?php echo htmlentities(json_encode([
'side-menu-dark-mode-current-app-background-color' => $_['defaults']['dark-mode-current-app-background-color'],
])) ?>"></div>
</div> </div>
</div> </div>
</div> </div>
@ -281,10 +309,15 @@ $choicesSizes = [
</div> </div>
<div class="side-menu-setting-form side-menu-setting-form-long"> <div class="side-menu-setting-form side-menu-setting-form-long">
<input <input
id="side-menu-dark-mode-text-color"
name="dark-mode-text-color" name="dark-mode-text-color"
type="color" type="color"
class="side-menu-setting" class="side-menu-setting"
value="<?php print_unescaped($_['dark-mode-text-color']); ?>"> value="<?php print_unescaped($_['dark-mode-text-color']); ?>">
<div class="theme-undo icon icon-history btn-reset" data-toggle="tooltip" data-original-title="<?php echo p($l->t('Reset to default')); ?>" data-reset="<?php echo htmlentities(json_encode([
'side-menu-dark-mode-text-color' => $_['defaults']['dark-mode-text-color'],
])) ?>"></div>
</div> </div>
</div> </div>
</div> </div>
@ -296,10 +329,15 @@ $choicesSizes = [
</div> </div>
<div class="side-menu-setting-form side-menu-setting-form-long"> <div class="side-menu-setting-form side-menu-setting-form-long">
<input <input
id="side-menu-dark-mode-loader-color"
name="dark-mode-loader-color" name="dark-mode-loader-color"
type="color" type="color"
class="side-menu-setting" class="side-menu-setting"
value="<?php print_unescaped($_['dark-mode-loader-color']); ?>"> value="<?php print_unescaped($_['dark-mode-loader-color']); ?>">
<div class="theme-undo icon icon-history btn-reset" data-toggle="tooltip" data-original-title="<?php echo p($l->t('Reset to default')); ?>" data-reset="<?php echo htmlentities(json_encode([
'side-menu-dark-mode-loader-color' => $_['defaults']['dark-mode-loader-color'],
])) ?>"></div>
</div> </div>
</div> </div>
</div> </div>
@ -441,64 +479,95 @@ $choicesSizes = [
<?php p($l->t('Panel')); ?> <?php p($l->t('Panel')); ?>
</h2> </h2>
<?php
$displays = [
'default' => !$_['always-displayed'] && !$_['big-menu'] && !$_['side-with-categories'],
'always-displayed' => $_['always-displayed'] && !$_['big-menu'] && !$_['side-with-categories'],
'side-with-categories' => $_['side-with-categories'] && !$_['always-displayed'] && !$_['big-menu'],
'big-menu' => $_['big-menu'] && !$_['always-displayed'] && !$_['side-with-categories'],
];
?>
<div> <div>
<label> <label>
<?php p($l->t('Default')); ?> <?php p($l->t('Default')); ?>
</label> </label>
</div> </div>
<?php
$displays = [
'default' => !$_['always-displayed'] && !$_['big-menu'],
'always-displayed' => $_['always-displayed'] && !$_['big-menu'],
'big-menu' => $_['big-menu'],
];
?>
<p> <p>
<img <img
class="side-menu-display <?php echo $displays['default'] ? 'is-active' : '' ?>" class="side-menu-display <?php echo $displays['default'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="0" data-alwaysdiplayed="0"
data-bigmenu="0" data-bigmenu="0"
data-sidewithcategories="0"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-default.svg')); ?>" alt="<?php p($l->t('Default')); ?>"> src="<?php print_unescaped(image_path('side_menu', 'admin/layout-default.svg')); ?>" alt="<?php p($l->t('Default')); ?>">
</p> </p>
<div>
<label>
<?php p($l->t('With categories')); ?>
</label>
</div>
<p><em><?php echo $l->t('This menu is not compatible with AppOrder.'); ?></em></p>
<p>
<img
class="side-menu-display <?php echo $displays['side-with-categories'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="0"
data-bigmenu="0"
data-sidewithcategories="1"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-side-with-categories.svg')); ?>" alt="<?php p($l->t('With categories')); ?>">
</p>
<div> <div>
<label for="side-menu-opener"> <label for="side-menu-opener">
<?php p($l->t('Big menu')); ?> <?php p($l->t('Big menu')); ?>
</label> </label>
</div> </div>
<p><em><?php echo $l->t('This menu is not compatible with AppOrder.'); ?></em></p>
<p><em><?php p($l->t('The big menu is not compatible with AppOrder.')); ?></em></p>
<p> <p>
<img <img
class="side-menu-display <?php echo $displays['big-menu'] ? 'is-active' : '' ?>" class="side-menu-display <?php echo $displays['big-menu'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="0" data-alwaysdiplayed="0"
data-bigmenu="1" data-bigmenu="1"
data-sidewithcategories="0"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-big-menu.svg')); ?>" alt="<?php p($l->t('Big menu')); ?>"> src="<?php print_unescaped(image_path('side_menu', 'admin/layout-big-menu.svg')); ?>" alt="<?php p($l->t('Big menu')); ?>">
</p> </p>
<div> <div>
<label for="side-menu-opener"> <label for="side-menu-opener">
<?php p($l->t('Always displayed')); ?> <?php p($l->t('Always displayed')); ?>
<small><span class="warning"><?php p($l->t('Experimental')); ?></span></small>
</label> </label>
</div> </div>
<p><em><?php p($l->t('Not compatible with touch screens.')); ?></em></p> <p><em><?php p($l->t('Not compatible with touch screens.')); ?></em></p>
<p> <p>
<img <img
class="side-menu-display <?php echo $displays['always-displayed'] ? 'is-active' : '' ?>" class="side-menu-display <?php echo $displays['always-displayed'] ? 'is-active' : '' ?>"
data-alwaysdiplayed="1" data-alwaysdiplayed="1"
data-bigmenu="0" data-bigmenu="0"
data-sidewithcategories="0"
src="<?php print_unescaped(image_path('side_menu', 'admin/layout-always-displayed.svg')); ?>" alt="<?php p($l->t('Always displayed')); ?>"> src="<?php print_unescaped(image_path('side_menu', 'admin/layout-always-displayed.svg')); ?>" alt="<?php p($l->t('Always displayed')); ?>">
</p> </p>
<input type="hidden" class="side-menu-setting" name="always-displayed" id="side-menu-always-displayed" value="<?php echo (int) $_['always-displayed'] ?>"> <input
<input type="hidden" class="side-menu-setting" name="big-menu" id="side-menu-big-menu" value="<?php echo (int) $_['big-menu'] ?>"> type="hidden"
class="side-menu-setting"
name="always-displayed"
id="side-menu-always-displayed"
value="<?php echo (int) $_['always-displayed'] ?>"
>
<input
type="hidden"
class="side-menu-setting"
name="big-menu"
id="side-menu-big-menu"
value="<?php echo (int) $_['big-menu'] ?>"
>
<input
type="hidden"
class="side-menu-setting"
name="side-with-categories"
id="side-menu-side-with-categories"
value="<?php echo (int) $_['side-with-categories'] ?>"
>
<br> <br>
@ -628,7 +697,7 @@ $choicesSizes = [
<select id="side-menu-size-icon" name="size-icon" class="side-menu-setting"> <select id="side-menu-size-icon" name="size-icon" class="side-menu-setting">
<?php foreach ($choicesSizes as $label => $value): ?> <?php foreach ($choicesSizes as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['size-icon']): ?>selected<?php endif; ?>> <option value="<?php echo $value ?>" <?php if ($value === $_['size-icon']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?> icon <?php echo $l->t($label.' icon'); ?>
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
@ -636,7 +705,7 @@ $choicesSizes = [
<select id="side-menu-size-text" name="size-text" class="side-menu-setting"> <select id="side-menu-size-text" name="size-text" class="side-menu-setting">
<?php foreach ($choicesSizes as $label => $value): ?> <?php foreach ($choicesSizes as $label => $value): ?>
<option value="<?php echo $value ?>" <?php if ($value === $_['size-text']): ?>selected<?php endif; ?>> <option value="<?php echo $value ?>" <?php if ($value === $_['size-text']): ?>selected<?php endif; ?>>
<?php echo $l->t($label); ?> text <?php echo $l->t($label.' text'); ?>
</option> </option>
<?php endforeach; ?> <?php endforeach; ?>
</select> </select>
@ -699,7 +768,7 @@ $choicesSizes = [
<div class="side-menu-setting-table"> <div class="side-menu-setting-table">
<div class="side-menu-setting-row"> <div class="side-menu-setting-row">
<div class="side-menu-setting-label"> <div class="side-menu-setting-label">
<?php p($l->t('Apps that not must be moved in the side menu')); ?> <?php p($l->t('Apps only visible in the top menu')); ?>
</div> </div>
<div class="side-menu-setting-form"> <div class="side-menu-setting-form">
<a class="side-menu-toggler" data-target="#top-menu-apps" href="#_"> <a class="side-menu-toggler" data-target="#top-menu-apps" href="#_">
@ -728,6 +797,39 @@ $choicesSizes = [
</div> </div>
</div> </div>
</div> </div>
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Apps visible in the top and side menus')); ?>
</div>
<div class="side-menu-setting-form">
<a class="side-menu-toggler" data-target="#top-side-menu-apps" href="#_">
🖱️ <?php p($l->t('Show and hide the list of applications')); ?>
</a>
<div class="side-menu-setting" data-name="top-side-menu-apps" id="top-side-menu-apps" data-checkbox style="display: none">
<ul class="side-menu-setting-list">
<?php foreach ($_['apps'] as $app): ?>
<li class="side-menu-setting-list-item">
<input
type="checkbox"
name="top-side-menu-apps[]"
value="<?php echo $app['id'] ?>"
id="top-side-menu-app-<?php echo $app['id'] ?>"
<?php if (in_array($app['id'], $_['top-side-menu-apps'])): ?>checked<?php endif; ?>
/>
<label for="top-side-menu-app-<?php echo $app['id'] ?>">
<?php echo p($l->t($app['name'])); ?>
</label>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
</div>
</div>
</div> </div>
@ -758,6 +860,60 @@ $choicesSizes = [
</div> </div>
</div> </div>
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Custom categories')); ?>
</div>
<div class="side-menu-setting-form">
<input type="hidden" name="categories-custom" class="side-menu-setting" data-langs="<?php echo htmlentities(json_encode($langs)) ?>" value="<?php echo htmlentities(json_encode($_['categories-custom'])) ?>">
<div id="side-menu-categories-custom">
</div>
</div>
</div>
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Customize application categories')); ?>
</div>
<div class="side-menu-setting-form">
<a class="side-menu-toggler" data-target="#apps-categories-custom-list" href="#_">
🖱️ <?php p($l->t('Show and hide the list of applications')); ?>
</a>
<div id="apps-categories-custom-list" style="display: none">
<ul class="side-menu-setting-list">
<?php foreach ($_['apps'] as $app): ?>
<li class="side-menu-setting-list-item">
<label for="apps-categories-custom-<?php echo $app['id'] ?>">
<?php echo p($l->t($app['name'])); ?>
</label>
<br>
<select data-app="<?php echo $app['id'] ?>" class="apps-categories-custom">
<option value=""></option>
<?php foreach ($_['categories'] as $id => $category): ?>
<?php if ($category): ?>
<option
value="<?php echo $id ?>"
<?php if (($_['apps-categories-custom'][$app['id']] ?? '') === $id): ?>
selected
<?php endif; ?>
><?php echo $category ?></option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</li>
<?php endforeach; ?>
</ul>
</div>
<input type="hidden" class="side-menu-setting" id="apps-categories-custom" name="apps-categories-custom" value="<?php echo htmlentities(json_encode($_['apps-categories-custom'])) ?>">
</div>
</div>
<div class="side-menu-setting-row"> <div class="side-menu-setting-row">
<div class="side-menu-setting-label"> <div class="side-menu-setting-label">
<?php p($l->t('Customize sorting')); ?> <?php p($l->t('Customize sorting')); ?>
@ -767,7 +923,7 @@ $choicesSizes = [
🖱️ <?php p($l->t('Show and hide the list of categories')); ?> 🖱️ <?php p($l->t('Show and hide the list of categories')); ?>
</a> </a>
<div class="side-menu-setting" data-name="categories" id="categories-list" style="display: none"> <div id="categories-list" style="display: none">
<ul class="side-menu-setting-list"> <ul class="side-menu-setting-list">
<?php foreach ($_['categories'] as $key => $label): ?> <?php foreach ($_['categories'] as $key => $label): ?>
<li data-id="<?php echo $key; ?>" class="side-menu-setting-list-item"> <li data-id="<?php echo $key; ?>" class="side-menu-setting-list-item">
@ -838,10 +994,9 @@ $choicesSizes = [
<div class="section" id="more"> <div class="section" id="more">
<button id="side-menu-save" class="btn btn-info"> <button id="side-menu-save" class="btn btn-info">
<?php p($l->t('Save')); ?> <?php p($l->t('Save')); ?>
<progress max="100" value="0" id="side-menu-save-progress"></progress>
</button> </button>
<span id="side-menu-message" class="msg"></span>
<a href="<?php echo $urlGenerator->linkToRoute('side_menu.AdminSetting.exportConfiguration') ?>" target="_blank"> <a href="<?php echo $urlGenerator->linkToRoute('side_menu.AdminSetting.exportConfiguration') ?>" target="_blank">
<button class="btn btn-primary" > <button class="btn btn-primary" >
<?php p($l->t('Export the configuration')); ?> <?php p($l->t('Export the configuration')); ?>

View file

@ -16,6 +16,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
vendor_script('side_menu', 'html5sortable.min');
script('side_menu', 'admin'); script('side_menu', 'admin');
style('side_menu', 'admin'); style('side_menu', 'admin');
@ -25,8 +26,6 @@ $choicesYesNo = [
]; ];
?> ?>
<div id="side-menu-section"> <div id="side-menu-section">
<?php if ($_['force']): ?> <?php if ($_['force']): ?>
<div class="section"> <div class="section">
@ -44,71 +43,73 @@ $choicesYesNo = [
<?php p($l->t('Menu')); ?> <?php p($l->t('Menu')); ?>
</h2> </h2>
<div>
<label for="side-menu-enabled">
<?php p($l->t('Enable the custom menu')); ?>
</label>
</div>
<p> <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> <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> </p>
<div> <div class="side-menu-setting-table">
<select id="side-menu-enabled" name="enabled" class="side-menu-setting" data-personal> <div class="side-menu-setting-row">
<?php foreach ($choicesYesNo as $label => $value): ?> <div class="side-menu-setting-label">
<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); ?>
</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> </div>
<?php endforeach; ?> <div class="side-menu-setting-form">
<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>
</div>
<div class="side-menu-setting-table">
<div class="side-menu-setting-row">
<div class="side-menu-setting-label">
<?php p($l->t('Open apps in new tab')); ?>
</div>
<div class="side-menu-setting-form">
<?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>
<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">
<ul class="side-menu-setting-list">
<?php foreach ($_['apps'] as $app): ?>
<li class="side-menu-setting-list-item">
<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>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
</div>
</div> </div>
</div> </div>
@ -116,47 +117,95 @@ $choicesYesNo = [
<h2> <h2>
<?php p($l->t('Top menu')); ?> <?php p($l->t('Top menu')); ?>
</h2> </h2>
<div>
<label for="side-menu-top-menu-apps"> <div class="side-menu-setting-table">
<?php p($l->t('Apps that not must be moved in the side menu')); ?> <div class="side-menu-setting-row">
</label> <div class="side-menu-setting-label">
<?php p($l->t('Apps only visible in the top menu')); ?>
<p>
<em>
<?php p($l->t('If there is no selection then the global configuration is applied.')); ?>
</em>
</p>
</div>
<div class="side-menu-setting-form">
<p>
<a class="side-menu-toggler" data-target="#top-menu-apps" href="#_">
🖱️ <?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">
<ul class="side-menu-setting-list">
<?php foreach ($_['apps'] as $app): ?>
<li class="side-menu-setting-list-item">
<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>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
</div>
</div> </div>
<p> <div class="side-menu-setting-table">
<em> <div class="side-menu-setting-row">
<?php p($l->t('If there is no selection then the global configuration is applied.')); ?> <div class="side-menu-setting-label">
</em> <?php p($l->t('Apps visible in the top and side menus')); ?>
</p> <p>
<em>
<p> <?php p($l->t('If there is no selection then the global configuration is applied.')); ?>
<a class="side-menu-toggler" data-target="#top-menu-apps" href="#_"> </em>
🖱️ <?php p($l->t('Show and hide the list of applications')); ?> </p>
</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> </div>
<?php endforeach; ?> <div class="side-menu-setting-form">
<p>
<a class="side-menu-toggler" data-target="#top-side-menu-apps" href="#_">
🖱️ <?php p($l->t('Show and hide the list of applications')); ?>
</a>
</p>
<div class="side-menu-setting" data-name="top-side-menu-apps" data-checkbox data-personal id="top-side-menu-apps" style="display: none">
<ul class="side-menu-setting-list">
<?php foreach ($_['apps'] as $app): ?>
<li class="side-menu-setting-list-item">
<input
type="checkbox"
name="top-side-menu-apps[]"
value="<?php echo $app['id'] ?>"
id="top-side-menu-app-<?php echo $app['id'] ?>"
<?php if (in_array($app['id'], $_['top-side-menu-apps'])): ?>checked<?php endif; ?>
/>
<label for="top-side-menu-app-<?php echo $app['id'] ?>">
<?php echo $app['name'] ?>
</label>
</li>
<?php endforeach; ?>
</ul>
</div>
</div>
</div>
</div> </div>
</div> </div>
<?php endif ?> <?php endif ?>
<div class="section"> <div class="section">
<?php if (!$_['force']): ?> <?php if (!$_['force']): ?>
<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')); ?>
<progress max="100" value="0" id="side-menu-save-progress"></progress>
</button>
<span id="side-menu-message" class="msg"></span> <span id="side-menu-message" class="msg"></span>

2
vendor/html5sortable.min.js vendored Normal file

File diff suppressed because one or more lines are too long