feature/branching #11
@ -81,15 +81,6 @@ class SnipController extends AbstractController
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
||||
|
||||
/**
|
||||
* Temporary solution to prevent editing of old versions
|
||||
* It technically fully works, but rendering the version history needs an update first
|
||||
*/
|
||||
$isLatest = $snip->getActiveVersion() === $snip->getLatestVersion();
|
||||
if (!$isLatest) {
|
||||
$this->addFlash('error', 'Snip is not the latest version, changes will not be saved.');
|
||||
}
|
||||
|
||||
$form = $this->createForm(SnipType::class, $snip);
|
||||
$form->add('Save', SubmitType::class);
|
||||
if ($snip->getId()) {
|
||||
@ -98,11 +89,6 @@ class SnipController extends AbstractController
|
||||
|
||||
$form->handleRequest($request);
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
if (!$isLatest) {
|
||||
return $this->redirectToRoute('snip_single', [
|
||||
'snip' => $snip->getId(),
|
||||
]);
|
||||
}
|
||||
$this->repository->save($snip);
|
||||
$contentService->update(
|
||||
$snip,
|
||||
@ -112,9 +98,7 @@ class SnipController extends AbstractController
|
||||
|
||||
$this->addFlash('success', sprintf('Snip "%s" saved', $snip));
|
||||
|
||||
return $this->redirectToRoute('snip_single', [
|
||||
'snip' => $snip->getId(),
|
||||
]);
|
||||
return $this->redirectToRoute('snip_single', ['snip' => $snip->getId()]);
|
||||
}
|
||||
|
||||
return $this->render('snip/edit.html.twig', [
|
||||
|
@ -5,6 +5,7 @@ namespace App\Controller;
|
||||
use App\Entity\Snip;
|
||||
use App\Entity\SnipContent;
|
||||
use App\Security\Voter\SnipVoter;
|
||||
use App\Service\SnipContent\FlowChartTreeBuilder;
|
||||
use App\Service\SnipContent\SnipContentService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
@ -18,12 +19,16 @@ class VersionController extends AbstractController
|
||||
) {}
|
||||
|
||||
#[Route('/', name: '_index')]
|
||||
public function index(Snip $snip): Response
|
||||
public function index(Snip $snip, FlowChartTreeBuilder $builder): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
||||
|
||||
$buildTree = $builder->buildTree($snip);
|
||||
dump($buildTree);
|
||||
return $this->render('version/index.html.twig', [
|
||||
'snip' => $snip,
|
||||
// 'versions' => new GitTreeBuilder($snip)->buildTree($snip->getSnipContents()->first()),
|
||||
'versions' => $buildTree,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,11 @@ class SnipContent
|
||||
$this->children = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name ?? $this->id->toBase32();
|
||||
}
|
||||
|
||||
public function getId(): ?Ulid
|
||||
{
|
||||
return $this->id;
|
||||
|
@ -6,7 +6,6 @@ use App\Entity\Snip;
|
||||
use App\Service\SnipParser\ParserFactory;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
@ -36,6 +35,7 @@ class SnipType extends AbstractType
|
||||
->add('contentName', TextType::class, [
|
||||
'label' => 'Change description (optional)',
|
||||
'mapped' => false,
|
||||
'required' => false,
|
||||
])
|
||||
;
|
||||
}
|
||||
|
37
src/Service/SnipContent/FlowChartTreeBuilder.php
Normal file
37
src/Service/SnipContent/FlowChartTreeBuilder.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\SnipContent;
|
||||
|
||||
use App\Entity\Snip;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
readonly class FlowChartTreeBuilder
|
||||
{
|
||||
public function __construct(
|
||||
private RouterInterface $router,
|
||||
) {}
|
||||
|
||||
public function buildTree(Snip $snip): array
|
||||
{
|
||||
$tree = [];
|
||||
|
||||
foreach ($snip->getSnipContents() as $content) {
|
||||
if ($content->getParent()) {
|
||||
$tree[] = sprintf('%s --> %s', $content->getParent(), $content);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($snip->getSnipContents() as $content) {
|
||||
$tree[] = sprintf(
|
||||
'click %s href "%s"',
|
||||
$content,
|
||||
$this->router->generate('version_set', ['snip' => $snip->getId(), 'version' => $content->getId()])
|
||||
);
|
||||
$tree[] = sprintf('%s@{ shape: rounded }', $content);
|
||||
}
|
||||
|
||||
$tree[] = sprintf('class %s active', $snip->getActiveVersion());
|
||||
|
||||
return $tree;
|
||||
}
|
||||
}
|
61
src/Service/SnipContent/GitTreeBuilder.php
Normal file
61
src/Service/SnipContent/GitTreeBuilder.php
Normal file
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Service\SnipContent;
|
||||
|
||||
use App\Entity\Snip;
|
||||
use App\Entity\SnipContent;
|
||||
|
||||
class GitTreeBuilder
|
||||
{
|
||||
private string $activeBranch = 'main';
|
||||
|
||||
public function __construct(private readonly Snip $snip) {}
|
||||
|
||||
public function buildTree(SnipContent $content, string $branch = 'main'): array
|
||||
{
|
||||
$tree = [];
|
||||
if ($this->activeBranch !== $branch) {
|
||||
$tree[] = $this->checkout($branch);
|
||||
}
|
||||
$commit = sprintf('commit id:"%s"', $content);
|
||||
if ($this->snip->getActiveVersion() === $content) {
|
||||
$commit .= ' tag: "active" type: REVERSE';
|
||||
}
|
||||
$tree[] = $commit;
|
||||
|
||||
$first = true;
|
||||
foreach ($content->getChildren() as $child) {
|
||||
if (!$first) {
|
||||
$tree[] = $this->branch($child);
|
||||
$tree[] = $this->checkout($branch);
|
||||
}
|
||||
$first = false;
|
||||
}
|
||||
|
||||
$first = true;
|
||||
foreach ($content->getChildren() as $child) {
|
||||
if ($first) {
|
||||
$myBranch = $branch;
|
||||
} else {
|
||||
$tree[] = $this->checkout($child);
|
||||
$myBranch = $child;
|
||||
}
|
||||
$tree = array_merge($tree, self::buildTree($child, $myBranch));
|
||||
$first = false;
|
||||
}
|
||||
|
||||
return $tree;
|
||||
}
|
||||
|
||||
private function branch(string $branch): string
|
||||
{
|
||||
$this->activeBranch = $branch;
|
||||
return sprintf('branch %s', $branch);
|
||||
}
|
||||
|
||||
private function checkout(string $branch): string
|
||||
{
|
||||
$this->activeBranch = $branch;
|
||||
return sprintf('checkout %s', $branch);
|
||||
}
|
||||
}
|
@ -12,17 +12,35 @@
|
||||
<a href="{{ path('content_compare', {to: snip.activeVersion.id}) }}" class="btn btn-info">
|
||||
<i class="fa fa-left-right"></i> Compare
|
||||
</a>
|
||||
<br><br>
|
||||
<div class="list-group">
|
||||
{% for version in snip.snipContents|reverse %}
|
||||
<a class="list-group-item {% if version.id == snip.activeVersion.id %}list-group-item-success{% endif %} d-flex justify-content-between"
|
||||
href="{{ path('version_set', {version: version.id, snip: snip.id}) }}">
|
||||
<span>
|
||||
{{ include('generic/datetime.badge.html.twig', {datetime: version.id.dateTime}) }}
|
||||
{% if version.name %}{{ version.name }}{% endif %}
|
||||
</span>
|
||||
<span class="text-muted">{{ version.id }}</span>
|
||||
</a>
|
||||
<pre class="mermaid">
|
||||
flowchart BT
|
||||
{% for versionData in versions %}
|
||||
{{~ versionData ~}}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</pre>
|
||||
{% endblock %}
|
||||
|
||||
{% block js %}
|
||||
{{ parent() }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@11.6.0/dist/mermaid.min.js"></script>
|
||||
<script>
|
||||
mermaid.initialize({startOnLoad: true});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block css %}
|
||||
{{ parent() }}
|
||||
<style>
|
||||
.node rect {
|
||||
fill: var(--bs-secondary) !important;
|
||||
stroke: var(--bs-secondary-text-emphasis) !important;
|
||||
}
|
||||
.node span {
|
||||
color: var(--bs-light) !important;
|
||||
}
|
||||
.active rect {
|
||||
fill: var(--bs-success) !important;
|
||||
stroke: var(--bs-success-text-emphasis) !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user