h2oai / wave

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

feat: Improve visibility of card commands button #2127 #2147

Closed marek-mihok closed 9 months ago

marek-mihok commented 9 months ago

The PR fulfills these requirements: (check all the apply)

After improvement: Screenshot 2023-10-02 at 13 28 16

Before: Screenshot 2023-10-02 at 13 28 01

Diff:

image

With all themes:

https://github.com/h2oai/wave/assets/23740173/53e79077-470e-4784-b9f1-ff64f839a974

Closes #2127

marek-mihok commented 9 months ago

Feel free to either fix it in this PR or within a new one

@mturoci, I'll fix it within current one. Do you want to shift the card content down if the CardMenu has some content (e.g. commands button like in this case)?

mturoci commented 9 months ago

shift the card content down if the CardMenu has some content

I don't think that would look good on all cards.

marek-mihok commented 9 months ago

@mturoci I've fixed the positioning of commands button for both ui.plot_card and ui.vega_card while keeping #1923 in mind.

With title and commands

image

With commands only

image

With title only

image

Without title and without commands

image

mturoci commented 9 months ago

Thanks @marek-mihok. Please go through the rest of the cards and make sure the context menu is rendered reasonably for each. The 2 posted above LGTM.

marek-mihok commented 9 months ago

Please go through the rest of the cards and make sure the context menu is rendered reasonably for each.

@mturoci sure.

For some cards (e.g. form card) it depends on its content:

image

Should we implement the fix for it anyway?

mturoci commented 9 months ago

Should we implement the fix for it anyway?

Sure. Just lift the menu up. We may not catch 100% of cases but should be okay for the majority.

marek-mihok commented 9 months ago

@mturoci I've tested all of our cards for commands button positioning and results are as follows:

https://github.com/h2oai/wave/assets/23740173/b7270250-ef57-42f7-b55b-013496208baa

Cards to be fixed:

toolbar
tall_info
section_card
profile_card
preview_card
markdown_card
chat_card
chatbot_card

Test example from screen recording:

import json
from h2o_wave import main, app, Q, ui, data, on, pack

choices = [
    ui.choice("default", "default"),
    ui.choice("h2o-dark", "h2o-dark"),
    ui.choice("one-dark-pro", "one-dark-pro"),
    ui.choice("monokai", "monokai"),
    ui.choice("nord", "nord"),
    ui.choice("winter-is-coming", "winter-is-coming"),
    ui.choice("fuchasia", "fuchasia"),
    ui.choice("nature", "nature"),
    ui.choice("solarized", "solarized"),
    ui.choice("oceanic", "oceanic"),
    ui.choice("ember", "ember"),
    ui.choice("lighting", "lighting"),
    ui.choice("kiwi", "kiwi"),
    ui.choice("benext", "benext"),
]

content = '''
Duis porttitor tincidunt justo ac semper. Vestibulum et molestie lectus. Proin vel eros a ex condimentum aliquam.
Sed accumsan tellus sit amet nulla ullamcorper. Suspendisse bibendum tristique sem, quis lacinia ex pulvinar quis.
'''

html = '''
<!DOCTYPE html>
<html>
<body>
  <h1>Hello World!</h1>
</body>
</html>
'''

menu = '''
<ol>
    <li>Spam</li>
    <li>Ham</li>
    <li>Eggs</li>
</ol>
'''

spec = '''
{
  "$schema": "https://vega.github.io/schema/vega/v5.json",
  "description": "A basic bar chart example, with value labels shown upon mouse hover.",
  "width": 500,
  "height": 250,
  "padding": 5,

  "data": [
    {
      "name": "table",
      "values": [
        {"category": "A", "amount": 28},
        {"category": "B", "amount": 55},
        {"category": "C", "amount": 43},
        {"category": "D", "amount": 91},
        {"category": "E", "amount": 81},
        {"category": "F", "amount": 53},
        {"category": "G", "amount": 19},
        {"category": "H", "amount": 87}
      ]
    }
  ],

  "signals": [
    {
      "name": "tooltip",
      "value": {},
      "on": [
        {"events": "rect:mouseover", "update": "datum"},
        {"events": "rect:mouseout",  "update": "{}"}
      ]
    }
  ],

  "scales": [
    {
      "name": "xscale",
      "type": "band",
      "domain": {"data": "table", "field": "category"},
      "range": "width",
      "padding": 0.05,
      "round": true
    },
    {
      "name": "yscale",
      "domain": {"data": "table", "field": "amount"},
      "nice": true,
      "range": "height"
    }
  ],

  "axes": [
    { "orient": "bottom", "scale": "xscale" },
    { "orient": "left", "scale": "yscale" }
  ],

  "marks": [
    {
      "type": "rect",
      "from": {"data":"table"},
      "encode": {
        "enter": {
          "x": {"scale": "xscale", "field": "category"},
          "width": {"scale": "xscale", "band": 1},
          "y": {"scale": "yscale", "field": "amount"},
          "y2": {"scale": "yscale", "value": 0}
        },
        "update": {
          "fill": {"value": "steelblue"}
        },
        "hover": {
          "fill": {"value": "red"}
        }
      }
    },
    {
      "type": "text",
      "encode": {
        "enter": {
          "align": {"value": "center"},
          "baseline": {"value": "bottom"},
          "fill": {"value": "#333"}
        },
        "update": {
          "x": {"scale": "xscale", "signal": "tooltip.category", "band": 0.5},
          "y": {"scale": "yscale", "signal": "tooltip.amount", "offset": -2},
          "text": {"signal": "tooltip.amount"},
          "fillOpacity": [
            {"test": "datum === tooltip", "value": 0},
            {"value": 1}
          ]
        }
      }
    }
  ]
}
'''

commands= [ui.command(name="command",label="Some command",icon="Heart")]

image = "https://images.pexels.com/photos/220453/pexels-photo-220453.jpeg?auto=compress&h=750&w=1260"

@app("/demo")
async def serve(q: Q):
    if not q.client.initialized:
        q.page["meta"] = ui.meta_card(box="", theme="default")
        q.page["example"] = ui.plot_card(
            box="1 1 4 5",
            title="",
            data=data("price low high", 8, rows=[
                (4, 50, 100),
                (6, 100, 150),
                (8, 150, 200),
                (16, 350, 400),
                (18, 400, 450),
                (10, 200, 250),
                (12, 250, 300),
                (14, 300, 350),
            ]),
            plot=ui.plot([ui.mark(type="interval", y="=price", x1="=low", x2="=high", y_min=0)]),
            commands=commands
        )
        q.page["form"] = ui.form_card(
            box="5 1 2 5",
            commands=commands,
            items=[ui.dropdown(name="theme", label="Theme", trigger=True, choices=choices, value="default")]
        )
        q.page['breadcrumbs'] = ui.breadcrumbs_card(
            box='7 1 3 1', 
            items= [
                ui.breadcrumb(name='#menu', label='Menu'),
                ui.breadcrumb(name='#submenu', label='Submenu'),
                ui.breadcrumb(name='#subsubmenu', label='Subsubmenu'),
            ], 
            commands=[ui.command(name="to_log_scale",label="Log Scale",icon="LineChart")]
        )
        q.page["post"] = ui.post_card(
            box="1 6 3 5",
            persona=ui.persona(title="John Doe", subtitle="Data Scientist", image=image, caption="caption"),
            commands=commands,
            items=[
                ui.inline(justify="end", items=[
                    ui.mini_buttons([
                        ui.mini_button(name="like", label="4", icon="Heart"),
                        ui.mini_button(name="comment", label="2", icon="Comment"),
                        ui.mini_button(name="share", label="1", icon="Share"),
                    ]),
                ]),
                ui.buttons(items=[
                    ui.button(name="like", label="Like"),
                    ui.button(name="comment", label="Comment"),
                    ui.button(name="share", label="Share"),
                ]),
            ],
            caption=content,
            aux_value="2h ago",
            image="https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260" # noqa
        )
        q.page["wide_article_preview"] = ui.wide_article_preview_card(
            box="4 6 6 5",
            name="wide_article_preview",
            persona=ui.persona(title="Jasmine Grand", subtitle="Marketing Executive",
                               image=image, caption="caption"),
            commands=commands,
            aux_value="2h ago",
            image="https://images.pexels.com/photos/1269968/pexels-photo-1269968.jpeg?auto=compress",
            title="Jasmine Grand",
            content=content,
            items=[
                ui.inline(justify="end", items=[
                    ui.mini_buttons([
                        ui.mini_button(name="like", label="4", icon="Heart"),
                        ui.mini_button(name="comment", label="2", icon="Comment"),
                        ui.mini_button(name="share", label="1", icon="Share"),
                    ]),
                ])
            ],
        )
        q.page['arcticle'] = ui.article_card(
            box='1 11 4 3',
            title='Title',
            items=[
                ui.mini_buttons([
                    ui.mini_button(name='like', label='4', icon='Heart'),
                    ui.mini_button(name='comment', label='2', icon='Blog'),
                    ui.mini_button(name='share', label='1', icon='Relationship'),
                ])
            ],
            content=content,
            commands=commands
        )
        q.page['canvas'] = ui.canvas_card(
            box='5 11 4 7',
            title='Sample Canvas',
            width=500,
            height=500,
            data=dict(),
            commands=commands
        )
        q.page['chat'] = ui.chat_card(
            box='1 14 4 4',
            title='Chat room',
            data=dict(),
            commands=commands
        )
        q.page['chatbot'] = ui.chatbot_card(
            box='1 18 4 5', 
            data=data('content from_user', t='list'), 
            name='chatbot',
            commands=commands
        )
        q.page['frame'] = ui.frame_card(
            box='5 18 2 2',
            title='Example',
            content=html,
            commands=commands
        )
        q.page['footer'] = ui.footer_card(
            box='1 23 -1 1', 
            caption='Made with 💛 by H2O Wave Team.',
            commands=commands
        )
        q.page['header'] = ui.header_card(
            box='1 24 -1 1',
            title='Transparent header',
            subtitle='And now for something completely different!',
            image='https://wave.h2o.ai/img/h2o-logo.svg',
            items=[ui.button(name='btn1', label='Button 1'),],
            secondary_items=[ui.textbox(name='search', icon='Search', width='300px', placeholder='Search...')],
            color='transparent',
            commands=commands
        )
        q.page['image'] = ui.image_card(
            box='1 25 2 4',
            title='An image',
            type='png',
            path='https://via.placeholder.com/600x400', 
            commands=commands
        )
        # q.page['large_stat'] = ui.large_stat_card(
        #     box='1 1 2 2',
        #     title='Large stat',
        #     value='=${{intl qux minimum_fraction_digits=2 maximum_fraction_digits=2}}',
        #     aux_value='={{intl quux style="percent" minimum_fraction_digits=1 maximum_fraction_digits=1}}',
        #     data=dict(qux=val, quux=pc),
        #     caption='Caption'
        # )
        q.page['list'] = ui.list_card(
            box='1 25 4 4',
            item_view='list_item1',
            item_props=pack(dict(title='=code', caption='=currency', value='=trades', aux_value='=returns')),
            title='Exchange Rates',
            data=data('currency code trades returns', -15),
            commands=commands
        )
        q.page['markdown'] = ui.markdown_card(
            box='5 25 4 4',
            title='I was made using markdown!',
            content='''
```py
from h2o_wave import main, app, Q, ui

async def serve(q: Q):
    # Display a Hello, world! message.
    q.page['hello'] = ui.markdown_card(
        box='1 1 4 4',
        title='Hello',
        content='Hello, world!'
    )

    await q.page`.save()
        ''',
        commands=commands
        )
        q.page['markup'] = ui.markup_card(
            box='1 29 2 2',
            title='Menu',
            content=menu,
            commands=commands
        )
        q.page['nav'] = ui.nav_card(
            box='5 29 4 8',
            value='#menu/ham',
            # persona=ui.persona(title='John Doe', subtitle='Data Scientist', caption='Online', size='xl', image=image),
            items=[
                ui.nav_group('Menu', items=[
                    ui.nav_item(name='#menu/spam', label='Spam'),
                    ui.nav_item(name='#menu/ham', label='Ham'),
                    ui.nav_item(name='#menu/eggs', label='Eggs'),
                    ui.nav_item(name='#menu/toast', label='Toast', disabled=True),
                ]),
                ui.nav_group('Help', items=[
                    ui.nav_item(name='#about', label='About', icon='Info'),
                    ui.nav_item(name='#support', label='Support', icon='Help'),
                    ui.nav_item(name='faq', label='FAQ', icon='OfficeChat', path='https://h2o.ai/'),
                ])
            ],
            secondary_items=[ui.button(name='logout', label='Logout', width='100%')],
            color='primary',
            commands=commands
        )
        q.page['preview'] = ui.preview_card(
            name='preview_card',
            box='1 31 3 4',
            image='https://images.pexels.com/photos/1269968/pexels-photo-1269968.jpeg?auto=compress',
            title='Post title',
            items=[ui.mini_buttons([
                ui.mini_button(name='like', label='4', icon='Heart'),
                ui.mini_button(name='comment', label='2', icon='Comment'),
                ui.mini_button(name='share', label='1', icon='Share'),
            ])
            ],
            caption='''
                  Lorem ipsum dolor sit amet, coectetur adipiscing elit. Etiam ut hendrerit lectus.As Etiam venenatis id nulla a molestie.
                  Lorem ipsum dolor sit amet, coectetur adipiscing elit. Etiam ut hendrerit lectus.As Etiam venenatis id nulla a molestie.
                   ''',
            label='Click me',
            commands=commands
        )
        q.page['profile'] = ui.profile_card(
            box='1 35 3 5',
            image='https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260', # noqa
            persona=ui.persona(title='John Doe', subtitle='Data Scientist', image=image),
            items=[
                ui.inline(justify='center', items=[
                    ui.mini_buttons([
                        ui.mini_button(name='upload', label='Upload', icon='Upload'),
                        ui.mini_button(name='share', label='Share', icon='Share'),
                        ui.mini_button(name='download', label='Download', icon='Download'),
                    ])
                ]),
                ui.inline(justify='center', items=[
                    ui.button(name='btn1', label='Button 1'),
                    ui.button(name='btn2', label='Button 2'),
                    ui.button(name='btn3', label='Button 3'),
                ]),
            ],
            commands=commands
        )
        q.page['section'] = ui.section_card(
            box='5 38 4 4',
            title='Section card',
            subtitle='Toggle theme to see default plot colors change!',
            items=[ui.toggle(name='toggle_theme', label='Dark theme', trigger=True)],
            commands=commands
        )
        q.page['stat'] = ui.small_stat_card(
            box='5 39 1 1',
            title='Dollars',
            value=f'$20.02',
            commands=commands
        )
        q.page['tab'] = ui.tab_card(
            box='1 40 2 1',
            items=[
                ui.tab(name='#menu/spam', label='Spam'),
                ui.tab(name='#menu/ham', label='Ham'),
                ui.tab(name='#menu/eggs', label='Eggs'),
                ui.tab(name='#about', label='About'),
            ],
            value=None,
            commands=commands
        )
        q.page['tall_info'] = ui.tall_info_card(
            box='5 40 2 5',
            name='info_card',
            title='Info Card',
            caption='Lorem ipsum dolor sit amet consectetur adipisicing elit.',
            category='Category',
            label='Click me',
            image='https://images.pexels.com/photos/3225517/pexels-photo-3225517.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260',
            commands=commands
        )
        q.page['stat_table'] = ui.stat_table_card(
            box='1 41 4 5',
            title='Contacts',
            subtitle=f'Last updated: 20.3.1211',
            columns=['Name', 'Job', 'City'],
            items=[
                ui.stat_table_item(
                    label='Label',
                    values=['job', 'city'],
                    colors=['darkblue', '$amber']
                ) for i in range(10)
            ],
            commands=commands
        )
        q.page['toolbar'] = ui.toolbar_card(
            box='5 45 4 1',
            items=[
                ui.command(
                    name='new', label='New', icon='Add', items=[
                        ui.command(name='email', label='Email Message', icon='Mail'),
                        ui.command(name='calendar', label='Calendar Event', icon='Calendar'),
                    ]
                ),
                ui.command(name='upload', label='Upload', icon='Upload'),
                ui.command(name='share', label='Share', icon='Share'),
                ui.command(name='download', label='Download', icon='Download'),
            ],
            secondary_items=[
                ui.command(name='tile', caption='Grid View', icon='Tiles'),
                ui.command(name='info', caption='Info', icon='Info'),
            ],
            overflow_items=[
                ui.command(name='move', label='Move to...', icon='MoveToFolder'),
                ui.command(name='copy', label='Copy to...', icon='Copy'),
                ui.command(name='rename', label='Rename', icon='Edit'),
            ],
            commands=commands
        )
        q.page['vega'] = ui.vega_card(
            box='1 46 4 4',
            title='Full Vega spec grammar',
            specification=spec,
            grammar='vega',
            commands=commands
        )
        q.page['wide_info'] = ui.wide_info_card(
            box='5 46 4 5',
            name='info_card',
            title='Info Card',
            subtitle='Subtitle',
            caption=content,
            category='Category',
            label='Click me',
            image=image,
            commands=commands
        )

        q.client.initialized = True
    if q.args.chatbot:
        # Append user message.
        q.page['chatbot'].data += [q.args.chatbot, True]
        # Append bot response.
        q.page['chatbot'].data += ['I am a fake chatbot. Sorry, I cannot help you.', False]
    if q.args.theme:
        q.page["meta"].theme = q.args.theme

    await q.page.save()