Skip to content

4. How do I create and add a new widget to Sema?

Francisco Bernardo edited this page Dec 4, 2020 · 1 revision

Sema's Playground and Tutorials provide a grid-based dashboard system with widgets that you can extend with your own components.

This page provides you with everything you need to understand and program your own component and be able to drag it to Sema's playground or tutorial.

Once your component is concluded and tested, you might want to contribute it to the main repo with a pull request.

What is a widget in Sema?

A widget is a high-level user interface component with a specific set of functionalities. In Sema there are a few kinds of default widgets:

  • Editors – code editors to edit the domain-specific language, the machine learning models Javascript code and the BNF grammar code.
  • Visualizers – the Audio Analyser provides a spectrogram and waveform visualizer
  • Debugger – tools that help you to inspect specific parts of the workflow, such as the grammar compiler output, the generated DSP code output, or even utility tools to which we can stream specific Sema data.

You can create a widget just like these by adding a Svelte component to the codebase. What is a Svelte component, you may ask?

"A component is a reusable self-contained block of code that encapsulates HTML, CSS and JavaScript that belong together, written into a .svelte file" – Svelte tutorial.

For instance, below is the code for StoreInspector.svelte, the simplest Sema widget that shows values from Sema's stores, which we use to manage the Sema's application state.

<script>
  import { items } from "../../stores/playground.js"
</script>

<style>
.container {
  position: relative;
  width: 100%;
  height: 100%;
  border: none;
}

.scrollable {
  flex: 1 1 auto;
  margin: 0 0 0.5em 0;
  overflow-y: auto;
}
</style>

<div class='container scrollable'>
  <pre> { JSON.stringify($items, null, 2) } </pre>
</div>

Additionally, your component will have to subscribe and/or publish specific messages to the environment through the Sema's messaging system. Finally, the data that your component creates and manipulates, needs to be managed and persisted across sessions.

We will look into these aspects separately in the next sections.

Where do I create a widget in Sema?

If you examine the structure of the repo and navigate to the folder sema/client/components/widgets you will find the StoreInspector widget alongside other widgets, such as the Analyser (a more complex widget for graphics rendering in an HTML canvas) or the LiveCodeParse output, which renders the AST or error from parsing.

So this folder is where you can add you widget.

What do I need to do to manage the data and application state for my widget?

If you read the wiki page about Stores, you need to import your component to the Playground stores (sema/client/components/stores/playground)

import ModelEditor from "../components/editors/ModelEditor.svelte";

and extend a few functions with its widget type (i.e. YOUR-WIDGET-TYPE):

in the createNewItem function, you need to add a case clause for your widget type

/**
 * @createNewItem creates a new widget as new grid item object with properties that will be (de)serialized to the layout 
 * wraps up components (e.g. LiveCodeEditor) which may have considerable load time thus needs to be asynchronous
 * ! NEED TO use traditional function declaration to prevent Temporal Dead Zone issue 
 * TODO: Refactor to TS to apply inheritance and define parameter types
 * @param widget type (e.g 'liveCodeEditor')
 * @param content data hold held by the widget (e.g. liveCodeSource)
 */
export async function createNewItem (type, content){
	let component;
	switch (type) {
		case "storeInspector":
			component = {
				component: StoreInspector,
				background: "#d1d5ff",
			};
			break;

		case "YOUR-WIDGET-TYPE":
			component = {
				component: Analyser,
				background: "#ffffff",
				mode: "",
			};
			break;
                ...

in the hydrateJSONcomponent function, you need to add a case clause for your widget type

/**
 * @hydrateJSONcomponent receives a JSON description for a grid item and creates a 'live' Svelte component for the grid as new grid item object 
 * TODO: Refactor to TS to apply inheritance and define parameter types
 * @param item JSON grid item component description
 */
export function hydrateJSONcomponent (item){
	if (item !== 'undefined' && item.type !== 'undefined') {
		switch (item.type) {
			case "liveCodeEditor":
				item.component = LiveCodeEditor;
				// await populateStoresWithFetchedProps(item);
				break;
			case "YOUR-WIDGET-TYPE":
				item.component = YOUR-WIDGET-SVELTE-COMPONENT;
				// grammarEditorValue.set(item.data); // Set the store value with grammar value deserialised from data
				break;
                        ...

Finally, you might need to add a label to the stores of a combo box selector sidebarDebuggerOptions

// Dashboard Store for Live Code Editor options in Sidebar component
export const sidebarDebuggerOptions = writable([
	{ id: 0, disabled: false, type: ``, text: `Debuggers`, content: "" },
	{
		id: 1,
		disabled: false,
		type: `grammarCompileOutput`,
		text: `Grammar Compiler Output`,
		content: "",
	},
        ...