DEV Community

david duymelinck
david duymelinck

Posted on

Creating a Symfony Tui application

After my first post about Symfony Tui, I really wanted to kick the tires of the component.

The idea

Non technical people don't like it when applications give them too much rules. And one of the biggest offenders for me are markdown driven applications.
So I wanted to make an application where they can add a directory where the markdown files are stored.

For the interface there are two parts, the filesystem part and the editor part.
The filesystem part has a filetree and an option to add new files. A sub function of the filetree is the possibility to delete a file.
The editor part has two tabs; markdown and frontmatter. It is possible to switch between them. And by pressing return the content of both editors is saved to the selected file in the filetree.

The good

The command does not have a lot of content, because I could extract the TUI code to a separate class.

Working with containers feels like divitis, but I would have never suspected that it would one day feel like a good thing.

$markdownTab = new TextWidget('Markdown');
$markdownTab->setId(self::MARKDOWN_TAB_ID);
$markdownTab->addStyleClass('bold');
$frontmatterTab = new TextWidget('Frontmatter')->setId(self::FRONTMATTER_TAB_ID);

$tabs = new ContainerWidget();
$tabs->setStyleClasses(['flex-row', 'gap-1']);
$tabs->setId(self::CONTENT_TABS_ID);
$tabs->add($markdownTab);
$tabs->add($frontmatterTab);

$markdownEditor = new EditorWidget()->setId(self::MARKDOWN_EDITOR_ID);
$markdownEditor->onSubmit($this->saveEditorsToFile(...));
$frontmatterEditor = new EditorWidget()->setId(self::FRONTMATTER_EDITOR_ID);
$frontmatterEditor->setStyleClasses(['hidden']);
$frontmatterEditor->onSubmit($this->saveEditorsToFile(...));

$editors = new ContainerWidget();
$editors->setId(self::CONTENT_EDITORS_ID);
$editors->add($markdownEditor);
$editors->add($frontmatterEditor);

$content = new ContainerWidget();
$content->setId(self::CONTENT_ID);
$content->setStyleClasses(['flex-col', 'gap-1']);
$content->add($tabs);
$content->add($editors);
Enter fullscreen mode Exit fullscreen mode

This is my poor mans attempt of a tabs widget, which is coming in a future Tui component PR.

The style classes are working because the Tui component comes with a TailwindStylesheet. To use the class you can add it to the Tui constructor as an argument or use the addStyleSheet method.
As I mentioned in my previous post if you like the class extend it and add your own rules, to have the best of both worlds.

Having the option to set ids for each widget is great. As you can see in the example I'm using class constants to keep them organized.

The on method and the more specified onX methods make it easy to add widget and TUI behavior.

The bad

Because the TUI runs in an eventloop some of the actions are going to require the need to dispatch them in order to see the change instantly.
As far as I figured it out all the methods from the widgets that can dispatch are private.

In my case I wanted to update the filetree as soon as a new file is submitted or when a file got deleted.

$newFile = new InputWidget();
$newFile->setId(self::NEW_FILE_ID);
$newFile->setPrompt('New File (can contain path): ');
$newFile->onSubmit(function(SubmitEvent $event) {
    if($event->isEmpty()) {
        return;
    }

    $input = $event->getValue();

    if(!str_ends_with($input, '.md')) {
       $input .=  '.md';
    }

    touch($this->source . '/' . $input);

    $event->getTarget()->setValue('');

    $sourceFiles = $this->tui->getById(self::SOURCE_FILES_ID);
    $sourceFiles->onFileAction();
    $this->tui->handleInput(self::NAV_SOURCE_FILES);
});
Enter fullscreen mode Exit fullscreen mode

I needed to create a child class of SelectListWidget to add the onFileAction method.

public function onFileAction() : void
{
    $items = $this->getSourceFiles();

    $this->myItems = $items;
    $this->myItemsCount = count($this->myItems);

    $this->setItems($items);

    $selectedItem = $this->myItems[0] ?? null;
    if (null !== $selectedItem) {
       $this->dispatch(new SelectionChangeEvent($this, $selectedItem));
    }
}
Enter fullscreen mode Exit fullscreen mode

As you can see there is more going in the method than just a dispatch.
But it feels like there is an easier way to get it working.
Of course there is also the possibility I'm just doing it wrong.

The ugly

I don't think it is completely the blame of the Tui component but shortcuts are the straw that broke the camel's back for me.

I'm using PHPStorm and a WSL debian terminal to manually test my application.

I needed to add a whole slew of shortcuts as a line in the command just to remember them myself.

Shortcuts: ctrl+b -> filetree (return to display content, ctrl+i -> delete file) ; ctrl+d -> new file (return to create) ; ctrl+e -> markdown ; ctrl+r -> frontmatter (both editors return to save) ; ctrl+j -> quit.'

I wanted similar shortcuts close to each other, or in the case of ctrl+d and ctrl+b look alike.

And then I started testing the editors.

My first eyebrow raise came from using ctrl+k to focus the frontmatter editor. It did focus but it also cleared the content.
To be honest I only had a single line of frontmatter.
It wasn't until I tried ctrl+m to figure out the editor also uses the ctrl+k shortcut.

My next test was adding more lines. Now that I was aware of the editor shortcuts I found out it is shift+return. That doesn't work for me.
So I tried other keys in combination with return, and If discovered there is a combination that deletes the selected file in the filetree while the editor is the focused widget. The shortcut for deleting a file is ctrl+i, and I wasn't even close pressing i.

Conclusion

Frontend is hard with all those limitations, that is why I rather do backend work.
But it was a fun time trying to create something with a new toy.

The full code is on Github. I didn't want to make it a repository because it is just for now code.
There are many parts left undeveloped like creating files in a directory and showing the error and success messages.

While Javascript and Python based CLI tools are ruling the world, I do think with the Tui component PHP has found an answer.

Top comments (0)