diff --git a/README.md b/README.md index 4914ee661..c21dceac9 100644 --- a/README.md +++ b/README.md @@ -21,3 +21,4 @@ Every description is translatable and can be found in the `studio_api_docs.en.ya - [Grid](./doc/03_Grid.md) - [Generic Execution Engine](doc/04_Generic_Execution_Engine.md) - [Additional Attributes](./doc/05_Additional_Custom_Attributes.md) +- [Studio User](./doc/07_User.md) diff --git a/config/pimcore/config.yaml b/config/pimcore/config.yaml index 2edc68c4f..53c3cf9c2 100644 --- a/config/pimcore/config.yaml +++ b/config/pimcore/config.yaml @@ -3,6 +3,7 @@ imports: - { resource: firewall.yaml } - { resource: execution_engine.yaml } - { resource: doctrine.yaml } + - { resource: user_key_binding.yaml } pimcore: translations: diff --git a/config/pimcore/user_key_binding.yaml b/config/pimcore/user_key_binding.yaml new file mode 100644 index 000000000..93e8f12aa --- /dev/null +++ b/config/pimcore/user_key_binding.yaml @@ -0,0 +1,128 @@ +pimcore_studio_backend: + user: + default_key_bindings: + save: + key: 'S' + action: save + ctrl: true + publish: + key: 'P' + action: publish + ctrl: true + shift: true + unpublish: + key: 'U' + action: unpublish + ctrl: true + shift: true + rename: + key: 'R' + action: rename + alt: true + shift: true + refresh: + key: 't' + action: refresh + open_asset: + key: 'A' + action: openAsset + ctrl: true + shift: true + open_object: + key: 'O' + action: openObject + ctrl: true + shift: true + open_document: + key: 'D' + action: openDocument + ctrl: true + shift: true + open_class_editor: + key: 'C' + action: openClassEditor + ctrl: true + shift: true + open_in_tree: + key: 'L' + action: openInTree + ctrl: true + shift: true + show_meta_info: + key: 'I' + action: showMetaInfo + alt: true + search_document: + key: 'W' + action: searchDocument + alt: true + search_asset: + key: 'A' + action: searchAsset + alt: true + search_object: + key: 'O' + action: searchObject + alt: true + show_element_history: + key: 'H' + action: showElementHistory + alt: true + close_all_tabs: + key: 'T' + action: closeAllTabs + alt: true + search_and_replace_assignments: + key: 'S' + action: searchAndReplaceAssignments + alt: true + redirects: + key: 'R' + action: redirects + ctrl: false + alt: true + shared_translations: + key: 'T' + action: sharedTranslations + ctrl: true + alt: true + recycle_bin: + key: 'R' + action: recycleBin + ctrl: true + alt: true + notes_events: + key: 'N' + action: notesEvents + ctrl: true + alt: true + tag_manager: + key: 'H' + action: tagManager + ctrl: true + alt: true + tag_configuration: + key: 'N' + action: tagConfiguration + ctrl: true + alt: true + users: + key: 'U' + action: users + ctrl: true + alt: true + roles: + key: 'P' + action: roles + ctrl: true + alt: true + clear_all_caches: + key: 'Q' + action: clearAllCaches + ctrl: false + alt: true + clear_data_cache: + key: 'C' + action: clearDataCache + ctrl: false + alt: true \ No newline at end of file diff --git a/config/users.yaml b/config/users.yaml index 0c2e16bc6..956043aff 100644 --- a/config/users.yaml +++ b/config/users.yaml @@ -44,6 +44,9 @@ services: Pimcore\Bundle\StudioBackendBundle\User\Service\ObjectDependenciesServiceInterface: class: Pimcore\Bundle\StudioBackendBundle\User\Service\ObjectDependenciesService + Pimcore\Bundle\StudioBackendBundle\User\Service\KeyBindingServiceInterface: + class: Pimcore\Bundle\StudioBackendBundle\User\Service\KeyBindingService + Pimcore\Bundle\StudioBackendBundle\User\Service\MailServiceInterface: class: Pimcore\Bundle\StudioBackendBundle\User\Service\MailService @@ -73,6 +76,9 @@ services: Pimcore\Bundle\StudioBackendBundle\User\Hydrator\DependencyHydratorInterface: class: Pimcore\Bundle\StudioBackendBundle\User\Hydrator\DependencyHydrator + Pimcore\Bundle\StudioBackendBundle\User\Hydrator\KeyBindingHydratorInterface: + class: Pimcore\Bundle\StudioBackendBundle\User\Hydrator\KeyBindingHydrator + # # Repositories diff --git a/doc/07_User.md b/doc/07_User.md new file mode 100644 index 000000000..95a62e0c5 --- /dev/null +++ b/doc/07_User.md @@ -0,0 +1,15 @@ +# Studio User +## Default Key Bindings +To change the default key bindings, you can add a synfony configuration file in your project. +The structure of the file should be like this: +```yaml +pimcore_studio_backend: + user: + default_key_bindings: + save: + key: 'S' + action: save + ctrl: true +``` + +You can find the predefined key bindings here `config/pimcore/user_key_binding.yaml` in the Studio Backend Bundle. diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index bd5812d84..308a1a009 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -57,6 +57,7 @@ public function getConfigTreeBuilder(): TreeBuilder $this->addGridConfiguration($rootNode); $this->addNoteTypes($rootNode); $this->addDataObjectAdapterMapping($rootNode); + $this->addUserNode($rootNode); return $treeBuilder; } @@ -284,4 +285,24 @@ private function addDataObjectAdapterMapping(ArrayNodeDefinition $node): void ->end() ->end(); } + + private function addUserNode(ArrayNodeDefinition $node): void + { + $node->children() + ->arrayNode('user') + ->addDefaultsIfNotSet() + ->children() + ->arrayNode('default_key_bindings') + ->prototype('array') + ->children() + ->scalarNode('key')->isRequired()->end() + ->scalarNode('action')->isRequired()->end() + ->scalarNode('alt')->defaultFalse()->end() + ->scalarNode('ctrl')->defaultFalse()->end() + ->scalarNode('shift')->defaultFalse()->end() + ->end() + ->end() + ->end() + ->end(); + } } diff --git a/src/DependencyInjection/PimcoreStudioBackendExtension.php b/src/DependencyInjection/PimcoreStudioBackendExtension.php index 9b57dce7a..23f0b0d79 100644 --- a/src/DependencyInjection/PimcoreStudioBackendExtension.php +++ b/src/DependencyInjection/PimcoreStudioBackendExtension.php @@ -30,6 +30,7 @@ use Pimcore\Bundle\StudioBackendBundle\Mercure\Service\HubServiceInterface; use Pimcore\Bundle\StudioBackendBundle\Note\Service\NoteServiceInterface; use Pimcore\Bundle\StudioBackendBundle\OpenApi\Service\OpenApiServiceInterface; +use Pimcore\Bundle\StudioBackendBundle\User\Service\KeyBindingServiceInterface; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; @@ -104,6 +105,9 @@ public function load(array $configs, ContainerBuilder $container): void $definition = $container->getDefinition(DataAdapterServiceInterface::class); $definition->setArgument('$dataAdapters', $config['data_object_data_adapter_mapping']); + + $definition = $container->getDefinition(KeyBindingServiceInterface::class); + $definition->setArgument('$defaultKeyBindings', $config['user']['default_key_bindings']); } public function prepend(ContainerBuilder $container): void diff --git a/src/User/Controller/GetDefaultKeyBindingsController.php b/src/User/Controller/GetDefaultKeyBindingsController.php new file mode 100644 index 000000000..ba0419098 --- /dev/null +++ b/src/User/Controller/GetDefaultKeyBindingsController.php @@ -0,0 +1,70 @@ +value)] + #[Get( + path: self::PREFIX . '/users/default-key-bindings', + operationId: 'user_default_key_bindings', + description: 'user_default_key_bindings_description', + summary: 'user_default_key_bindings_summary', + tags: [Tags::User->value] + )] + #[SuccessResponse( + description: 'user_default_key_bindings_response', + content: new CollectionJson(new GenericCollection(KeyBinding::class)) + )] + #[DefaultResponses] + public function getDefaultKeyBindings(): JsonResponse + { + $bindings = $this->keyBindingService->getDefaultKeyBindings(); + + return $this->getPaginatedCollection($this->serializer, $bindings, count($bindings)); + } +} diff --git a/src/User/Hydrator/KeyBindingHydrator.php b/src/User/Hydrator/KeyBindingHydrator.php new file mode 100644 index 000000000..4f271e6a5 --- /dev/null +++ b/src/User/Hydrator/KeyBindingHydrator.php @@ -0,0 +1,41 @@ +keyBindingHydrator->hydrate($decoded); } catch (Exception $e) { $this->pimcoreLogger->warning('Failed to decode key bindings', ['exception' => $e]); diff --git a/src/User/Service/KeyBindingService.php b/src/User/Service/KeyBindingService.php new file mode 100644 index 000000000..1b030d99e --- /dev/null +++ b/src/User/Service/KeyBindingService.php @@ -0,0 +1,51 @@ +convertKeyToAscii(); + + return $this->keyBindingHydrator->hydrate($this->defaultKeyBindings); + } + + private function convertKeyToAscii(): void + { + foreach ($this->defaultKeyBindings as $keyName => $keyValue) { + + $this->defaultKeyBindings[$keyName]['key'] = ord($keyValue['key']); + } + } +} diff --git a/src/User/Service/KeyBindingServiceInterface.php b/src/User/Service/KeyBindingServiceInterface.php new file mode 100644 index 000000000..a7590db53 --- /dev/null +++ b/src/User/Service/KeyBindingServiceInterface.php @@ -0,0 +1,30 @@ + 65, + 'action' => 'test', + 'ctrl' => true, + 'alt' => false, + 'shift' => true, + ], + [ + 'key' => 66, + 'action' => 'test2', + 'ctrl' => false, + 'alt' => true, + 'shift' => false, + ], + ]; + + $keyBindings = $hydrator->hydrate($data); + + $this->assertCount(2, $keyBindings); + $this->assertSame(65, $keyBindings[0]->getKey()); + $this->assertSame('test', $keyBindings[0]->getAction()); + $this->assertTrue($keyBindings[0]->getCtrl()); + $this->assertFalse($keyBindings[0]->getAlt()); + $this->assertTrue($keyBindings[0]->getShift()); + } +} diff --git a/tests/Unit/User/Service/KeyBindingServiceTest.php b/tests/Unit/User/Service/KeyBindingServiceTest.php new file mode 100644 index 000000000..2e60ae3d2 --- /dev/null +++ b/tests/Unit/User/Service/KeyBindingServiceTest.php @@ -0,0 +1,58 @@ + 'L', + 'action' => 'test', + 'ctrl' => true, + 'alt' => false, + 'shift' => true, + ], + [ + 'key' => 'P', + 'action' => 'test2', + 'ctrl' => false, + 'alt' => true, + 'shift' => false, + ], + ]; + + $keyBindingHydrator = new KeyBindingHydrator(); + $keyBindingService = new KeyBindingService($data, $keyBindingHydrator); + + $keyBindings = $keyBindingService->getDefaultKeyBindings(); + $this->assertCount(2, $keyBindings); + $this->assertSame(76, $keyBindings[0]->getKey()); + $this->assertSame('test', $keyBindings[0]->getAction()); + $this->assertTrue($keyBindings[0]->getCtrl()); + $this->assertFalse($keyBindings[0]->getAlt()); + $this->assertTrue($keyBindings[0]->getShift()); + } +} diff --git a/translations/studio_api_docs.en.yaml b/translations/studio_api_docs.en.yaml index 56a8b6956..709fc860d 100644 --- a/translations/studio_api_docs.en.yaml +++ b/translations/studio_api_docs.en.yaml @@ -592,4 +592,8 @@ asset_delete_grid_configuration_by_configurationId_description: | Delete grid configuration for a specific folder and given configuration ID {configurationId} asset_delete_grid_configuration_by_configurationId_summary: Delete grid configuration for a specific folder and given configuration ID user_search_summary: Search for users by query. The query can be a part of the username, first name, last name, email or user ID. -user_search_response: List of users \ No newline at end of file +user_search_response: List of users +user_default_key_bindings_description: | + Get default key bindings for user management +user_default_key_bindings_summary: Get default key bindings +user_default_key_bindings_response: List of default key bindings \ No newline at end of file