Skip to content

Commit

Permalink
refactored project according our previous discussions
Browse files Browse the repository at this point in the history
  • Loading branch information
Halleck45 committed Feb 1, 2025
1 parent ba179e1 commit f35483a
Show file tree
Hide file tree
Showing 23 changed files with 530 additions and 214 deletions.
27 changes: 27 additions & 0 deletions src/Toolkit/registry/default/registry.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"$schema": "https://ui.shadcn.com/schema/registry.json",
"name": "symfony",
"homepage": "https://symfony.com",
"items": [
{
"name": "badge",
"manifest": "components/Badge.json",
"type": "registry:component",
"title": "Badge",
"description": "A simple badge component",
"registryDependencies": [],
"files": [],
"code": "{%- props variant = 'default', outline = false -%}\n{%- set style = html_cva(\n base: 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',\n variants: {\n variant: {\n default: \"border-transparent bg-primary text-primary-foreground hover:bg-primary/80\",\n secondary: \"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n destructive: \"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80\",\n },\n outline: {\n true: \"text-foreground bg-white\",\n }\n },\n compoundVariants: [{\n variant: ['default'],\n outline: ['true'],\n class: 'border-primary',\n }, {\n variant: ['secondary'],\n outline: ['true'],\n class: 'border-secondary',\n }, {\n variant: ['destructive'],\n outline: ['true'],\n class: 'border-destructive',\n },]\n) -%}\n\n<div\n class=\"{{ style.apply({variant, outline}, attributes.render('class'))|tailwind_merge }}\"\n {{ attributes.defaults({}).without('class') }}\n>\n {% block content %}{% endblock %}\n</div>\n"
},
{
"name": "button",
"manifest": "components/Button.json",
"type": "registry:component",
"title": "Button",
"description": "A Button component",
"registryDependencies": [],
"files": [],
"code": ",<button {{ attributes.without('class') }}\n class=\"{{ (' ' ~ attributes.render('class'))|tailwind_merge }}\"\n>\n {% block content %}Button{% endblock %}\n</button>\n"
}
]
}
56 changes: 27 additions & 29 deletions src/Toolkit/src/Command/UxToolkitInstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist;
use Symfony\UX\Toolkit\Compiler\TwigComponentCompiler;
use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentifier;
use Symfony\UX\Toolkit\ComponentRepository\RepositoryFactory;
use Symfony\UX\Toolkit\ComponentRepository\CurrentTheme;
use Symfony\UX\Toolkit\Registry\RegistryFactory;

/**
* @author Jean-François Lépine
Expand All @@ -35,8 +34,8 @@
class UxToolkitInstallCommand extends Command
{
public function __construct(
private readonly RepositoryFactory $repositoryFactory,
private readonly ComponentIdentifier $componentIdentifier,
private readonly CurrentTheme $currentTheme,
private readonly RegistryFactory $registryFactory,
private readonly TwigComponentCompiler $compiler,
) {
parent::__construct();
Expand All @@ -58,52 +57,51 @@ protected function configure(): void
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$filesystem = new Filesystem();

$name = $input->getArgument('component');
// Download sources, or get them from vendors
$repository = $this->currentTheme->getIdentity();

try {
$component = $this->componentIdentifier->identify($name);
$io->info(
\sprintf(
'Downloading medata for %s/%s..',
$repository->getVendor(),
$repository->getPackage(),
)
);

// Get the correct source (remote or official)
$repository = $this->repositoryFactory->factory($component);
} catch (\Throwable $e) {
$io->error($e->getMessage());
$name = $input->getArgument('component');
$finder = $this->currentTheme->getRepository()->fetch($repository);
$registry = $this->registryFactory->create($finder);

return Command::FAILURE;
}
if (!$registry->has($name)) {
$io->error(\sprintf('The component "%s" does not exist.', $name));

if ($io->isVerbose()) {
$io->text('Component information:');
$io->table(['Vendor', 'Package', 'Version', 'Name'], [
[$component->getVendor(), $component->getPackage(), $component->getVersion(), $component->getName()],
]);
return Command::FAILURE;
}
$component = $registry->get($name);

$destination = $input->getOption('destination');
try {
$this->compiler->compile($component, $repository, $input->getOption('destination'));
$io->info(\sprintf('Installing component "%s"...', $component->name));
$this->compiler->compile($component, $destination);
} catch (TwigComponentAlreadyExist $e) {
if (!$input->isInteractive()) {
$io->error(\sprintf('The component "%s" already exists.', $component->getName()));
$io->error(\sprintf('The component "%s" already exists.', $component->name));

return Command::FAILURE;
}

if (!$io->confirm(
\sprintf('The component "%s" already exists. Do you want to overwrite it?', $component->getName())
\sprintf('The component "%s" already exists. Do you want to overwrite it?', $component->name)
)) {
return Command::FAILURE;
}

// again
$this->compiler->compile($component, $repository, $input->getOption('destination'));
}

if ($io->isVerbose()) {
$io->text(\sprintf('The component "%s" has been installed in "%s".', $component->getName(), $filename));
$this->compiler->compile($component, $destination);
}

$io->success(\sprintf('The component "%s" has been installed.', $component->getName()));
$io->success(\sprintf('The component "%s" has been installed.', $component->name));

return Command::SUCCESS;
}
Expand Down
14 changes: 5 additions & 9 deletions src/Toolkit/src/Compiler/TwigComponentCompiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@

use Symfony\Component\Filesystem\Filesystem;
use Symfony\UX\Toolkit\Compiler\Exception\TwigComponentAlreadyExist;
use Symfony\UX\Toolkit\ComponentRepository\ComponentIdentity;
use Symfony\UX\Toolkit\ComponentRepository\ComponentRepository;
use Symfony\UX\Toolkit\Registry\RegistryItem;

/**
* @author Jean-François Lépine
Expand All @@ -24,16 +23,15 @@
class TwigComponentCompiler
{
public function __construct(
private readonly string $theme,
private readonly string $prefix,
) {
}

public function compile(
ComponentIdentity $component,
ComponentRepository $repository,
RegistryItem $item,
string $directory,
): void {

$filesystem = new Filesystem();
if (!$filesystem->exists($directory)) {
$filesystem->mkdir($directory);
Expand All @@ -42,15 +40,13 @@ public function compile(
$filename = implode(\DIRECTORY_SEPARATOR, [
$directory,
$this->prefix,
$component->getName().'.html.twig',
$item->name.'.html.twig',
]);

if ($filesystem->exists($filename)) {
throw new TwigComponentAlreadyExist();
}

$content = $repository->getContent($component); // @todo we should probably inject theme here

$filesystem->dumpFile($filename, $content);
$filesystem->dumpFile($filename, $item->code);
}
}
41 changes: 0 additions & 41 deletions src/Toolkit/src/ComponentRepository/ComponentIdentifier.php

This file was deleted.

4 changes: 3 additions & 1 deletion src/Toolkit/src/ComponentRepository/ComponentRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@

namespace Symfony\UX\Toolkit\ComponentRepository;

use Symfony\Component\Finder\Finder;

/**
* @author Jean-François Lépine
*
* @internal
*/
interface ComponentRepository
{
public function getContent(ComponentIdentity $component): string;
public function fetch(RepositoryIdentity $component): Finder;
}
42 changes: 42 additions & 0 deletions src/Toolkit/src/ComponentRepository/CurrentTheme.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\UX\Toolkit\ComponentRepository;

/**
* @author Jean-François Lépine
*
* @internal
*/
final class CurrentTheme
{
private ComponentRepository $repository;
private RepositoryIdentity $identity;

public function __construct(
string $theme,
RepositoryFactory $repositoryFactory,
RepositoryIdentifier $repositoryIdentifier,
) {
$this->identity = $repositoryIdentifier->identify($theme);
$this->repository = $repositoryFactory->factory($this->identity);
}

public function getRepository(): ComponentRepository
{
return $this->repository;
}

public function getIdentity(): RepositoryIdentity
{
return $this->identity;
}
}
66 changes: 60 additions & 6 deletions src/Toolkit/src/ComponentRepository/GithubRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

namespace Symfony\UX\Toolkit\ComponentRepository;

use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Contracts\HttpClient\HttpClientInterface;

Expand All @@ -21,26 +23,78 @@
*/
class GithubRepository implements ComponentRepository
{
private readonly \ArrayObject $manifest;

public function __construct(
private readonly Filesystem $filesystem,
private readonly ?HttpClientInterface $httpClient = null,
) {
if (!class_exists(HttpClient::class)) {
throw new \LogicException('You must install "symfony/http-client" to use ux-toolkit with remote component. Try running "composer require symfony/http-client".');
}

if (!class_exists(\ZipArchive::class)) {
throw new \LogicException('You must have the Zip extension installed to use ux-toolkit with remote components.');
}
}

public function getContent(ComponentIdentity $component): string
public function fetch(RepositoryIdentity $component): Finder
{
$url = \sprintf(
'https://raw.githubusercontent.com/%s/%s/%s/templates/components/%s.html.twig',
// download a zip file of the github repository, place it in a temporary directory in cache
$zipUrl = \sprintf(
'http://github.com/%s/%s/archive/%s.zip',
$component->getVendor(),
$component->getPackage(),
$component->getVersion(),
$component->getName()
);

$response = $this->httpClient->request('GET', $url);
$destination = $this->getCacheDir();
$zipFile = $destination.'/'.basename($zipUrl);

$response = $this->httpClient->request('GET', $zipUrl, [
'sink' => $zipFile,
]);

// Ensure the request was successful
if (200 !== $response->getStatusCode()) {
throw new \RuntimeException(\sprintf('Failed to download the file from "%s".', $zipUrl));
}

// Ensure response contains valid github headers
$headers = $response->getHeaders();
if (!isset($headers['content-type']) || !\in_array('application/zip', $headers['content-type'])) {
throw new \RuntimeException(\sprintf('The file from "%s" is not a valid zip file.', $zipUrl));
}

// Flush the response to the file
$this->filesystem->dumpFile($zipFile, $response->getContent());

// unzip the file
$zip = new \ZipArchive();
$zip->open($zipFile);
$zip->extractTo($destination);
$zip->close();

$rootDir = $destination;
$finder = new Finder();
$finder->files()->in($rootDir);

return $finder;
}

public function getCacheDir(): string
{
return $this->createTmpDir('cache');
}

private function createTmpDir(string $type): string
{
$dir = sys_get_temp_dir().'/ux_toolkit/'.uniqid($type.'_', true);

if (!$this->filesystem->exists($dir)) {
$this->filesystem->mkdir($dir);
}

return $response->getContent();
return $dir;
}
}
13 changes: 3 additions & 10 deletions src/Toolkit/src/ComponentRepository/OfficialRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,11 @@
*/
class OfficialRepository implements ComponentRepository
{
public function getContent(ComponentIdentity $component): string
public function fetch(RepositoryIdentity $repository): Finder
{
$finder = new Finder();
$finder->files()->in(__DIR__.'/../../templates/default/components/')->name($component->getName().'.html.twig');
if (!$finder->hasResults()) {
throw new \InvalidArgumentException(\sprintf('The component "%s" does not exist', $component->getName()));
}
$finder->in(\sprintf(__DIR__.'/../../registry/%s', $repository->getPackage()));

foreach ($finder as $file) {
return $file->getContents();
}

throw new \InvalidArgumentException(\sprintf('The component "%s" does not exist', $component->getName()));
return $finder;
}
}
Loading

0 comments on commit f35483a

Please sign in to comment.