Skip to content

Latest commit

 

History

History
419 lines (350 loc) · 12.4 KB

schema.md

File metadata and controls

419 lines (350 loc) · 12.4 KB

Schema

Schema provides structure for configuring apps. We use schema inside of the Tidbyt mobile app to allow configuring starlark applications and store the values inside of our database.

In order to supply fields to the mobile app, we call get_schema(). When a user saves the installation, we store the results in our database. Every time we render the app, we pass the values into the config key/value at the provided identifier.

Note: Schema is for configuring apps submitted to Community Apps. We're working on adding tighter integration into Pixlet so that pixlet commands make better use of schema.

Quick Start

Let's add a toggle and a text input to our hello world example. Here it is before we add schema:

load("render.star", "render")

def main():
    return render.Root(
        child = render.Text("Hello, World!"),
    )

This is a quick start, so let's start with the code and we'll break it down:

load("render.star", "render")
load("schema.star", "schema")

DEFAULT_WHO = "World"

def main(config):
    message = "Hello, %s!" % config.str("who", DEFAULT_WHO)

    if config.bool("small"):
        msg = render.Text(message, font = "CG-pixel-3x5-mono")
    else:
        msg = render.Text(message)

    return render.Root(
        child = msg,
    )

def get_schema():
    return schema.Schema(
        version = "1",
        fields = [
            schema.Text(
                id = "who",
                name = "Who?",
                desc = "Who to say hello to.",
                icon = "user",
            ),
            schema.Toggle(
                id = "small",
                name = "Display small text",
                desc = "A toggle to display smaller text.",
                icon = "compress",
                default = False,
            ),
        ],
    )

The big change here is the get_schema method. This is the method we will call before rendering your app when running inside of our Community Apps repo. A quick note - we don't call this method using Pixlet at this time.

The get_schema method returns a schema.Schema object that contains fields. See below for a complete breakdown of what fields are available. In our example, we use a Toggle and a Text field.

Next up should be more familiar. We're now passing config into main(). This is the same for current pixlet scripts that take config today. In Community Apps, we will populate the config hashmap with values configured from the mobile app.

Icons

Each schema field takes an icon value. We use the free icons from Font Awesome at version 6.1.1 with the names camel cased. For example users-cog should be usersCog in the icon value. When submitting to the community repo, the icon names are validated against this icon map.

Dynamic Fields

Pixlet offers two types of fields: basic fields like Toggle or Text and dynamic fields that take a handler method like LocationBased or Typeahead. For dynamic fields, the handler will get called with user inputs. What the handler returns is specific to the field.

Fields

These are the current fields we support through schema today. Note that any addition of a field will require changes in our mobile app before we can truly support them.

Color

color example

Example App

Color provides a color picker. It is provided in config as a hex color string with a # prefix. The value is ready to use in widgets, like render.Box().

def get_schema():
    return schema.Schema(
        version = "1",
        fields = [
            schema.Color(
                id = "color",
                name = "Color",
                desc = "Color of the screen.",
                icon = "brush",
                default = "#7AB0FF",
            ),
        ],
    )

You can also provide an optional palette parameter to guide your users towards reasonable color options:

def get_schema():
    return schema.Schema(
        version = "1",
        fields = [
            schema.Color(
                id = "color",
                name = "Color",
                desc = "Color of the screen.",
                icon = "brush",
                default = "#7AB0FF",
                palette = [
                    "#7AB0FF",
                    "#BFEDC4",
                    "#78DECC",
                    "#DBB5FF",
                ],
            ),
        ],
    )

Datetime

datetime example

Example App

Datetime provides a picker for a date and time. It is provided in config as a string that is parsable by time.parse_time().

schema.DateTime(
    id = "event_time",
    name = "Event Time",
    desc = "The time of the event.",
    icon = "gear",
)

Dropdown

dropdown example

A dropdown provides a selection from a list of options. Options are a key/value pair where the display is the text displayed in the mobile app and the value is what is returned in config to the starlark app.

Options:

options = [
    schema.Option(
        display = "Pink",
        value = "#FF94FF",
    ),
    schema.Option(
        display = "Mustard",
        value = "#FFD10D",
    ),
]

Dropdown:

schema.Dropdown(
    id = "color",
    name = "Text Color",
    desc = "The color of text to be displayed.",
    icon = "brush",
    default = options[0].value,
    options = options,
)

Generated

Example App

The generated field allows for a schema field to generate additional schema fields 🤯. User beware - this field is both not user friendly and our tooling likely has a fair number of bugs. The benefit though is the ability to ask the user for additional fields depending on their input.

In this example, source is the id of the field that will be passed to the handler. So if there is a text field with id = pet, the value of the pet text field will be passed to more_options:

schema.Generated(
    id = "generated",
    source = "pet",
    handler = more_options,
)

Note - the value that is passed to the handler is dependent on the source field type. A handler might look as follows:

def more_options(pet):
    if pet == "dog":
        return [
            schema.Toggle(
                id = "leash",
                name = "Leash",
                desc = "A toggle to enable a dog leash.",
                icon = "gear",
                default = False,
            ),
        ]
    elif pet == "cat":
        return [
            schema.Toggle(
                id = "litter-box",
                name = "Litter Box",
                desc = "A toggle to enable a litter box.",
                icon = "gear",
                default = False,
            ),
        ]
    else:
        return []

Location

location example

Example App

A location field provides a location selection option inside of the mobile app. It's populated with the devices location. Note - if you're adding location to your app and want to call external APIs with this data, we will ask you to truncate the location to avoid leaking location data to third-parties.

schema.Location(
    id = "location",
    name = "Location",
    desc = "Location for which to display time.",
    icon = "locationDot",
)

When you get location using config.get("location"), we will return the location in the following format:

{
	"lat": "40.6781784",
	"lng": "-73.9441579",
	"description": "Brooklyn, NY, USA",
	"locality": "Brooklyn",
	"place_id": "ChIJCSF8lBZEwokRhngABHRcdoI",
	"timezone": "America/New_York"
}

LocationBased

locationbased example

Example App

A LocationBased field provides a list of Option objects to the user by calling a handler method with the user provided location. This field can be used to populate a list of options based on the location provided by the user.

schema.LocationBased(
    id = "station",
    name = "Train Station",
    desc = "A list of train stations based on a location.",
    icon = "train",
    handler = get_stations,
)

A handler for LocationBased is provided a location object:

{
	"lat": "40.6781784",
	"lng": "-73.9441579",
	"description": "Brooklyn, NY, USA",
	"locality": "Brooklyn",
	"place_id": "ChIJCSF8lBZEwokRhngABHRcdoI",
	"timezone": "America/New_York"
}

The handler needs to return a list of Option objects where the display is value is what the user will see and the value is what will be provided to your app:

def get_stations(location):
    loc = json.decode(location)

    return [
        schema.Option(
            display = "Grand Central",
            value = "grand_central",
        ),
    ]

The value provided to config.get() is a JSON string with display and values provided:

{"display": "Grand Central", "value": "grand_central"}

OAuth2

oauth2 example

Example App

The OAuth2 field provides mechanism to authenticate a user via an OAuth2 compatible API.

schema.OAuth2(
    id = "auth",
    name = "GitHub",
    desc = "Connect your GitHub account.",
    icon = "github",
    handler = oauth_handler,
    client_id = "your-client-id",
    authorization_endpoint = "https://github.com/login/oauth/authorize",
    scopes = [
        "read:user",
    ],
)

The handler for OAuth2 looks as follows:

def oauth_handler(params):
    params = json.decode(params)
	# handle oauth2 flow (see Example App)
	return "access-token"

Params is a JSON encoded string:

{"code": "your-code", "grant_type": "authorization_code", "client_id": "your-client-id", "redirect_uri": "https://appauth.tidbyt.com/your-app-id"}

When configuring your OAuth2 app with the third-party provider, the authorization callback URL should be as follows:

https://appauth.tidbyt.com/{{ your_app_id }}

PhotoSelect

photoselect example

Example App

The PhotoSelect field provides a photo picker to the user. The selected image will be cropped to 64x32 pixels and be available through config as a base64 encoded string.

schema.PhotoSelect(
    id = "photo",
    name = "Add Photo",
    desc = "A photo to display.",
    icon = "gear",
)

You can use the provided image as follows in your app:

img = base64.decode(config.get("photo"))
render.Image(img)

Text

text example

Example App

The Text field provides a text entry box for a string entered by the user.

schema.Text(
    id = "msg",
    name = "Message",
    desc = "A message to display.",
    icon = "gear",
    default = "Hello",
)

Toggle

toggle example

Example App

A toggle provides an on/off switch for your app. The values returned in config are either True or False. Remember to use config.bool() to ensure the results are cast to a boolean.

schema.Toggle(
    id = "party_mode",
    name = "Party Mode",
    desc = "A toggle to enable party mode.",
    icon = "gear",
    default = False,
)

Remember to request the value as a bool when getting the value from config:

config.bool("party_mode", False)

Typeahead

typeahead example

Example App

The Typeahead field provides a list of options based on user input.

schema.Typeahead(
    id = "search",
    name = "Search",
    desc = "A list of items that match search.",
    icon = "gear",
    handler = search,
)

A handler for Typeahead takes the search string as a parameter to the function and returns a list of Option objects:

def search(pattern):
    if pattern.startswith("a"):
        return [
            schema.Option(
                display = "Apple",
                value = "apple",
            )
		]

	return []

The value provided to config.get() is a JSON string with display and values provided:

{"display": "Apple", "value": "apple"}