From 75c3fe1a005d686f16af4a480360c48020a36ad4 Mon Sep 17 00:00:00 2001 From: zklaschka Date: Fri, 15 Dec 2023 18:20:07 +0100 Subject: [PATCH] TELESTION-447 Add API reference Delete old frontend tutorials --- .../tutorials/100-dev-env-setup.md | 94 --- .../tutorials/200-bootstrapping-a-widget.md | 194 ----- .../210-building-ui-using-react-spectrum.md | 185 ----- .../220-connecting-with-event-bus.md | 353 --------- .../230-adding-configuration-options.md | 310 -------- .../tutorials/300-writing-a-static-dialog.md | 338 -------- .../301-writing-a-stateful-dialog.md | 742 ------------------ 7 files changed, 2216 deletions(-) delete mode 100644 docs/docs/Frontend Development/tutorials/100-dev-env-setup.md delete mode 100644 docs/docs/Frontend Development/tutorials/200-bootstrapping-a-widget.md delete mode 100644 docs/docs/Frontend Development/tutorials/210-building-ui-using-react-spectrum.md delete mode 100644 docs/docs/Frontend Development/tutorials/220-connecting-with-event-bus.md delete mode 100644 docs/docs/Frontend Development/tutorials/230-adding-configuration-options.md delete mode 100644 docs/docs/Frontend Development/tutorials/300-writing-a-static-dialog.md delete mode 100644 docs/docs/Frontend Development/tutorials/301-writing-a-stateful-dialog.md diff --git a/docs/docs/Frontend Development/tutorials/100-dev-env-setup.md b/docs/docs/Frontend Development/tutorials/100-dev-env-setup.md deleted file mode 100644 index 437c5b91..00000000 --- a/docs/docs/Frontend Development/tutorials/100-dev-env-setup.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Setting up the development environment -# e.g., "Publishing messages to the event bus" or "Installing a dependency using npm" - -description: Start a new telestion-client project with the tc-cli ---- - -In this tutorial, you install the telstion-client-cli and generate a new -telestion-client project. - -!!! info "Prerequisites" - To complete this tutorial, you should be familiar with basic terminal commands. - -## What you'll build - -The result is a project structure that you can begin to customize. - -## Step 1: Installing `tc-cli` - -Install the `tc-cli` by running the following command in your terminal: - -```sh -npm install --global @wuespace/telestion-client-cli -``` - -This command installs the CLI, which you can call using the `tc-cli` command. - -## Step 2: Initializing the project - -This step initializes the project. - -Open the terminal where you want to generate the project and run - -```sh -tc-cli init myproject -``` - -Replace `myproject` with your own desired name. - -!!! tip "Using the `telestion-project-template`" -When using the -[`telestion-project-template` repository](https://github.com/wuespace/telestion-project-template) -for your project, just run `tc-cli init` (without a folder name) inside the -repository's root folder. This command, then, automatically bootstraps a Client -project into the repository's `client` folder. - - -This command generates a folder with the chosen name that contains a -ready-to-use client project and installs all necessary dependencies. - -!!! info "Windows execution policies" -On Windows machines, you might get an error message about execution policies. In -this case, run the power shell as administrator and use this command: - - ``` - Set-ExecutionPolicy RemoteSigned -Scope CurrentUser - ``` - - Restart your other terminal and re-run the `tc-cli` command. - -## Step 3: Run the client - -Run the client by navigating into your folder and run - -```sh -npm start -``` - -!!! tip -This runs `tc-cli start --electron` in the background. - -An electron window opens, and you can see your application. - -## Next steps - - - -Now that you've generated your client project, you can configure and extend your -client in the following tutorials. - - - - diff --git a/docs/docs/Frontend Development/tutorials/200-bootstrapping-a-widget.md b/docs/docs/Frontend Development/tutorials/200-bootstrapping-a-widget.md deleted file mode 100644 index c6410e15..00000000 --- a/docs/docs/Frontend Development/tutorials/200-bootstrapping-a-widget.md +++ /dev/null @@ -1,194 +0,0 @@ ---- -title: Creating your first own widget -# e.g., "Publishing messages to the event bus" or "Installing a dependency using npm" - -description: - Telestion Client projects need their custom widgets. Thankfully, the Telestion - tooling makes it straightforward to generate new widgets. Learn how to - bootstrap a new widget and include it in a dashboard. ---- - - -In this tutorial, you'll generate a new widget from scratch and include it in -one of your dashboards. - -!!! info "Prerequisites" - To complete this tutorial, you should be familiar with React, TypeScript, and have a Telestion Client project that you want to work with. - -If you use `npm start` within your project, you'll see a window with a login -screen: - - - -If you log in using the username `admin` and an arbitrary password (since you -haven't configured any authentication yet, you can just log in using random -credentials), you can see a dashboard with a sample widget (_Hello world_) and -some missing widgets: - - - -## What you'll build - -In this tutorial, you won't build anything fancy, but you'll learn: - -1. how to bootstrap a new widget using the Telestion Client CLI -2. how to add your new widget to a dashboard -3. how to add some simple adjustments to the widget - -In the end, you'll have one less _Missing widget_ message on your dashboard: - - - -## Step 1: Bootstrap a new widget using the Telestion Client CLI - -Within your project folder, use the `tc-cli` to automatically bootstrap a new -widget for you: - -```sh -tc-cli generate widget my-first-widget -``` - -The result looks something like this: - -```sh -$ tc-cli generate widget my-first-widget - SUCCESS widget-generator Widget myFirstWidget created successfully. - INFO widget-generator You can find it at src/widgets/my-first-widget. -``` - -This command does four things: - -1. the CLI created a folder for your widget in `src/widgets/my-first-widget`, - where the source files of your widgets can live -2. the CLI created a file `src/widgets/my-first-widget/widget.tsx` that contains - the React component that the dashboard renders for your widget -3. the CLI created a file `src/widgets/my-first-widget/index.ts` that exports - the component from `widget.tsx` with some metadata -4. the CLI modified the file `src/widgets/index.ts` to include your new widget - `myFirstWidget` in the array of available widgets. - -With that, you already have all the files you need and even have a working -widget. But when you look at your dashboard again, your widget isn't visible -yet. - -## Step 2: Add the widget to your dashboard - -To add the new widget to your dashboard, you must edit the dashboard -configuration. For that, you need a way to reference your newly created widget. - -Thankfully, if you take a closer look at the `index.ts` of your new widget, you -can see that it already exports a name (`'myFirstWidget'`): - -```typescript title='src/widgets/my-new-widget/index.ts' -import { Widget } from '@wuespace/telestion-client-types'; -import { Widget as WidgetRenderer } from './widget'; - -export const widget: Widget = { - // highlight-next-line - name: 'myFirstWidget', - title: 'my-first-widget', - version: '0.0.0', - Widget: WidgetRenderer -}; -``` - -You can find the administrator user's dashboard's configuration in the -`src/model/sample-user-config.ts` file. Replace the second widget's `widgetName` -property with your widget name: - -```typescript title='src/model/sample-user-config.ts' -import { UserConfig } from '@wuespace/telestion-client-types'; - -export const userConfig: UserConfig = { - admin: { - dashboards: [ - { - title: 'Overview', - columns: 4, - rows: 4, - widgets: [ - { - id: '0', - widgetName: 'sampleWidget', - width: 4, - height: 1 - }, - { - id: '1', - // highlight-next-line - widgetName: 'myFirstWidget', - width: 2, - height: 2 - }, - // [...] - ] - } - ] - } -}; -``` - -When you save the file and return to the client's window, it automatically -reloads the page, and you can see your widget on the left of the second row: - - - -## Step 3: Add first customizations to the widget - -While it's cool that you just created your first own widget, that headline -_myFirstWidget widget_ looks a bit hideous :wink:. - -But since the React component within your `widget.tsx` defines your UI, you can -create whatever UI you want. - -For demonstration purposes, you'll adjust the heading to something more -readable. Open the file and edit the headline to something you like: - -```typescript title="src/widgets/my-first-widget/widget.tsx" -import { Heading } from '@adobe/react-spectrum'; - -export function Widget() { - // highlight-next-line - return Hello from my first widget!; -} -``` - -Once again, all you need to do is save the file, wait for a couple of seconds, -and return to the client window. Now, you should be able to see your first "own" -widget come to life: - - - -## Next steps - - - -Congratulations: you've already built your first widget. While, of course, it -doesn't look like much, yet, all you now need to do is write a "plain" React -component with the help of abstractions for communicating with the backend -Application and much more. - -Even better: Telestion does all the heavy lifting in the background -(authentication, online/offline-handling, dashboard/widget configuration, etc.), -meaning you can focus on an isolated context within your widget. - - - -In the following tutorials, you'll take a look at how you can use the React -Spectrum library to build widgets that are consistent with the other parts of -the UI, interact with Telestion's event bus abstractions to interact with the -backend, and using a simple way of adding user configuration options to your -widget. - - diff --git a/docs/docs/Frontend Development/tutorials/210-building-ui-using-react-spectrum.md b/docs/docs/Frontend Development/tutorials/210-building-ui-using-react-spectrum.md deleted file mode 100644 index b3356ffb..00000000 --- a/docs/docs/Frontend Development/tutorials/210-building-ui-using-react-spectrum.md +++ /dev/null @@ -1,185 +0,0 @@ ---- -title: Building the UI using React Spectrum -# e.g., "Publishing messages to the event bus" or "Installing a dependency using npm" - -description: - The Telestion Client ecosystem uses the Adobe Spectrum Design system for its - UI. Learn how to use the React Spectrum implementation to build your widget's - UI. ---- - -In this tutorial, you'll use the React Spectrum library to build your widget's -UI. React Spectrum is a React-based implementation of Adobe's Spectrum Design -system. Spectrum Design is the design system used by -`@wuespace/telestion-client-common` library and integrates well into the other -parts of the client's UI. - -!!! info "Prerequisites" - To complete this tutorial, you should be familiar with React, TypeScript, and have a widget bootstrapped according to the [Bootstrapping a widget tutorial](../bootstrapping-a-widget). - -## What you'll build - -You'll build your widget's UI. It should display the connection status -(Connected / Disconnected) of three different I/O interfaces: `SAT A`, `SAT B`, -and `SAT C` and have "Reset" buttons that could, for example, trigger a -Telecommand: - - - -In this tutorial, you won't connect this to any actual data source (the event -bus) that's a part of the following tutorial. - -The code you'll write looks like this: - -```typescript title="src/widgets/my-new-widget/widget.tsx" -import { - Divider, - Flex, - Heading, - StatusLight, - View, - ActionButton -} from '@adobe/react-spectrum'; - -export function Widget() { - return ( - - - Connection Status - - - - - - - ); -} - -function Indicator(props: { system: string }) { - return ( - - {props.system} Connected - Reset - - ); -} -``` - -## Step 1: Build the layout - -Adjust the `widget.tsx` to have a base layout for your widget's UI: - -```typescript title='src/widgets/my-new-widget/widget.tsx' -// highlight-start -import { Divider, Flex, Heading, View } from '@adobe/react-spectrum'; -// highlight-end - -export function Widget() { - // highlight-start - return ( - - - Connection Status - - {/* Content goes here */} - - - ); - // highlight-end -} -``` - -The result should look something like this: - - - -## Step 2: Add component for a system's indicator - -Since you have three different systems, you'll extract their connection status -UI into one reusable `` component and use it for your three -different systems: - -```typescript title='src/widgets/my-new-widget/widget.tsx' -import { - Divider, - Flex, - Heading, - // highlight-next-line - StatusLight, - View, - // highlight-next-line - ActionButton -} from '@adobe/react-spectrum'; - -export function Widget() { - return ( - - - Connection Status - - // highlight-start - - - - // highlight-end - - - ); -} - -// highlight-start -function Indicator(props: { system: string }) { - return ( - - {props.system} Connected - Reset - - ); -} -// highlight-end -``` - -Now, your UI has status-light indicators for the connection status of the three -different systems and reset buttons: - - - -## Next steps - - - -You have now developed a realistic widget using the Spectrum Design system. - -You should familiarize yourself with both the Spectrum Design system in general -as well as the React Spectrum implementation using Adobe's resources: - - - Adobe Spectrum Design System - - - React Spectrum Documentation - - -Of course, this widget, right now, doesn't reflect the actual connection status. -To change that, you'll learn how to connect this widget to the Application's -event bus using the APIs from `@wuespace/telestion-client-core` in the next -tutorial: - - - - - Connecting the widget with the Event Bus - - - diff --git a/docs/docs/Frontend Development/tutorials/220-connecting-with-event-bus.md b/docs/docs/Frontend Development/tutorials/220-connecting-with-event-bus.md deleted file mode 100644 index 66067f05..00000000 --- a/docs/docs/Frontend Development/tutorials/220-connecting-with-event-bus.md +++ /dev/null @@ -1,353 +0,0 @@ ---- -title: Connecting the widget with the Event Bus -# e.g., "Publishing messages to the event bus" or "Installing a dependency using npm" - -description: - Use the APIs provided by the `@wuespace/telestion-client-core` package to - integrate your widgets into the Application's event bus to visualize actual - Telemetry data and send Telecommands. ---- - -In this tutorial, you'll extend your widget from the -[Building UI using React Spectrum tutorial](../building-ui-using-react-spectrum) -to show some actual data and be able to send commands when pressing the "Reset" -button. - -!!! info "Prerequisites" - To complete this tutorial, you should be familiar with React hooks, NodeJS, and - the concept of the Event Bus. You should also have access to the code from the - [Building UI using React Spectrum tutorial](../building-ui-using-react-spectrum) - as you'll extend the widget from that tutorial. - -## What you'll build - -You'll use the Event Bus bridge and its APIs from the -`@wuespace/telestion-client-core` package to interact with the Event Bus -directly from your widget. You'll also build a mock server so that you can test -your widget without the overhead of running the entire (Java-based) Application -layer. - -First, you'll add a mock Application server. Then, you'll use the -`useSubjectLatest()` hook to display actual data. Lastly, you'll send an Event -Bus message when an operator presses the "Reset" button. - -In the end, you'll have written the following code: - -```typescript -import { - CallbackId, - MockServer, - OnClose, - OnInit -} from '@wuespace/vertx-mock-server'; - -class MyMockServer extends MockServer implements OnInit, OnClose { - private resetListener: CallbackId = 0; - private systemStatusIntervalId: any; - - onInit() { - const systems = ['SAT A', 'SAT B', 'SAT C']; - - this.resetListener = this.register('reset', raw => { - console.log('Received reset request:', raw.message); - }); - - this.systemStatusIntervalId = setInterval(() => { - systems.forEach(system => { - this.send(`system-status/${system}`, Math.random() > 0.5); - }); - }, 2000); - } - - onClose() { - this.unregister(this.resetListener); - clearInterval(this.systemStatusIntervalId); - } -} - -const mockServer = new MyMockServer(); - -export function onReady() { - console.log('Starting mock Application Server'); - mockServer.listen(); -} -``` - -```js -const path = require('path'); - -module.exports = { - plugins: [path.join(__dirname, 'src', 'plugins', 'mock-server.ts')] -}; -``` - -```typescript -import { - useBroadcast, - useSubjectLatest -} from '@wuespace/telestion-client-core'; - -// [...] - -function Indicator(props: { system: string }) { - const broadcast = useBroadcast('reset'); - const status = useSubjectLatest(`system-status/${props.system}`) ?? false; - - return ( - - - {props.system} {status ? 'Connected' : 'Disconnected'} - - broadcast({ system: props.system })}> - Reset - - - ); -} -``` - -## Step 1: Add an event bus mock server for local testing - -Install the `@wuespace/vertx-mock-server` package by running the following -terminal in your client project folder: - -```sh -npm i -D @wuespace/vertx-mock-server -``` - -Add a file for your new plugin called `src/plugins/mock-server.ts` with the -following code: - -```typescript -import { - CallbackId, - MockServer, - OnClose, - OnInit -} from '@wuespace/vertx-mock-server'; - -class MyMockServer extends MockServer implements OnInit, OnClose { - private resetListener: CallbackId = 0; - private systemStatusIntervalId: any; - - onInit() { - const systems = ['SAT A', 'SAT B', 'SAT C']; - - this.resetListener = this.register('reset', raw => { - console.log('Received reset request:', raw.message); - }); - - this.systemStatusIntervalId = setInterval(() => { - systems.forEach(system => { - this.send(`system-status/${system}`, Math.random() > 0.5); - }); - }, 2000); - } - - onClose() { - this.unregister(this.resetListener); - clearInterval(this.systemStatusIntervalId); - } -} - -const mockServer = new MyMockServer(); - -export function onReady() { - console.log('Starting mock Application Server'); - mockServer.listen(); -} -``` - -This code creates a plugin that gets called in the Electron thread once the -Electron thread gets started (when running `npm start`). It creates a mock -server which does two things: - -1. it registers a listener on the `reset` subject address and logs messages to - that subject to the console (for your reset buttons) -2. every two seconds, send a connected status (`boolean`) to the subject address - `system-status/[system]` for your three systems - -To load the plugin, you need to do one last step: in the client's root folder, -there is a `telestion.config.js` that exports an empty object right now. Adjust -it to include a list of plugins with your new plugin: - -```js -const path = require('path'); - -module.exports = { - plugins: [path.join(__dirname, 'src', 'plugins', 'mock-server.ts')] -}; -``` - -When you now restart the `npm start` command (that is, re-run -`tc-cli start --electron`), you can see the message -`Starting Mock Application Server` in the terminal. - -:::tip - -When making changes to the `telestion.config.js` file or any file referenced by -it (for example, plugins), you need to restart the `tc-cli start` command (or -`npm start`) to use your changes. - -::: - -After logging in as `admin` into `http://localhost:9870/bridge` (with an -arbitrary password) in your Electron application, after a couple of seconds, you -should see the connection status indicator in the navigation bar turning green -and saying "Connected." - -## Step 2: Connect the widget's connection status indicators to the event bus - -Now that you have a mock server publishing your system status every two seconds, -you need to connect your widget to that data. Thankfully, this isn't too -difficult with the help of the `useSubjectLatest` hook exported by the -`@wuespace/telestion-client-core` package. - -The `useSubjectLatest()` hook listens to messages on a specific subject address -and always returns the latest status from there. - -Adjust the `` functional component to use the hook to listen to the -system status on the system's system status subject and wire up your UI to use -the new status: - -```typescript -// [...] -// highlight-start -import { useSubjectLatest } from '@wuespace/telestion-client-core'; -// highlight-end - -// [...] - -function Indicator(props: { system: string }) { - // highlight-next-line - const status = useSubjectLatest(`system-status/${props.system}`) ?? false; - - return ( - - // highlight-start - - {props.system} {status ? 'Connected' : 'Disconnected'} - - // highlight-end - Reset - - ); -} -``` - -Take note of the `?? false`. This defaults the value to `false` in case it's -`undefined`. The default value is for when the widget hasn't received a system -status yet (which happens when initially loading the widget). - -After saving the file and reloading the client, you can see the status -indicators change randomly every two seconds: - - - -## Step 3: Wiring up the "Reset" button - -To make the "Reset" button broadcast a message to the `reset` subject (that -you're listening for in your mock server) when pressed, you can use the -`useBroadcast()` hook from the `@wuespace/telestion-client-core` package. - -Like before, you can do this directly in your `` component. The -`useBroadcast()` hook takes the subject address as its first argument and -returns a function to broadcast a message to that address. - -Define a function `broadcast` that publishes to your `reset` subject. Use the -Reset button's `onPress` event to call that function to broadcast a message to -that subject. Pass an object containing details about the system into the -message: - -```typescript -// [...] -import { - // highlight-next-line - useBroadcast, - useSubjectLatest -} from '@wuespace/telestion-client-core'; - -// [...] - -function Indicator(props: { system: string }) { - // highlight-next-line - const broadcast = useBroadcast('reset'); - const status = useSubjectLatest(`system-status/${props.system}`) ?? false; - - return ( - - - {props.system} {status ? 'Connected' : 'Disconnected'} - - // highlight-start - broadcast({ system: props.system })}> - Reset - - // highlight-end - - ); -} -``` - -When you reload your client application and press the reset buttons, you can see -corresponding output in the terminal where you ran `npm start`: - -```text -Received reset request: { system: 'SAT C' } -Received reset request: { system: 'SAT B' } -Received reset request: { system: 'SAT A' } -Received reset request: { system: 'SAT B' } -Received reset request: { system: 'SAT C' } -``` - -## Next steps - - - -Congratulations, your widget is now fully wired up to the Event Bus. It's now up -to the backend developers to create Verticles that connect the actual mission -I/O interfaces to these Event Bus messages :wink:. - -But even more, you're now capable of wiring up any system to the event bus, -meaning you can build any widget, you need using plain React for the UI and the -APIs to connect to the event bus. - -In the following tutorial, you'll learn how you can add configuration options to -your widget to allow Ground Station operators (that is, your users) to -re-configure your widget's behavior on the fly. - -You should also familiarize yourself with the API Reference for the -`@wuespace/vertx-mock-server` package to build more complex mock servers as well -as the API Reference for the various Event Bus hooks in the -`@wuespace/telestion-client-core` package: - - - - - @wuespace/vertx-mock-server API Reference - - - useBroadcast Hook API Reference - - - useSubject Hook API Reference - - - useSubjectLatest Hook API Reference - - - useRequest Hook API Reference - - - diff --git a/docs/docs/Frontend Development/tutorials/230-adding-configuration-options.md b/docs/docs/Frontend Development/tutorials/230-adding-configuration-options.md deleted file mode 100644 index ffffd2f3..00000000 --- a/docs/docs/Frontend Development/tutorials/230-adding-configuration-options.md +++ /dev/null @@ -1,310 +0,0 @@ ---- -title: Adding configuration options -# e.g., "Publishing messages to the event bus" or "Installing a dependency using npm" - -description: - Make your widget configurable and re-usable by adding configuration options ---- - -In this tutorial, you'll add configuration options to the widget from the -previous tutorials. - -!!! info "Prerequisites" - To complete this tutorial, you should be familiar with TypeScript and React. - - You should also have the widget's code from - [Connecting with the event bus](/client/tutorials/connecting-with-event-bus). - -## What you'll build - -In this tutorial, you'll add a `isReadonly` configuration option to your widget. -You'll also add a user interface to let Ground Station Operators configure your -widget without having to adjust the code. - - - -```typescript title='src/widgets/my-first-widget/model.ts' -import { GenericProps } from '@wuespace/telestion-client-types'; - -/** - * Props for the "my-first-widget" widget - */ -export interface WidgetProps extends GenericProps { - /** - * Whether the widget should be read-only. If `true`, the reset buttons get disabled. - */ - isReadonly: boolean; -} -``` - -```typescript title='src/widgets/my-first-widget/config.tsx' -import { Checkbox, View } from '@adobe/react-spectrum'; -import { BaseConfigControlsProps } from '@wuespace/telestion-client-types'; -import { WidgetProps } from './model'; - -export function ConfigControls(props: BaseConfigControlsProps) { - return ( - - props.onUpdate({ isReadonly })} -> - Readonly (disable buttons) - - -); -} -``` - -```typescript title='src/widgets/my-first-widget/index.ts' -import { Widget } from '@wuespace/telestion-client-types'; -// highlight-start -import { ConfigControls } from './config'; -import { WidgetProps } from './model'; -// highlight-end -import { Widget as WidgetRenderer } from './widget'; - -// highlight-next-line -export const widget: Widget = { - name: 'myFirstWidget', - title: 'my-first-widget', - version: '0.0.0', - Widget: WidgetRenderer, - // highlight-next-line - ConfigControls -}; -``` - -## Step 1: Define the configuration options - -First, you need to define a type for your configuration options. - -Create a new file called `model.ts`, and export an interface `WidgetProps` with -the corresponding type: - -```typescript title='src/widgets/my-first-widget/model.ts' -import { GenericProps } from '@wuespace/telestion-client-types'; - -/** - * Props for the "my-first-widget" widget - */ -export interface WidgetProps extends GenericProps { - /** - * Whether the widget should be read-only. If `true`, the reset buttons get disabled. - */ - isReadonly: boolean; -} -``` - -Update your widget's `index.ts` to define your new interface as the widget's -configuration type: - -```typescript title='src/widgets/my-first-widget/index.ts' -import { Widget } from '@wuespace/telestion-client-types'; -// highlight-next-line -import { WidgetProps } from './model'; -import { Widget as WidgetRenderer } from './widget'; - -// highlight-next-line -export const widget: Widget = { - name: 'myFirstWidget', - title: 'my-first-widget', - version: '0.0.0', - Widget: WidgetRenderer -}; -``` - -:::warning Type error in `src/widgets/index.ts` depending on `tc-cli` version - -Depending on the version of `tc-cli` you're using, this might lead to a type -error in your `src/widgets/index.ts` file. - -If that's the case, append `as Widget` to your widget's declaration: - -```typescript title='src/widgets/index.ts' -// highlight-next-line -import { Widget } from '@wuespace/telestion-client-types'; -// [...] -import { widget as myFirstWidget } from './my-first-widget'; -// IMPORT_INSERT_MARK - -export const projectWidgets: Widget[] = [ - // ARRAY_FIRST_ELEMENT_INSERT_MARK - // highlight-next-line - myFirstWidget as Widget, - // [...] - sampleWidget -]; -``` - -::: - -## Step 2: Adjust the widget - -The props passed to your widget now have the `WidgetProps` type. - -Use the props' `isReadonly` property to deactivate the action buttons if the -property's value is true: - -```typescript title="src/widgets/my-first-widget/widget.tsx" -// [...] -// highlight-next-line -import { WidgetProps } from './model'; - -// highlight-next-line -export function Widget(props: WidgetProps) { - return ( - - - Connection Status - - // highlight-start - - - - // highlight-end - - -); -} - -// highlight-next-line -function Indicator(props: { system: string; isReadonly: boolean }) { - const broadcast = useBroadcast('reset'); - const status = useSubjectLatest(`system-status/${props.system}`) ?? false; - - return ( - - - {props.system} {status ? 'Connected' : 'Disconnected'} - - broadcast({ system: props.system })} - // highlight-next-line - isDisabled={props.isReadonly} - > - Reset - - -); -} -``` - -## Step 3: Add default options to the user configuration - -To make sure that the widget always gets the correct type, you need to specify -the widget usage's default configuration options in your dashboard's -declaration. - -Add the `initialProps` field with `isReadonly` set to `false` to your widget's -declaration in your `sample-user-config.ts`: - -```typescript title='src/model/sample-user-config.ts' -// [...] -{ - id: '1', - widgetName: 'myFirstWidget', - width: 2, - height: 2, - initialProps: { - isReadonly: false -} -}, -// [...] -``` - -## Step 4: Add the configuration control interface component - -The `telestion-client` library makes it easy to not only make your widgets -configurable through the dashboard declaration, but also let your users adjust a -widget's configuration. - -You don't have to worry about how to make this accessible to the user, all you -need to do is to define a user interface for adjusting the configuration options -and declare it. - -Create a file `config.tsx` in your widget's folder and define a `ConfigControls` -component like this: - -```typescript title='src/widgets/my-first-widget/config.tsx' -import { Checkbox, View } from '@adobe/react-spectrum'; -import { BaseConfigControlsProps } from '@wuespace/telestion-client-types'; -import { WidgetProps } from './model'; - -export function ConfigControls(props: BaseConfigControlsProps) { - return ( - - props.onUpdate({ isReadonly })} -> - Readonly (disable buttons) - - -); -} -``` - -The `props` contain two properties: - -- `currentProps`, which is of your own `WidgetProps` type, and represents the - current configuration -- `onUpdate(partial: Partial)`, which you can use to set the user - configuration (comparable to the `useState()` setter function) - -:::tip Partial state in `onUpdate` - -The `onUpdate` function merges the partial state you pass into it with your -current state. - -For example, if `currentProps` is `{ a: 1, c: 3 }` and you call -`onUpdate({ b: 2, c: 4 })`, the resulting `currentProps` are -`{ a: 1, b: 2, c: 4 }`. - -::: - -All you need to do now to expose these configuration controls to your users is -to declare your component in the widget's `index.ts`: - -```typescript title='src/widgets/my-first-widget/index.ts' -import { Widget } from '@wuespace/telestion-client-types'; -// highlight-next-line -import { ConfigControls } from './config'; -import { WidgetProps } from './model'; -import { Widget as WidgetRenderer } from './widget'; - -export const widget: Widget = { - name: 'myFirstWidget', - title: 'my-first-widget', - version: '0.0.0', - Widget: WidgetRenderer, - // highlight-next-line - ConfigControls -}; -``` - -With that, your users can now open your widget's configuration page through the -widget's context menu (right mouse button): - - - -## Next steps - - - -You now know all the basics for building Telestion Client Widgets. But there are -lots of other features the `telestion-client` APIs allow you to do. If you want, -you can just keep reading through the tutorials. - - diff --git a/docs/docs/Frontend Development/tutorials/300-writing-a-static-dialog.md b/docs/docs/Frontend Development/tutorials/300-writing-a-static-dialog.md deleted file mode 100644 index 6ad64600..00000000 --- a/docs/docs/Frontend Development/tutorials/300-writing-a-static-dialog.md +++ /dev/null @@ -1,338 +0,0 @@ ---- -title: Writing a static dialog -# e.g., "Publishing messages to the event bus" or "Installing a dependency using npm" - -description: - Dialogs are an essential part of every UI framework. Learn how you can - efficiently write and configure static dialogs with the Telestion Client - Common package. ---- - - -In this tutorial, you'll write a static dialog for a widget in the web client. A static dialog has no state and doesn't change its content during rendering. - -!!! info "Prerequisites" - To complete this tutorial, you should be familiar with TypeScript language and - the React framework. It's good to have basic knowledge about the Telestion - Client framework and Adobe's React Spectrum UI library. - -## What you'll build - -In this tutorial, you'll build a static dialog in a widget which asks the user -for a confirmation to send a specific telecommand. - -The full code for this tutorial looks like this: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; -import { showDialog } from '@wuespace/telestion-client-common'; - -function sendTC(command: string): void { - console.log('Send telecommand: ' + command); -} - -export function Widget() { - const [command, setCommand] = useState('default-command'); - - const handle = () => { - showDialog('custom-tc', { - title: 'Send telecommand', - content: - `Are you sure you want to send the telecommand "${command}"?` , - initialState: undefined - }).then(() => sendTC(command)); - }; - - return ( - - - - Send - - - ); -} -``` - -## Step 1: Generate a new widget - -Create a new widget with the Telestion Client CLI: - -```sh -tc-cli generate widget custom-tc-widget -``` - -This generates the required files for you. Now navigate to -`src/widgets/custom-tc-widget` and open the `widget.tsx` source file. - -If you already created a widget, open the widget component source file to begin. - -[//]: # () - -[//]: # ( telestion-client-cli) - -[//]: # () - -## Step 2: Define the widget structure - -You get greeted with a `Heading` component generated by the CLI. - -Remove it and add a `View` component with a `Flex` component as child: - -```typescript title="widget.tsx" -import { Flex, View } from '@adobe/react-spectrum'; - -export function Widget() { - return ( - - {/* ... */} - - ); -} -``` - -Add some padding to the `View` and configure the `Flex` to column mode with a -gap size. - -```typescript title='widget.tsx' -import { Flex, View } from '@adobe/react-spectrum'; - -export function Widget() { - return ( - - - {/* ... */} - - - ); -} -``` - -To input and send custom telecommands, you need a text field and a button. Add -these two components into the `Flex` container. Give the `TextField` a label for -better usability and set the width to `100%`: - -```typescript title='widget.tsx' -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; - -export function Widget() { - return ( - - - - Send - - - ); -} -``` - -Now, add the widget to your dashboard. If you need help, please take a look at -the tutorial on bootstrapping a widget: - -[//]: # () - -[//]: # ( Bootstrapping a widget) - -[//]: # () - -Now, start the Web Client and you should see something like this: - -The custom telecommand widget - -## Step 3: Add some state - -To send custom telecommands, you need access to the user input from the -`TextField`. Add a React state which holds the current user input and switch the -`TextField` from uncontrolled to controlled mode: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; - -export function Widget() { - const [command, setCommand] = useState('default-command'); - - return ( - - - - Send - - - ); -} -``` - -The `useState` hook from React provides the current state and a function to -update it. Thus, you only need to pass these two things to the `TextField` which -now renders the current command and updates it on user input. - -Add an `onPress` event handler to react on send requests from the -`ActionButton`: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; - -function sendTC(command: string): void { - console.log('A telecommand was sent: ' + command); -} - -export function Widget() { - const [command, setCommand] = useState('default-command'); - - const handle = () => { - sendTC(command); - }; - - return ( - - - - Send - - - ); -} -``` - -This introduces an external function which represents a call method to an -external API to actually send the telecommand. - -Next, create an event handler which sends the entered telecommand to the -external function you defined before. The `ActionButton` calls the event handler -if the user presses the action button. - -## Step 4: Show a dialog before sending - -Because telecommands can be generally disruptive to the remote system, it's -useful to add a modal to prevent sending of unintended commands by the user. - -Import the `showDialog` function from the `@wuespace/telestion-client-common` -package and include it into the event handler: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; -import { showDialog } from '@wuespace/telestion-client-common'; - -function sendTC(command: string): void { - console.log('Send telecommand: ' + command); -} - -export function Widget() { - const [command, setCommand] = useState('default-command'); - - const handle = () => { - showDialog('custom-tc', { - title: 'Send telecommand', - content: - `Are you sure you want to send the telecommand "${command}"?`, - initialState: undefined - }).then(() => sendTC(command)); - }; - - return ( - - - - Send - - - ); -} -``` - -The `showDialog` function needs a unique id across the Web Client and a -configuration object. - -The object is the main part of the dialog. It defines the content of the -different parts in the dialog. For example, the `title` property defines the -content in the upper left corner or the `content` property the center part of -the dialog. - -For a better understanding take a look at the Spectrum design guidelines for a -modal: - - - Spectrum Dialog - - -The current example only using simple strings as parameters and build a -"dynamic" string out of the entered command. - -The dialog can also be stateful. The `initialState` property defines the initial -state of the dialog. In this example, you need no state, so set the dialog state -to `undefined`. - -When the `showDialog` function gets called, a dialog opens with the given -configuration. If the user confirms, the returned promise resolves. If the user -cancels the dialog, the promise rejects. - -Here, you can then react on the success of the promise and send the telecommand. - -Now go back to your open Web Client and press the "Send" button. A dialog should -open, asking you if you're sure that you want to send this telecommand: - -The open telecommand confirm dialog - -## Next steps - - - -That's it. You've created your first dialog. :tada: - -As mentioned earlier, the dialog itself can be stateful and is much more -customizable. - -In the next tutorial, you'll create a more complex example with a stateful -dialog. - - - - - Writing a stateful dialog - - - diff --git a/docs/docs/Frontend Development/tutorials/301-writing-a-stateful-dialog.md b/docs/docs/Frontend Development/tutorials/301-writing-a-stateful-dialog.md deleted file mode 100644 index f333abcf..00000000 --- a/docs/docs/Frontend Development/tutorials/301-writing-a-stateful-dialog.md +++ /dev/null @@ -1,742 +0,0 @@ ---- -title: Writing a stateful dialog -# e.g., "Publishing messages to the event bus" or "Installing a dependency using npm" - -description: - Dialogs can do much more than just showing static content. Learn how you can - create a stateful and complex dialog with the Telestion Client Common package. ---- - -In this tutorial, you'll write a more complex and stateful dialog for a widget -in the web client. - -!!! info "Prerequisites" - To complete this tutorial, you should be familiar with TypeScript language and the React framework. This tutorials builds upon the code from the simple dialog tutorial. - -## What you'll build - -In this tutorial, you'll extend the features of your simple dialog from the last -tutorial and add delivery options to the telecommand you want to send. - -Take a look at the last tutorial, in case you missed anything: - -[//]: # () - -[//]: # ( Writing a static dialog) - -[//]: # () - -The full code for this tutorial looks like this: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { - ActionButton, - Checkbox, - Flex, - TextField, - View -} from '@adobe/react-spectrum'; -import { showDialog } from '@wuespace/telestion-client-common'; - -interface DeliveryOptions { - /** - * Request a transmission receipt from the target. - */ - requestReceipt: boolean; - /** - * Resend the telecommand if a failure has happened during transmission. - */ - resendOnFailure: boolean; - /** - * Force the telecommand to be executed on the target. - */ - forceExecution: boolean; -} - -const defaultOptions: DeliveryOptions = { - requestReceipt: false, - resendOnFailure: false, - forceExecution: false -}; - -function sendTC(command: string, options: DeliveryOptions): void { - console.log('Send telecommand:', command, ', with options:', options); -} - -export function Widget() { - const [options, setOptions] = useState(defaultOptions); - const [command, setCommand] = useState('default-command'); - - const onOptions = () => { - showDialog('options-tc', { - title: 'Telecommand options', - content: (state, setState) => ( - - setState({ requestReceipt })} - > - Request a transmission receipt from the target - - setState({ resendOnFailure })} - > - Resend the telecommand if a failure has happened during transmission - - setState({ forceExecution })} - > - Force telecommand execution on the target - - - ), - initialState: options - }).then(setOptions); - }; - - const onSend = () => { - showDialog('custom-tc', { - title: 'Send telecommand', - content: - 'Are you sure you want to send the telecommand "' + command + '"?', - initialState: undefined - }).then(() => sendTC(command, options)); - }; - - return ( - - - - - - Options - - - Send - - - ); -} -``` - -## Step 1: Define the delivery options - -To be able to send delivery options with the telecommand, you need a new type -that defines the delivery options first. - -Add an interface to your widget describing the new delivery options: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; -import { showDialog } from '@wuespace/telestion-client-common'; - -interface DeliveryOptions { - /** - * Request a transmission receipt from the target. - */ - requestReceipt: boolean; - /** - * Resend the telecommand if a failure has happened during transmission. - */ - resendOnFailure: boolean; - /** - * Force the telecommand to be executed on the target. - */ - forceExecution: boolean; -} - -const defaultOptions: DeliveryOptions = { - requestReceipt: false, - resendOnFailure: false, - forceExecution: false -}; - -function sendTC(command: string): void { - console.log('Send telecommand: ' + command); -} - -export function Widget() { - const [command, setCommand] = useState('default-command'); - - const handle = () => { - showDialog('custom-tc', { - title: 'Send telecommand', - content: - 'Are you sure you want to send the telecommand "' + command + '"?', - initialState: undefined - }).then(() => sendTC(command)); - }; - - return ( - - - - Send - - - ); -} -``` - -This also creates default delivery options which you'll use later as initial -value in the React state. - -Now update your `sendTC` placeholder function to accept the delivery options you -defined before: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; -import { showDialog } from '@wuespace/telestion-client-common'; - -interface DeliveryOptions { - /** - * Request a transmission receipt from the target. - */ - requestReceipt: boolean; - /** - * Resend the telecommand if a failure has happened during transmission. - */ - resendOnFailure: boolean; - /** - * Force the telecommand to be executed on the target. - */ - forceExecution: boolean; -} - -const defaultOptions: DeliveryOptions = { - requestReceipt: false, - resendOnFailure: false, - forceExecution: false -}; - -function sendTC(command: string, options: DeliveryOptions): void { - console.log('Send telecommand:', command, ', with options:', options); -} - -export function Widget() { - const [command, setCommand] = useState('default-command'); - - const handle = () => { - showDialog('custom-tc', { - title: 'Send telecommand', - content: - 'Are you sure you want to send the telecommand "' + command + '"?', - initialState: undefined - }).then(() => sendTC(command)); - }; - - return ( - - - - Send - - - ); -} -``` - -## Step 2: Add state for storing the delivery options - -To pass the user defined delivery options to your external telecommand API, you -need another React state which stores them. Then, you can pass these options to -the `sendTC` function in the resolved promise: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; -import { showDialog } from '@wuespace/telestion-client-common'; - -interface DeliveryOptions { - /** - * Request a transmission receipt from the target. - */ - requestReceipt: boolean; - /** - * Resend the telecommand if a failure has happened during transmission. - */ - resendOnFailure: boolean; - /** - * Force the telecommand to be executed on the target. - */ - forceExecution: boolean; -} - -const defaultOptions: DeliveryOptions = { - requestReceipt: false, - resendOnFailure: false, - forceExecution: false -}; - -function sendTC(command: string, options: DeliveryOptions): void { - console.log('Send telecommand:', command, ', with options:', options); -} - -export function Widget() { - const [options, setOptions] = useState(defaultOptions); - const [command, setCommand] = useState('default-command'); - - const handle = () => { - showDialog('custom-tc', { - title: 'Send telecommand', - content: - 'Are you sure you want to send the telecommand "' + command + '"?', - initialState: undefined - }).then(() => sendTC(command, options)); - }; - - return ( - - - - Send - - - ); -} -``` - -## Step 3: Add an action button for the options dialog - -Right now, the user cannot change the delivery options through the widget. To -change that, you need another action button which triggers a dialog that present -the current selection to the user. - -Place it beside the telecommand text field: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; -import { showDialog } from '@wuespace/telestion-client-common'; - -interface DeliveryOptions { - /** - * Request a transmission receipt from the target. - */ - requestReceipt: boolean; - /** - * Resend the telecommand if a failure has happened during transmission. - */ - resendOnFailure: boolean; - /** - * Force the telecommand to be executed on the target. - */ - forceExecution: boolean; -} - -const defaultOptions: DeliveryOptions = { - requestReceipt: false, - resendOnFailure: false, - forceExecution: false -}; - -function sendTC(command: string, options: DeliveryOptions): void { - console.log('Send telecommand:', command, ', with options:', options); -} - -export function Widget() { - const [options, setOptions] = useState(defaultOptions); - const [command, setCommand] = useState('default-command'); - - const handle = () => { - showDialog('custom-tc', { - title: 'Send telecommand', - content: - 'Are you sure you want to send the telecommand "' + command + '"?', - initialState: undefined - }).then(() => sendTC(command, options)); - }; - - return ( - - - - - Options - - Send - - - ); -} -``` - -This wraps the existing text field in another flex container with the new action -button and aligns them horizontally. Give the action button a flex shrink of `0` -so it doesn't collapse in the flex container. Add some spacing between both -items with `gap="size-100"` and align them with `alignItems="end"` in the flex -container. This puts the action button on the same height as the text field -input. - -Now, your widget should look like this: - -The custom telecommand widget with the options button - -## Step 4: Open the options dialog - -Now, the added action button does nothing. Attach an event handler that opens -the dialog which presents the delivery options to the user: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; -import { showDialog } from '@wuespace/telestion-client-common'; - -interface DeliveryOptions { - /** - * Request a transmission receipt from the target. - */ - requestReceipt: boolean; - /** - * Resend the telecommand if a failure has happened during transmission. - */ - resendOnFailure: boolean; - /** - * Force the telecommand to be executed on the target. - */ - forceExecution: boolean; -} - -const defaultOptions: DeliveryOptions = { - requestReceipt: false, - resendOnFailure: false, - forceExecution: false -}; - -function sendTC(command: string, options: DeliveryOptions): void { - console.log('Send telecommand:', command, ', with options:', options); -} - -export function Widget() { - const [options, setOptions] = useState(defaultOptions); - const [command, setCommand] = useState('default-command'); - - const onOptions = () => { - showDialog('options-tc', { - title: 'Telecommand options', - content: 'TODO', - initialState: options - }).then(setOptions); - }; - - const onSend = () => { - showDialog('custom-tc', { - title: 'Send telecommand', - content: - 'Are you sure you want to send the telecommand "' + command + '"?', - initialState: undefined - }).then(() => sendTC(command, options)); - }; - - return ( - - - - - - Options - - - Send - - - ); -} -``` - -This creates a new event handler called `onOptions` which opens the options -dialog. The new dialog receives the current options as initial state and sets -the "global" options state via the setter when it succeeds. - -## Step 5: Make the options dialog interactive - -The dialog has no content which can interact with the dialog state. Define the -dialog's content: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; -import { showDialog } from '@wuespace/telestion-client-common'; - -interface DeliveryOptions { - /** - * Request a transmission receipt from the target. - */ - requestReceipt: boolean; - /** - * Resend the telecommand if a failure has happened during transmission. - */ - resendOnFailure: boolean; - /** - * Force the telecommand to be executed on the target. - */ - forceExecution: boolean; -} - -const defaultOptions: DeliveryOptions = { - requestReceipt: false, - resendOnFailure: false, - forceExecution: false -}; - -function sendTC(command: string, options: DeliveryOptions): void { - console.log('Send telecommand:', command, ', with options:', options); -} - -export function Widget() { - const [options, setOptions] = useState(defaultOptions); - const [command, setCommand] = useState('default-command'); - - const onOptions = () => { - showDialog('options-tc', { - title: 'Telecommand options', - content: (state, setState) =>
TODO
, - initialState: options - }).then(setOptions); - }; - - const onSend = () => { - showDialog('custom-tc', { - title: 'Send telecommand', - content: - 'Are you sure you want to send the telecommand "' + command + '"?', - initialState: undefined - }).then(() => sendTC(command, options)); - }; - - return ( - - - - - - Options - - - Send - - - ); -} -``` - -Here comes the trick: Instead of using strings or static components as content, -you give the dialog a function that returns the content. This function receives -from the dialog the current dialog state (here called `state`) and as second -argument a function to change it (here called `setState`). - -Now, define the layout in the dialogs center part: - -```typescript title='widget.tsx' -import { useState } from 'react'; -import { ActionButton, Flex, TextField, View } from '@adobe/react-spectrum'; -import { showDialog } from '@wuespace/telestion-client-common'; - -interface DeliveryOptions { - /** - * Request a transmission receipt from the target. - */ - requestReceipt: boolean; - /** - * Resend the telecommand if a failure has happened during transmission. - */ - resendOnFailure: boolean; - /** - * Force the telecommand to be executed on the target. - */ - forceExecution: boolean; -} - -const defaultOptions: DeliveryOptions = { - requestReceipt: false, - resendOnFailure: false, - forceExecution: false -}; - -function sendTC(command: string, options: DeliveryOptions): void { - console.log('Send telecommand:', command, ', with options:', options); -} - -export function Widget() { - const [options, setOptions] = useState(defaultOptions); - const [command, setCommand] = useState('default-command'); - - const onOptions = () => { - showDialog('options-tc', { - title: 'Telecommand options', - content: (state, setState) => ( - - setState({ requestReceipt })} - > - Request a transmission receipt from the target - - setState({ resendOnFailure })} - > - Resend the telecommand if a failure has happened during transmission - - setState({ forceExecution })} - > - Force telecommand execution on the target - - - ), - initialState: options - }).then(setOptions); - }; - - const onSend = () => { - showDialog('custom-tc', { - title: 'Send telecommand', - content: - 'Are you sure you want to send the telecommand "' + command + '"?', - initialState: undefined - }).then(() => sendTC(command, options)); - }; - - return ( - - - - - - Options - - - Send - - - ); -} -``` - -Every option gets an own checkbox which shows the current state of the specific -option. When the user presses on the checkbox, the checkbox emits an event which -changes the specific option while leaving the rest of the dialog state -untouched. - -This comes from the nature of the `setState` function. It accepts a new partial -dialog state (e.g. one specific `boolean` value like in this tutorial) and -shallow merges it with current state. In the end, the changes "win" but the -other values remain intact. - -To wrap it up: - -1. the dialog receives the "global" options state -2. the dialog it opens -3. the dialog content receives the dialog state (which is equal to the "global" - state) and renders -4. the user changes something -5. the dialog content changes the dialog state through the given function -6. the dialog state diverts from the "global" state -7. the user accepts the changes -8. the promise resolves and returns the dialog state (which is still diverged) -9. the promise handler updates the "global" options state - -!!!tip "Tip: Dynamic content" - Not only the `content` property can receive the dialog state, but also every other property in the dialog configuration, which defines the dialog layout. - - Take a look and the API definition of the `showDialog` function for further information. - -Now, press the "Options" button in your widget and change some delivery options: - -The open options dialog from the custom telecommand widget - -The send a new telecommand and look in the development console of your browser: - -Console log of send API - -## Next steps - - - -That's it. You've created your first stateful dialog. - -Due to the usage of the `showDialog` function, you also can add modals in other -parts of the web client. - -As mentioned, you should take a look at the API reference of the dialog -function: - - - -[//]: # () - -[//]: # ( showDialog API reference) - -[//]: # () - -