diff --git a/.gitignore b/.gitignore index d10e458..7cadaec 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ /package-lock.json !/l10n/.gitkeep /yarn*.log +/src/admin.js.bk +/templates/settings/admin-form.php.bk diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 0b9fd24..370750a 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -13,6 +13,7 @@ use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; use OCP\AppFramework\Bootstrap\IRegistrationContext; +use OCA\Theming\ThemingDefaults; use OCP\EventDispatcher\IEventDispatcher; use OCP\IConfig; use OCP\INavigationManager; @@ -20,6 +21,7 @@ use OCP\IUserSession; use OCP\L10N\IFactory; use OCP\Util; use Psr\Container\ContainerInterface; +use OCA\SideMenu\Service\Color; /** * class Application. @@ -81,6 +83,12 @@ class Application extends App implements IBootstrap $c->get(IConfig::class), ); }); + + $context->registerService(Color::class, function (ContainerInterface $c) { + return new Color( + $c->get(ThemingDefaults::class), + ); + }); } public function boot(IBootContext $context): void diff --git a/lib/Controller/AdminSettingController.php b/lib/Controller/AdminSettingController.php index 279a34c..839d65c 100644 --- a/lib/Controller/AdminSettingController.php +++ b/lib/Controller/AdminSettingController.php @@ -20,14 +20,17 @@ namespace OCA\SideMenu\Controller; use OCA\SideMenu\AppInfo\Application; +use OCA\SideMenu\Service\ConfigProxy; use OCP\AppFramework\Controller; use OCP\AppFramework\Http\Attribute\FrontpageRoute; use OCP\AppFramework\Http\Attribute\NoCSRFRequired; use OCP\AppFramework\Http\DataDownloadResponse; +use OCP\AppFramework\Http\JSONResponse; use OCP\AppFramework\Http\RedirectResponse; use OCP\IConfig; use OCP\IRequest; use OCP\IURLGenerator; +use OCA\SideMenu\Service\Color; class AdminSettingController extends Controller { @@ -35,7 +38,9 @@ class AdminSettingController extends Controller $appName, IRequest $request, protected IConfig $config, - protected IURLGenerator $urlGenerator + protected ConfigProxy $configProxy, + protected IURLGenerator $urlGenerator, + protected Color $color, ) { parent::__construct($appName, $request); } @@ -76,4 +81,129 @@ class AdminSettingController extends Controller 'text/json' ); } + + #[NoCSRFRequired] + #[FrontpageRoute(verb: 'GET', url: '/admin/config')] + public function configuration(): JSONResponse + { + $keys = $this->config->getAppKeys(Application::APP_ID); + $booleans = [ + 'opener-only', + 'opener-hover', + 'display-logo', + 'use-avatar', + 'add-logo-link', + 'show-settings', + 'loader-enabled', + 'top-menu-mouse-over-hidden-label', + 'always-displayed', + 'enabled', + 'force', + 'big-menu', + 'external-sites-in-top-menu', + 'force-light-icon', + 'side-with-categories', + 'default-enabled', + ]; + + $arrays = [ + 'apps-categories-custom', + 'big-menu-hidden-apps', + 'apps-order', + 'categories-custom', + 'categories-order', + 'target-blank-apps', + 'top-menu-apps', + 'top-side-menu-apps', + ]; + + $integers = [ + 'background-color-opacity', + 'dark-mode-background-color-opacity', + 'dark-mode-icon-invert-filter', + 'dark-mode-icon-opacity', + 'icon-invert-filter', + 'icon-opacity', + 'target-blank-mode', + 'top-menu-mouse-over-hidden-label', + ]; + + $defaults = [ + 'opener-only' => '0', + 'opener-hover' => '0', + 'display-logo' => '1', + 'use-avatar' => '0', + 'add-logo-link' => '1', + 'show-settings' => '0', + 'loader-enabled' => '1', + 'top-menu-mouse-over-hidden-label' => '0', + 'always-displayed' => '0', + 'enabled' => '1', + 'force' => '0', + 'big-menu' => '0', + 'external-sites-in-top-menu' => '0', + 'force-light-icon' => '0', + 'side-with-categories' => '0', + 'cache' => '1', + 'default-enabled' => '1', + + 'apps-categories-custom' => '[]', + 'big-menu-hidden-apps' => '[]', + 'apps-order' => '[]', + 'categories-custom' => '[]', + 'categories-order' => '[]', + 'target-blank-apps' => '[]', + 'top-menu-apps' => '[]', + 'top-side-menu-apps' => '[]', + 'cache-categories' => '[]', + + 'background-color-opacity' => '100', + 'dark-mode-background-color-opacity' => '100', + 'dark-mode-icon-invert-filter' => '0', + 'dark-mode-icon-opacity' => '100', + 'icon-invert-filter' => '0', + 'icon-opacity' => '100', + 'top-menu-mouse-over-hidden-label' => '0', + + 'opener' => 'side-menu-opener', + 'dark-mode-opener' => 'side-menu-opener', + 'size-icon' => 'normal', + 'size-text' => 'normal', + 'opener-position' => 'before', + + 'background-color' => $this->color->getPrimaryColor(), + 'background-color-to' => $this->color->getLightenPrimaryColor(), + 'current-app-background-color' => $this->color->getDarkenPrimaryColor(), + 'text-color' => $this->color->getTextColorPrimary(), + 'loader-color' => $this->color->getLightenPrimaryColor(), + + 'dark-mode-background-color' => $this->color->getDarkenPrimaryColor(), + 'dark-mode-background-color-to' => $this->color->getDarkenPrimaryColor(), + 'dark-mode-current-app-background-color' => $this->color->getDarkenPrimaryColor2(), + 'dark-mode-text-color' => $this->color->getTextColorPrimary(), + 'dark-mode-loader-color' => $this->color->getLightenPrimaryColor(), + + 'categories-order-type' => 'default', + ]; + + $config = []; + + foreach ($keys as $key) { + if (!isset($defaults[$key])) { + continue; + } + + if (in_array($key, $booleans)) { + $config[$key] = $this->configProxy->getAppValueBool($key, $defaults[$key]); + } elseif (in_array($key, $arrays)) { + $config[$key] = $this->configProxy->getAppValueArray($key, $defaults[$key]); + } elseif (in_array($key, $integers)) { + $config[$key] = $this->configProxy->getAppValueInt($key, $defaults[$key]); + } else { + $config[$key] = $this->configProxy->getAppValue($key, $defaults[$key]); + } + } + + return new JSONResponse($config); + } } diff --git a/lib/Controller/CssController.php b/lib/Controller/CssController.php index 5b3a2dd..cf855cd 100644 --- a/lib/Controller/CssController.php +++ b/lib/Controller/CssController.php @@ -95,11 +95,10 @@ class CssController extends Controller $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(); + $lightenPrimaryColor = $this->color->getLightenPrimaryColor(); + $darkenPrimaryColor = $this->color->getDarkenPrimaryColor(); + $darkenPrimaryColor2 = $this->color->getDarkenPrimaryColor2(); + $textColor = $this->color->getTextColorPrimary(); if ($isDarkMode) { $backgroundColor = $this->config->getAppValue('dark-mode-background-color', $darkenPrimaryColor); diff --git a/lib/Service/Color.php b/lib/Service/Color.php index cf90dd6..98e4ed5 100644 --- a/lib/Service/Color.php +++ b/lib/Service/Color.php @@ -2,6 +2,8 @@ namespace OCA\SideMenu\Service; +use OCA\Theming\ThemingDefaults; + /** * class Color. * @@ -9,6 +11,10 @@ namespace OCA\SideMenu\Service; */ class Color { + public function __construct(protected ThemingDefaults $theming) + { + } + /** * @thanks https://stackoverflow.com/posts/54393956/revision */ @@ -31,4 +37,29 @@ class Color return '#'.implode($hexCode); } + + public function getPrimaryColor() + { + return $this->theming->getColorPrimary(); + } + + public function getLightenPrimaryColor() + { + return $this->adjustBrightness($this->getPrimaryColor(), 0.2); + } + + public function getDarkenPrimaryColor() + { + return $this->adjustBrightness($this->getPrimaryColor(), -0.2); + } + + public function getDarkenPrimaryColor2() + { + return $this->adjustBrightness($this->getPrimaryColor(), -0.3); + } + + public function getTextColorPrimary() + { + return $this->theming->getTextColorPrimary(); + } } diff --git a/src/admin.js b/src/admin.js index cf5e607..1749f6f 100644 --- a/src/admin.js +++ b/src/admin.js @@ -27,6 +27,6 @@ waitContainer('#side-menu-admin-settings').then((selector) => { const pinia = createPinia() const app = createApp(AdminSettings) app.use(pinia) - app.mixin({ methods: { t, n }}) + app.mixin({ methods: { t, n } }) app.mount(selector) }) diff --git a/src/components/settings/MenuDisplay.vue b/src/components/settings/MenuDisplay.vue index 789e165..fd65a0f 100644 --- a/src/components/settings/MenuDisplay.vue +++ b/src/components/settings/MenuDisplay.vue @@ -1,6 +1,4 @@ - + diff --git a/src/components/settings/SectionTitle.vue b/src/components/settings/SectionTitle.vue new file mode 100644 index 0000000..c17dc45 --- /dev/null +++ b/src/components/settings/SectionTitle.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/src/components/settings/TableLabel.vue b/src/components/settings/TableLabel.vue index 535de47..0e5a5a6 100644 --- a/src/components/settings/TableLabel.vue +++ b/src/components/settings/TableLabel.vue @@ -1,10 +1,18 @@ @@ -29,5 +37,10 @@ const { short, label } = defineProps({ required: false, default: true, }, + help: { + type: [String, null], + required: false, + default: false, + }, }) diff --git a/src/components/settings/TableValue.vue b/src/components/settings/TableValue.vue index 68e793c..0dae548 100644 --- a/src/components/settings/TableValue.vue +++ b/src/components/settings/TableValue.vue @@ -1,5 +1,8 @@ diff --git a/src/components/settings/form/FormAppPicker.vue b/src/components/settings/form/FormAppPicker.vue new file mode 100644 index 0000000..c1fde4e --- /dev/null +++ b/src/components/settings/form/FormAppPicker.vue @@ -0,0 +1,82 @@ + + + + + diff --git a/src/components/settings/form/FormColorPicker.vue b/src/components/settings/form/FormColorPicker.vue new file mode 100644 index 0000000..554502d --- /dev/null +++ b/src/components/settings/form/FormColorPicker.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/components/settings/form/FormOpener.vue b/src/components/settings/form/FormOpener.vue new file mode 100644 index 0000000..ddae359 --- /dev/null +++ b/src/components/settings/form/FormOpener.vue @@ -0,0 +1,20 @@ + + + diff --git a/src/components/settings/form/FormRange.vue b/src/components/settings/form/FormRange.vue new file mode 100644 index 0000000..cecb5bf --- /dev/null +++ b/src/components/settings/form/FormRange.vue @@ -0,0 +1,56 @@ + + + + + diff --git a/src/components/settings/form/FormSelect.vue b/src/components/settings/form/FormSelect.vue new file mode 100644 index 0000000..4f6fd5a --- /dev/null +++ b/src/components/settings/form/FormSelect.vue @@ -0,0 +1,48 @@ + + + diff --git a/src/components/settings/form/FormSize.vue b/src/components/settings/form/FormSize.vue new file mode 100644 index 0000000..8fd484b --- /dev/null +++ b/src/components/settings/form/FormSize.vue @@ -0,0 +1,18 @@ + + + diff --git a/src/components/settings/form/FormYesNo.vue b/src/components/settings/form/FormYesNo.vue new file mode 100644 index 0000000..4df509c --- /dev/null +++ b/src/components/settings/form/FormYesNo.vue @@ -0,0 +1,12 @@ + + + diff --git a/src/lib/menu.js b/src/lib/menu.js index a572373..fe35fd4 100644 --- a/src/lib/menu.js +++ b/src/lib/menu.js @@ -1,7 +1,6 @@ const focusActiveApp = (menu) => { window.setTimeout(() => { - const a = menu.querySelector('.side-menu-app.active a') - || menu.querySelector('.side-menu-app a') + const a = menu.querySelector('.side-menu-app.active a') || menu.querySelector('.side-menu-app a') if (a) { a.focus() diff --git a/src/menus/SideMenuWithCategories.vue b/src/menus/SideMenuWithCategories.vue index 2d7d1c6..9fc41f4 100644 --- a/src/menus/SideMenuWithCategories.vue +++ b/src/menus/SideMenuWithCategories.vue @@ -109,11 +109,14 @@ const openerHover = ref(false) const menu = useTemplateRef('menu') const isTouchDevice = window.matchMedia('(pointer: coarse)').matches -watch(() => open, (val) => { - if (val) { - focusActiveApp(menu.value) - } -}) +watch( + () => open, + (val) => { + if (val) { + focusActiveApp(menu.value) + } + }, +) onMounted(async () => { const config = await configStore.getConfig() diff --git a/src/menus/SimpleSideMenu.vue b/src/menus/SimpleSideMenu.vue index 369bd4d..f6a8232 100644 --- a/src/menus/SimpleSideMenu.vue +++ b/src/menus/SimpleSideMenu.vue @@ -30,7 +30,10 @@ along with this program. If not, see . :label="settings.name" :avatar="settings.avatar" /> - + { document.querySelector('html').classList.toggle('side-menu-always-displayed', alwaysDisplayed.value && val.length) }) -watch(() => open, (val) => { - if (val) { - focusActiveApp(menu.value) - } -}) +watch( + () => open, + (val) => { + if (val) { + focusActiveApp(menu.value) + } + }, +) function getFiltredAndSortedApps(items, order, topMenuApps, topSideMenuApps) { const data = [] @@ -148,10 +154,7 @@ onMounted(async () => { avatar.value = config['avatar'] logo.value = config['logo'] useAvatarAsLogo.value = config['use-avatar'] - displayLogo.value = config['display-logo'] && !alwaysDisplayed.value && ( - (!useAvatarAsLogo.value && logo.value) || - (useAvatarAsLogo.value && avatar.value) - ) + displayLogo.value = config['display-logo'] && !alwaysDisplayed.value && ((!useAvatarAsLogo.value && logo.value) || (useAvatarAsLogo.value && avatar.value)) logoLink.value = config['logo-link'] settings.value = config['settings'] diff --git a/src/menus/TopWideMenu.vue b/src/menus/TopWideMenu.vue index d677645..ab2b052 100644 --- a/src/menus/TopWideMenu.vue +++ b/src/menus/TopWideMenu.vue @@ -114,11 +114,14 @@ const openerHover = ref(false) const menu = useTemplateRef('menu') const isTouchDevice = window.matchMedia('(pointer: coarse)').matches -watch(() => open, (val) => { - if (val) { - focusActiveApp(menu.value) - } -}) +watch( + () => open, + (val) => { + if (val) { + focusActiveApp(menu.value) + } + }, +) onMounted(async () => { const config = await configStore.getConfig() diff --git a/src/pages/AdminSettings.vue b/src/pages/AdminSettings.vue index 4c57ac9..0d63b52 100644 --- a/src/pages/AdminSettings.vue +++ b/src/pages/AdminSettings.vue @@ -1,92 +1,505 @@ diff --git a/src/scss/admin.scss b/src/scss/admin.scss index fafdd14..d43e70a 100644 --- a/src/scss/admin.scss +++ b/src/scss/admin.scss @@ -114,7 +114,7 @@ .side-menu-setting-table { display: table; width: 100%; - padding: 10px; + padding: 20px; } .side-menu-setting-row { @@ -225,9 +225,9 @@ } .side-menu-setting { - padding: 10px; + padding: 20px; display: flex; - gap: 12px; + gap: 5px; } .side-menu-setting-color-picker { diff --git a/src/scss/menu.scss b/src/scss/menu.scss index c1dd6cf..cecd2b3 100644 --- a/src/scss/menu.scss +++ b/src/scss/menu.scss @@ -66,7 +66,8 @@ } } - &.side-menu-big, &.side-menu-with-categories { + &.side-menu-big, + &.side-menu-with-categories { height: auto; } } @@ -225,7 +226,8 @@ } } -.side-menu-big, .side-menu-with-categories { +.side-menu-big, +.side-menu-with-categories { .side-menu-apps-list { height: auto; position: static; @@ -284,7 +286,6 @@ stroke: var(--side-menu-text-color, #fff); } - .side-menu-always-displayed { body { width: calc(100% - 50px) !important; diff --git a/src/store/config.js b/src/store/config.js index 29c5693..22be6ab 100644 --- a/src/store/config.js +++ b/src/store/config.js @@ -5,6 +5,7 @@ import { generateUrl } from '@nextcloud/router' export const useConfigStore = defineStore('config', () => { const config = ref(null) + const appConfig = ref(null) async function getConfig() { if (config.value !== null) { @@ -16,7 +17,18 @@ export const useConfigStore = defineStore('config', () => { return config.value } + async function getAppConfig() { + if (appConfig.value !== null) { + return appConfig.value + } + + appConfig.value = await axios.get(generateUrl('/apps/side_menu/admin/config')).then((response) => response.data) + + return appConfig.value + } + return { getConfig, + getAppConfig, } })