Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live preview #434

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open

Live preview #434

wants to merge 45 commits into from

Conversation

johnpuddephatt
Copy link

@johnpuddephatt johnpuddephatt commented Feb 5, 2023

A bit of background to my use-case/thinking can be seen here on Twitter. In short I want to provide a visually intuitive interface for content managers when dealing with "marketing" pages on a site I'm about to start building.

Initially I was playing around with a new "preview" field that would work with Nova Flexible Content (NFC) but the extra "layer" between NFC and the fields introduced challenges, so I've looked at incorporating the functionality directly into NFC.

This is being shared as work in progress. There are still a number of things to improve/iron out, but I think the progress made is promising (or at least interesting!) and worth sharing in case anyone wants to take a look/contribute.

Feb-05-2023.18-06-44.mov

Benefits

  • Visually intuitive interface similar to that offered by platforms like StoryBlok or TinaCMS, but all the benefits of native Nova fields and Nova Flexible Content functionality.
  • Feels very responsive. Of course there are lots of factors here, including hosting/latency/connection speed, complexity of Blade template, etc. but in my own testing it has felt pretty snappy. There is debouncing in place to limit server requests, this could be adjusted if necessary.
  • Use the same Blade view for your frontend and for previews in Nova
  • Easy to use – just provide a blade template name, a path to your frontend stylesheet, and ensure the Blade template renders variables directly (e.g. {{ $title }} not {{ $page->title }}). Achieving this might be as simple as ensuring your @includes are something like @include('flexible.hero', $page) as then $page->title will be available within your view as $title. Of course it all depends on how your views are written.

Instructions for use

Enable preview on your Flexible field and provide the path to the frontend stylesheet you want your preview to use, e.g. (if using Vite):

Flexible::make('Content')
    ->enablePreview(\Illuminate\Support\Facades\Vite::asset("resources/css/app.css"))

Create a Blade view at resources/views/flexible/{LayoutName}.blade.php

Optionally define additional data you want to be passed to your layout:

public function previewData()
    {
        return [
            "posts" => \App\Models\Post::take(3)->get(),
        ];
    }

The fields defined on your layout are passed to the previewData method, so if you have a field on your layout named "Number of posts" you can do the following, which opens up some interesting possibilities:

public function previewData($parameters)
    {
        return [
            "posts" => \App\Models\Post::take($parameters["number_of_posts"])->get(),
        ];
    }
Feb-05-2023.15-45-05.mov

Limitations

  • I've not tested this with all fields, however basic input fields work fine, and I've done some work to handle Image/File fields; albeit in a rough/overly simplistic way at the minute.
  • Some fields don't work well in the limited space offered by the sidebar, e.g. ebess/advanced-media-library. In some cases the fix could be fairly straightforward if other maintainers are open to PRs that make their fields work better in/adapt to smaller spaces.

To do

  • Consider security implications. Uses XHR to get the preview HTML, currently this route has no security checks so is probably available to any authenticated Nova user which could easily expose sensitive data. Shouldn't be too hard to tighten this up, but still may need to warn people about risks, e.g. consider carefully what is provided through previewData() or logic within Blade templates.
  • Ensure previewData() can be set on layouts that are defined inline not just in a layout file (i.e. using withMeta)
  • Consider switching to base64 previews of images instead of storing temporary files on server OR deal with cleaning up temporary images. The challenge with base64 images is to keep request sizes small, i.e. avoid sending base64 strings back and forth constantly.
    – I would love to be able to divide fields into 'tabs' or even accordions, in much the same way as the Wordpress sidebar does. This would allow, for example to separate out "content" fields – title, description etc – from "settings" fields, e.g. background colour, number of posts, etc.
  • it could be useful to provide some additional responsive viewing options, possibly only in full screen mode – e.g. toggle mobile view. Currently when not in full screen view the iframe matches the parent window size but is scaled to fit the preview window. Otherwise when viewing on a 13" laptop the actual iframe size is approximately tablet size...

@johnpuddephatt
Copy link
Author

johnpuddephatt commented Feb 14, 2023

When it comes to rendering on the frontend, I was imagining doing something like the following:

    @foreach ($post->flexiblecontent as $block)
        @include('flexible.' . $block->view(), $block->getAttributes())
    @endforeach

However I've realised that the "$block" above is an instance of Whitecube\NovaFlexibleContent\Layouts\Layout not my specified layout (e.g. App\Nova\Flexible\Layouts\MyLayout.php) so I can't access the "view" that is defined on my layout class.

I'm going to get around this by naming my blade templates consistently with my Flexible Layout names, as then I can do:

@foreach ($production->content as $block)
    @include('flexible.' . $block->name(), $block->getAttributes())
@endforeach

This is simpler as it doesn't require a view to be defined on each layout, instead the view name is inferred from the title. However the trade-off is it's less flexible.

EDIT: I've now updated the PR in line with the above – a (Blade) view is no longer specified on the layout; instead there must be a blade template in /resources/views/flexible with a name that matches the layout name.

@voidgraphics
Copy link
Member

Thanks for the PR! This is a really cool idea. Let us know when it's ready for review.

@johnpuddephatt
Copy link
Author

@voidgraphics will do! I'm using it in a new project right now which is allowing me to test it out and make improvements.

For example I've realised my introduction of a method for passing additional data to the view is redundant because accessors on the layout can be utilised instead. This means passing Layout instances through to the Blade template when creating previews rather than just raw form data... I think will be better in the long term as it keeps the preview and the frontend more closely aligned which should mean less change of them diverging.

@johnpuddephatt
Copy link
Author

Still got a few things to figure out, but based on my use of this in a current project I'm continuing to evolve this and I think it's progressing into something I can see myself using a lot.

An update!

One of my initial aims was to move away from "form overload" for editors, and while my initial approach (shown in the videos above) felt like progress because it introduced content previews, in practice there were still a lot of forms to look at, and when those forms were long this was a problem because it increased the height of each layout and created something that felt quite disjointed. I considered making the form in the "sidebar" scrolling (i.e. overflow-y-auto) but this then meant that a layout with a small height would have a very small form, and scrolling elements within a scrolling page always feels a bit messy.

I was also struggling with showing the content preview and the form side-by-side on smaller screens. It felt great on my 27" external display but crowded on my 13" laptop screen.

So I've reworked the 'fullscreen' toggle I introduced to be for the whole field not for each individual layout. And I think it's working much better. Forms are only visible when the field is being viewed full screen.

In addition I've now also got previews displaying on the 'detail' view of a resource.

I'll keep refining this and if I reach a point where I think it's ready to review I'll be sure to say, but in the meantime I do welcome informal feedback from anyone interested in this!

Untitled.mp4

@johnpuddephatt
Copy link
Author

johnpuddephatt commented Feb 23, 2023

Image handling (for standard Nova Image/File/Avatar fields) is now improved. Images for use in previews are no longer stored automatically – instead you can add an imagePreviews() method to your layout class that creates the temporary file. This means you can do things like resize/crop images, which is really important for accurate layout previews as the wrong image aspect ratio can mess things up!

e.g. if I add the following method to MyFlexibleLayoutClass.php (where "main_image" is the name of the image field defined on my layout):

public function imagePreviews()
    {
        return [
            "main_image" => function ($file) {
                $filename = "temporary_uploads/" . $file->hashName() . ".jpg";
                \Illuminate\Support\Facades\Storage::disk("public")->put(
                    $filename,
                    \Intervention\Image\Facades\Image::make($file)
                        ->fit(720, 480)
                        ->encode("jpg", 75)
                );
                return $filename;
            },
           "second_image" => function($file) { .....
        ];
    }

Then the image for the field 'main_image' will be stored in a folder called temporary_uploads, resized to 720x480px.

This approach works. It doesn't feel too cumbersome and gives a lot of flexibility. However I can't decide if it's the right approach or if there's a better way, perhaps leveraging the store() method on the Image field itself. I'm not sure how straightforward this would be, and I also feel like I would need to override the storage path because I'd want to keep temporary files in one place to allow for them to be periodically cleaned up.

@toonvandenbos
Copy link
Member

Hi @johnpuddephatt,

This is starting to look very nice. I really appreciate your commitment on this matter.

I will be glad to give you feedback once you're feeling ready with this. I do not have much time at the moment, so I won't be able to give you an intermediate review, sorry about that.

All I can say right now it that I'm blown away by the results you showcased in the videos. Really love the user-experience you put together with the fullscreen editor and form sidebars. I think it is a good way to handle the available space issue.

Regarding the changes made to the Layouts class in order to handle file previews, this could maybe be an interesting lead for other file-related issues on this repository. I hope I'll be able to give it a little bit more thought soon.

Thanks again for your time and work, you rock!

@johnpuddephatt
Copy link
Author

@toonvandenbos thanks for the positive words! and thank you for the work on Nova Flexible Content as obviously what I've done wouldn't be possible without it.

No worries at all about you not having time to input, I run a small agency and know how tough it can be to find time for everything. There's no expectation on my part – I'd love it if this got refined and became available to more people but I'm using it right now in my own projects without issue so I'm happy.

I'm going to keep working on this as I've just used it in one project and have another starting that will involve a lot of long 'marketing' pages and I feel like this is going to work really well for it. It feels amazing getting these 'live previews' for 'free' – I'd be writing the Blade views and defining the layouts anyway, but now I get this extra functionality with (almost) no extra work.

@waylay
Copy link

waylay commented Apr 28, 2023

@johnpuddephatt you rock! Just tried it today and it blew my mind! This makes the nova-flexible-content so sexy and so easy to use by non-technical people!

@johnpuddephatt
Copy link
Author

@waylay awesome, thanks!

I'm moving onto a project that will make big use of this in the next couple of weeks, so I'm hoping to keep testing/improving it

@FabrizioKruisland
Copy link

@johnpuddephatt Do you have any update on this project?

@mateusz-peczkowski
Copy link

This looks incredible :) Good luck man! :)

@johnpuddephatt
Copy link
Author

@FabrizioKruisland not really! I'm using it in a few projects and it's working really well, I will continue to make improvements as I use it or in response to feedback from others. I'd encourage anyone interested to install the fork and have a play around!

I think the reality is that getting this working well enough to fold it into the main package is going to be quite hard, as getting it to work consistently with every other nova package will be tough (and in some cases would require other packages to make changes at their end).

@majdghithan
Copy link

Hello! can you provide us with a full usage example

@t1mofe1
Copy link

t1mofe1 commented Jan 15, 2024

Yo, this is amazing bro!! Good job 🔥

@ImkDenis
Copy link

ImkDenis commented Oct 9, 2024

Hey there! 👋

First off, we just want to say thank you for this amazing work! It has really made our lives a lot easier and saved us tons of time. 🙌

We ran into a small issue that others might have faced as well: when adding a layout with addLayout, the preview doesn't show up on the edit page. It seems that the preview isn't passed along unless we create a separate layout class and add a variable for it.

Maybe a solution could be to extend addLayout so we can directly specify a preview variable when adding the layout?

Thanks again for the great work and for making this available! 😄 Keep up the awesome work!

Cheers! 🍻

@johnpuddephatt
Copy link
Author

@ImkDenis hi! yes I know what you mean.

It could be done by adding another parameter to the Layout constructor, but to not be a breaking change I guess it would need to come last, which would mean when using addLayout you'd have to write:

->addLayout('Test', 'test', [
                    Text::make('Emoji'),
                    Text::make('Title'),
                    Textarea::make('Description'),
                    Text::make('Link text'),
                ], null, [], null, true)

where the final 'true' value is what is needed to enable preview for the layout. (the full list of attributes ion the constructor is $title,$name,$fields,$key,$attributes,$removeCallbackMethod,$preview).

This doesn't feel great, but I'm not expecting this to ever be merged, so maybe it doesn't matter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants