tfwright / live_admin

Low-config admin UI for Phoenix apps, built on LiveView
MIT License
251 stars 22 forks source link

Custom components for various 'types' or specified fields. #17

Closed harmon25 closed 1 year ago

harmon25 commented 1 year ago

I have some dates and also image urls i would like to render more precisely with live_admin in the table anyway.

A super handy and powerful feature would being able to supply custom MFAs that could be used to either format a string, or more powerfully be a heex function component with a defined API.

This would be an opt in feature - and not require any breaking changes, I do not thing.

This may need to be a compile time app config to ensure it is available at compile time.

I am thinking something like this, thoughts?

config :live_admin,
   # create a keyword list or something to 'register' custom components
   custom_components: [{:custom_utc_datetime, MFA}],
   # defines a global override for all  `utc_datetime` type
   content_overrides: %{utc_datetime:  :custom_utc_datetime} 
   live_admin("/",
    resources: [
        {
          LiveAdminTest.User,
          # override rendering of specified fields with the registered components
          field_overrides: %{inserted_at: :custom_utc_datetime} 
        },
        LiveAdminTest.Post
      ]
    )

I might create a PR with a proof of concept - but would like to bounce around some ideas first! Maybe you have already thought about this?

tfwright commented 1 year ago

I think customizable "formatting" for data types definitely makes sense, so I would suggest we start there. These could work just by pattern matching on whatever the record returns for the field, rather than just converting everything to a string.

I don't think it would need to be compile time config because the formatter wouldn't be retrieved until that function is called, when the table view renders.

I'm less sure about the use case for field specific formatting. What would be the reason to display insert_at timestamps differently than updated_at, for example?

harmon25 commented 1 year ago

it would be more for opaque strings - think a URL to an avatar image that is just a :string but i want to wrap it in a custom image component to actually render it inline, or maybe its' a hexcode for a colour - also a string but could be rendered as a little coloured circle. But this would not be how all strings should be handled only that specific field of a resource.

this also assumes we are not rendering just strings, but instead passing the information into a component as a prop, and letting it decide what to do, and returning some 'safe' html - i think if we were to allow custom components it might have to be at compile time for heex to work i am not sure though...

tfwright commented 1 year ago

I see, those examples make sense. I can see how custom components would be a lot more powerful for those cases.

One thought I had is that using custom Ecto Types would allow for a lot more flexibility in handling what was returned, by example, returning an Avatar struct instead of a plain string for that field. Granted, this would potentially impact the rest of the system. Not sure if there might be other drawbacks?

And in terms of what was rendered, I think we could probably support simple html safe strings to say, handle images/svgs?

Just brainstorming, I would like to avoid expanding the component override system except as a last resort, since pretty much anything is already possible just by overriding the entire index view.

harmon25 commented 1 year ago

Ok here is a pretty simple override for the field contents - take a look at this commit - https://github.com/harmon25/live_admin/commit/fa8d638485bc6cbb9215f5225cc3f8d59178968b

at a high level, it adds a new component override named :renderer that expects a module that implements a field function that works pretty much just like any other function component.

It gets passed 3 assigns, the field content (not printed) the field name, and the record itself.

That branch also includes my other lv 18 changes, so might want to just riff off of it if you like the idea.

Could solve all the mentioned use cases with this implementation I believe. Just pattern matching!

tfwright commented 1 year ago

I started playing around with a plain function based approach here: https://github.com/tfwright/live_admin/pull/18 I think it keeps the API/config a bit simpler so I'm leaning in that direction, since users can always override the whole view if they choose.

Also, it still allows for injecting html as the clickable email example in the PR demonstrates, but that actually raises an issue with both solutions, since the table currently wraps the cell contents in some html needed to handle the cell copying and in the case of the id cell, links to editing and other actions. Rendering a link breaks that behavior in an awkward way and there are probably other problematic cases as well.

Currently I am working around that by only rendering the html if the cell render is plain text, but I'm wondering if it might be better to either enforce the plain text requirement, or modify/move the affected UI logic so that it's more compatible with custom rendered cell contents...probably the latter because the former would excessively impact the value of this feature....

tfwright commented 1 year ago

Resolved in https://github.com/tfwright/live_admin/pull/18