h2oai / wave

Realtime Web Apps and Dashboards for Python and R
https://wave.h2o.ai
Apache License 2.0
3.9k stars 327 forks source link

Add support for third-party icons #580

Open lo5 opened 3 years ago

lo5 commented 3 years ago

Related to #297

Add mechanism to load and reference icon fonts (FA, etc.) in the icon attributes of cards and components.

lo5 commented 2 years ago

Since we already have the ability to add external stylesheets in master, any app that wants to use, say, FontAwesome, can load it from the CDN.

Maybe a fa: prefix to any icon attribute value can be special-cased to generate FA nodes instead of Fluent nodes (e.g. icon='fa:foo').

mturoci commented 2 years ago

The problem with different icon sets is they expect various markups, e.g.

FontAwesome

<i class="fab fa-android"></i>

MaterialDesign

<span class="material-icons">face</span>

Icofont

<i class="icofont-dart icofont-1x"></i>

This can change also among versions.

I would suggest introducing some kind of icon register mechanism, similar to this:

q.page['meta'] = ui.meta_card(stylesheets=[font_stylesheet_url], custom_icons=[
    ui.custom_icon(name='faAndroid', markup='<i class="fab fa-android"></i>'
]

This way we would free ourselves from the burden of supporting particular icon sets ourselves and let app devs to pick whatever icons / version they want.

Btw, Fluent provides a register mechanism for these cases so should be a no-brainer.

@lo5 wdyt?

lo5 commented 2 years ago

The Fluent register mechanism requires import { FontAwesomeIcon } from '@fortawesome/react-fontawesome', which probably means we need to bundle it with Wave at build-time (unless there's some way to pull assets at run time).

I like the icon register mechanism, but prefer it to use patterns rather than having to pre-register every icon in use individually, e.g. custom_icons=[ui.custom_icon(prefix='fa', markup='<i class="fab fa-${{name}}"></i>')] using our expression syntax.

Also consider that we now have the ability to expose arbitrary directories to the browser, so it should be possible to serve application-defined webfonts, scripts, stylesheets directly.

mturoci commented 2 years ago

The Fluent register mechanism requires import { FontAwesomeIcon } from '@fortawesome/react-fontawesome', which probably means we need to bundle it with Wave at build-time (unless there's some way to pull assets at run time).

Yes, this example is a specific FontAwesome implementation. However, we would use markup provided instead of a specific icon wrapper. We only need to register new icons with Fluent to make it work across all existing components.

unless there's some way to pull assets at run time

Little side node: Yes, this is totally doable and even encouraged (see code splitting / lazy loading). We should probably do this for most of our 3rd party libs - especially visualization ones. Currently, Wave loads all viz packages (g2, vega, d3) on load, but it is highly unlikely a user will use all types at once. People usually only stick with a single one for consistency purposes.

I like the icon register mechanism, but prefer it to use patterns rather than having to pre-register every icon in use individually, e.g. custom_icons=[ui.custom_icon(prefix='fa', markup='')] using our expression syntax.

Agree, having to specify every icon individually can be tedious. However, the problem is I am not aware of any way how to extract icon key names (e.g. fa-android) from woff or other font file.

However, the icon registration can be simply wrapped into a helper function that would fairly simplify the process:

def register_icon(name: str):
    return ui.custom_icon(name=name, markup='<i class=f"fab {name}"></i>'

Also consider that we now have the ability to expose arbitrary directories to the browser, so it should be possible to serve application-defined webfonts, scripts, stylesheets directly.

+1 on this