diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..fa1e1956 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,38 @@ +name: build + +on: + push: + branches: + - 'main' + pull_request: ~ + +jobs: + test: + name: "Test (PHP ${{ matrix.php-versions }}, Neos ${{ matrix.neos-versions }})" + + strategy: + fail-fast: false + matrix: + php-versions: ['8.0', '8.1', '8.2', '8.3'] + neos-versions: ['8.3'] + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + path: ${{ env.FLOW_FOLDER }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: mbstring, xml, json, zlib, iconv, intl, pdo_sqlite + ini-values: date.timezone="Africa/Tunis", opcache.fast_shutdown=0, apc.enable_cli=on + + - name: Set Neos Version + run: composer require neos/neos ^${{ matrix.neos-versions }} --no-progress --no-interaction + + - name: Run Tests + run: composer test diff --git a/.gitignore b/.gitignore index 95e8bbb9..364ca6b6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ node_modules/ .idea npm-debug.log +composer.lock +Packages +vendor diff --git a/Classes/Aspects/FusionCachingAspect.php b/Classes/Aspects/FusionCachingAspect.php index c87f3625..c721b479 100644 --- a/Classes/Aspects/FusionCachingAspect.php +++ b/Classes/Aspects/FusionCachingAspect.php @@ -1,5 +1,28 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +/** + * This file is part of the Sitegeist.Monocle package + * + * (c) 2020 + * Martin Ficzel + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ /** * This file is part of the Sitegeist.Monocle package @@ -13,6 +36,8 @@ * source code. */ +namespace Sitegeist\Monocle\Aspects; + use Neos\Flow\Annotations as Flow; use Neos\Flow\Aop\JoinPointInterface; use Neos\Cache\Frontend\VariableFrontend; @@ -38,7 +63,7 @@ class FusionCachingAspect public function cacheFusionConfigurationForPackageKey(JoinPointInterface $joinPoint) { $packageKey = $joinPoint->getMethodArgument('packageKey'); - $cacheIdentifier = str_replace('.', '_', $packageKey); + $cacheIdentifier = str_replace(['.', ':'], '_', $packageKey); if ($this->fusionCache->has($cacheIdentifier)) { $fusionConfigurationArray = $this->fusionCache->get($cacheIdentifier); diff --git a/Classes/Command/StyleguideCommandController.php b/Classes/Command/StyleguideCommandController.php index 818fbf8e..40d7fea3 100644 --- a/Classes/Command/StyleguideCommandController.php +++ b/Classes/Command/StyleguideCommandController.php @@ -1,5 +1,28 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +/** + * This file is part of the Sitegeist.Monocle package + * + * (c) 2020 + * Martin Ficzel + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ /** * This file is part of the Sitegeist.Monocle package @@ -13,8 +36,15 @@ * source code. */ +namespace Sitegeist\Monocle\Command; + use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\StreamInterface; +use Sitegeist\Monocle\Domain\PrototypeDetails\PropSets\PropSetName; +use Sitegeist\Monocle\Domain\PrototypeDetails\UseCases\UseCaseName; +use Sitegeist\Monocle\Domain\StyleguideAddress; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectIdentifier; +use Sitegeist\Monocle\Domain\StyleguideRepository; use Sitegeist\Monocle\Fusion\FusionService; use Sitegeist\Monocle\Fusion\FusionView; use Neos\Flow\Annotations as Flow; @@ -30,30 +60,38 @@ */ class StyleguideCommandController extends CommandController { - use DummyControllerContextTrait, PackageKeyTrait; + #[Flow\Inject] + protected StyleguideRepository $styleguideRepository; - /** - * @Flow\Inject - * @var FusionService - */ - protected $fusionService; + #[Flow\Inject] + protected ConfigurationService $configurationService; /** - * @Flow\Inject - * @var ConfigurationService + * Get a list of all available styleguides + * + * @param string $format Result encoding ``yaml`` and ``json`` are supported */ - protected $configurationService; + public function listCommand($format = 'json'): void + { + $styleguides = $this->styleguideRepository->getAllStyleGuides(); + + $data = []; + foreach ($styleguides as $styleguide) { + $data[$styleguide->address->toString()] = $styleguide->name->value; + } + $this->outputData($data, $format); + } /** * Get a list of all configured default styleguide viewports * * @param string $format Result encoding ``yaml`` and ``json`` are supported - * @param string $packageKey site-package (defaults to first found) + * @param string $styleguide site-package (defaults to first found) */ - public function viewportsCommand($format = 'json', $packageKey = null) + public function viewportsCommand(string $format = 'json', ?string $styleguide = null): void { - $sitePackageKey = $packageKey ?: $this->getDefaultSitePackageKey(); - $viewportPresets = $this->configurationService->getSiteConfiguration($sitePackageKey, 'ui.viewportPresets'); + $styleguide = $styleguide ? $this->styleguideRepository->getStyleGuide(StyleguideAddress::fromString($styleguide)) : $this->styleguideRepository->getDefault(); + $viewportPresets = $this->configurationService->getSiteConfiguration($styleguide->address->toString(), 'ui.viewportPresets'); $this->outputData($viewportPresets, $format); } @@ -61,71 +99,37 @@ public function viewportsCommand($format = 'json', $packageKey = null) * Get all styleguide items currently available * * @param string $format Result encoding ``yaml`` and ``json`` are supported - * @param string $packageKey site-package (defaults to first found) + * @param string $styleguide site-package (defaults to first found) */ - public function itemsCommand($format = 'json', $packageKey = null) + public function itemsCommand(string $format = 'json', ?string $styleguide = null): void { - $sitePackageKey = $packageKey ?: $this->getDefaultSitePackageKey(); - - $fusionAst = $this->fusionService->getMergedFusionObjectTreeForSitePackage($sitePackageKey); - $styleguideObjects = $this->fusionService->getStyleguideObjectsFromFusionAst($fusionAst); - + $styleguide = $styleguide ? $this->styleguideRepository->getStyleGuide(StyleguideAddress::fromString($styleguide)) : $this->styleguideRepository->getDefault(); + $styleguideObjects = $styleguide->getStyleguideObjectList(); + $styleguideObjects = json_decode(json_encode($styleguideObjects, JSON_THROW_ON_ERROR), true, 512, JSON_THROW_ON_ERROR); $this->outputData($styleguideObjects, $format); } /** * Render a given fusion component to HTML * - * @param string $prototypeName The prototype name of the component - * @param string|null $packageKey site-package (defaults to first found) + * @param string $styleguide The prototype name of the component + * @param string $item site-package (defaults to first found) * @param string|null $useCase The useCase for the preview * @param string|null $propSet The propSet used for the preview - * @param string|null $props Custom props for the preview + * @param string|null $props Custom props for the preview * @param string|null $locales Custom locales for the preview * @return void */ - public function renderCommand($prototypeName, $packageKey = null, ?string $useCase = '__default', ?string $propSet = '__default', ?string $props = '', ?string $locales = '') + public function renderCommand(string $styleguide, string $item, ?string $useCase = '__default', ?string $propSet = '__default', ?string $props = '', ?string $locales = '') { - $sitePackageKey = $packageKey ?: $this->getDefaultSitePackageKey(); - $convertedProps = json_decode($props, true) ?? []; - $convertedLocales = json_decode($locales, true) ?? []; - - $controllerContext = $this->createDummyControllerContext(); - - $fusionView = new FusionView(); - $fusionView->setControllerContext($controllerContext); - $fusionView->setPackageKey($sitePackageKey); - - $fusionRootPath = $this->configurationService->getSiteConfiguration($sitePackageKey, ['cli', 'fusionRootPath']); - - $fusionView->setPackageKey($sitePackageKey); - $fusionView->setFusionPath($fusionRootPath); - - if ($useCase == '__default') { - $useCase = null; - } - - if ($propSet == '__default') { - $propSet = null; - } - - $fusionView->assignMultiple([ - 'sitePackageKey' => $packageKey, - 'prototypeName' => $prototypeName, - 'useCase' => $useCase, - 'propSet' => $propSet, - 'props' => $convertedProps, - 'locales' => $convertedLocales - ]); - $result = $fusionView->render(); - if ($result instanceof ResponseInterface) { - return (string) $result->getBody(); - } - if ($result instanceof StreamInterface) { - return (string) $result; - } - // support for Neos 8.3 - $this->output($result); + $styleguide = $this->styleguideRepository->getStyleGuide(StyleguideAddress::fromString($styleguide)); + $styleguide->renderStyleguideObject( + StyleguideObjectIdentifier::fromString($item), + json_decode($props, true) ?? [], + $propSet ? PropSetName::fromString($propSet) : null, + $useCase ? UseCaseName::fromString($useCase) : null, + json_decode($locales, true) ?? [] + ); } protected function outputData($data, $format) diff --git a/Classes/Controller/ApiController.php b/Classes/Controller/ApiController.php index 84b4203a..17d30062 100644 --- a/Classes/Controller/ApiController.php +++ b/Classes/Controller/ApiController.php @@ -1,5 +1,16 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ /** * This file is part of the Sitegeist.Monocle package @@ -13,13 +24,17 @@ * source code. */ +declare(strict_types=1); + +namespace Sitegeist\Monocle\Controller; + + + use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\Controller\ActionController; -use Neos\Flow\ResourceManagement\ResourceManager; -use Sitegeist\Monocle\Domain\Fusion\PrototypeRepository; -use Sitegeist\Monocle\Domain\PrototypeDetails\PrototypeDetailsFactoryInterface; -use Sitegeist\Monocle\Fusion\FusionService; -use Sitegeist\Monocle\Service\PackageKeyTrait; +use Sitegeist\Monocle\Domain\StyleguideAddress; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectIdentifier; +use Sitegeist\Monocle\Domain\StyleguideRepository; use Sitegeist\Monocle\Service\ConfigurationService; /** @@ -28,177 +43,54 @@ */ class ApiController extends ActionController { - use PackageKeyTrait; - - /** - * @var array - */ protected $defaultViewObjectName = 'Neos\Flow\Mvc\View\JsonView'; - /** - * @Flow\Inject - * @var FusionService - */ - protected $fusionService; + #[Flow\Inject] + protected ConfigurationService $configurationService; - /** - * @Flow\Inject - * @var ResourceManager - */ - protected $resourceManager; - - /** - * @var array - * @Flow\InjectConfiguration("preview.additionalResources") - */ - protected $additionalResources; - - /** - * @Flow\Inject - * @var ConfigurationService - */ - protected $configurationService; - - /** - * @Flow\Inject - * @var PrototypeRepository - */ - protected $prototypeRepository; - - /** - * @Flow\Inject - * @var PrototypeDetailsFactoryInterface - */ - protected $prototypeDetailsFactory; + #[Flow\Inject] + protected StyleguideRepository $styleguideRepository; /** * Get all configurations for this site package - * - * @param string $sitePackageKey */ - public function configurationAction($sitePackageKey = null) + public function configurationAction(?string $sitePackageKey = null): void { - $sitePackageKey = $sitePackageKey ?: $this->getDefaultSitePackageKey(); + if ($sitePackageKey) { + $styleguideAddress = StyleguideAddress::fromString($sitePackageKey); + } else { + $styleguideAddress = $this->styleguideRepository->getDefault()->address; + } + + $allStyleguides = $this->styleguideRepository->getAllStyleGuides(); + $styleguide = $this->styleguideRepository->getStyleGuide($styleguideAddress); $value = []; - $value['sitePackage'] = $sitePackageKey; + $value['styleguide'] = $styleguideAddress->toString(); + $value['sitePackage'] = $styleguideAddress->toString(); $value['ui'] = [ - 'sitePackages' => $this->getSitePackages(), - 'viewportPresets' => $this->configurationService->getSiteConfiguration($sitePackageKey, 'ui.viewportPresets'), - 'localePresets' => $this->configurationService->getSiteConfiguration($sitePackageKey, 'ui.localePresets'), - 'hotkeys' => $this->configurationService->getSiteConfiguration($sitePackageKey, 'ui.hotkeys'), - 'preview' => $this->configurationService->getSiteConfiguration($sitePackageKey, 'preview') + 'sitePackages' => $allStyleguides, + 'styleguides' => $allStyleguides, + 'viewportPresets' => $this->configurationService->getStyleguideConfiguration($styleguideAddress, 'ui.viewportPresets'), + 'localePresets' => $this->configurationService->getStyleguideConfiguration($styleguideAddress, 'ui.localePresets'), + 'hotkeys' => $this->configurationService->getStyleguideConfiguration($styleguideAddress, 'ui.hotkeys'), + 'preview' => $this->configurationService->getStyleguideConfiguration($styleguideAddress, 'preview'), + 'grids' => $this->configurationService->getStyleguideConfiguration($styleguideAddress, 'ui.grids') ]; - $value['styleguideObjects'] = $this->getStyleguideObjects($sitePackageKey); + $value['styleguideObjects'] = $styleguide->getStyleguideObjectList(); + $this->view->setVariablesToRender(['value']); $this->view->assign('value', $value); } /** * Render informations about the given prototype - * - * @Flow\SkipCsrfProtection - * @param string $sitePackageKey - * @param string $prototypeName - * @return void - */ - public function prototypeDetailsAction($sitePackageKey, $prototypeName) - { - $this->response->setContentType('application/json'); - - $prototype = $this->prototypeRepository - ->findOneByPrototypeNameInSitePackage( - $prototypeName, - $sitePackageKey - ); - $prototypeDetails = $this->prototypeDetailsFactory - ->forPrototype($prototype); - - return json_encode($prototypeDetails); - } - - /** - * Render the given prototype - * - * @Flow\SkipCsrfProtection - * @param string $prototypeName - * @param string $sitePackageKey - * @return void - * @deprecated - */ - public function renderPrototypeAction($prototypeName, $sitePackageKey = null) - { - return $this->prototypeDetailsAction($sitePackageKey, $prototypeName); - } - - /** - * @return array - */ - protected function getSitePackages(): array - { - $sitePackageKeys = $this->getActiveSitePackageKeys(); - $result = []; - - foreach ($sitePackageKeys as $sitePackageKey) { - $result[$sitePackageKey] = $this->configurationService->getSiteConfiguration($sitePackageKey, 'title') ?? $sitePackageKey; - } - return $result; - } - - /** - * @param $sitePackageKey - * @param $styleguideObject - * @return array - * @throws \Neos\Neos\Domain\Exception */ - protected function getStyleguideObjects($sitePackageKey): array + public function prototypeDetailsAction(string $sitePackageKey, string $prototypeName): void { - $fusionAst = $this->fusionService->getMergedFusionObjectTreeForSitePackage($sitePackageKey); - $styleguideObjects = $this->fusionService->getStyleguideObjectsFromFusionAst($fusionAst); - $prototypeStructures = $this->configurationService->getSiteConfiguration($sitePackageKey, 'ui.structure'); - - foreach ($styleguideObjects as $prototypeName => &$styleguideObject) { - $styleguideObject['structure'] = $this->getStructureForPrototypeName($prototypeStructures, $prototypeName); - } - - $hiddenPrototypeNamePatterns = $this->configurationService->getSiteConfiguration($sitePackageKey, 'hiddenPrototypeNamePatterns'); - if (is_array($hiddenPrototypeNamePatterns)) { - $alwaysShowPrototypes = $this->configurationService->getSiteConfiguration($sitePackageKey, 'alwaysShowPrototypes'); - foreach ($hiddenPrototypeNamePatterns as $pattern) { - $styleguideObjects = array_filter( - $styleguideObjects, - function ($prototypeName) use ($pattern, $alwaysShowPrototypes) { - if (in_array($prototypeName, $alwaysShowPrototypes, true)) { - return true; - } - return fnmatch($pattern, $prototypeName) === false; - }, - ARRAY_FILTER_USE_KEY - ); - } - } - return $styleguideObjects; - } - - /** - * Find the matching structure for a prototype - * - * @param $prototypeStructures - * @param $prototypeName - * @return array - */ - protected function getStructureForPrototypeName($prototypeStructures, $prototypeName) - { - foreach ($prototypeStructures as $structure) { - if (preg_match(sprintf('!%s!', $structure['match']), $prototypeName)) { - return $structure; - } - } - - return [ - 'label' => 'Other', - 'icon' => 'icon-question', - 'color' => 'white' - ]; + $styleguideAddress = StyleguideAddress::fromString($sitePackageKey); + $styleguide = $this->styleguideRepository->getStyleGuide($styleguideAddress); + $styleguideObjectDetails = $styleguide->getStyleguideObjectDetails(StyleguideObjectIdentifier::fromString($prototypeName)); + $this->view->assign('value', $styleguideObjectDetails); } } diff --git a/Classes/Controller/MockController.php b/Classes/Controller/MockController.php deleted file mode 100644 index cc5b2ac2..00000000 --- a/Classes/Controller/MockController.php +++ /dev/null @@ -1,62 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Neos\Flow\Mvc\Controller\ActionController; -use Neos\Flow\Http\Component\SetHeaderComponent; -use Neos\Flow\Property\Exception\TargetNotFoundException; - -/** - * Class MockController - * @package Sitegeist\Monocle\Controller - */ -class MockController extends ActionController -{ - /** - * @var array - * @Flow\InjectConfiguration(path="uriMock.static") - */ - protected $staticUriMocks; - - /** - * Return the given content and the type header - * - * @param string $content - * @param string $type - * @return void - */ - public function mirrorAction($content = '', $type = 'text/html') - { - $this->response->setComponentParameter(SetHeaderComponent::class, 'Content-Type', $type); - return $content; - } - - /** - * Return the given static content as defined in the configuration - * - * @param string $key - * @return void - */ - public function staticAction($key) - { - if ($key && is_array($this->staticUriMocks) && array_key_exists($key, $this->staticUriMocks)) { - $config = $this->staticUriMocks[$key]; - $this->response->setComponentParameter(SetHeaderComponent::class, 'Content-Type', $config['contentType']); - return file_get_contents($config['path']); - } - - throw new TargetNotFoundException(); - } -} diff --git a/Classes/Controller/ModuleController.php b/Classes/Controller/ModuleController.php index 669b3e67..65af6150 100644 --- a/Classes/Controller/ModuleController.php +++ b/Classes/Controller/ModuleController.php @@ -1,5 +1,16 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ /** * This file is part of the Sitegeist.Monocle package @@ -13,10 +24,14 @@ * source code. */ +namespace Sitegeist\Monocle\Controller; + + + use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\View\ViewInterface; use Neos\Flow\Mvc\Controller\ActionController; -use Sitegeist\Monocle\Service\PackageKeyTrait; +use Sitegeist\Monocle\Domain\StyleguideRepository; use Sitegeist\Monocle\Service\ConfigurationService; /** @@ -25,13 +40,11 @@ */ class ModuleController extends ActionController { - use PackageKeyTrait; + #[Flow\Inject] + protected StyleguideRepository $styleguideRepository; - /** - * @Flow\Inject - * @var ConfigurationService - */ - protected $configurationService; + #[Flow\Inject] + protected ConfigurationService $configurationService; /** * Initialize the view @@ -41,8 +54,10 @@ class ModuleController extends ActionController */ public function initializeView(ViewInterface $view) { - $sitePackageKey = $this->getDefaultSitePackageKey(); - $this->view->assign('defaultSitePackageKey', $sitePackageKey); + $defaultStyleguide = $this->styleguideRepository->getDefault(); + $styleguideAddress = $defaultStyleguide->address->toString(); + + $this->view->assign('defaultStyleguideAddress', $styleguideAddress); } /** diff --git a/Classes/Controller/PreviewController.php b/Classes/Controller/PreviewController.php index b0a0c4b5..0c6d8de6 100644 --- a/Classes/Controller/PreviewController.php +++ b/Classes/Controller/PreviewController.php @@ -1,5 +1,16 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ /** * This file is part of the Sitegeist.Monocle package @@ -13,14 +24,18 @@ * source code. */ -use GuzzleHttp\Psr7\Message; +namespace Sitegeist\Monocle\Controller; + + + use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\Controller\ActionController; -use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\StreamInterface; -use Sitegeist\Monocle\Service\PackageKeyTrait; +use Sitegeist\Monocle\Domain\StyleguideAddress; +use Sitegeist\Monocle\Domain\StyleguideObjects\PropSets\PropSetName; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectIdentifier; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCaseName; +use Sitegeist\Monocle\Domain\StyleguideRepository; use Sitegeist\Monocle\Fusion\FusionView; -use Sitegeist\Monocle\Service\ConfigurationService; /** * Class PreviewController @@ -28,8 +43,6 @@ */ class PreviewController extends ActionController { - use PackageKeyTrait; - /** * @var string */ @@ -40,23 +53,17 @@ class PreviewController extends ActionController */ protected $view; - /** - * @Flow\Inject - * @var ConfigurationService - */ - protected $configurationService; + #[Flow\InjectConfiguration(package: "Neos.Flow", path: "i18n.defaultLocale")] + protected string $defaultLocale; /** - * @var string - * @Flow\InjectConfiguration(package="Neos.Flow", path="i18n.defaultLocale"); + * @var string[] */ - protected $defaultLocale; + #[Flow\InjectConfiguration(package: "Neos.Flow", path: "i18n.fallbackRule.order")] + protected array $localeFallback; - /** - * @var string - * @Flow\InjectConfiguration(package="Neos.Flow", path="i18n.fallbackRule.order"); - */ - protected $localeFallback; + #[Flow\Inject] + protected StyleguideRepository $styleguideRepository; /** * @param string $prototypeName @@ -65,10 +72,9 @@ class PreviewController extends ActionController * @param string|null $propSet * @param string|null $props props as json encoded string * @param string|null $locales locales-fallback-chain as comma sepertated string - * @param bool|null $showGrid * @return string */ - public function indexAction(string $prototypeName, string $sitePackageKey, ?string $useCase = '__default', ?string $propSet = '__default', ?string $props = '', ?string $locales = '', ?bool $showGrid = false) + public function indexAction(string $prototypeName, string $sitePackageKey, ?string $useCase = '__default', ?string $propSet = '__default', ?string $props = '', ?string $locales = ''): string { $renderProps = []; if ($props) { @@ -92,62 +98,15 @@ public function indexAction(string $prototypeName, string $sitePackageKey, ?stri $renderLocales = $this->localeFallback ?: [$this->defaultLocale]; } - $sitePackageKey = $sitePackageKey ?: $this->getDefaultSitePackageKey(); - $fusionRootPath = $this->configurationService->getSiteConfiguration($sitePackageKey, ['preview', 'fusionRootPath']); - - $this->view->setPackageKey($sitePackageKey); - $this->view->setFusionPath($fusionRootPath); - $this->view->setLocales($renderLocales); - - if ($showGrid) { - $gridConfigurations = $this->configurationService->getSiteConfiguration($sitePackageKey, ['ui', 'grids']); - } - - $this->view->assignMultiple([ - 'sitePackageKey' => $sitePackageKey, - 'prototypeName' => $prototypeName, - 'useCase' => $useCase, - 'propSet' => $propSet, - 'props' => $renderProps, - 'locales' => $renderLocales, - 'grids' => $gridConfigurations ?? null - ]); - - // get the status and headers from the view - $result = $this->view->render(); - if ($result instanceof ResponseInterface) { - return (string)$result->getBody(); - } - if ($result instanceof StreamInterface) { - return (string)$result; - } - // support for neos 8.3 - return $this->mergeHttpResponseFromOutput($result); - } - - /** - * @param string $output - * @return string The message body without the message head - * @deprecated remove once Neos 8.3 is no longer supported - */ - protected function mergeHttpResponseFromOutput($output) - { - if (strpos($output, 'HTTP/') === 0) { - $endOfHeader = strpos($output, "\r\n\r\n"); - if ($endOfHeader !== false) { - $header = substr($output, 0, $endOfHeader + 4); - try { - $renderedResponse = Message::parseResponse($header); - $this->response->setStatusCode($renderedResponse->getStatusCode()); - foreach ($renderedResponse->getHeaders() as $headerName => $headerValues) { - $this->response->setHttpHeader($headerName, $headerValues); - } - $output = substr($output, strlen($header)); - } catch (\InvalidArgumentException $exception) { - } - } - } + $styleguide = $this->styleguideRepository->getStyleGuide(StyleguideAddress::fromString($sitePackageKey)); + $result = $styleguide->renderStyleguideObject( + StyleguideObjectIdentifier::fromString($prototypeName), + $renderProps, + $propSet ? PropSetName::fromString($propSet) : null, + $useCase ? UseCaseName::fromString($useCase) : null, + $renderLocales + ); - return $output; + return $result; } } diff --git a/Classes/Controller/StyleguideController.php b/Classes/Controller/StyleguideController.php index 7388bc19..03c107b8 100644 --- a/Classes/Controller/StyleguideController.php +++ b/Classes/Controller/StyleguideController.php @@ -1,5 +1,16 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ /** * This file is part of the Sitegeist.Monocle package @@ -13,6 +24,10 @@ * source code. */ +namespace Sitegeist\Monocle\Controller; + + + use Neos\Flow\Annotations as Flow; use Neos\Neos\Controller\Module\AbstractModuleController; diff --git a/Classes/Domain/Fusion/Prototype.php b/Classes/Domain/Fusion/Prototype.php deleted file mode 100644 index b3b657dd..00000000 --- a/Classes/Domain/Fusion/Prototype.php +++ /dev/null @@ -1,141 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Neos\Utility\Arrays; -use Neos\Fusion\Core\Runtime as FusionRuntime; - -/** - * @Flow\Proxy(false) - */ -final class Prototype -{ - /** - * @var PrototypeName - */ - private $name; - - /** - * @var array - */ - private $ast; - - /** - * @var FusionRuntime - */ - private $runtime; - - /** - * @param PrototypeName $name - * @param array $ast - * @param FusionRuntime $runtime - */ - public function __construct( - PrototypeName $name, - array $ast, - FusionRuntime $runtime - ) { - $this->name = $name; - $this->ast = $ast; - $this->runtime = $runtime; - } - - /** - * @return PrototypeName - */ - public function getName(): PrototypeName - { - return $this->name; - } - - /** - * @return array - */ - public function getAst(): array - { - return $this->ast; - } - - /** - * @return boolean - */ - public function isComponent(): bool - { - return $this->extends( - PrototypeName::fromString('Neos.Fusion:Component') - ); - } - - /** - * @param PrototypeName $ancestorPrototypeName - * @return boolean - */ - public function extends(PrototypeName $ancestorPrototypeName): bool - { - if (isset($this->ast['__prototypeChain'])) { - return in_array( - (string) $ancestorPrototypeName, - $this->ast['__prototypeChain'] - ); - } - - return false; - } - - /** - * @param null|string $path - * @return array|string[] - */ - public function getKeys(?string $path = null): array - { - if ($path !== null) { - $ast = Arrays::getValueByPath($this->ast, $path); - if (!is_array($ast)) { - $ast = []; - } - } else { - $ast = $this->ast; - } - - return array_filter(array_keys($ast), function (string $key): bool { - return substr($key, 0, 2) !== '__'; - }); - } - - /** - * @param string $path - * @param array $context - * @return mixed - */ - public function evaluate(string $path, array $context = []) - { - if ($path[0] !== '/') { - throw new \InvalidArgumentException( - '$path must start with "/".' - ); - } - - $currentContext = $this->runtime->getCurrentContext(); - $this->runtime->pushContextArray(array_merge($currentContext ?: [], $context)); - - $result = $this->runtime->evaluate( - sprintf('/<%s>%s', $this->name, $path) - ); - - $this->runtime->popContext(); - - return $result; - } -} diff --git a/Classes/Domain/Fusion/PrototypeRepository.php b/Classes/Domain/Fusion/PrototypeRepository.php deleted file mode 100644 index 9abed89f..00000000 --- a/Classes/Domain/Fusion/PrototypeRepository.php +++ /dev/null @@ -1,59 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Neos\Fusion\Core\FusionConfiguration; -use Neos\Fusion\Core\Runtime as FusionRuntime; -use Neos\Fusion\Core\RuntimeFactory as FusionRuntimeFactory; -use Sitegeist\Monocle\Fusion\FusionService; - -/** - * @Flow\Scope("singleton") - */ -final class PrototypeRepository -{ - /** - * @Flow\Inject - * @var FusionRuntimeFactory - */ - protected $fusionRuntimeFactory; - - /** - * @Flow\Inject - * @var FusionService - */ - protected $fusionService; - - public function findOneByPrototypeNameInSitePackage( - string $prototypeName, - string $sitePackageKey - ): ?Prototype { - $fusionConfiguration = $this->fusionService->getFusionConfigurationForPackageKey($sitePackageKey); - $fusionObjectTree = $fusionConfiguration->toArray(); - if (isset($fusionObjectTree['__prototypes'][$prototypeName])) { - $fusionAst = $fusionObjectTree['__prototypes'][$prototypeName]; - $fusionRuntime = $this->fusionRuntimeFactory->create($fusionObjectTree); - $fusionRuntime->setEnableContentCache(false); - - return new Prototype( - PrototypeName::fromString($prototypeName), - $fusionAst, - $fusionRuntime - ); - } - - return null; - } -} diff --git a/Classes/Domain/PrototypeDetails/Anatomy.php b/Classes/Domain/PrototypeDetails/Anatomy.php deleted file mode 100644 index 7107da27..00000000 --- a/Classes/Domain/PrototypeDetails/Anatomy.php +++ /dev/null @@ -1,72 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\PrototypeName; - -/** - * @Flow\Proxy(false) - */ -final class Anatomy implements \JsonSerializable -{ - /** - * @var PrototypeName - */ - private $prototypeName; - - /** - * @var array|AnatomyInterface[] - */ - private $children; - - /** - * @param PrototypeName $prototypeName - * @param array $children - */ - public function __construct( - PrototypeName $prototypeName, - array $children - ) { - $this->prototypeName = $prototypeName; - $this->children = $children; - } - - /** - * @return PrototypeName - */ - public function getPrototypeName(): PrototypeName - { - return $this->prototypeName; - } - - /** - * @return array|AnatomyInterface[] - */ - public function getChildren(): array - { - return $this->children; - } - - /** - * @return array - */ - public function jsonSerialize() - { - return [ - 'prototypeName' => $this->prototypeName, - 'children' => $this->children - ]; - } -} diff --git a/Classes/Domain/PrototypeDetails/AnatomyFactory.php b/Classes/Domain/PrototypeDetails/AnatomyFactory.php deleted file mode 100644 index 646efaa8..00000000 --- a/Classes/Domain/PrototypeDetails/AnatomyFactory.php +++ /dev/null @@ -1,67 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\Prototype; - -/** - * @Flow\Scope("singleton") - */ -final class AnatomyFactory -{ - /** - * @param Prototype $prototype - * @return Anatomy - */ - public function fromPrototypeForPrototypeDetails( - Prototype $prototype - ): Anatomy { - $children = []; - $referencedFusionObjects = FusionPrototypeAst::fromArray( - $prototype->getAst() - )->getAllReferencedFusionObjects(); - - foreach ($referencedFusionObjects as $fusionObjectAst) { - $children[] = $this->fromFusionObjectAstForPrototypeDetails( - $fusionObjectAst - ); - } - - return new Anatomy($prototype->getName(), $children); - } - - /** - * @param FusionObjectAst $fusionObjectAst - * @return Anatomy - */ - public function fromFusionObjectAstForPrototypeDetails( - FusionObjectAst $fusionObjectAst - ): Anatomy { - $children = []; - $referencedFusionObjects = $fusionObjectAst - ->getAllReferencedFusionObjects(); - - foreach ($referencedFusionObjects as $childFusionObjectAst) { - $children[] = $this->fromFusionObjectAstForPrototypeDetails( - $childFusionObjectAst - ); - } - - return new Anatomy( - $fusionObjectAst->getPrototypeName(), - $children - ); - } -} diff --git a/Classes/Domain/PrototypeDetails/FusionObjectAst.php b/Classes/Domain/PrototypeDetails/FusionObjectAst.php deleted file mode 100644 index 60de0f43..00000000 --- a/Classes/Domain/PrototypeDetails/FusionObjectAst.php +++ /dev/null @@ -1,83 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\PrototypeName; - -/** - * @Flow\Proxy(false) - */ -final class FusionObjectAst -{ - /** - * @var array - */ - private $value; - - /** - * @param array $value - */ - private function __construct(array $value) - { - if (!isset($value['__objectType'])) { - throw new \UnexpectedValueException('__objectType key must be set.'); - } - - $this->value = $value; - } - - public function getPrototypeName(): PrototypeName - { - return PrototypeName::fromString($this->value['__objectType']); - } - - /** - * @return \Iterator - */ - public function getAllReferencedFusionObjects(): \Iterator - { - $findReferencedFusionPrototypeAstsRecursively = function ( - array $astExcerpt - ) use (&$findReferencedFusionPrototypeAstsRecursively): \Iterator { - if (isset($astExcerpt['__objectType'])) { - yield self::fromArray($astExcerpt); - } else { - foreach ($astExcerpt as $key => $value) { - if (is_string($key) && substr($key, 0, 2) === '__') { - continue; - } - - if (is_array($value)) { - yield from $findReferencedFusionPrototypeAstsRecursively($value); - } - } - } - }; - - $value = $this->value; - unset($value['__objectType']); - - yield from $findReferencedFusionPrototypeAstsRecursively($value); - } - - /** - * @param array $array - * @return self - */ - public static function fromArray(array $array): self - { - return new self($array); - } -} diff --git a/Classes/Domain/PrototypeDetails/FusionPrototypeAst.php b/Classes/Domain/PrototypeDetails/FusionPrototypeAst.php deleted file mode 100644 index ce3ac5f1..00000000 --- a/Classes/Domain/PrototypeDetails/FusionPrototypeAst.php +++ /dev/null @@ -1,82 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; - -/** - * @Flow\Proxy(false) - */ -final class FusionPrototypeAst implements \JsonSerializable -{ - /** - * @var array - */ - private $value; - - /** - * @param array $value - */ - private function __construct(array $value) - { - if (!isset($value['__prototypeObjectName'])) { - throw new \UnexpectedValueException('__prototypeObjectName key must be set.'); - } - - $this->value = $value; - } - - /** - * @return \Iterator - */ - public function getAllReferencedFusionObjects(): \Iterator - { - $findReferencedFusionPrototypeAstsRecursively = function ( - array $astExcerpt - ) use (&$findReferencedFusionPrototypeAstsRecursively): \Iterator { - if (isset($astExcerpt['__objectType'])) { - yield FusionObjectAst::fromArray($astExcerpt); - } else { - foreach ($astExcerpt as $key => $value) { - if (substr((string) $key, 0, 2) === '__') { - continue; - } - - if (is_array($value)) { - yield from $findReferencedFusionPrototypeAstsRecursively($value); - } - } - } - }; - - yield from $findReferencedFusionPrototypeAstsRecursively($this->value); - } - - /** - * @param array $array - * @return self - */ - public static function fromArray(array $array): self - { - return new self($array); - } - - /** - * @return array - */ - public function jsonSerialize() - { - return $this->value; - } -} diff --git a/Classes/Domain/PrototypeDetails/PropSets/PropSetCollection.php b/Classes/Domain/PrototypeDetails/PropSets/PropSetCollection.php deleted file mode 100644 index b5ca58b8..00000000 --- a/Classes/Domain/PrototypeDetails/PropSets/PropSetCollection.php +++ /dev/null @@ -1,83 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\Prototype; -use Sitegeist\Monocle\Domain\PrototypeDetails\Props\PropValue; - -/** - * @Flow\Proxy(false) - */ -final class PropSetCollection implements \JsonSerializable -{ - /** - * @var array|PropSet[] - */ - private $propSets; - - /** - * @param PropSet ...$propSets - */ - public function __construct(PropSet ...$propSets) - { - $this->propSets = $propSets; - } - - /** - * @param Prototype $prototype - * @return self - */ - public static function fromPrototype(Prototype $prototype): self - { - $ast = $prototype->getAst(); - $propSets = []; - - if (isset($ast['__meta']['styleguide']['propSets'])) { - $propSetNames = array_keys( - $ast['__meta']['styleguide']['propSets'] - ); - - foreach ($propSetNames as $propSetNameAsString) { - $propSetName = PropSetName::fromString($propSetNameAsString); - $values = $prototype->evaluate(sprintf( - '/__meta/styleguide/propSets/%s', - $propSetName - )); - $overrides = []; - - if (is_array($values)) { - foreach ($values as $name => $value) { - if (PropValue::isValid($value)) { - $overrides[(string) $name] = - PropValue::fromAny($value); - } - } - } - - $propSets[] = new PropSet($propSetName, $overrides); - } - } - - return new self(...$propSets); - } - - /** - * @return array|PropSet[] - */ - public function jsonSerialize() - { - return $this->propSets; - } -} diff --git a/Classes/Domain/PrototypeDetails/Props/Editor.php b/Classes/Domain/PrototypeDetails/Props/Editor.php deleted file mode 100644 index 754806dc..00000000 --- a/Classes/Domain/PrototypeDetails/Props/Editor.php +++ /dev/null @@ -1,71 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; - -/** - * @Flow\Proxy(false) - */ -final class Editor implements EditorInterface -{ - /** - * @var EditorIdentifier - */ - private $identifier; - - /** - * @var EditorOptions - */ - private $options; - - /** - * @param EditorIdentifier $identifier - * @param EditorOptions $options - */ - public function __construct( - EditorIdentifier $identifier, - EditorOptions $options - ) { - $this->identifier = $identifier; - $this->options = $options; - } - - /** - * @return EditorIdentifier - */ - public function getIdentifier(): EditorIdentifier - { - return $this->identifier; - } - - /** - * @return EditorOptions - */ - public function getOptions(): EditorOptions - { - return $this->options; - } - - /** - * @return array - */ - public function jsonSerialize() - { - return [ - 'identifier' => $this->identifier, - 'options' => $this->options - ]; - } -} diff --git a/Classes/Domain/PrototypeDetails/Props/EditorFactory.php b/Classes/Domain/PrototypeDetails/Props/EditorFactory.php deleted file mode 100644 index 64b7d5d6..00000000 --- a/Classes/Domain/PrototypeDetails/Props/EditorFactory.php +++ /dev/null @@ -1,271 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\Prototype; - -/** - * @Flow\Proxy(false) - */ -final class EditorFactory -{ - /** - * Get an editor that fits the given prop of the given prototype - * - * @param Prototype $prototype - * @param PropName $propName - * @return EditorInterface|null - */ - public function for( - Prototype $prototype, - PropName $propName - ): ?EditorInterface { - if (($hidePropsInInspector = $prototype->evaluate('/__meta/styleguide/options/hidePropsInInspector')) && in_array((string)$propName, $hidePropsInInspector, true)) { - return null; - } - if ($manualConfiguration = $prototype->evaluate( - sprintf( - '/__meta/styleguide/options/propEditors/%s', - $propName - ) - )) { - try { - return $this->fromManualConfiguration($manualConfiguration); - } catch (\UnexpectedValueException $e) { - throw new \DomainException( - sprintf( - 'Invalid editor configuration for prop "%s" of "%s": %s', - $propName, - $prototype->getName(), - $e->getMessage() - ) - ); - } - } - - if ($propValue = PropValue::of($prototype, $propName)) { - return $this->forPropValue($propValue); - } - - return null; - } - - /** - * Provides a fitting editor for any given prop value - * - * @param PropValue $propValue - * @return null|EditorInterface - */ - public function forPropValue(PropValue $propValue): ?EditorInterface - { - switch (true) { - case $propValue->isBoolean(): - return $this->checkBox(); - case $propValue->isNumber(): - return $this->number('float'); - case $propValue->isString(): - if ($propValue->getLength() <= 80) { - return $this->text(); - } else { - return $this->textArea(); - } - break; - default: - return null; - } - } - - /** - * @param array $manualConfiguration - * @return EditorInterface - */ - public function fromManualConfiguration(array $manualConfiguration): EditorInterface - { - if (!isset($manualConfiguration['editor'])) { - throw new \UnexpectedValueException( - 'Path "editor" must be defined.' - ); - } - - if (!is_string($manualConfiguration['editor'])) { - throw new \UnexpectedValueException( - 'Path "editor" must evaluate to a string.' - ); - } - - if (!isset($manualConfiguration['editorOptions'])) { - $manualConfiguration['editorOptions'] = []; - } - - if (!is_array($manualConfiguration['editorOptions'])) { - throw new \UnexpectedValueException( - 'Path "editorOptions" must evaluate to an array.' - ); - } - - switch ($manualConfiguration['editor']) { - case 'Sitegeist.Monocle/Props/Editors/Checkbox': - return $this->checkbox(); - case 'Sitegeist.Monocle/Props/Editors/Text': - return $this->text(); - case 'Sitegeist.Monocle/Props/Editors/TextArea': - return $this->textArea(); - case 'Sitegeist.Monocle/Props/Editors/SelectBox': - return $this->selectBox($manualConfiguration['editorOptions']); - default: - throw new \UnexpectedValueException( - sprintf( - 'Unknown editor "%s".', - $manualConfiguration['editor'] - ) - ); - } - } - - /** - * Provides a CheckBox editor - * - * @return EditorInterface - */ - public function checkbox(): EditorInterface - { - return new Editor( - EditorIdentifier::fromString( - 'Sitegeist.Monocle/Props/Editors/Checkbox' - ), - EditorOptions::empty() - ); - } - - /** - * Provides a Text editor - * - * @return EditorInterface - */ - public function text(): EditorInterface - { - return new Editor( - EditorIdentifier::fromString( - 'Sitegeist.Monocle/Props/Editors/Text' - ), - EditorOptions::empty() - ); - } - - /** - * Provides a TextArea editor - * - * @return EditorInterface - */ - public function textArea(): EditorInterface - { - return new Editor( - EditorIdentifier::fromString( - 'Sitegeist.Monocle/Props/Editors/TextArea' - ), - EditorOptions::empty() - ); - } - - /** - * Provides a Number editor - * - * @return EditorInterface - */ - public function number(string $numberType): EditorInterface - { - if (!in_array($numberType, ['integer', 'float'])) { - throw new \UnexpectedValueException( - '$numberType must be either "integer" or "float".' - ); - } - - return new Editor( - EditorIdentifier::fromString( - 'Sitegeist.Monocle/Props/Editors/Text' - ), - EditorOptions::fromArray([ - 'castValueTo' => $numberType - ]) - ); - } - - /** - * Provides a SelectBox Editor - * - * @param array $options - * @return EditorInterface - */ - public function selectBox(array $options): EditorInterface - { - if (!isset($options['options'])) { - $options['options'] = []; - } - - if (!is_array($options['options'])) { - throw new \UnexpectedValueException( - sprintf( - 'SelectBox options must be an array. Got "%" instead.', - gettype($options['options']) - ) - ); - } else { - $options['options'] = array_values($options['options']); - } - - foreach ($options['options'] as $option) { - if (!isset($option['label'])) { - throw new \UnexpectedValueException( - 'All SelectBox options must have a label.' - ); - } - - if (!is_string($option['label'])) { - throw new \UnexpectedValueException( - sprintf( - 'All SelectBox option labels must be of type string. Got "%s" instead.', - gettype($option['label']) - ) - ); - } - - if (!isset($option['value'])) { - throw new \UnexpectedValueException( - sprintf( - 'All SelectBox options must have a value. Found option "%s" without one.', - $option['label'] - ) - ); - } - - if (!is_string($option['value']) && !is_int($option['value']) && !is_float($option['value'])) { - throw new \UnexpectedValueException( - sprintf( - 'All SelectBox option labels must be either of type string, integer or float. Got "%s" for option "%s" instead.', - gettype($option['value']), - $option['label'] - ) - ); - } - } - - return new Editor( - EditorIdentifier::fromString( - 'Sitegeist.Monocle/Props/Editors/SelectBox' - ), - EditorOptions::fromArray($options) - ); - } -} diff --git a/Classes/Domain/PrototypeDetails/Props/EditorIdentifier.php b/Classes/Domain/PrototypeDetails/Props/EditorIdentifier.php deleted file mode 100644 index 1dd85d54..00000000 --- a/Classes/Domain/PrototypeDetails/Props/EditorIdentifier.php +++ /dev/null @@ -1,52 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; - -/** - * @Flow\Proxy(false) - */ -final class EditorIdentifier implements \JsonSerializable -{ - /** - * @var string - */ - private $value; - - /** - * @param string $value - */ - private function __construct(string $value) - { - $this->value = $value; - } - - /** - * @param string $string - * @return self - */ - public static function fromString(string $string): self - { - return new self($string); - } - - /** - * @return string - */ - public function jsonSerialize() - { - return $this->value; - } -} diff --git a/Classes/Domain/PrototypeDetails/Props/EditorInterface.php b/Classes/Domain/PrototypeDetails/Props/EditorInterface.php deleted file mode 100644 index b5085d79..00000000 --- a/Classes/Domain/PrototypeDetails/Props/EditorInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -interface EditorInterface extends \JsonSerializable -{ - /** - * @return EditorIdentifier - */ - public function getIdentifier(): EditorIdentifier; - - /** - * @return EditorOptions - */ - public function getOptions(): EditorOptions; -} diff --git a/Classes/Domain/PrototypeDetails/Props/EditorOptions.php b/Classes/Domain/PrototypeDetails/Props/EditorOptions.php deleted file mode 100644 index 3373b322..00000000 --- a/Classes/Domain/PrototypeDetails/Props/EditorOptions.php +++ /dev/null @@ -1,83 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use UnexpectedValueException; - -/** - * @Flow\Proxy(false) - */ -final class EditorOptions implements \JsonSerializable -{ - /** - * @var array - */ - private $value; - - /** - * @param array $value - */ - private function __construct(array $value) - { - $this->value = $value; - } - - /** - * @return self - */ - public static function empty(): self - { - return new self([]); - } - - /** - * @param array $array - * @return self - */ - public static function fromArray(array $array): self - { - return new self($array); - } - - /** - * @param \JsonSerializable $jsonSerializable - * @return self - */ - public static function fromJsonSerializable( - \JsonSerializable $jsonSerializable - ): self { - $jsonSerializeResult = $jsonSerializable->jsonSerialize(); - - if (!is_array($jsonSerializeResult)) { - throw new UnexpectedValueException( - sprintf( - '$jsonSerializable->jsonSerialize() must return an ' . - 'array. Got "%" instead.', - gettype($jsonSerializeResult) - ) - ); - } - - return new self($jsonSerializeResult); - } - - /** - * @return array - */ - public function jsonSerialize() - { - return $this->value ?: new \stdClass; - } -} diff --git a/Classes/Domain/PrototypeDetails/Props/Prop.php b/Classes/Domain/PrototypeDetails/Props/Prop.php deleted file mode 100644 index ae9aedd9..00000000 --- a/Classes/Domain/PrototypeDetails/Props/Prop.php +++ /dev/null @@ -1,85 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; - -/** - * @Flow\Proxy(false) - */ -final class Prop implements PropInterface -{ - /** - * @var PropName - */ - private $name; - - /** - * @var PropValue - */ - private $value; - - /** - * @var EditorInterface - */ - private $editor; - - /** - * @param PropName $name - * @param PropValue $value - * @param EditorInterface $editor - */ - public function __construct( - PropName $name, - PropValue $value, - EditorInterface $editor - ) { - $this->name = $name; - $this->value = $value; - $this->editor = $editor; - } - - /** - * @return PropName - */ - public function getName(): PropName - { - return $this->name; - } - - /** - * @return PropValue - */ - public function getValue(): PropValue - { - return $this->value; - } - - /** - * @return EditorInterface - */ - public function getEditor(): EditorInterface - { - return $this->editor; - } - - public function jsonSerialize() - { - return [ - 'name' => $this->name, - 'value' => $this->value, - 'editor' => $this->editor - ]; - } -} diff --git a/Classes/Domain/PrototypeDetails/Props/PropInterface.php b/Classes/Domain/PrototypeDetails/Props/PropInterface.php deleted file mode 100644 index 333a358b..00000000 --- a/Classes/Domain/PrototypeDetails/Props/PropInterface.php +++ /dev/null @@ -1,32 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -interface PropInterface extends \JsonSerializable -{ - /** - * @return PropName - */ - public function getName(): PropName; - - /** - * @return PropValue - */ - public function getValue(): PropValue; - - /** - * @return EditorInterface - */ - public function getEditor(): EditorInterface; -} diff --git a/Classes/Domain/PrototypeDetails/Props/PropName.php b/Classes/Domain/PrototypeDetails/Props/PropName.php deleted file mode 100644 index 67d502d5..00000000 --- a/Classes/Domain/PrototypeDetails/Props/PropName.php +++ /dev/null @@ -1,85 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\Prototype; - -/** - * @Flow\Proxy(false) - */ -final class PropName implements \JsonSerializable -{ - /** - * @var string - */ - private $value; - - /** - * @param string $value - */ - private function __construct(string $value) - { - $this->value = $value; - } - - /** - * @param Prototype $prototype - * @return array|PropName[] - */ - public static function fromPrototype(Prototype $prototype): array - { - $propNames = $prototype->getKeys('__meta.styleguide.props'); - if ($prototype->isComponent()) { - $propNames = array_unique( - array_merge( - array_filter( - $prototype->getKeys(), - function (string $key): bool { - return $key !== 'renderer'; - } - ), - $propNames - ) - ); - } - - return array_map([self::class, 'fromString'], $propNames); - } - - /** - * @param string $string - * @return self - */ - public static function fromString(string $string): self - { - return new self($string); - } - - /** - * @return string - */ - public function jsonSerialize() - { - return $this->value; - } - - /** - * @return string - */ - public function __toString(): string - { - return $this->value; - } -} diff --git a/Classes/Domain/PrototypeDetails/Props/PropsCollectionBuilder.php b/Classes/Domain/PrototypeDetails/Props/PropsCollectionBuilder.php deleted file mode 100644 index 6fe95620..00000000 --- a/Classes/Domain/PrototypeDetails/Props/PropsCollectionBuilder.php +++ /dev/null @@ -1,56 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; - -/** - * @Flow\Proxy("false") - */ -final class PropsCollectionBuilder -{ - /** - * @var array - */ - private $props = []; - - /** - * @param PropInterface $prop - * @return self - */ - public function addProp(PropInterface $prop): self - { - if (isset($this->props[(string) $prop->getName()])) { - throw new \DomainException( - sprintf( - 'Tried to add duplicate prop "%s". Props must be unique ' . - 'within a PropCollection.', - $prop->getName() - ) - ); - } - - $this->props[(string) $prop->getName()] = $prop; - - return $this; - } - - /** - * @return PropsCollection - */ - public function build(): PropsCollection - { - return new PropsCollection(...array_values($this->props)); - } -} diff --git a/Classes/Domain/PrototypeDetails/Props/PropsCollectionFactory.php b/Classes/Domain/PrototypeDetails/Props/PropsCollectionFactory.php deleted file mode 100644 index 40a86702..00000000 --- a/Classes/Domain/PrototypeDetails/Props/PropsCollectionFactory.php +++ /dev/null @@ -1,52 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\Prototype; -use Sitegeist\Monocle\Domain\Fusion\PrototypeName; - -/** - * @Flow\Scope("singleton") - */ -final class PropsCollectionFactory implements PropsCollectionFactoryInterface -{ - /** - * @Flow\Inject - * @var EditorFactory - */ - protected $editorFactory; - - /** - * @param Prototype $fusionPrototypeAst - * @return PropsCollectionInterface - */ - public function fromPrototypeForPrototypeDetails( - Prototype $prototype - ): PropsCollectionInterface { - $propsCollectionBuilder = new PropsCollectionBuilder(); - - foreach (PropName::fromPrototype($prototype) as $propName) { - if ($propValue = PropValue::of($prototype, $propName)) { - if ($editor = $this->editorFactory->for($prototype, $propName)) { - $propsCollectionBuilder->addProp( - new Prop($propName, $propValue, $editor) - ); - } - } - } - - return $propsCollectionBuilder->build(); - } -} diff --git a/Classes/Domain/PrototypeDetails/Props/PropsCollectionFactoryInterface.php b/Classes/Domain/PrototypeDetails/Props/PropsCollectionFactoryInterface.php deleted file mode 100644 index 225d2981..00000000 --- a/Classes/Domain/PrototypeDetails/Props/PropsCollectionFactoryInterface.php +++ /dev/null @@ -1,27 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Sitegeist\Monocle\Domain\Fusion\Prototype; - -interface PropsCollectionFactoryInterface -{ - /** - * @param Prototype $prototype - * @return PropsCollectionInterface - */ - public function fromPrototypeForPrototypeDetails( - Prototype $prototype - ): PropsCollectionInterface; -} diff --git a/Classes/Domain/PrototypeDetails/PrototypeDetails.php b/Classes/Domain/PrototypeDetails/PrototypeDetails.php deleted file mode 100644 index f0d65969..00000000 --- a/Classes/Domain/PrototypeDetails/PrototypeDetails.php +++ /dev/null @@ -1,177 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\PrototypeName; -use Sitegeist\Monocle\Domain\PrototypeDetails\Props\PropsCollectionInterface; -use Sitegeist\Monocle\Domain\PrototypeDetails\PropSets\PropSetCollection; -use Sitegeist\Monocle\Domain\PrototypeDetails\UseCases\UseCaseCollection; - -/** - * @Flow\Proxy(false) - */ -final class PrototypeDetails implements PrototypeDetailsInterface -{ - /** - * @var PrototypeName - */ - private $prototypeName; - - /** - * @var RenderedCode - */ - private $renderedCode; - - /** - * @var ParsedCode - */ - private $parsedCode; - - /** - * @var FusionPrototypeAst - */ - private $fusionAst; - - /** - * @var Anatomy - */ - private $anatomy; - - /** - * @var PropsCollectionInterface - */ - private $props; - - /** - * @var PropSetCollection - */ - private $propSets; - - /** - * @var UseCaseCollection - */ - private $useCases; - - /** - * @param PrototypeName $prototypeName - * @param RenderedCode $renderedCode - * @param ParsedCode $parsedCode - * @param FusionPrototypeAst $fusionAst - * @param Anatomy $anatomy - * @param PropsCollectionInterface $props - * @param PropSetCollection $propSets - * @param UseCaseCollection $useCases - */ - public function __construct( - PrototypeName $prototypeName, - RenderedCode $renderedCode, - ParsedCode $parsedCode, - FusionPrototypeAst $fusionAst, - Anatomy $anatomy, - PropsCollectionInterface $props, - PropSetCollection $propSets, - UseCaseCollection $useCases - ) { - $this->prototypeName = $prototypeName; - $this->renderedCode = $renderedCode; - $this->parsedCode = $parsedCode; - $this->fusionAst = $fusionAst; - $this->anatomy = $anatomy; - $this->props = $props; - $this->propSets = $propSets; - $this->useCases = $useCases; - } - - /** - * @return PrototypeName - */ - public function getPrototypeName(): PrototypeName - { - return $this->prototypeName; - } - - /** - * @return RenderedCode - */ - public function getRenderedCode(): RenderedCode - { - return $this->renderedCode; - } - - /** - * @return ParsedCode - */ - public function getParsedCode(): ParsedCode - { - return $this->parsedCode; - } - - /** - * @return FusionPrototypeAst - */ - public function getFusionAst(): FusionPrototypeAst - { - return $this->fusionAst; - } - - /** - * @return Anatomy - */ - public function getAnatomy(): Anatomy - { - return $this->anatomy; - } - - /** - * @return PropsCollectionInterface - */ - public function getProps(): PropsCollectionInterface - { - return $this->props; - } - - /** - * @return PropSetCollection - */ - public function getPropSets(): PropSetCollection - { - return $this->propSets; - } - - /** - * @return UseCaseCollection - */ - public function getUseCases(): UseCaseCollection - { - return $this->useCases; - } - - /** - * @return array - */ - public function jsonSerialize() - { - return [ - 'prototypeName' => $this->prototypeName, - 'renderedCode' => $this->renderedCode, - 'parsedCode' => $this->parsedCode, - 'fusionAst' => $this->fusionAst, - 'anatomy' => $this->anatomy, - 'props' => $this->props, - 'propSets' => $this->propSets, - 'useCases' => $this->useCases - ]; - } -} diff --git a/Classes/Domain/PrototypeDetails/PrototypeDetailsFactory.php b/Classes/Domain/PrototypeDetails/PrototypeDetailsFactory.php deleted file mode 100644 index 9bbd50af..00000000 --- a/Classes/Domain/PrototypeDetails/PrototypeDetailsFactory.php +++ /dev/null @@ -1,69 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\Prototype; -use Sitegeist\Monocle\Domain\PrototypeDetails\Props\PropsCollectionFactoryInterface; -use Sitegeist\Monocle\Domain\PrototypeDetails\PropSets\PropSetCollection; -use Sitegeist\Monocle\Domain\PrototypeDetails\UseCases\UseCase; -use Sitegeist\Monocle\Domain\PrototypeDetails\UseCases\UseCaseCollection; -use Sitegeist\Monocle\Fusion\ReverseFusionParser; -use Symfony\Component\Yaml\Yaml; - -/** - * @Flow\Scope("singleton") - */ -final class PrototypeDetailsFactory implements PrototypeDetailsFactoryInterface -{ - /** - * @Flow\Inject - * @var AnatomyFactory - */ - protected $anatomyFactory; - - /** - * @Flow\Inject - * @var PropsCollectionFactoryInterface - */ - protected $propsCollectionFactory; - - /** - * @param string $prototypeNameString - * @param string $sitePackageKey - * @return PrototypeDetailsInterface - */ - public function forPrototype(Prototype $prototype): PrototypeDetailsInterface - { - return new PrototypeDetails( - $prototype->getName(), - RenderedCode::fromString( - ReverseFusionParser::restorePrototypeCode( - (string) $prototype->getName(), - $prototype->getAst() - ) - ), - ParsedCode::fromString( - Yaml::dump($prototype->getAst(), 99) - ), - FusionPrototypeAst::fromArray($prototype->getAst()), - $this->anatomyFactory - ->fromPrototypeForPrototypeDetails($prototype), - $this->propsCollectionFactory - ->fromPrototypeForPrototypeDetails($prototype), - PropSetCollection::fromPrototype($prototype), - UseCaseCollection::fromPrototype($prototype) - ); - } -} diff --git a/Classes/Domain/PrototypeDetails/PrototypeDetailsFactoryInterface.php b/Classes/Domain/PrototypeDetails/PrototypeDetailsFactoryInterface.php deleted file mode 100644 index deb377d4..00000000 --- a/Classes/Domain/PrototypeDetails/PrototypeDetailsFactoryInterface.php +++ /dev/null @@ -1,10 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\PrototypeName; -use Sitegeist\Monocle\Domain\PrototypeDetails\Props\PropsCollectionInterface; -use Sitegeist\Monocle\Domain\PrototypeDetails\PropSets\PropSetCollection; -use Sitegeist\Monocle\Domain\PrototypeDetails\UseCases\UseCaseCollection; - -/** - * @Flow\Proxy(false) - */ -interface PrototypeDetailsInterface extends \JsonSerializable -{ - /** - * @return PrototypeName - */ - public function getPrototypeName(): PrototypeName; - - /** - * @return RenderedCode - */ - public function getRenderedCode(): RenderedCode; - - /** - * @return ParsedCode - */ - public function getParsedCode(): ParsedCode; - - /** - * @return FusionPrototypeAst - */ - public function getFusionAst(): FusionPrototypeAst; - - /** - * @return Anatomy - */ - public function getAnatomy(): Anatomy; - - /** - * @return PropsCollectionInterface - */ - public function getProps(): PropsCollectionInterface; - - /** - * @return UseCaseCollection - */ - public function getUseCases(): UseCaseCollection; - - /** - * @return PropSetCollection - */ - public function getPropSets(): PropSetCollection; -} diff --git a/Classes/Domain/PrototypeDetails/UseCases/UseCase.php b/Classes/Domain/PrototypeDetails/UseCases/UseCase.php deleted file mode 100644 index c6a8f6e4..00000000 --- a/Classes/Domain/PrototypeDetails/UseCases/UseCase.php +++ /dev/null @@ -1,64 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\PrototypeDetails\Props\PropValue; - -/** - * @Flow\Proxy(false) - */ -final class UseCase implements \JsonSerializable -{ - /** - * @var UseCaseName - */ - private $name; - - /** - * @var UseCaseTitle - */ - private $title; - - /** - * @var array - */ - private $overrides; - - /** - * @param UseCaseName $name - * @param array $overrides - */ - public function __construct( - UseCaseName $name, - UseCaseTitle $title, - array $overrides - ) { - $this->name = $name; - $this->title = $title; - $this->overrides = $overrides; - } - - /** - * @return array - */ - public function jsonSerialize() - { - return [ - 'name' => $this->name, - 'title' => $this->title, - 'overrides' => $this->overrides - ]; - } -} diff --git a/Classes/Domain/PrototypeDetails/UseCases/UseCaseCollection.php b/Classes/Domain/PrototypeDetails/UseCases/UseCaseCollection.php deleted file mode 100644 index e0672c3b..00000000 --- a/Classes/Domain/PrototypeDetails/UseCases/UseCaseCollection.php +++ /dev/null @@ -1,80 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; -use Sitegeist\Monocle\Domain\Fusion\Prototype; -use Sitegeist\Monocle\Domain\PrototypeDetails\Props\PropValue; - -/** - * @Flow\Proxy(false) - */ -final class UseCaseCollection implements \JsonSerializable -{ - /** - * @var array|UseCase[] - */ - private $useCases; - - /** - * @param UseCase ...$useCases - */ - public function __construct(UseCase ...$useCases) - { - $this->useCases = $useCases; - } - - /** - * @param Prototype $prototype - * @return self - */ - public static function fromPrototype(Prototype $prototype): self - { - $ast = $prototype->getAst(); - $useCases = []; - - if (isset($ast['__meta']['styleguide']['useCases'])) { - foreach ($ast['__meta']['styleguide']['useCases'] as $useCaseNameAsString => $useCaseAst) { - $useCaseName = UseCaseName::fromString((string)$useCaseNameAsString); - $useCaseTitle = UseCaseTitle::fromString((string)($useCaseAst['title'] ?? $useCaseNameAsString)); - $values = $prototype->evaluate(sprintf( - '/__meta/styleguide/useCases/%s/props', - $useCaseName - )); - $overrides = []; - - if (is_array($values)) { - foreach ($values as $name => $value) { - if (PropValue::isValid($value)) { - $overrides[(string) $name] = - PropValue::fromAny($value); - } - } - } - - $useCases[] = new UseCase($useCaseName, $useCaseTitle, $overrides); - } - } - - return new self(...$useCases); - } - - /** - * @return array|UseCases[] - */ - public function jsonSerialize() - { - return $this->useCases; - } -} diff --git a/Classes/Domain/StyleguideAddress.php b/Classes/Domain/StyleguideAddress.php new file mode 100644 index 00000000..0f33ee79 --- /dev/null +++ b/Classes/Domain/StyleguideAddress.php @@ -0,0 +1,43 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class StyleguideAddress +{ + public function __construct( + public StyleguideProviderIdentifier $provider, + public StyleguideIdentifier $styleguide, + ) { + } + + public static function fromString(string $value): self + { + list($provider, $styleguide) = explode('::', $value, 2); + return new self( + new StyleguideProviderIdentifier($provider), + new StyleguideIdentifier($styleguide) + ); + } + + public function toString(): string + { + return $this->provider->value . '::' . $this->styleguide->value; + } +} diff --git a/Classes/Domain/StyleguideIdentifier.php b/Classes/Domain/StyleguideIdentifier.php new file mode 100644 index 00000000..491484b6 --- /dev/null +++ b/Classes/Domain/StyleguideIdentifier.php @@ -0,0 +1,38 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class StyleguideIdentifier +{ + public function __construct( + public string $value + ) { + } + + public static function fromString(string $value): self + { + return new self($value); + } + + public function equals(StyleguideIdentifier $other): bool + { + return $this->value === $other->value; + } +} diff --git a/Classes/Domain/StyleguideInterface.php b/Classes/Domain/StyleguideInterface.php new file mode 100644 index 00000000..a53a563b --- /dev/null +++ b/Classes/Domain/StyleguideInterface.php @@ -0,0 +1,37 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain; + +use Neos\Flow\I18n\LocaleCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectIdentifier; +use Sitegeist\Monocle\Domain\StyleguideObjects\PropSets\PropSetName; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectDetails; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCaseName; + +interface StyleguideInterface +{ + public function getStyleguideAddress(): StyleguideAddress; + + public function getStyleguideIdentifier(): StyleguideIdentifier; + + public function getStyleguideObjectList(): StyleguideObjectCollection; + + public function getStyleguideObjectDetails(StyleguideObjectIdentifier $identifier): StyleguideObjectDetails; + + public function renderStyleguideObject(StyleguideObjectIdentifier $identifier, array $props, ?PropSetName $propSet, ?UseCaseName $useCase, array $locales): string; +} diff --git a/Classes/Domain/StyleguideMetadata.php b/Classes/Domain/StyleguideMetadata.php new file mode 100644 index 00000000..e8a3de1b --- /dev/null +++ b/Classes/Domain/StyleguideMetadata.php @@ -0,0 +1,30 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class StyleguideMetadata +{ + public function __construct( + public StyleguideIdentifier $identifier, + public StyleguideName $name, + public StyleguideAddress $address, + ) { + } +} diff --git a/Classes/Domain/StyleguideMetadataCollection.php b/Classes/Domain/StyleguideMetadataCollection.php new file mode 100644 index 00000000..be4d3e05 --- /dev/null +++ b/Classes/Domain/StyleguideMetadataCollection.php @@ -0,0 +1,76 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain; + +use Exception; +use Neos\Flow\Annotations as Flow; +use Traversable; + +#[Flow\Proxy(false)] +final readonly class StyleguideMetadataCollection implements \JsonSerializable, \IteratorAggregate +{ + /** + * @var array + */ + public array $metadataItems; + + public function __construct( + StyleguideMetadata ...$metadata + ) { + $items = []; + foreach ($metadata as $metadataItem) { + $items[$metadataItem->address->toString()] = $metadataItem; + } + $this->metadataItems = $items; + } + + public function byAddress(StyleguideAddress $address): ?StyleguideMetadata + { + return $this->metadataItems[$address->toString()] ?? null; + } + + public function first(): ?StyleguideMetadata + { + return $this->metadataItems[array_key_first($this->metadataItems)] ?? null; + } + + public static function fromMultiple(StyleguideMetadataCollection ...$metadataCollection): self + { + $allItems = []; + foreach ($metadataCollection as $metadata) { + $allItems[] = $metadata->metadataItems; + } + return new self(...array_merge(...$allItems)); + } + + /** + * @return \Generator + */ + public function getIterator(): \Generator + { + yield from $this->metadataItems; + } + + public function jsonSerialize(): mixed + { + $result = []; + foreach ($this->metadataItems as $metadataItem) { + $result[$metadataItem->address->toString()] = $metadataItem->address->styleguide->value; + } + return $result; + } +} diff --git a/Classes/Domain/StyleguideName.php b/Classes/Domain/StyleguideName.php new file mode 100644 index 00000000..a9fc55c7 --- /dev/null +++ b/Classes/Domain/StyleguideName.php @@ -0,0 +1,33 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class StyleguideName +{ + public function __construct( + public string $value + ) { + } + + public static function fromString(string $value): self + { + return new self($value); + } +} diff --git a/Classes/Domain/StyleguideObjects/PropSets/PropSet.php b/Classes/Domain/StyleguideObjects/PropSets/PropSet.php new file mode 100644 index 00000000..769a5a8a --- /dev/null +++ b/Classes/Domain/StyleguideObjects/PropSets/PropSet.php @@ -0,0 +1,44 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects\PropSets; + +use Neos\Flow\Annotations as Flow; +use Sitegeist\Monocle\Domain\StyleguideObjects\Props\PropValue; + +#[Flow\Proxy(false)] +final readonly class PropSet implements \JsonSerializable +{ + /** + * @param array $overrides + */ + public function __construct( + public PropSetName $name, + public array $overrides + ) { + } + + /** + * @return array + */ + public function jsonSerialize() + { + return [ + 'name' => $this->name, + 'overrides' => $this->overrides + ]; + } +} diff --git a/Classes/Domain/StyleguideObjects/PropSets/PropSetCollection.php b/Classes/Domain/StyleguideObjects/PropSets/PropSetCollection.php new file mode 100644 index 00000000..e0fac5fc --- /dev/null +++ b/Classes/Domain/StyleguideObjects/PropSets/PropSetCollection.php @@ -0,0 +1,44 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects\PropSets; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class PropSetCollection implements \JsonSerializable +{ + /** + * @var PropSet[] + */ + private array $propSets; + + /** + * @param PropSet ...$propSets + */ + public function __construct(PropSet ...$propSets) + { + $this->propSets = $propSets; + } + + /** + * @return array|PropSet[] + */ + public function jsonSerialize() + { + return $this->propSets; + } +} diff --git a/Classes/Domain/Fusion/PrototypeName.php b/Classes/Domain/StyleguideObjects/PropSets/PropSetName.php similarity index 70% rename from Classes/Domain/Fusion/PrototypeName.php rename to Classes/Domain/StyleguideObjects/PropSets/PropSetName.php index c207bb29..53ed0a2a 100644 --- a/Classes/Domain/Fusion/PrototypeName.php +++ b/Classes/Domain/StyleguideObjects/PropSets/PropSetName.php @@ -1,5 +1,4 @@ -value = $value; + private function __construct( + public string $value + ) { } /** @@ -45,7 +41,7 @@ public static function fromString(string $string): self /** * @return string */ - public function jsonSerialize() + public function jsonSerialize(): mixed { return $this->value; } diff --git a/Classes/Domain/StyleguideObjects/Props/Editor.php b/Classes/Domain/StyleguideObjects/Props/Editor.php new file mode 100644 index 00000000..13b4ef49 --- /dev/null +++ b/Classes/Domain/StyleguideObjects/Props/Editor.php @@ -0,0 +1,40 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects\Props; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class Editor implements \JsonSerializable +{ + public function __construct( + public EditorIdentifier $identifier, + public EditorOptions $options + ) { + } + + /** + * @return array + */ + public function jsonSerialize() + { + return [ + 'identifier' => $this->identifier, + 'options' => $this->options + ]; + } +} diff --git a/Classes/Domain/PrototypeDetails/RenderedCode.php b/Classes/Domain/StyleguideObjects/Props/EditorIdentifier.php similarity index 51% rename from Classes/Domain/PrototypeDetails/RenderedCode.php rename to Classes/Domain/StyleguideObjects/Props/EditorIdentifier.php index 47f3ca4a..04f57e12 100644 --- a/Classes/Domain/PrototypeDetails/RenderedCode.php +++ b/Classes/Domain/StyleguideObjects/Props/EditorIdentifier.php @@ -1,5 +1,4 @@ -value = $value; + private function __construct( + public string $value + ) { } - /** - * @param string $string - * @return self - */ public static function fromString(string $string): self { return new self($string); } - /** - * @return string - */ - public function jsonSerialize() + public function jsonSerialize(): string { return $this->value; } diff --git a/Classes/Domain/StyleguideObjects/Props/EditorOptions.php b/Classes/Domain/StyleguideObjects/Props/EditorOptions.php new file mode 100644 index 00000000..d762578e --- /dev/null +++ b/Classes/Domain/StyleguideObjects/Props/EditorOptions.php @@ -0,0 +1,49 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects\Props; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class EditorOptions implements \JsonSerializable +{ + /** + * @param array $value + */ + private function __construct( + public array $value + ) { + } + + public static function empty(): self + { + return new self([]); + } + + public static function fromArray(array $array): self + { + return new self($array); + } + + /** + * @return array + */ + public function jsonSerialize() + { + return $this->value ?: new \stdClass(); + } +} diff --git a/Classes/Domain/StyleguideObjects/Props/Prop.php b/Classes/Domain/StyleguideObjects/Props/Prop.php new file mode 100644 index 00000000..c9b8780d --- /dev/null +++ b/Classes/Domain/StyleguideObjects/Props/Prop.php @@ -0,0 +1,39 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects\Props; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class Prop implements \JsonSerializable +{ + public function __construct( + public PropName $name, + public PropValue $value, + public Editor $editor + ) { + } + + public function jsonSerialize() + { + return [ + 'name' => $this->name, + 'value' => $this->value, + 'editor' => $this->editor + ]; + } +} diff --git a/Classes/Domain/PrototypeDetails/PropSets/PropSetName.php b/Classes/Domain/StyleguideObjects/Props/PropName.php similarity index 68% rename from Classes/Domain/PrototypeDetails/PropSets/PropSetName.php rename to Classes/Domain/StyleguideObjects/Props/PropName.php index 1433ed44..265f4b9d 100644 --- a/Classes/Domain/PrototypeDetails/PropSets/PropSetName.php +++ b/Classes/Domain/StyleguideObjects/Props/PropName.php @@ -1,5 +1,4 @@ -value = $value; + private function __construct( + public string $value + ) { } /** diff --git a/Classes/Domain/PrototypeDetails/Props/PropValue.php b/Classes/Domain/StyleguideObjects/Props/PropValue.php similarity index 76% rename from Classes/Domain/PrototypeDetails/Props/PropValue.php rename to Classes/Domain/StyleguideObjects/Props/PropValue.php index 4f445b71..01441ac9 100644 --- a/Classes/Domain/PrototypeDetails/Props/PropValue.php +++ b/Classes/Domain/StyleguideObjects/Props/PropValue.php @@ -1,5 +1,4 @@ -value = $value; - } - - /** - * @param Prototype $prototype - * @param PropName $propName - * @return null|self - */ - public static function of(Prototype $prototype, PropName $propName): ?self - { - $value = $prototype - ->evaluate('/__meta/styleguide/props/' . $propName); - - if ($prototype->extends(PrototypeName::fromString('Neos.Fusion:Component'))) { - $value = $value ?? $prototype->evaluate('/' . $propName); - } - - if (PropValue::isValid($value)) { - return PropValue::fromAny($value); - } - - return null; } /** diff --git a/Classes/Domain/PrototypeDetails/Props/PropsCollection.php b/Classes/Domain/StyleguideObjects/Props/PropsCollection.php similarity index 51% rename from Classes/Domain/PrototypeDetails/Props/PropsCollection.php rename to Classes/Domain/StyleguideObjects/Props/PropsCollection.php index 9f3dee49..d62135df 100644 --- a/Classes/Domain/PrototypeDetails/Props/PropsCollection.php +++ b/Classes/Domain/StyleguideObjects/Props/PropsCollection.php @@ -1,5 +1,4 @@ -props = $props; } /** - * @return iterable - */ - public function getProps(): iterable - { - return $this->props; - } - - /** - * @return array|PropInterface[] + * @return array|Prop[] */ public function jsonSerialize() { diff --git a/Classes/Domain/StyleguideObjects/StyleguideObject.php b/Classes/Domain/StyleguideObjects/StyleguideObject.php new file mode 100644 index 00000000..ace74d4d --- /dev/null +++ b/Classes/Domain/StyleguideObjects/StyleguideObject.php @@ -0,0 +1,48 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class StyleguideObject implements \JsonSerializable +{ + public function __construct( + public StyleguideObjectIdentifier $identifier, + public StyleguideObjectName $name, + public StyleguideObjectPath $path, + public StyleguideStructure $structure, + public string $description, + ) { + } + + public function jsonSerialize(): mixed + { + // this reflects the old format that the monocle ui expects + // @todo it should be refactored to math the new names later + return [ + 'identifier' => $this->identifier, + 'path' => $this->path, + 'structure' => $this->structure, + 'title' => $this->name, + 'description' => $this->description, + 'options' => null, + 'propSets' => null, + 'useCases' => null, + ]; + } +} diff --git a/Classes/Domain/StyleguideObjects/StyleguideObjectCollection.php b/Classes/Domain/StyleguideObjects/StyleguideObjectCollection.php new file mode 100644 index 00000000..822903c5 --- /dev/null +++ b/Classes/Domain/StyleguideObjects/StyleguideObjectCollection.php @@ -0,0 +1,43 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class StyleguideObjectCollection implements \JsonSerializable +{ + /** + * @var StyleguideObject[] + */ + private array $styleguideObjects; + + public function __construct( + StyleguideObject ...$styleguideObject + ) { + $this->styleguideObjects = $styleguideObject; + } + + public function jsonSerialize(): array + { + $result = []; + foreach ($this->styleguideObjects as $styleguideObject) { + $result[$styleguideObject->identifier->value] = $styleguideObject; + } + return $result; + } +} diff --git a/Classes/Domain/StyleguideObjects/StyleguideObjectDetails.php b/Classes/Domain/StyleguideObjects/StyleguideObjectDetails.php new file mode 100644 index 00000000..a87c4657 --- /dev/null +++ b/Classes/Domain/StyleguideObjects/StyleguideObjectDetails.php @@ -0,0 +1,50 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects; + +use Neos\Flow\Annotations as Flow; +use Sitegeist\Monocle\Domain\StyleguideObjects\Props\PropsCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\PropSets\PropSetCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCaseCollection; + +#[Flow\Proxy(false)] +final readonly class StyleguideObjectDetails implements \JsonSerializable +{ + public function __construct( + public StyleguideObjectIdentifier $identifier, + public StyleguideObjectName $name, + public PropsCollection $props, + public PropSetCollection $propSets, + public UseCaseCollection $useCases + ) { + } + + /** + * @return array + */ + public function jsonSerialize() + { + return [ + 'identifier' => $this->identifier, + 'prototypeName' => $this->identifier, + 'name' => $this->name, + 'props' => $this->props, + 'propSets' => $this->propSets, + 'useCases' => $this->useCases + ]; + } +} diff --git a/Classes/Domain/StyleguideObjects/StyleguideObjectIdentifier.php b/Classes/Domain/StyleguideObjects/StyleguideObjectIdentifier.php new file mode 100644 index 00000000..63e46c14 --- /dev/null +++ b/Classes/Domain/StyleguideObjects/StyleguideObjectIdentifier.php @@ -0,0 +1,57 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class StyleguideObjectIdentifier implements \JsonSerializable +{ + private function __construct(public string $value) + { + } + + /** + * @param string $string + * @return self + */ + public static function fromString(string $string): self + { + return new self($string); + } + + public function equals(StyleguideObjectIdentifier $other): bool + { + return $this->value === $other->value; + } + + /** + * @return string + */ + public function jsonSerialize(): mixed + { + return $this->value; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->value; + } +} diff --git a/Classes/Domain/PrototypeDetails/ParsedCode.php b/Classes/Domain/StyleguideObjects/StyleguideObjectName.php similarity index 63% rename from Classes/Domain/PrototypeDetails/ParsedCode.php rename to Classes/Domain/StyleguideObjects/StyleguideObjectName.php index 7ab6afc9..0df83644 100644 --- a/Classes/Domain/PrototypeDetails/ParsedCode.php +++ b/Classes/Domain/StyleguideObjects/StyleguideObjectName.php @@ -1,5 +1,4 @@ -value = $value; } /** @@ -45,7 +37,15 @@ public static function fromString(string $string): self /** * @return string */ - public function jsonSerialize() + public function jsonSerialize(): mixed + { + return $this->value; + } + + /** + * @return string + */ + public function __toString(): string { return $this->value; } diff --git a/Classes/Domain/StyleguideObjects/StyleguideObjectPath.php b/Classes/Domain/StyleguideObjects/StyleguideObjectPath.php new file mode 100644 index 00000000..3399f539 --- /dev/null +++ b/Classes/Domain/StyleguideObjects/StyleguideObjectPath.php @@ -0,0 +1,52 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class StyleguideObjectPath implements \JsonSerializable +{ + private function __construct(public string $value) + { + } + + /** + * @param string $string + * @return self + */ + public static function fromString(string $string): self + { + return new self($string); + } + + /** + * @return string + */ + public function jsonSerialize(): mixed + { + return $this->value; + } + + /** + * @return string + */ + public function __toString(): string + { + return $this->value; + } +} diff --git a/Classes/Domain/PrototypeDetails/Props/PropsCollectionInterface.php b/Classes/Domain/StyleguideObjects/StyleguideStructure.php similarity index 53% rename from Classes/Domain/PrototypeDetails/Props/PropsCollectionInterface.php rename to Classes/Domain/StyleguideObjects/StyleguideStructure.php index 19405b8f..1d7ec7ca 100644 --- a/Classes/Domain/PrototypeDetails/Props/PropsCollectionInterface.php +++ b/Classes/Domain/StyleguideObjects/StyleguideStructure.php @@ -1,5 +1,4 @@ - - */ - public function getProps(): iterable; + public function __construct( + public string $label, + public string $icon, + public string $color, + ) { + } } diff --git a/Classes/Domain/PrototypeDetails/PropSets/PropSet.php b/Classes/Domain/StyleguideObjects/UseCases/UseCase.php similarity index 59% rename from Classes/Domain/PrototypeDetails/PropSets/PropSet.php rename to Classes/Domain/StyleguideObjects/UseCases/UseCase.php index 9565c83b..a370d4d3 100644 --- a/Classes/Domain/PrototypeDetails/PropSets/PropSet.php +++ b/Classes/Domain/StyleguideObjects/UseCases/UseCase.php @@ -1,5 +1,4 @@ - - */ - private $overrides; +use Neos\Flow\Annotations as Flow; +#[Flow\Proxy(false)] +final readonly class UseCase implements \JsonSerializable +{ /** - * @param PropSetName $name + * @param UseCaseName $name * @param array $overrides */ public function __construct( - PropSetName $name, - array $overrides + public UseCaseName $name, + public UseCaseTitle $title, + public array $overrides ) { - $this->name = $name; - $this->overrides = $overrides; } /** @@ -49,6 +39,7 @@ public function jsonSerialize() { return [ 'name' => $this->name, + 'title' => $this->title, 'overrides' => $this->overrides ]; } diff --git a/Classes/Domain/StyleguideObjects/UseCases/UseCaseCollection.php b/Classes/Domain/StyleguideObjects/UseCases/UseCaseCollection.php new file mode 100644 index 00000000..3949e9b3 --- /dev/null +++ b/Classes/Domain/StyleguideObjects/UseCases/UseCaseCollection.php @@ -0,0 +1,45 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain\StyleguideObjects\UseCases; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class UseCaseCollection implements \JsonSerializable +{ + /** + * @var UseCase[] + */ + private array $useCases; + + /** + * @param UseCase ...$useCases + */ + public function __construct( + UseCase ...$useCases + ) { + $this->useCases = $useCases; + } + + /** + * @return UseCases[] + */ + public function jsonSerialize() + { + return $this->useCases; + } +} diff --git a/Classes/Domain/PrototypeDetails/UseCases/UseCaseName.php b/Classes/Domain/StyleguideObjects/UseCases/UseCaseName.php similarity index 69% rename from Classes/Domain/PrototypeDetails/UseCases/UseCaseName.php rename to Classes/Domain/StyleguideObjects/UseCases/UseCaseName.php index f5f20ace..91eb975b 100644 --- a/Classes/Domain/PrototypeDetails/UseCases/UseCaseName.php +++ b/Classes/Domain/StyleguideObjects/UseCases/UseCaseName.php @@ -1,5 +1,4 @@ -value = $value; + private function __construct( + public string $value + ) { } /** @@ -45,7 +41,7 @@ public static function fromString(string $string): self /** * @return string */ - public function jsonSerialize() + public function jsonSerialize(): mixed { return $this->value; } diff --git a/Classes/Domain/PrototypeDetails/UseCases/UseCaseTitle.php b/Classes/Domain/StyleguideObjects/UseCases/UseCaseTitle.php similarity index 77% rename from Classes/Domain/PrototypeDetails/UseCases/UseCaseTitle.php rename to Classes/Domain/StyleguideObjects/UseCases/UseCaseTitle.php index 4c9a933f..aee0e22d 100644 --- a/Classes/Domain/PrototypeDetails/UseCases/UseCaseTitle.php +++ b/Classes/Domain/StyleguideObjects/UseCases/UseCaseTitle.php @@ -1,5 +1,4 @@ -value = $value; + private function __construct( + public string $value + ) { } /** diff --git a/Classes/Domain/StyleguideProviderIdentifier.php b/Classes/Domain/StyleguideProviderIdentifier.php new file mode 100644 index 00000000..8656eb8b --- /dev/null +++ b/Classes/Domain/StyleguideProviderIdentifier.php @@ -0,0 +1,41 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain; + +use Neos\Flow\Annotations as Flow; + +#[Flow\Proxy(false)] +final readonly class StyleguideProviderIdentifier +{ + public function __construct( + public string $value + ) { + if (str_contains(':', $value)) { + throw new \InvalidArgumentException('StyleguideProviderIdentifier must not contain ":".'); + } + } + + public function equals(StyleguideProviderIdentifier $other): bool + { + return $this->value === $other->value; + } + + public static function fromString(string $string): self + { + return new self($string); + } +} diff --git a/Classes/Domain/StyleguideProviderInterface.php b/Classes/Domain/StyleguideProviderInterface.php new file mode 100644 index 00000000..06df1686 --- /dev/null +++ b/Classes/Domain/StyleguideProviderInterface.php @@ -0,0 +1,24 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain; + +interface StyleguideProviderInterface +{ + public function getStyleguideMetadataCollection(StyleguideProviderIdentifier $providerIdentifier): StyleguideMetadataCollection; + + public function getStyleguide(StyleguideAddress $address): StyleguideInterface; +} diff --git a/Classes/Domain/StyleguideRepository.php b/Classes/Domain/StyleguideRepository.php new file mode 100644 index 00000000..b8689669 --- /dev/null +++ b/Classes/Domain/StyleguideRepository.php @@ -0,0 +1,82 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\Domain; + +use Neos\Flow\Annotations as Flow; +use Neos\Flow\ObjectManagement\ObjectManagerInterface; + +#[Flow\Scope("singleton")] +class StyleguideRepository +{ + #[Flow\Inject] + protected ObjectManagerInterface $objectManager; + + #[Flow\InjectConfiguration(package: 'Sitegeist.Monocle', path: 'styleguideProviders')] + protected array $styleguideProviderConfiguration; + + #[Flow\InjectConfiguration(package: 'Sitegeist.Monocle', path: 'defaultStyleguide')] + protected ?string $defaultStyleguide; + + /** + * @var array + */ + protected ?array $styleguideProviders = null; + + public function getDefault(): StyleguideMetadata + { + $all = $this->getAllStyleGuides(); + if ($this->defaultStyleguide) { + return $all->byAddress(StyleguideAddress::fromString($this->defaultStyleguide)); + } + return $all->first(); + } + + public function getAllStyleGuides(): StyleguideMetadataCollection + { + $providers = $this->getProviders(); + $styleguides = []; + foreach ($providers as $identifier => $provider) { + $styleguides[] = $provider->getStyleguideMetadataCollection(StyleguideProviderIdentifier::fromString($identifier)); + } + return StyleguideMetadataCollection::fromMultiple(...$styleguides); + } + + public function getStyleGuide(StyleguideAddress $address): StyleguideInterface + { + $providers = $this->getProviders(); + $provider = $providers[$address->provider->value] ?? null; + if ($provider instanceof StyleguideProviderInterface) { + return $provider->getStyleguide($address); + } + + throw new \Exception(sprintf('Styleguide %s in Provider %s was not found', $address->styleguide->value, $address->provider->value)); + } + + /** + * @return StyleguideProviderInterface[] + */ + protected function getProviders(): array + { + if (is_null($this->styleguideProviders)) { + $this->styleguideProviders = []; + foreach ($this->styleguideProviderConfiguration as $identifier => $providerClassName) { + $this->styleguideProviders[$identifier] = $this->objectManager->get($providerClassName); + } + } + return $this->styleguideProviders; + } +} diff --git a/Classes/Fusion/FusionService.php b/Classes/Fusion/FusionService.php index bfebfa42..7f3923da 100644 --- a/Classes/Fusion/FusionService.php +++ b/Classes/Fusion/FusionService.php @@ -1,5 +1,16 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ /** * This file is part of the Sitegeist.Monocle package @@ -13,6 +24,8 @@ * source code. */ +namespace Sitegeist\Monocle\Fusion; + use Neos\Flow\Annotations as Flow; use Neos\Flow\Package\PackageManager; use Neos\Fusion\Core\FusionConfiguration; @@ -89,6 +102,7 @@ public function getFusionConfigurationForPackageKey(string $packageKey): FusionC /** * Get all styleguide objects for the given fusion-ast + * @return array}> */ public function getStyleguideObjectsFromFusionAst(array|FusionConfiguration $fusionAst): array { diff --git a/Classes/Fusion/FusionView.php b/Classes/Fusion/FusionView.php index ff1712f5..34167005 100644 --- a/Classes/Fusion/FusionView.php +++ b/Classes/Fusion/FusionView.php @@ -1,5 +1,16 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ /** * This file is part of the Sitegeist.Monocle package @@ -13,6 +24,8 @@ * source code. */ +namespace Sitegeist\Monocle\Fusion; + use Neos\Flow\Annotations as Flow; use Neos\Fusion\Core\FusionConfiguration; use Neos\Fusion\View\FusionView as BaseFusionView; diff --git a/Classes/Fusion/ReverseFusionParser.php b/Classes/Fusion/ReverseFusionParser.php deleted file mode 100644 index f9ac6c64..00000000 --- a/Classes/Fusion/ReverseFusionParser.php +++ /dev/null @@ -1,173 +0,0 @@ - - * Wilhelm Behncke - * - * This package is Open Source Software. For the full copyright and license - * information, please view the LICENSE file which was distributed with this - * source code. - */ - -use Neos\Flow\Annotations as Flow; - -/** - * Class ReverseFusionParser - * @package Sitegeist\Monocle\Fusion - */ -class ReverseFusionParser -{ - protected static $RESERVED_KEYS = [ - '__prototypeObjectName', - '__meta', - '__prototypeChain', - '__prototypes', - '__objectType', - '__eelExpression', - '__value' - ]; - - protected static $TOP_META_KEYS = ['context','styleguide']; - - const INDENTATION = ' '; - - /** - * Get PrototypeName and AST and restore the original Code representation - * - * @param string $prototypeName - * @param array $aspectSyntaxTree - * @return string - */ - public static function restorePrototypeCode($prototypeName, $abstractSyntaxTree, $indentation = '') - { - $result = ''; - - if (array_key_exists('__prototypeObjectName', $abstractSyntaxTree)) { - $result .= sprintf( - 'prototype(%s) < prototype(%s) {', - $prototypeName, - $abstractSyntaxTree['__prototypeObjectName'] - ) . chr(10); - } else { - $result .= sprintf('prototype(%s) {', $prototypeName) . chr(10); - } - - // handle __meta context - if (array_key_exists('__meta', $abstractSyntaxTree)) { - foreach (array_keys($abstractSyntaxTree['__meta']) as $key) { - if (in_array($key, self::$TOP_META_KEYS, true) === false) { - continue; - } - $result .= self::restoreValueCode('@' . $key, $abstractSyntaxTree['__meta'][$key], $indentation . self::INDENTATION); - } - } - - // handle __prototypes - if (array_key_exists('__prototypes', $abstractSyntaxTree)) { - foreach (array_keys($abstractSyntaxTree['__prototypes']) as $key) { - $result .= self::restorePrototypeCode($key, $abstractSyntaxTree['__prototypes'][$key], $indentation . self::INDENTATION); - } - } - - // add properties - foreach (array_keys($abstractSyntaxTree) as $key) { - if (in_array($key, self::$RESERVED_KEYS, true)) { - continue; - } - $result .= self::restoreValueCode($key, $abstractSyntaxTree[$key], $indentation . self::INDENTATION); - } - - - // handle remaining __meta keys - if (array_key_exists('__meta', $abstractSyntaxTree)) { - foreach (array_keys($abstractSyntaxTree['__meta']) as $key) { - if (in_array($key, self::$TOP_META_KEYS, true) === true) { - continue; - } - $result .= self::restoreValueCode('@' . $key, $abstractSyntaxTree['__meta'][$key], $indentation . self::INDENTATION); - } - } - - $result .= $indentation . '}' . chr(10); - return $result; - } - - /** - * Get ValueName and AST and restore the original Code representation - * - * @param string $valueName - * @param array $aspectSyntaxTree - * @return string - */ - public static function restoreValueCode($valueName, $abstractSyntaxTree, $indentation) - { - $result = ''; - if (is_array($abstractSyntaxTree)) { - $multiline = false; - if (array_key_exists('__objectType', $abstractSyntaxTree) && $abstractSyntaxTree['__objectType'] !== null) { - $multiline = true; - $result .= $indentation . $valueName . ' = ' . $abstractSyntaxTree['__objectType'] . ' {' . chr(10); - foreach (array_keys($abstractSyntaxTree) as $key) { - if (in_array($key, self::$RESERVED_KEYS, true)) { - continue; - } - $result .= self::restoreValueCode($key, $abstractSyntaxTree[$key], $indentation . self::INDENTATION); - } - } elseif (array_key_exists('__eelExpression', $abstractSyntaxTree) && $abstractSyntaxTree['__eelExpression'] !== null) { - $result .= $indentation . $valueName . ' = ${' . $abstractSyntaxTree['__eelExpression'] . '}' . chr(10); - } elseif (array_key_exists('__value', $abstractSyntaxTree)) { - if (is_string($abstractSyntaxTree['__value'])) { - $result .= $indentation . $valueName . ' = "' . $abstractSyntaxTree['__value'] . '"' . chr(10); - } else { - $result .= $indentation . $valueName . ' = ' . (string)$abstractSyntaxTree['__value'] . chr(10); - } - } else { - $multiline = true; - $result .= $indentation . $valueName . ' {' . chr(10); - foreach (array_keys($abstractSyntaxTree) as $key) { - $result .= self::restoreValueCode($key, $abstractSyntaxTree[$key], $indentation . self::INDENTATION); - } - } - - if ($multiline) { - if (array_key_exists('__meta', $abstractSyntaxTree)) { - foreach (array_keys($abstractSyntaxTree['__meta']) as $key) { - $result .= self::restoreValueCode('@' . $key, $abstractSyntaxTree['__meta'][$key], $indentation . self::INDENTATION); - } - } - - // handle __prototypes - if (array_key_exists('__prototypes', $abstractSyntaxTree)) { - foreach (array_keys($abstractSyntaxTree['__prototypes']) as $key) { - $result .= self::restorePrototypeCode($key, $abstractSyntaxTree['__prototypes'][$key], $indentation . self::INDENTATION); - } - } - $result .= $indentation . '}' . chr(10); - } else { - // handle __meta - if (array_key_exists('__meta', $abstractSyntaxTree)) { - foreach (array_keys($abstractSyntaxTree['__meta']) as $key) { - $result .= self::restoreValueCode($valueName . '.@' . $key, $abstractSyntaxTree['__meta'][$key], $indentation); - } - } - } - } else { - if (is_string($abstractSyntaxTree)) { - $result .= $indentation . $valueName . ' = "' . $abstractSyntaxTree . '"' . chr(10); - } elseif (is_bool($abstractSyntaxTree)) { - $result .= $indentation . $valueName . ' = ' . ($abstractSyntaxTree ? 'true' : 'false') . chr(10); - } elseif (is_null($abstractSyntaxTree)) { - $result .= $indentation . $valueName . ' = null' . chr(10); - } elseif (is_scalar($abstractSyntaxTree)) { - $result .= $indentation . $valueName . ' = ' . (string)$abstractSyntaxTree . chr(10); - } else { - $result .= $indentation . $valueName . ' = ' . json_encode($abstractSyntaxTree) . chr(10); - } - } - return $result; - } -} diff --git a/Classes/FusionObjects/CanRenderImplementation.php b/Classes/FusionObjects/CanRenderImplementation.php deleted file mode 100644 index 2b071354..00000000 --- a/Classes/FusionObjects/CanRenderImplementation.php +++ /dev/null @@ -1,46 +0,0 @@ -fusionValue('renderPath'); - } - - /** - * Fusion type which shall be checked - * - * @return string - */ - public function getType() - { - return $this->fusionValue('type'); - } - - /** - * @return boolean - */ - public function evaluate() - { - if ($this->getRenderPath()) { - return $this->runtime->canRender($this->getRenderPath()); - } elseif ($this->getType()) { - return $this->runtime->canRender('/type<' . $this->getType() . '>'); - } else { - return false; - } - } -} diff --git a/Classes/FusionObjects/DataUriImplementation.php b/Classes/FusionObjects/DataUriImplementation.php deleted file mode 100644 index 06f0eae6..00000000 --- a/Classes/FusionObjects/DataUriImplementation.php +++ /dev/null @@ -1,41 +0,0 @@ -fusionValue('type'); - } - - /** - * @return string - */ - public function getContent() - { - return $this->fusionValue('content'); - } - - /** - * Render a prototype - * - * @return mixed - */ - public function evaluate() - { - $type = $this->getType(); - $content = $this->getContent(); - if ($type && $content) { - return 'data:' . $type . ';base64,' . base64_encode($content); - } - } -} diff --git a/Classes/Package.php b/Classes/Package.php index c7d414b5..a1f9d501 100644 --- a/Classes/Package.php +++ b/Classes/Package.php @@ -1,5 +1,16 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ /** * This file is part of the Sitegeist.Monocle package @@ -13,6 +24,8 @@ * source code. */ +namespace Sitegeist\Monocle; + use Neos\Flow\Cache\CacheManager; use Neos\Flow\Core\Booting\Sequence; use Neos\Flow\Core\Bootstrap; diff --git a/Classes/Routing/IgnoreRoutePartHandler.php b/Classes/Routing/IgnoreRoutePartHandler.php index ab0f53ee..8483325c 100644 --- a/Classes/Routing/IgnoreRoutePartHandler.php +++ b/Classes/Routing/IgnoreRoutePartHandler.php @@ -1,4 +1,17 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + namespace Sitegeist\Monocle\Routing; use Neos\Flow\Annotations as Flow; diff --git a/Classes/Service/ConfigurationService.php b/Classes/Service/ConfigurationService.php index 9f460a5a..0b26b1e3 100644 --- a/Classes/Service/ConfigurationService.php +++ b/Classes/Service/ConfigurationService.php @@ -1,8 +1,22 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + namespace Sitegeist\Monocle\Service; use Neos\Flow\Annotations as Flow; use Neos\Utility\Arrays; +use Sitegeist\Monocle\Domain\StyleguideAddress; /** * @Flow\Scope("singleton") @@ -20,9 +34,42 @@ class ConfigurationService */ protected $mergedConfigurationCache = []; + public function getStyleguideConfiguration(StyleguideAddress $address, ?string $path = null): mixed + { + $configuration = $this->getMergedConfigurationForStyleguide($address); + if ($path === null) { + return $configuration; + } else { + return Arrays::getValueByPath($configuration, $path); + } + } + + /** + * @return mixed[] + */ + protected function getMergedConfigurationForStyleguide(StyleguideAddress $address): array + { + $addressAsString = $address->toString(); + if (array_key_exists($addressAsString, $this->mergedConfigurationCache)) { + return $this->mergedConfigurationCache[$addressAsString]; + } + + $configuration = $this->configuration; + $styleguideConfiguration = Arrays::getValueByPath($configuration, ['styleguides' , $addressAsString]); + if ($styleguideConfiguration) { + $result = Arrays::arrayMergeRecursiveOverrule($configuration, $styleguideConfiguration); + } else { + $result = $configuration; + } + + $this->mergedConfigurationCache[$addressAsString] = $result; + return $result; + } + /** * @param $sitePackageKey * @param $path + * @deprecated */ public function getSiteConfiguration($sitePackageKey, $path = null) { @@ -39,6 +86,7 @@ public function getSiteConfiguration($sitePackageKey, $path = null) * * @param $sitePackageKey * @return array + * @deprecated */ protected function getMergedConfigurationForSitePackage($sitePackageKey) { diff --git a/Classes/Service/DummyControllerContextTrait.php b/Classes/Service/DummyControllerContextTrait.php index c8a797e8..67e0471c 100644 --- a/Classes/Service/DummyControllerContextTrait.php +++ b/Classes/Service/DummyControllerContextTrait.php @@ -1,4 +1,17 @@ + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + namespace Sitegeist\Monocle\Service; use Neos\Flow\Mvc\ActionRequest; diff --git a/Classes/Service/PackageKeyTrait.php b/Classes/Service/PackageKeyTrait.php deleted file mode 100644 index 550743e1..00000000 --- a/Classes/Service/PackageKeyTrait.php +++ /dev/null @@ -1,85 +0,0 @@ -domainRepository->findOneByActiveRequest(); - if ($domain && $domain->getSite()) { - return $domain->getSite()->getSiteResourcesPackageKey(); - } - } catch (\Exception $e) { - // ignore errors that may occur if no database is present - } - - if ($this->defaultPackageKey) { - return $this->defaultPackageKey; - } - - $sitePackageKeys = $this->getActiveSitePackageKeys(); - return reset($sitePackageKeys); - } - - /** - * Get a list of all active site package keys - * @return string[] - */ - protected function getActiveSitePackageKeys(): array - { - /** - * Adjust to a breaking api change in neos 8 - * @todo remove once Neos 7 Support is dropped - */ - if (version_compare(FLOW_VERSION_BRANCH, '8.0') >= 0) { - $sitePackages = $this->packageManager->getFilteredPackages('available', 'neos-site'); - } else { - $sitePackages = $this->packageManager->getFilteredPackages('available', null, 'neos-site'); - } - $sitePackageKeys = []; - foreach ($sitePackages as $sitePackage) { - $packageKey = $sitePackage->getPackageKey(); - $sitePackageKeys[] = $packageKey; - } - $configuredPackageKeys = $this->packageConfigurations ? array_keys($this->packageConfigurations) : []; - return array_unique(array_merge($sitePackageKeys, $configuredPackageKeys)); - } -} diff --git a/Classes/StyleguideProvider/CpxPackage/CpxComponentFactory.php b/Classes/StyleguideProvider/CpxPackage/CpxComponentFactory.php new file mode 100644 index 00000000..cf39e8f3 --- /dev/null +++ b/Classes/StyleguideProvider/CpxPackage/CpxComponentFactory.php @@ -0,0 +1,358 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\StyleguideProvider\CpxPackage; + +use PackageFactory\ComponentEngine\ComponentCollection; +use PackageFactory\ComponentEngine\ComponentCollectionInterface; +use PackageFactory\ComponentEngine\ComponentInterface; +use PackageFactory\ComponentEngine\StringComponent; +use Sitegeist\Monocle\Domain\StyleguideObjects\PropSets\PropSetName; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectIdentifier; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCaseName; +use Symfony\Component\Yaml\Yaml; + +final class CpxComponentFactory +{ + /** + * @var array> + */ + private static array $styleguideConfigurationCache = []; + + public static function create( + CpxComponentMetadata $metadata, + array $props = [], + ?PropSetName $propSetName = null, + ?UseCaseName $useCaseName = null, + bool $withContainer = false + ): ComponentInterface { + $componentClass = $metadata->componentPhpClassName; + $styleguideConfiguration = self::readStyleguideConfiguration($metadata->componentStyleguideConfigFile); + $styleguideProps = self::readStyleguidePropsFromConfiguration( + $styleguideConfiguration, + $propSetName, + $useCaseName + ); + $propsConfiguration = array_replace($styleguideProps, $props); + $arguments = self::mapPropsToArguments($componentClass, $propsConfiguration); + + $component = $componentClass::create(...$arguments); + if ($withContainer && array_key_exists('container', $styleguideConfiguration)) { + $containerConfiguration = $styleguideConfiguration['container']; + if (self::isComponentConfiguration($containerConfiguration)) { + $containerConfiguration['content'] = $component; + return self::createComponentFromConfiguration($containerConfiguration); + } + } + return $component; + } + + /** + * @return array + */ + private static function readStyleguidePropsFromConfiguration( + array $configuration, + ?PropSetName $propSetName = null, + ?UseCaseName $useCaseName = null + ): array { + $props = $configuration['props'] ?? []; + + $props = is_array($props) ? $props : []; + if ($useCaseName && isset($configuration['useCases'][$useCaseName->value]) && is_array($configuration['useCases'][$useCaseName->value])) { + $useCaseConfig = $configuration['useCases'][$useCaseName->value]; + $useCaseProps = $useCaseConfig['props'] ?? []; + $props = is_array($useCaseProps) ? $useCaseProps : []; + } + if ($propSetName && isset($configuration['propSets'][$propSetName->value]) && is_array($configuration['propSets'][$propSetName->value])) { + $propSetConfig = $configuration['propSets'][$propSetName->value]; + $propSetProps = is_array($propSetConfig['props'] ?? null) ? $propSetConfig['props'] : $propSetConfig; + $props = array_replace($props, $propSetProps); + } + + return $props; + } + + /** + * @return array + */ + private static function readStyleguideConfiguration(string $styleguideFile): array + { + if (!is_file($styleguideFile)) { + throw new \InvalidArgumentException(sprintf('Missing styleguide file "%s"', $styleguideFile)); + } + + if (!array_key_exists($styleguideFile, self::$styleguideConfigurationCache)) { + $parsedConfiguration = Yaml::parseFile($styleguideFile); + self::$styleguideConfigurationCache[$styleguideFile] = is_array($parsedConfiguration) ? $parsedConfiguration : []; + } + + return self::$styleguideConfigurationCache[$styleguideFile]; + } + + /** + * @param class-string $className + * @param array $props + * @return array + */ + private static function mapPropsToArguments(string $className, array $props): array + { + $classReflection = new \ReflectionClass($className); + $factoryMethodReflection = $classReflection->getMethod('create'); + + $arguments = []; + foreach ($factoryMethodReflection->getParameters() as $parameterReflection) { + $name = $parameterReflection->getName(); + if (array_key_exists($name, $props)) { + $arguments[$name] = self::mapPropValue($props[$name], $parameterReflection); + } elseif ($parameterReflection->isDefaultValueAvailable()) { + $arguments[$name] = $parameterReflection->getDefaultValue(); + continue; + } else { + $arguments[$name] = null; + } + unset($props[$name]); + } + + if (count($props) > 0) { + throw new \Exception(sprintf('Superficial props "%s" were given for "%s"', implode(', ', array_keys($props)), $className)); + } + + return $arguments; + } + + private static function mapPropValue(mixed $value, ?\ReflectionParameter $reflection = null): mixed + { + if (!is_array($value)) { + if ( + is_string($value) + && $reflection !== null + && self::parameterSupportsComponentList($reflection) + && self::looksLikeHtml($value) + ) { + return StringComponent::fromHtmlString($value); + } + + if ($reflection !== null) { + $type = $reflection->getType(); + if ($type instanceof \ReflectionNamedType) { + $typeName = $type->getName(); + if ( + enum_exists($typeName) + && method_exists($typeName, 'from') + ) { + return $typeName::from($value); + } + } + } + return $value; + } + + if (array_is_list($value) && $reflection !== null && self::parameterSupportsComponentList($reflection)) { + return self::createComponentCollectionFromValues($value); + } + + if (array_is_list($value)) { + return array_map( + static fn (mixed $item): mixed => self::mapPropValue($item), + $value + ); + } + + if (self::isComponentConfiguration($value)) { + return self::createComponentFromConfiguration($value); + } + + if (self::isStructConfiguration($value)) { + return self::createStructFromConfiguration($value); + } + + if (self::isEnumConfiguration($value)) { + return self::createEnumFromConfiguration($value); + } + + throw new \Exception('Unexpected props configuration is neither scalar, component, struct nur enum'); + } + + /** + * @param array $configuration + */ + private static function isComponentConfiguration(array $configuration): bool + { + if (!array_key_exists('__type', $configuration) || !is_string($configuration['__type'])) { + return false; + } + $className = self::classNameFromIdentifier($configuration['__type']); + if (class_exists($className) && is_subclass_of($className, ComponentInterface::class)) { + return true; + } + return false; + } + + /** + * @param array $configuration + */ + private static function isStructConfiguration(array $configuration): bool + { + if (!array_key_exists('__type', $configuration) || !is_string($configuration['__type'])) { + return false; + } + $className = self::classNameFromIdentifier($configuration['__type']); + if (class_exists($className) && method_exists($className, 'create')) { + return true; + } + return false; + } + + /** + * @param array $configuration + */ + private static function isEnumConfiguration(array $configuration): bool + { + if (!array_key_exists('__type', $configuration) || !array_key_exists('value', $configuration)) { + return false; + } + $className = self::classNameFromIdentifier($configuration['__type']); + if (enum_exists($className) && method_exists($className, 'from')) { + return true; + } + return false; + } + + /** + * @param array $configuration + */ + private static function createComponentFromConfiguration(array $configuration): ComponentInterface + { + $propsFiltered = []; + foreach ($configuration as $key => $value) { + if (str_starts_with($key, '__')) { + continue; + } + $propsFiltered[$key] = $value; + } + + $metadata = CpxComponentMetadata::fromComponentIdentifier(StyleguideObjectIdentifier::fromString($configuration['__type'])); + $useCase = $configuration['__useCase'] ?? null; + $propSet = $configuration['__propSet'] ?? null; + + return self::create( + $metadata, + $propsFiltered, + $propSet ? PropSetName::fromString($propSet) : null, + $useCase ? UseCaseName::fromString($useCase) : null + ); + } + + /** + * @param array $configuration + */ + private static function createStructFromConfiguration(array $configuration): object + { + $propsFiltered = []; + foreach ($configuration as $key => $value) { + if (str_starts_with($key, '__')) { + continue; + } + $propsFiltered[$key] = $value; + } + + $className = self::classNameFromIdentifier($configuration['__type']); + $arguments = self::mapPropsToArguments($className, $propsFiltered); + return $className::create(...$arguments); + } + + /** + * @param array $configuration + */ + private static function createEnumFromConfiguration(array $configuration): \BackedEnum + { + $className = self::classNameFromIdentifier($configuration['__type']); + $value = $configuration['value']; + return $className::from($value); + } + + private static function createComponentCollectionFromValues(array $values): ?ComponentCollectionInterface + { + $items = []; + foreach ($values as $value) { + $mappedValue = self::mapPropValue($value); + if (is_string($mappedValue)) { + $items[] = self::looksLikeHtml($mappedValue) + ? StringComponent::fromHtmlString($mappedValue) + : StringComponent::fromString($mappedValue); + continue; + } + + if ($mappedValue instanceof ComponentInterface || $mappedValue === null) { + $items[] = $mappedValue; + continue; + } + + throw new \InvalidArgumentException('List props must resolve to components or strings.'); + } + + return ComponentCollection::list(...$items); + } + + private static function parameterSupportsComponentList(\ReflectionParameter $parameter): bool + { + return self::reflectionTypeSupportsComponentList($parameter->getType()); + } + + private static function reflectionTypeSupportsComponentList(?\ReflectionType $reflectionType): bool + { + if ($reflectionType instanceof \ReflectionNamedType) { + $typeName = $reflectionType->getName(); + return $typeName === ComponentCollectionInterface::class + || $typeName === ComponentInterface::class; + } + + if ($reflectionType instanceof \ReflectionUnionType) { + foreach ($reflectionType->getTypes() as $unionedType) { + if (self::reflectionTypeSupportsComponentList($unionedType)) { + return true; + } + } + } + + return false; + } + + private static function looksLikeHtml(string $value): bool + { + return preg_match('/<[^>]+>/', $value) === 1; + } + + /** + * @return class-string + */ + private static function classNameFromIdentifier(string $identifier): string + { + $componentId = str_replace('.cpx', '', $identifier); + if (!str_contains($componentId, '/')) { + throw new \InvalidArgumentException(sprintf('Invalid component identifier "%s"', $identifier)); + } + + [$package, $path] = explode('/', $componentId, 2); + $phpClass = str_replace('.', '\\', $package) . '\\Components\\' . str_replace('/', '\\', $path); + + if (!class_exists($phpClass)) { + throw new \InvalidArgumentException(sprintf('Component class "%s" could not be resolved', $phpClass)); + } + + return $phpClass; + } +} diff --git a/Classes/StyleguideProvider/CpxPackage/CpxComponentMetadata.php b/Classes/StyleguideProvider/CpxPackage/CpxComponentMetadata.php new file mode 100644 index 00000000..b6646467 --- /dev/null +++ b/Classes/StyleguideProvider/CpxPackage/CpxComponentMetadata.php @@ -0,0 +1,246 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\StyleguideProvider\CpxPackage; + +use Neos\Flow\Annotations as Flow; +use Neos\Flow\Reflection\ClassReflection; +use PackageFactory\ComponentEngine\ComponentInterface; +use ReflectionNamedType; +use ReflectionParameter; +use ReflectionType; +use Sitegeist\Monocle\Domain\StyleguideObjects\Props\Editor; +use Sitegeist\Monocle\Domain\StyleguideObjects\Props\EditorIdentifier; +use Sitegeist\Monocle\Domain\StyleguideObjects\Props\EditorOptions; +use Sitegeist\Monocle\Domain\StyleguideObjects\Props\Prop; +use Sitegeist\Monocle\Domain\StyleguideObjects\Props\PropName; +use Sitegeist\Monocle\Domain\StyleguideObjects\Props\PropsCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\Props\PropValue; +use Sitegeist\Monocle\Domain\StyleguideObjects\PropSets\PropSet; +use Sitegeist\Monocle\Domain\StyleguideObjects\PropSets\PropSetName; +use Sitegeist\Monocle\Domain\StyleguideObjects\PropSets\PropSetCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObject; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectDetails; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectIdentifier; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectName; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectPath; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideStructure; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCase; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCaseCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCaseName; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCaseTitle; +use Symfony\Component\Yaml\Yaml; + +readonly class CpxComponentMetadata +{ + /** + * @param StyleguideObject $styleguideObject + * @param class-name-string $componentPhpClassName + * @param string $componentStyleguideConfigFile + */ + public function __construct( + public StyleguideObject $styleguideObject, + public string $componentPhpClassName, + public string $componentStyleguideConfigFile, + ) { + } + + public static function fromComponentIdentifier(StyleguideObjectIdentifier $identifier): self + { + $identifierValue = $identifier->value; + $componentId = str_replace('.cpx', '', $identifierValue); + if (!str_contains($componentId, '/')) { + throw new \InvalidArgumentException(sprintf('Invalid component identifier "%s"', $identifierValue)); + } + + [, $path] = explode('/', $componentId, 2); + $pathSegments = explode('/', $path); + + $styleguideObject = new StyleguideObject( + StyleguideObjectIdentifier::fromString($identifierValue), + StyleguideObjectName::fromString($pathSegments[array_key_last($pathSegments)]), + StyleguideObjectPath::fromString(str_replace('/', '.', $path)), + new StyleguideStructure('', '', ''), + '' + ); + + $componentClass = self::classNameFromComponentIdentifier($identifierValue); + $componentReflection = new \ReflectionClass($componentClass); + $componentFileName = $componentReflection->getFileName(); + if (!$componentFileName) { + throw new \InvalidArgumentException(sprintf('Component class "%s" has no source file', $componentClass)); + } + $styleguideFile = dirname($componentFileName) . '/' . pathinfo($componentFileName, PATHINFO_FILENAME) . '.styleguide.yaml'; + + return new self($styleguideObject, $componentClass, $styleguideFile); + } + + /** + * @return class-string + */ + private static function classNameFromComponentIdentifier(string $identifier): string + { + $componentId = str_replace('.cpx', '', $identifier); + if (!str_contains($componentId, '/')) { + throw new \InvalidArgumentException(sprintf('Invalid component identifier "%s"', $identifier)); + } + + [$package, $path] = explode('/', $componentId, 2); + $phpClass = str_replace('.', '\\', $package) . '\\Components\\' . str_replace('/', '\\', $path); + + if (!class_exists($phpClass)) { + throw new \InvalidArgumentException(sprintf('Component class "%s" could not be resolved', $phpClass)); + } + + if (!is_subclass_of($phpClass, ComponentInterface::class, true)) { + throw new \InvalidArgumentException(sprintf('Component class "%s" must implement %s', $phpClass, ComponentInterface::class)); + } + + return $phpClass; + } + + public function isHidden(): bool + { + return (bool)($this->readStyleguideConfiguration()['hidden'] ?? false); + } + + public function getGroup(): ?string + { + $group = $this->readStyleguideConfiguration()['group'] ?? null; + return is_string($group) && $group !== '' ? $group : null; + } + + public function prepareStyleguideObjectDetails(): StyleguideObjectDetails + { + $config = $this->readStyleguideConfiguration(); + + $props = []; + + $componentClassReflection = new \ReflectionClass($this->componentPhpClassName); + $componentFactoryMethodReflection = $componentClassReflection->getMethod('create'); + + foreach ($componentFactoryMethodReflection->getParameters() as $parameter) { + $propValue = $config['props'][$parameter->getName()] ?? null; + + if (!$propValue || !$parameter->hasType()) { + continue; + } + + $parameterType = $parameter->getType(); + + // string + if ($parameterType instanceof \ReflectionNamedType && ($parameterType->getName() === 'string')) { + $props[] = new Prop( + PropName::fromString($parameter->getName()), + PropValue::fromAny($propValue), + new Editor( + EditorIdentifier::fromString('Sitegeist.Monocle/Props/Editors/Text'), + EditorOptions::empty() + ) + ); + } + + // int, float + if ($parameterType instanceof \ReflectionNamedType && ($parameterType->getName() === 'int' || $parameterType->getName() === 'float')) { + $props[] = new Prop( + PropName::fromString($parameter->getName()), + PropValue::fromAny($propValue), + new Editor( + EditorIdentifier::fromString( + 'Sitegeist.Monocle/Props/Editors/Text' + ), + EditorOptions::fromArray([ + 'castValueTo' => match ($parameterType->getName()) { + 'int' => 'integer', + 'float' => 'float', + } + ]) + ) + ); + } + + // boolean + if ($parameterType instanceof \ReflectionNamedType && ($parameterType->getName() === 'bool')) { + $props[] = new Prop( + PropName::fromString($parameter->getName()), + PropValue::fromAny($propValue), + new Editor( + EditorIdentifier::fromString('Sitegeist.Monocle/Props/Editors/Checkbox'), + EditorOptions::empty() + ) + ); + } + + // typed objects + if ($parameterType instanceof \ReflectionNamedType && ($parameterType->isBuiltin() === false)) { + $parameterClassReflection = new \ReflectionClass($parameterType->getName()); + + // enum + if ($parameterClassReflection->isEnum()) { + $parameterEnumReflection = new \ReflectionEnum($parameterType->getName()); + $options = []; + foreach ($parameterEnumReflection->getCases() as $case) { + if ($case instanceof \ReflectionEnumBackedCase) { + $options[] = ['label' => $case->getName(), 'value' => $case->getBackingValue()]; + } + } + $props[] = new Prop( + PropName::fromString($parameter->getName()), + PropValue::fromAny($propValue), + new Editor( + EditorIdentifier::fromString('Sitegeist.Monocle/Props/Editors/SelectBox'), + EditorOptions::fromArray([ + 'options' => $options + ]) + ) + ); + } + } + } + + $propSets = []; + if (array_key_exists('propSets', $config)) { + foreach ($config[ 'propSets' ] as $propSetName => $propSetConfig) { + $propSets[] = new PropSet(PropSetName::fromString($propSetName), $propSetConfig); + } + } + + $useCases = []; + if (array_key_exists('useCases', $config)) { + foreach ($config[ 'useCases' ] as $useCaseName => $useCaseConfig) { + $useCases[] = new UseCase( + UseCaseName::fromString($useCaseName), + UseCaseTitle::fromString($useCaseConfig['title'] ?? $useCaseName), + $useCaseConfig + ); + } + } + + return new StyleguideObjectDetails( + $this->styleguideObject->identifier, + $this->styleguideObject->name, + new PropsCollection(...$props), + new PropSetCollection(...$propSets), + new UseCaseCollection(...$useCases) + ); + } + + private function readStyleguideConfiguration(): array + { + $config = Yaml::parseFile($this->componentStyleguideConfigFile); + return is_array($config) ? $config : []; + } +} diff --git a/Classes/StyleguideProvider/CpxPackage/CpxComponentMetadataCollection.php b/Classes/StyleguideProvider/CpxPackage/CpxComponentMetadataCollection.php new file mode 100644 index 00000000..2ec13cbd --- /dev/null +++ b/Classes/StyleguideProvider/CpxPackage/CpxComponentMetadataCollection.php @@ -0,0 +1,66 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\StyleguideProvider\CpxPackage; + +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectIdentifier; + +/** + * @implements \IteratorAggregate + */ +readonly class CpxComponentMetadataCollection implements \IteratorAggregate +{ + /** + * @var array + */ + public array $items; + + public function __construct( + CpxComponentMetadata ...$items + ) { + $itemsIndexedById = []; + foreach ($items as $item) { + $itemsIndexedById[$item->styleguideObject->identifier->value] = $item; + } + $this->items = $itemsIndexedById; + } + + public function find(StyleguideObjectIdentifier $identifier): ?CpxComponentMetadata + { + if (array_key_exists($identifier->value, $this->items)) { + return $this->items[$identifier->value]; + } + return null; + } + + /** + * @return \Generator + */ + public function getIterator(): \Generator + { + yield from $this->items; + } + + public function asStyleguideObjectCollection(): StyleguideObjectCollection + { + $items = []; + foreach ($this->items as $item) { + $items[] = $item->styleguideObject; + } + return new StyleguideObjectCollection(... $items); + } +} diff --git a/Classes/StyleguideProvider/CpxPackage/CpxPackageStyleguide.php b/Classes/StyleguideProvider/CpxPackage/CpxPackageStyleguide.php new file mode 100644 index 00000000..69ea3638 --- /dev/null +++ b/Classes/StyleguideProvider/CpxPackage/CpxPackageStyleguide.php @@ -0,0 +1,188 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\StyleguideProvider\CpxPackage; + +use Neos\Flow\Annotations as Flow; +use Neos\Flow\Package\FlowPackageInterface; +use Neos\Flow\Package\PackageManager; +use Neos\Flow\ResourceManagement\ResourceManager; +use PackageFactory\Neos\ComponentEngine\Application\Transpiler\TranspilerConfiguration; +use PackageFactory\Neos\ComponentEngine\Application\Transpiler\TranspilerConfigurationLoader; +use Sitegeist\Monocle\Domain\StyleguideAddress; +use Sitegeist\Monocle\Domain\StyleguideIdentifier; +use Sitegeist\Monocle\Domain\StyleguideInterface; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObject; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideStructure; +use Sitegeist\Monocle\Domain\StyleguideObjects\PropSets\PropSetName; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectDetails; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectIdentifier; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCaseName; +use Sitegeist\Monocle\Service\ConfigurationService; + +class CpxPackageStyleguide implements StyleguideInterface +{ + #[Flow\Inject] + protected ConfigurationService $configurationService; + + #[Flow\Inject] + protected PackageManager $packageManager; + + #[Flow\Inject] + protected ResourceManager $resourceManager; + + #[Flow\Inject] + protected TranspilerConfigurationLoader $transpilerConfigurationLoader; + + /** + * @var CpxComponentMetadata[] + */ + protected array $items; + + public function __construct( + protected StyleguideAddress $styleguideAddress, + protected FlowPackageInterface $package, + ) { + $this->package = $package; + } + + public function getStyleguideAddress(): StyleguideAddress + { + return $this->styleguideAddress; + } + + public function getStyleguideIdentifier(): StyleguideIdentifier + { + return $this->styleguideAddress->styleguide; + } + + public function getStyleguideObjectList(): StyleguideObjectCollection + { + $list = $this->buildItemList(); + return $list->asStyleguideObjectCollection(); + } + + public function getStyleguideObjectDetails(StyleguideObjectIdentifier $identifier): StyleguideObjectDetails + { + $list = $this->buildItemList(); + $item = $list->find($identifier); + if ($item instanceof CpxComponentMetadata) { + return $item->prepareStyleguideObjectDetails(); + } + throw new \InvalidArgumentException($identifier->value . ' not found'); + } + + public function renderStyleguideObject(StyleguideObjectIdentifier $identifier, array $props, ?PropSetName $propSet, ?UseCaseName $useCase, array $locales): string + { + $metadata = CpxComponentMetadata::fromComponentIdentifier($identifier); + $component = CpxComponentFactory::create($metadata, $props, $propSet, $useCase, true); + + $styles = $this->configurationService->getStyleguideConfiguration($this->getStyleguideAddress(), 'preview.styles'); + $styleTags = array_reduce( + is_array($styles) ? array_filter($styles) : [], + fn (string $carry, string $path) => $carry . '', + '' + ); + + $scripts = $this->configurationService->getStyleguideConfiguration($this->getStyleguideAddress(), 'preview.scripts'); + $scriptTags = array_reduce( + is_array($scripts) ? array_filter($scripts) : [], + fn (string $carry, string $path) => $carry . '', + '' + ); + + return << + + + {$identifier->value} + {$styleTags} + {$scriptTags} + + {$component->render()} + + EOL; + } + + private function buildItemList(): CpxComponentMetadataCollection + { + $items = []; + $prototypeStructures = $this->configurationService->getStyleguideConfiguration($this->getStyleguideAddress(), 'ui.structure'); + $transpilerConfiguration = $this->transpilerConfigurationLoader->forPackage($this->package->getPackageKey()); + foreach ($transpilerConfiguration as $configuration) { + /** + * @var TranspilerConfiguration $configuration + */ + $identifier = StyleguideObjectIdentifier::fromString($configuration->moduleId); + try { + $metadata = CpxComponentMetadata::fromComponentIdentifier($identifier); + } catch (\InvalidArgumentException) { + continue; + } + if (!file_exists($metadata->componentStyleguideConfigFile)) { + continue; + } + if ($metadata->isHidden()) { + continue; + } + + $items[] = new CpxComponentMetadata( + $this->resolveStyleguideObject($metadata, is_array($prototypeStructures) ? $prototypeStructures : []), + $metadata->componentPhpClassName, + $metadata->componentStyleguideConfigFile + ); + } + return new CpxComponentMetadataCollection(...$items); + } + + private function resolveStyleguideObject(CpxComponentMetadata $metadata, array $prototypeStructures): StyleguideObject + { + $styleguideObject = $metadata->styleguideObject; + $groupOrPath = $metadata->getGroup() ?? $styleguideObject->path->value; + + return new StyleguideObject( + $styleguideObject->identifier, + $styleguideObject->name, + $styleguideObject->path, + $this->getStructureForComponentPath($prototypeStructures, $groupOrPath), + $styleguideObject->description + ); + } + + private function getStructureForComponentPath(array $prototypeStructures, string $componentPath): StyleguideStructure + { + foreach ($prototypeStructures as $structure) { + if (!isset($structure['match'], $structure['label'], $structure['icon'], $structure['color'])) { + continue; + } + + if (preg_match(sprintf('!%s!', $structure['match']), $componentPath)) { + return new StyleguideStructure( + $structure['label'], + $structure['icon'], + $structure['color'], + ); + } + } + + return new StyleguideStructure( + 'Other', + 'icon-question', + 'white' + ); + } +} diff --git a/Classes/StyleguideProvider/CpxPackage/CpxPackageStyleguideProvider.php b/Classes/StyleguideProvider/CpxPackage/CpxPackageStyleguideProvider.php new file mode 100644 index 00000000..bcdff305 --- /dev/null +++ b/Classes/StyleguideProvider/CpxPackage/CpxPackageStyleguideProvider.php @@ -0,0 +1,77 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\StyleguideProvider\CpxPackage; + +use Neos\Flow\Annotations as Flow; +use Neos\Flow\Package\FlowPackageInterface; +use Neos\Flow\Package\PackageManager; +use PackageFactory\Neos\ComponentEngine\Application\Transpiler\TranspilerConfigurationLoader; +use Sitegeist\Monocle\Domain\StyleguideAddress; +use Sitegeist\Monocle\Domain\StyleguideIdentifier; +use Sitegeist\Monocle\Domain\StyleguideInterface; +use Sitegeist\Monocle\Domain\StyleguideMetadata; +use Sitegeist\Monocle\Domain\StyleguideMetadataCollection; +use Sitegeist\Monocle\Domain\StyleguideName; +use Sitegeist\Monocle\Domain\StyleguideProviderIdentifier; +use Sitegeist\Monocle\Domain\StyleguideProviderInterface; +use Sitegeist\Monocle\StyleguideProvider\NeosFusionSite\CpxPackageStyleguide; + +/** + * This provider will generate a styleguide for each neos site package + */ +class CpxPackageStyleguideProvider implements StyleguideProviderInterface +{ + #[Flow\Inject] + protected PackageManager $packageManager; + + public function getStyleguideMetadataCollection(StyleguideProviderIdentifier $providerIdentifier): StyleguideMetadataCollection + { + $items = []; + if (class_exists(\PackageFactory\Neos\ComponentEngine\Application\Transpiler\TranspilerConfigurationLoader::class)) { + $packages = $this->packageManager->getFlowPackages(); + foreach ($packages as $package) { + $componentPath = $package->getPackagePath() . '/Components'; + if (file_exists($componentPath) && is_dir($componentPath)) { + $identifier = StyleguideIdentifier::fromString($package->getPackageKey()); + $items[] = new StyleguideMetadata( + $identifier, + StyleguideName::fromString($package->getComposerName()), + new StyleguideAddress( + $providerIdentifier, + $identifier, + ) + ); + } + } + } + return new StyleguideMetadataCollection(...$items); + } + + public function getStyleguide(StyleguideAddress $address): StyleguideInterface + { + if (class_exists(\PackageFactory\Neos\ComponentEngine\Application\Transpiler\TranspilerConfigurationLoader::class)) { + $package = $this->packageManager->getPackage($address->styleguide->value); + if ($package instanceof FlowPackageInterface) { + return new \Sitegeist\Monocle\StyleguideProvider\CpxPackage\CpxPackageStyleguide( + $address, + $package, + ); + } + } + throw new \InvalidArgumentException('Neos Fusion styleguides can only work with Neos'); + } +} diff --git a/Classes/StyleguideProvider/NeosFusionSite/NeosFusionSiteStyleguide.php b/Classes/StyleguideProvider/NeosFusionSite/NeosFusionSiteStyleguide.php new file mode 100644 index 00000000..bb1e87e4 --- /dev/null +++ b/Classes/StyleguideProvider/NeosFusionSite/NeosFusionSiteStyleguide.php @@ -0,0 +1,186 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\StyleguideProvider\NeosFusionSite; + +use Neos\Flow\Annotations as Flow; +use Neos\Flow\I18n\LocaleCollection; +use Neos\Fusion\View\FusionView; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; +use Sitegeist\Monocle\Domain\StyleguideAddress; +use Sitegeist\Monocle\Domain\StyleguideIdentifier; +use Sitegeist\Monocle\Domain\StyleguideInterface; +use Sitegeist\Monocle\Domain\StyleguideObjects\Props\PropsCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\PropSets\PropSetName; +use Sitegeist\Monocle\Domain\StyleguideObjects\PropSets\PropSetCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObject; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectDetails; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectIdentifier; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectName; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideObjectPath; +use Sitegeist\Monocle\Domain\StyleguideObjects\StyleguideStructure; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCaseCollection; +use Sitegeist\Monocle\Domain\StyleguideObjects\UseCases\UseCaseName; +use Sitegeist\Monocle\Fusion\FusionService; +use Sitegeist\Monocle\Service\ConfigurationService; +use Sitegeist\Monocle\Service\DummyControllerContextTrait; + +class NeosFusionSiteStyleguide implements StyleguideInterface +{ + use DummyControllerContextTrait; + + #[Flow\Inject] + protected FusionService $fusionService; + + #[Flow\Inject] + protected ConfigurationService $configurationService; + + private string $sitePackageKey; + + public function __construct( + protected StyleguideAddress $address, + ) { + $this->sitePackageKey = $address->styleguide->value; + } + + public function getStyleguideAddress(): StyleguideAddress + { + return $this->address; + } + + public function getStyleguideIdentifier(): StyleguideIdentifier + { + return $this->address->styleguide; + } + + public function getStyleguideObjectList(): StyleguideObjectCollection + { + $fusionAst = $this->fusionService->getFusionConfigurationForPackageKey($this->sitePackageKey); + $styleguideObjects = $this->fusionService->getStyleguideObjectsFromFusionAst($fusionAst); + $prototypeStructures = $this->configurationService->getSiteConfiguration($this->sitePackageKey, 'ui.structure'); + + $hiddenPrototypeNamePatterns = $this->configurationService->getSiteConfiguration($this->sitePackageKey, 'hiddenPrototypeNamePatterns'); + if (is_array($hiddenPrototypeNamePatterns)) { + $alwaysShowPrototypes = $this->configurationService->getSiteConfiguration($this->sitePackageKey, 'alwaysShowPrototypes'); + foreach ($hiddenPrototypeNamePatterns as $pattern) { + $styleguideObjects = array_filter( + $styleguideObjects, + function ($prototypeName) use ($pattern, $alwaysShowPrototypes) { + if (in_array($prototypeName, $alwaysShowPrototypes, true)) { + return true; + } + return fnmatch($pattern, $prototypeName) === false; + }, + ARRAY_FILTER_USE_KEY + ); + } + } + + $result = []; + foreach ($styleguideObjects as $prototypeName => $styleguideObject) { + $result[] = new StyleguideObject( + StyleguideObjectIdentifier::fromString($prototypeName), + StyleguideObjectName::fromString($styleguideObject['title']), + StyleguideObjectPath::fromString($prototypeName), + $this->getStructureForPrototypeName($prototypeStructures, $prototypeName), + $styleguideObject['description'] + ); + } + return new StyleguideObjectCollection(... $result); + } + + public function getStyleguideObjectDetails(StyleguideObjectIdentifier $identifier): StyleguideObjectDetails + { + $prototypeName = $identifier->value; + + $fusionAst = $this->fusionService->getFusionConfigurationForPackageKey($this->sitePackageKey); + $styleguideObjectsFromFusion = $this->fusionService->getStyleguideObjectsFromFusionAst($fusionAst); + + $styleguideObjectFromFusion = $styleguideObjectsFromFusion[$prototypeName] ?? null; + if (is_null($styleguideObjectFromFusion)) { + throw new \Exception($prototypeName . " not found"); + } + + return new StyleguideObjectDetails( + StyleguideObjectIdentifier::fromString($prototypeName), + StyleguideObjectName::fromString($styleguideObjectFromFusion['title']), + new PropsCollection(), + new PropSetCollection(), + new UseCaseCollection() + ); + } + + public function renderStyleguideObject(StyleguideObjectIdentifier $identifier, array $props, ?PropSetName $propSet, ?UseCaseName $useCase, array $locales): string + { + $sitePackageKey = $this->address->styleguide->value; + + $fusionRootPath = $this->configurationService->getSiteConfiguration($sitePackageKey, ['preview', 'fusionRootPath']); + + $view = new \Sitegeist\Monocle\Fusion\FusionView(); + $view->setControllerContext($this->createDummyControllerContext()); + + $view->setPackageKey($sitePackageKey); + $view->setFusionPath($fusionRootPath); + $view->setLocales($locales); + + $view->assignMultiple([ + 'sitePackageKey' => $sitePackageKey, + 'prototypeName' => $identifier->value, + 'useCase' => $useCase, + 'propSet' => $propSet, + 'props' => $props, + 'locales' => $locales + ]); + + // get the status and headers from the view + $result = $view->render(); + if ($result instanceof ResponseInterface) { + return (string)$result->getBody(); + } + if ($result instanceof StreamInterface) { + return (string)$result; + } + throw new \Exception("Unexpected result"); + } + + /** + * Find the matching structure for a prototype + * + * @param $prototypeStructures + * @param $prototypeName + * @return array + */ + protected function getStructureForPrototypeName($prototypeStructures, $prototypeName): StyleguideStructure + { + foreach ($prototypeStructures as $structure) { + if (preg_match(sprintf('!%s!', $structure['match']), $prototypeName)) { + return new StyleguideStructure( + $structure['label'], + $structure['icon'], + $structure['color'], + ); + } + } + + return new StyleguideStructure( + 'Other', + 'icon-question', + 'white' + ); + } +} diff --git a/Classes/StyleguideProvider/NeosFusionSite/NeosFusionSiteStyleguideProvider.php b/Classes/StyleguideProvider/NeosFusionSite/NeosFusionSiteStyleguideProvider.php new file mode 100644 index 00000000..3cd848fa --- /dev/null +++ b/Classes/StyleguideProvider/NeosFusionSite/NeosFusionSiteStyleguideProvider.php @@ -0,0 +1,72 @@ + + * Wilhelm Behncke + * + * This package is Open Source Software. For the full copyright and license + * information, please view the LICENSE file which was distributed with this + * source code. + */ + +declare(strict_types=1); + +namespace Sitegeist\Monocle\StyleguideProvider\NeosFusionSite; + +use Neos\Flow\Annotations as Flow; +use Neos\Flow\Package\FlowPackageInterface; +use Neos\Flow\Package\PackageManager; +use Sitegeist\Monocle\Domain\StyleguideAddress; +use Sitegeist\Monocle\Domain\StyleguideIdentifier; +use Sitegeist\Monocle\Domain\StyleguideInterface; +use Sitegeist\Monocle\Domain\StyleguideMetadata; +use Sitegeist\Monocle\Domain\StyleguideMetadataCollection; +use Sitegeist\Monocle\Domain\StyleguideName; +use Sitegeist\Monocle\Domain\StyleguideProviderIdentifier; +use Sitegeist\Monocle\Domain\StyleguideProviderInterface; + +/** + * This provider will generate a styleguide for each neos site package + */ +class NeosFusionSiteStyleguideProvider implements StyleguideProviderInterface +{ + #[Flow\Inject] + protected PackageManager $packageManager; + + public function getStyleguideMetadataCollection(StyleguideProviderIdentifier $providerIdentifier): StyleguideMetadataCollection + { + if (class_exists(\Neos\Neos\Domain\Service\FusionSourceCodeFactory::class)) { + $sitePackages = $this->packageManager->getFilteredPackages('available', 'neos-site'); + $metadataItems = []; + foreach ($sitePackages as $sitePackage) { + if (!$sitePackage instanceof FlowPackageInterface) { + throw new \Exception(sprintf("site package %s is not instance of FlowPackageInterface", get_class($sitePackage))); + } + $styleguideIdentifier = new StyleguideIdentifier($sitePackage->getPackageKey()); + $metadataItems[] = new StyleguideMetadata( + $styleguideIdentifier, + new StyleguideName($sitePackage->getPackageKey()), + new StyleguideAddress( + $providerIdentifier, + $styleguideIdentifier + ), + ); + } + return new StyleguideMetadataCollection(...$metadataItems); + } else { + return new StyleguideMetadataCollection(); + } + } + + public function getStyleguide(StyleguideAddress $address): StyleguideInterface + { + if (class_exists(\Neos\Neos\Domain\Service\FusionSourceCodeFactory::class)) { + return new NeosFusionSiteStyleguide($address); + } else { + throw new \InvalidArgumentException('Neos Fusion styleguides can only work with Neos'); + } + } +} diff --git a/Configuration/Objects.yaml b/Configuration/Objects.yaml index f341d855..21b7152b 100644 --- a/Configuration/Objects.yaml +++ b/Configuration/Objects.yaml @@ -13,5 +13,3 @@ Sitegeist\Monocle\Aspects\FusionCachingAspect: 1: value: Sitegeist_Monocle_Fusion -Sitegeist\Monocle\Domain\PrototypeDetails\Props\PropsCollectionFactoryInterface: - className: Sitegeist\Monocle\Domain\PrototypeDetails\Props\PropsCollectionFactory diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml index f55e6387..917cd1ff 100644 --- a/Configuration/Settings.yaml +++ b/Configuration/Settings.yaml @@ -1,19 +1,23 @@ Sitegeist: Monocle: - uriMock: - #the static file uris to mock - static: {} - # key: - # path: 'resource://Vendor.Package/Private/Json/example.json' - # contentType: 'application/json' + + # The providers that are asked for styleguides. + # each provider has to implement the StyleguideProviderInterface + styleguideProviders: + NeosFusionSite: Sitegeist\Monocle\StyleguideProvider\NeosFusionSite\NeosFusionSiteStyleguideProvider + CpxPackage: Sitegeist\Monocle\StyleguideProvider\CpxPackage\CpxPackageStyleguideProvider + fusion: enableObjectTreeCache: true - defaultPackageKey: null + # the styleguide to open by default + # Example: 'CpxPackage::Vendor.Site' + defaultStyleguide: null - packages: { } + # configurations per styleguide address + styleguides: {} ui: hotkeys: diff --git a/README.md b/README.md index 3f738609..ebee7cca 100644 --- a/README.md +++ b/README.md @@ -499,93 +499,6 @@ prototype(Vendor.Site:ContainerExample) { } ``` -### Simulate API-Endpoints - -Monocle has fusion-prototypes to simulate json api responses for components. - -#### `Sitegeist.Monocle:DataUri` - -Generic data uri implementation that expects `type` and `content` as string - -``` - endpointUrl = Sitegeist.Monocle:DataUri { - content = '{"hello":"world"}' - type = 'application/json' - } -``` - -The DataUri-Prototypes will encode the content as base64. -Attention: Data Uris do not accept url-parameters. If you frontend code adds arguments to the mock you have to be aware of that. - -For convenience special prototypes for json and text exist: - -- `Sitegeist.Monocle:DataUri.Json`: And endpoint-mock with media-type `application/json` that will pass `content` trough Json.stringify -- `Sitegeist.Monocle:DataUri.Text`: And endpoint-mock with media-type `text/plain` - -#### `Sitegeist.Monocle:MirrorUri` - -Create an uri to an monocle endpoint that returns the passed content with the given type - -``` - endpointUrl = Sitegeist.Monocle:MirrorUri { - content = '{"hello":"world"}' - type = 'application/json' - } -``` - -Attention: Browsers will often crop the urls to a maximal length, be aware of that if you mock large json-structures. - -For convenience special prototypes for json and text exist: - -- `Sitegeist.Monocle:MirrorUri.Json`: And endpoint-mock with media-type `application/json` that accepts RawArray data -- `Sitegeist.Monocle:MirrorUri.Text`: And endpoint-mock with media-type `text/plain` - - -#### `Sitegeist.Monocle:StaticUri` - -Create an URI that will return the content of the file and the contentType for the given key. - -``` - endpointUrl = Sitegeist.Monocle:StaticUri { - key = 'example' - } -``` - -The path and content type for each key are configured via Settings: - -```yaml -Sitegeist: - Monocle: - uriMock: - static: - example: - path: 'resource://Vendor.Package/Private/Json/example.json' - contentType: 'application/json' -``` - -#### Mocking Uris inside the styleguide - -``` -prototype(Vendor.Package:Component.SearchExample) < prototype(Neos.Fusion:Component) { - @styleguide { - props { - endpointUrl = Sitegeist.Monocle:DataUri.Json { - content = Neos.Fusion:RawArray { - term = 'hamburch' - suggestedTerm = 'hamburg' - } - } - } - } - - endpointUrl = null - - renderer = afx` -
- ` -} -``` - ### Props & Prop Editors While previewing Fusion prototypes the Monocle UI offers a mechanism to override certain properties of that prototype in an ad-hoc fashion. This allows you to quickly examine whether your prototype works in certain unforseen configurations (longer or shorter text for instance). diff --git a/Resources/Private/Fusion/Backend/Module/Module.fusion b/Resources/Private/Fusion/Backend/Module/Module.fusion index c1a42bd5..b98445f5 100644 --- a/Resources/Private/Fusion/Backend/Module/Module.fusion +++ b/Resources/Private/Fusion/Backend/Module/Module.fusion @@ -52,7 +52,8 @@ prototype(Sitegeist.Monocle:Backend.Module) < prototype(Neos.Fusion:Component) {
/__meta/styleguide/useCases/' + useCase + '/container'} } @@ -99,7 +99,7 @@ prototype(Sitegeist.Monocle:Preview.Page) < prototype(Neos.Fusion:Http.Message) } hasStyleguideContainer { - condition = Sitegeist.Monocle:CanRender { + condition = Neos.Fusion:CanRender { renderPath = ${'/<' + prototypeName + '>/__meta/styleguide/container'} } renderer = Neos.Fusion:Renderer { diff --git a/Resources/Private/Fusion/Prototypes/Preview/Prototype.fusion b/Resources/Private/Fusion/Prototypes/Preview/Prototype.fusion index 84f0f256..a8f941d0 100644 --- a/Resources/Private/Fusion/Prototypes/Preview/Prototype.fusion +++ b/Resources/Private/Fusion/Prototypes/Preview/Prototype.fusion @@ -13,7 +13,7 @@ prototype(Sitegeist.Monocle:Preview.Prototype) < prototype(Neos.Fusion:Component defaultProps = Neos.Fusion:Case { directly { - condition = Sitegeist.Monocle:CanRender { + condition = Neos.Fusion:CanRender { renderPath = ${'/<' + props.prototypeName + '>/__meta/styleguide/props'} } renderer = Neos.Fusion:Renderer { @@ -32,7 +32,7 @@ prototype(Sitegeist.Monocle:Preview.Prototype) < prototype(Neos.Fusion:Component propsFromUseCase = Neos.Fusion:Case { @if.hasUseCase = ${props.useCase} directly { - condition = Sitegeist.Monocle:CanRender { + condition = Neos.Fusion:CanRender { renderPath = ${'/<' + props.prototypeName + '>/__meta/styleguide/useCases/' + props.useCase + '/props'} } renderer = Neos.Fusion:Renderer { @@ -52,7 +52,7 @@ prototype(Sitegeist.Monocle:Preview.Prototype) < prototype(Neos.Fusion:Component @if.hasProSet = ${props.propSet} @if.hasNoUseCase = ${!props.useCase} directly { - condition = Sitegeist.Monocle:CanRender { + condition = Neos.Fusion:CanRender { renderPath = ${'/<' + props.prototypeName + '>/__meta/styleguide/propSets/' + props.propSet} } renderer = Neos.Fusion:Renderer { diff --git a/Resources/Private/JavaScript/bootstrap/createStore.ts b/Resources/Private/JavaScript/bootstrap/createStore.ts index 12994d5e..ecf2b677 100644 --- a/Resources/Private/JavaScript/bootstrap/createStore.ts +++ b/Resources/Private/JavaScript/bootstrap/createStore.ts @@ -41,7 +41,8 @@ export function createStore(env: Environment) { isOpen: false }, gridPreview: { - isVisible: false + isVisible: false, + grids: {} }, qrCode: { isVisible: false diff --git a/Resources/Private/JavaScript/containers/styleguide/main/index.tsx b/Resources/Private/JavaScript/containers/styleguide/main/index.tsx index 8001dd86..60ddb7b0 100644 --- a/Resources/Private/JavaScript/containers/styleguide/main/index.tsx +++ b/Resources/Private/JavaScript/containers/styleguide/main/index.tsx @@ -1,7 +1,6 @@ import * as React from "react"; import { PreviewFrame } from "./preview-frame"; -import { InfoTabs } from "./info-tabs"; import style from "./style.module.css"; @@ -9,7 +8,6 @@ export function Main() { return (
-
); } diff --git a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/anatomy/anatomy-item/index.tsx b/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/anatomy/anatomy-item/index.tsx deleted file mode 100644 index be385090..00000000 --- a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/anatomy/anatomy-item/index.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import * as React from "react"; -import { PureComponent } from "react"; - -import style from "./style.module.css"; - -interface AnatomyItemProps { - name: string, - children: AnatomyItemChild[], - onSelect: (name: string) => void -} - -interface AnatomyItemChild { - prototypeName: string - children: AnatomyItemChild[] -} - -export class AnatomyItem extends PureComponent { - handleSelect = (event: React.MouseEvent) => { - const {name, onSelect} = this.props; - onSelect(name); - event.stopPropagation(); - }; - - render() { - const {name, children, onSelect} = this.props; - - return ( -
-
{name}
- - {children.length ? -
    - {children.map( - ({prototypeName, children}) => ( -
  • - -
  • - ) - )} -
: null - } -
- ); - } -} diff --git a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/anatomy/anatomy-item/style.module.css b/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/anatomy/anatomy-item/style.module.css deleted file mode 100644 index 80258fd7..00000000 --- a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/anatomy/anatomy-item/style.module.css +++ /dev/null @@ -1,19 +0,0 @@ -.name { - background-color: black; - padding: 1em 2em; -} - -.list { - list-style-type: none; - margin: 0 0 0 2em; - padding: 0; -} - -.child { - margin: 1px 0 0; - padding: 0; -} - -.child .name { - cursor: pointer; -} diff --git a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/anatomy/index.tsx b/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/anatomy/index.tsx deleted file mode 100644 index a4d9d9c1..00000000 --- a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/anatomy/index.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import * as React from "react"; -import { PureComponent } from "react"; -import { connect } from "react-redux"; - -import { actions } from "../../../../../state"; - -import { AnatomyItem } from "./anatomy-item"; - -export interface AnatomyType { - prototypeName: string - children: AnatomyType[] -} - -function reduceAnatomicalTreeToComponents( - anatomy: AnatomyType | AnatomyType[], - prototypeNames: string[] -): AnatomyType[] { - if (Array.isArray(anatomy)) { - return anatomy.reduce((acc, a) => { - return acc.concat(reduceAnatomicalTreeToComponents(a, prototypeNames)); - }, [] as AnatomyType[]); - } - - if (anatomy.prototypeName && prototypeNames.includes(anatomy.prototypeName)) { - return [{ - prototypeName: anatomy.prototypeName, - children: reduceAnatomicalTreeToComponents(anatomy.children, prototypeNames) - }]; - } else if (anatomy.children) { - return reduceAnatomicalTreeToComponents(anatomy.children, prototypeNames); - } - - return []; -} - -interface AnatomyProps { - anatomy: AnatomyType | AnatomyType[] - prototypes: Record - prototypeName: string - select: (prototypeName: string) => void -} - -class AnatomyC extends PureComponent { - handleSelectPrototype = (prototypeName: string) => { - const { select } = this.props; - - select(prototypeName); - }; - - render() { - const { anatomy, prototypes, prototypeName } = this.props; - const processedAnatomy = reduceAnatomicalTreeToComponents(anatomy, Object.keys(prototypes)); - - return ( -
- -
- ); - } -} - -export const Anatomy = connect(() => ({}), { - select: actions.prototypes.select -})(AnatomyC); diff --git a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/code/codeStyle.ts b/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/code/codeStyle.ts deleted file mode 100644 index ad17dcf9..00000000 --- a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/code/codeStyle.ts +++ /dev/null @@ -1,114 +0,0 @@ -export default { - 'hljs': { - display: 'block', - overflowX: 'auto', - color: '#f8f8f2' - }, - 'hljs-tag': { - color: '#f8f8f2' - }, - 'hljs-subst': { - color: '#f8f8f2' - }, - 'hljs-strong': { - color: '#a8a8a2', - fontWeight: 'bold' - }, - 'hljs-emphasis': { - color: '#a8a8a2', - fontStyle: 'italic' - }, - 'hljs-bullet': { - color: '#ae81ff' - }, - 'hljs-quote': { - color: '#ae81ff' - }, - 'hljs-number': { - color: '#ae81ff' - }, - 'hljs-regexp': { - color: '#ae81ff' - }, - 'hljs-literal': { - color: '#ae81ff' - }, - 'hljs-link': { - color: '#ae81ff' - }, - 'hljs-code': { - color: '#a6e22e' - }, - 'hljs-title': { - color: '#a6e22e' - }, - 'hljs-section': { - color: '#a6e22e' - }, - 'hljs-selector-class': { - color: '#a6e22e' - }, - 'hljs-keyword': { - color: '#f92672' - }, - 'hljs-selector-tag': { - color: '#f92672' - }, - 'hljs-name': { - color: '#f92672' - }, - 'hljs-attr': { - color: '#f92672' - }, - 'hljs-symbol': { - color: '#66d9ef' - }, - 'hljs-attribute': { - color: '#66d9ef' - }, - 'hljs-params': { - color: '#f8f8f2' - }, - 'hljs-class .hljs-title': { - color: '#f8f8f2' - }, - 'hljs-string': { - color: '#e6db74' - }, - 'hljs-type': { - color: '#e6db74' - }, - 'hljs-built_in': { - color: '#e6db74' - }, - 'hljs-builtin-name': { - color: '#e6db74' - }, - 'hljs-selector-id': { - color: '#e6db74' - }, - 'hljs-selector-attr': { - color: '#e6db74' - }, - 'hljs-selector-pseudo': { - color: '#e6db74' - }, - 'hljs-addition': { - color: '#e6db74' - }, - 'hljs-variable': { - color: '#e6db74' - }, - 'hljs-template-variable': { - color: '#e6db74' - }, - 'hljs-comment': { - color: '#75715e' - }, - 'hljs-deletion': { - color: '#75715e' - }, - 'hljs-meta': { - color: '#75715e' - } -}; diff --git a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/code/index.tsx b/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/code/index.tsx deleted file mode 100644 index f0a1381e..00000000 --- a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/code/index.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as React from 'react'; -import { Component } from 'react'; -import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/light'; -import codeStyle from './codeStyle'; - -import vim from 'react-syntax-highlighter/dist/cjs/languages/hljs/vim'; -import xml from 'react-syntax-highlighter/dist/cjs/languages/hljs/xml'; -import yaml from 'react-syntax-highlighter/dist/cjs/languages/hljs/yaml'; - -SyntaxHighlighter.registerLanguage('vim', vim); -SyntaxHighlighter.registerLanguage('xml', xml); -SyntaxHighlighter.registerLanguage('yaml', yaml); - -interface CodeProps { - content: string, - language: string, - style?: React.CSSProperties -} - -export class Code extends Component { - render() { - const { content, language, style } = this.props; - - return ( - - {content} - - ); - } -} diff --git a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/index.tsx b/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/index.tsx deleted file mode 100644 index 435a930f..00000000 --- a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/index.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import * as React from "react"; -import { PureComponent } from "react"; -import { connect } from "react-redux"; -import prettify from "html-prettify"; - -import { Tabs } from "@neos-project/react-ui-components"; - -import { visibility, resizable } from "../../../../components"; -import { selectors, State } from "../../../../state"; - -import { Code } from "./code"; -import { Anatomy, AnatomyType } from "./anatomy"; - -import style from "./style.module.css"; -import tabTheme from "./tabTheme.module.css"; -import tabPanelTheme from "./tabPanelTheme.module.css"; - -interface InfoTabsProps { - title?: string - prototypeName: string - description?: string - renderedHtml: string - renderedCode: string - parsedCode: string - anatomy: AnatomyType | AnatomyType[] - prototypes: Record -} - -class InfoTabsC extends PureComponent { - render() { - const { - title, - prototypeName, - description, - renderedHtml, - renderedCode, - parsedCode, - anatomy, - prototypes - } = this.props; - - return ( - - -

{title} {prototypeName}

- {description} -
- - - - - - - - - - - - -
- ); - } -} - -export const InfoTabs = connect((state: State) => { - const prototypes = selectors.prototypes.all(state); - const currentlyRenderedPrototype = selectors.prototypes.currentlyRendered(state); - const currentlySelectedPrototype = selectors.prototypes.currentlySelected(state); - const currentHtml = selectors.prototypes.currentHtml(state); - - return { - prototypes, - ...currentlyRenderedPrototype, - ...currentlySelectedPrototype, - renderedHtml: currentHtml ? currentHtml : '', - isVisible: Boolean(currentlyRenderedPrototype) && Boolean(currentlySelectedPrototype) - }; -})(visibility(resizable({ - initialHeight: 320, - collapsedHeight: 40, - toggleHandleClassName: style.toggleHandle -})(InfoTabsC))); diff --git a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/style.module.css b/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/style.module.css deleted file mode 100644 index b4d1db90..00000000 --- a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/style.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.toggleHandle { - position: absolute; - right: 0; -} - -.prototypeLabel { - display: block; - color: var(--brandColorsContrastBright); - font-size: 75%; -} diff --git a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/tabPanelTheme.module.css b/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/tabPanelTheme.module.css deleted file mode 100644 index aa6e937b..00000000 --- a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/tabPanelTheme.module.css +++ /dev/null @@ -1,6 +0,0 @@ -.panel { - padding: 20px; - height: 100%; - overflow-x: visible; - overflow-y: visible; -} diff --git a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/tabTheme.module.css b/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/tabTheme.module.css deleted file mode 100644 index e3b69600..00000000 --- a/Resources/Private/JavaScript/containers/styleguide/main/info-tabs/tabTheme.module.css +++ /dev/null @@ -1,17 +0,0 @@ -.tabNavigation__item--isActive { - background-color: #00ADEE; - > .tabNavigation__itemBtn { - color: #fff; - } -} - -.tabNavigation__itemBtnIcon { - width: 1em; - height: 1em; -} - -.tabs__content { - height: calc(100% - 45px); - overflow-x: auto; - overflow-y: auto; -} diff --git a/Resources/Private/JavaScript/containers/styleguide/main/preview-frame/grid-overlay.tsx b/Resources/Private/JavaScript/containers/styleguide/main/preview-frame/grid-overlay.tsx new file mode 100644 index 00000000..8030fd09 --- /dev/null +++ b/Resources/Private/JavaScript/containers/styleguide/main/preview-frame/grid-overlay.tsx @@ -0,0 +1,95 @@ +import * as React from "react"; +import { PureComponent } from "react"; +import { connect } from "react-redux"; +import cx from "classnames"; + +import { Grid } from "../../../../grid"; +import { selectors, State } from "../../../../state"; +import { iframeWindow } from "../../../../dom"; +import type { GridDefinition } from "../../../../schema"; + +import style from "./style.module.css"; + +type NormalizedGridDefinition = GridDefinition & { columns: number }; + +interface PreviewGridOverlayProps { + isVisible: boolean + isLocked: boolean + styles: React.CSSProperties + grids: NormalizedGridDefinition[] +} + +class PreviewGridOverlayC extends PureComponent { + render() { + const { isVisible, isLocked, styles, grids } = this.props; + + if (!isVisible || grids.length === 0) { + return null; + } + + const frameStyles = { + ...styles, + height: styles.height ?? "100%" + }; + + return ( + + ); + } +} + +export const PreviewGridOverlay = connect((state: State) => { + const currentlySelectedBreakpoint = selectors.breakpoints.currentlySelected(state); + const isLocked = Boolean(currentlySelectedBreakpoint); + const isPropsInspectorOpen = selectors.propsInspector.isOpen(state); + const gridsByName = selectors.grid.gridsByName(state); + const grids = Object.entries(gridsByName).map(([name, grid]) => { + const columns = typeof grid.columns === "string" + ? parseInt(grid.columns, 10) + : grid.columns; + return { + ...grid, + label: grid.label ?? name, + columns + }; + }).filter((grid): grid is NormalizedGridDefinition => typeof grid.columns === "number" && !Number.isNaN(grid.columns)); + + const styles = currentlySelectedBreakpoint ? { + width: currentlySelectedBreakpoint.width, + transform: window.innerWidth < currentlySelectedBreakpoint.width ? + `translate(-50%) scale(${window.innerWidth / currentlySelectedBreakpoint.width})` : 'translate(-50%)', + height: currentlySelectedBreakpoint.height + } : { + width: isPropsInspectorOpen ? 'calc(100% - 50vw - 2rem)' : '100%', + minWidth: isPropsInspectorOpen ? 'calc(100% - 400px - 2rem)' : '100%' + }; + + return { + isVisible: selectors.grid.isVisible(state), + isLocked, + styles, + grids + }; +})(PreviewGridOverlayC); diff --git a/Resources/Private/JavaScript/containers/styleguide/main/preview-frame/index.tsx b/Resources/Private/JavaScript/containers/styleguide/main/preview-frame/index.tsx index 19d555fc..d0859627 100644 --- a/Resources/Private/JavaScript/containers/styleguide/main/preview-frame/index.tsx +++ b/Resources/Private/JavaScript/containers/styleguide/main/preview-frame/index.tsx @@ -6,6 +6,7 @@ import cx from "classnames"; import { selectors, actions, State } from "../../../../state"; import { visibility } from "../../../../components"; +import { PreviewGridOverlay } from "./grid-overlay"; import style from "./style.module.css"; @@ -47,8 +48,7 @@ class PreviewFrameC extends PureComponent { iframeLoaded = () => { const { onLoad, setCurrentHtml, sourceQuerySelector } = this.props; const html = this.iframe?.contentDocument?.querySelector(sourceQuerySelector)?.innerHTML ?? ''; - const htmlWithoutGrids = html.replace(/]*><\/monocle-layout-grid>/g, ""); - setCurrentHtml(htmlWithoutGrids); + setCurrentHtml(html); onLoad(); } @@ -57,6 +57,7 @@ class PreviewFrameC extends PureComponent { return (
+