Skip to content

Commit

Permalink
feat: Wave init CLI - app templates #225 (#1636)
Browse files Browse the repository at this point in the history
  • Loading branch information
mturoci authored Nov 3, 2022
1 parent 20d3dca commit 8d7142e
Show file tree
Hide file tree
Showing 11 changed files with 948 additions and 17 deletions.
49 changes: 49 additions & 0 deletions py/h2o_wave/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import platform
import uvicorn
import click
import inquirer
import os
from urllib import request
from urllib.parse import urlparse
Expand All @@ -31,6 +32,13 @@

_localhost = '127.0.0.1'

def read_file(file: str) -> str:
with open(file, 'r') as f:
return f.read()

def write_file(file: str, content: str) -> None:
with open(file, 'w') as f:
f.write(content)

def _scan_free_port(port: int = 8000):
while True:
Expand Down Expand Up @@ -189,3 +197,44 @@ def fetch():

for label, location in everything:
print(f"{label} {resolved_path.joinpath(location)}")

@main.command()
def init():
"""Initial scaffolding for your Wave project.
\b
$ wave init
"""
try:
theme = inquirer.themes.load_theme_from_dict({"List": {"selection_color": "yellow"}})
project = inquirer.prompt([inquirer.List('project', message="Choose a starter template",
choices=[
'Hello World app (for beginners)',
'App with header',
'App with header + navigation',
'App with sidebar + navigation',
'App with header & sidebar + navigation'
]),
], theme=theme)['project']
# Ctrl-C causes TypeError within inquirer, resulting in ugly stacktrace. Catch the error and return early on CTRL-C.
except (KeyboardInterrupt, TypeError):
return

app_content = ''
base_path = os.path.join(sys.exec_prefix, 'project_templates')
if 'Hello World' in project:
app_content = read_file(os.path.join(base_path, 'hello_world.py'))
elif 'header & sidebar' in project:
app_content = read_file(os.path.join(base_path, 'header_sidebar_nav.py'))
elif 'header +' in project:
app_content = read_file(os.path.join(base_path, 'header_nav.py'))
elif 'header' in project:
app_content = read_file(os.path.join(base_path, 'header.py'))
elif 'sidebar +' in project:
app_content = read_file(os.path.join(base_path, 'sidebar_nav.py'))

write_file('app.py', app_content)
write_file('requirements.txt', f'h2o-wave=={__version__}')
write_file('README.md', read_file(os.path.join(base_path, 'README.md')))

print('Run \x1b[7;30;43mwave run app\x1b[0m to start your Wave app at \x1b[7;30;43mhttp://localhost:10101\x1b[0m.')
41 changes: 41 additions & 0 deletions py/project_templates/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Getting Started with H2O Wave

This project was bootstrapped with `wave init` command.

## Running the app

Make sure you have activated a Python virtual environment with `h2o-wave` installed.

If you haven't created a python env yet, simply run the following command (assuming Python 3.7 is installed properly).

For MacOS / Linux:

```sh
python3 -m venv venv
source venv/bin/activate
pip install h2o-wave
```

For Windows:

```sh
python3 -m venv venv
venv\Scripts\activate.bat
pip install h2o-wave
```

Once the virtual environment is setup and active, run:

```sh
wave run app.py
```

Which will start a Wave app at <http://localhost:10101>.

## Interactive examples

If you prefer learning by doing, you can run `wave fetch` command that will download all the existing small Python examples that show Wave in action. The best part is that all these examples are interactive, meaning you can edit their code directly within the browser and observe the changes.

## Learn More

To learn more about H2O Wave, check out the [docs](https://wave.h2o.ai/).
85 changes: 85 additions & 0 deletions py/project_templates/header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
from h2o_wave import main, app, Q, ui, on, handle_on


@app('/')
async def serve(q: Q):
# First time a browser comes to the app
if not q.client.initialized:
await init(q)
q.client.initialized = True

# Other browser interactions
await handle_on(q)
await q.page.save()


async def init(q: Q) -> None:
q.client.cards = set()
q.client.dark_mode = False

q.page['meta'] = ui.meta_card(
box='',
title='My Wave App',
theme='light',
layouts=[
ui.layout(
breakpoint='xs',
min_height='100vh',
max_width='1200px',
zones=[
ui.zone('header'),
ui.zone('content', size='1', zones=[
ui.zone('horizontal', direction=ui.ZoneDirection.ROW),
ui.zone('vertical', size='1', ),
ui.zone('grid', direction=ui.ZoneDirection.ROW, wrap='stretch', justify='center')
]),
ui.zone(name='footer'),
]
)
]
)
q.page['header'] = ui.header_card(
box='header',
title='My Wave App',
subtitle="Example to get us started",
image='https://wave.h2o.ai/img/h2o-logo.svg',
items=[ui.menu(icon='', items=[ui.command(name='change_theme', icon='ClearNight', label='Dark Mode')])]
)
q.page['footer'] = ui.footer_card(
box='footer',
caption='Made with 💛 using [H2O Wave](https://wave.h2o.ai).'
)

await home(q)


@on()
async def home(q: Q):
clear_cards(q)
add_card(q, 'form', ui.form_card(box='vertical', items=[ui.text('This is my app!')]))


@on()
async def change_theme(q: Q):
"""Change the app from light to dark mode"""
if q.client.dark_mode:
q.page["header"].items = [ui.menu([ui.command(name='change_theme', icon='ClearNight', label='Dark mode')])]
q.page["meta"].theme = "light"
q.client.dark_mode = False
else:
q.page["header"].items = [ui.menu([ui.command(name='change_theme', icon='Sunny', label='Light mode')])]
q.page["meta"].theme = "h2o-dark"
q.client.dark_mode = True


# Use for cards that should be deleted on calling `clear_cards`. Useful for routing and page updates.
def add_card(q, name, card) -> None:
q.client.cards.add(name)
q.page[name] = card


def clear_cards(q, ignore=[]) -> None:
for name in q.client.cards.copy():
if name not in ignore:
del q.page[name]
q.client.cards.remove(name)
Loading

0 comments on commit 8d7142e

Please sign in to comment.