feature/branching #11
@ -81,15 +81,6 @@ class SnipController extends AbstractController
|
|||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
$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 = $this->createForm(SnipType::class, $snip);
|
||||||
$form->add('Save', SubmitType::class);
|
$form->add('Save', SubmitType::class);
|
||||||
if ($snip->getId()) {
|
if ($snip->getId()) {
|
||||||
@ -98,11 +89,6 @@ class SnipController extends AbstractController
|
|||||||
|
|
||||||
$form->handleRequest($request);
|
$form->handleRequest($request);
|
||||||
if ($form->isSubmitted() && $form->isValid()) {
|
if ($form->isSubmitted() && $form->isValid()) {
|
||||||
if (!$isLatest) {
|
|
||||||
return $this->redirectToRoute('snip_single', [
|
|
||||||
'snip' => $snip->getId(),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
$this->repository->save($snip);
|
$this->repository->save($snip);
|
||||||
$contentService->update(
|
$contentService->update(
|
||||||
$snip,
|
$snip,
|
||||||
@ -112,9 +98,7 @@ class SnipController extends AbstractController
|
|||||||
|
|
||||||
$this->addFlash('success', sprintf('Snip "%s" saved', $snip));
|
$this->addFlash('success', sprintf('Snip "%s" saved', $snip));
|
||||||
|
|
||||||
return $this->redirectToRoute('snip_single', [
|
return $this->redirectToRoute('snip_single', ['snip' => $snip->getId()]);
|
||||||
'snip' => $snip->getId(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->render('snip/edit.html.twig', [
|
return $this->render('snip/edit.html.twig', [
|
||||||
|
@ -5,6 +5,7 @@ namespace App\Controller;
|
|||||||
use App\Entity\Snip;
|
use App\Entity\Snip;
|
||||||
use App\Entity\SnipContent;
|
use App\Entity\SnipContent;
|
||||||
use App\Security\Voter\SnipVoter;
|
use App\Security\Voter\SnipVoter;
|
||||||
|
use App\Service\SnipContent\FlowChartTreeBuilder;
|
||||||
use App\Service\SnipContent\SnipContentService;
|
use App\Service\SnipContent\SnipContentService;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
@ -18,12 +19,16 @@ class VersionController extends AbstractController
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
#[Route('/', name: '_index')]
|
#[Route('/', name: '_index')]
|
||||||
public function index(Snip $snip): Response
|
public function index(Snip $snip, FlowChartTreeBuilder $builder): Response
|
||||||
{
|
{
|
||||||
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
$this->denyAccessUnlessGranted(SnipVoter::EDIT, $snip);
|
||||||
|
|
||||||
|
$buildTree = $builder->buildTree($snip);
|
||||||
|
dump($buildTree);
|
||||||
return $this->render('version/index.html.twig', [
|
return $this->render('version/index.html.twig', [
|
||||||
'snip' => $snip,
|
'snip' => $snip,
|
||||||
|
// 'versions' => new GitTreeBuilder($snip)->buildTree($snip->getSnipContents()->first()),
|
||||||
|
'versions' => $buildTree,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,11 @@ class SnipContent
|
|||||||
$this->children = new ArrayCollection();
|
$this->children = new ArrayCollection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function __toString(): string
|
||||||
|
{
|
||||||
|
return $this->name ?? $this->id->toBase32();
|
||||||
|
}
|
||||||
|
|
||||||
public function getId(): ?Ulid
|
public function getId(): ?Ulid
|
||||||
{
|
{
|
||||||
return $this->id;
|
return $this->id;
|
||||||
|
@ -6,7 +6,6 @@ use App\Entity\Snip;
|
|||||||
use App\Service\SnipParser\ParserFactory;
|
use App\Service\SnipParser\ParserFactory;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
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\TextareaType;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
@ -36,6 +35,7 @@ class SnipType extends AbstractType
|
|||||||
->add('contentName', TextType::class, [
|
->add('contentName', TextType::class, [
|
||||||
'label' => 'Change description (optional)',
|
'label' => 'Change description (optional)',
|
||||||
'mapped' => false,
|
'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">
|
<a href="{{ path('content_compare', {to: snip.activeVersion.id}) }}" class="btn btn-info">
|
||||||
<i class="fa fa-left-right"></i> Compare
|
<i class="fa fa-left-right"></i> Compare
|
||||||
</a>
|
</a>
|
||||||
<br><br>
|
<pre class="mermaid">
|
||||||
<div class="list-group">
|
flowchart BT
|
||||||
{% for version in snip.snipContents|reverse %}
|
{% for versionData in versions %}
|
||||||
<a class="list-group-item {% if version.id == snip.activeVersion.id %}list-group-item-success{% endif %} d-flex justify-content-between"
|
{{~ versionData ~}}
|
||||||
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>
|
|
||||||
{% endfor %}
|
{% 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 %}
|
{% endblock %}
|
Loading…
x
Reference in New Issue
Block a user