@industream/flowmaker-flowbox-ui
v1.0.15
Published
FlowMaker box configuration UI toolkit
Readme
@industream/flowmaker-flowbox-ui
Svelte-based configuration UI toolkit for FlowMaker boxes.
Installation
npm install @industream/flowmaker-flowbox-uiQuick Start
# In your box's frontend folder
cd your-box/frontend
# Initialize project (creates package.json, vite.config.js)
npx flowbox-ui init
# Add a new config component
npx flowbox-ui add my-config
# Live preview (dev server with hot reload)
npx flowbox-ui start my-config
# Build
npm run buildProject Structure
After initialization:
your-box/
├── frontend/
│ ├── package.json
│ ├── vite.config.js
│ └── src/
│ └── my-config/
│ ├── main.js
│ └── ConfigForm.svelte
└── config/
└── config.my-config.jsbundle.js # Build outputCLI Commands
| Command | Description |
|---------|-------------|
| npx flowbox-ui init | Initialize a new project (auto-detects package manager) |
| npx flowbox-ui add <name> | Add a new config component |
| npx flowbox-ui start <name> | Start dev server with hot reload |
| npx flowbox-ui update [name] | Update vite configs and index.html from package templates |
| npx flowbox-ui help | Show help |
Dev Server
The start command launches a development server with:
- Hot module replacement (HMR) for instant updates
- Carbon Design System components and styles pre-loaded
- Test data from
src/<component>/test/config.json(auto-created if missing) - Live display of current values and context
npx flowbox-ui start my-configEdit src/my-config/test/config.json to customize test data:
{
"values": { "myField": "test value" },
"context": {
"nodeDefinition": {
"flowElement": { "id": "vendor/my-box/1.0.0", "name": "My Box", "type": "pipe" },
"inputs": ["input-1"],
"outputs": ["output-1"]
}
}
}Press Ctrl+S in the browser to simulate a save event.
Update Command
The update command refreshes template files after upgrading @industream/flowmaker-flowbox-ui:
npx flowbox-ui update # Auto-detects components from src/
npx flowbox-ui update my-config # Update for specific component onlyFiles updated:
vite.config.js- Production build config (with @cdn-cache plugin support)vite.config.dev.js- Dev server config (with @cdn-cache plugin support)index.html- Dev server HTML with component name substituted
Component handling:
- Single component in
src/: Createsindex.html - Multiple components: Creates
index.<name>.htmlfor each (e.g.,index.sql-editor.html) - No components found: Creates
index.template.htmlwith__COMPONENT__placeholders
Usage
main.js - Entry point
import ConfigForm from './ConfigForm.svelte';
import { FlowBoxSvelteUI } from '@industream/flowmaker-flowbox-ui';
register('box-config-frontend', new FlowBoxSvelteUI(ConfigForm));ConfigForm.svelte - Your UI component
<script>
import { flowMakerBridge, getValue } from '@industream/flowmaker-flowbox-ui';
const props = $props();
const { context, values, validity, emit } = flowMakerBridge(props);
const onInputChange = (e) => {
$values.myField = getValue(e);
// Optional: validation
const isValid = ($values.myField ?? '').length > 0;
if (isValid !== $validity.myField) {
$validity.myField = isValid;
emit('validity', $validity);
}
};
</script>
<cds-text-input
label="My Field"
value={$values?.myField ?? ''}
invalid={$validity?.myField}
oninput={onInputChange}>
</cds-text-input>
<style>
/* Your scoped styles */
</style>Box Registration
In your box's registration decorator, set uiConfig.type to "js-bundle":
import { readFileSync } from 'fs';
const jsBundle = readFileSync('./config/config.my-config.jsbundle.js', 'utf-8');
const base64Bundle = Buffer.from(jsBundle).toString('base64');
@flowboxregistration({
// ... other config
uiConfig: {
type: "js-bundle",
implementation: base64Bundle, // base64-encoded JS bundle (required)
defaultOptions: {
// your default form values
}
}
})uiConfig.implementation
- For
type: "js-bundle": Must contain the base64-encoded JS bundle.nullis not compatible with js-bundle type. - When
null: FlowMaker uses the default UI maker project (only valid whentypeis"ui-maker"or omitted).
Context
The $context store provides information about the current box and flow:
nodeDefinition - The current box instance in the flow
flowElement.id- Box identifier (e.g.,vendor/box-name/1.0.0)flowElement.name- Instance name in the flowflowElement.options- Current configuration optionsflowElement.type- Box type (source,pipe, orsink)position- Position on the canvas (x, y)inputs- Array of input names (as redefined in the flow)outputs- Array of output names (as redefined in the flow)
flowRepoItem - The current flow
repositoryItem- Flow definitionlastEditAt- Last edit timestamppublished- Whether the flow is publishedtags- Associated tags
flowBoxRegistrationInfo - The box registration metadata
id- Box identifierdisplayName- Human-readable namecurrentVersion- Version stringioInterfaces- Input/output definitionsuiConfig- UI configuration
Access in your component:
<p>Box: {$context?.nodeDefinition?.flowElement?.name}</p>
<p>Type: {$context?.nodeDefinition?.flowElement?.type}</p>
<p>Flow published: {$context?.flowRepoItem?.published}</p>Multiple Components
Create multiple folders under src/ for multiple config UIs:
src/
├── js-expression/
│ ├── main.js
│ └── ConfigForm.svelte
├── merge/
│ ├── main.js
│ └── ConfigForm.svelte
└── filter/
├── main.js
└── ConfigForm.svelteRunning npm run build generates:
config/config.js-expression.jsbundle.jsconfig/config.merge.jsbundle.jsconfig/config.filter.jsbundle.js
CDN Imports (@cdn-cache)
Load external packages from CDN at runtime using @cdn-cache/package@version.
JavaScript - Async Import
<script>
import { onMount } from 'svelte';
onMount(async () => {
const cmModule = await import("@cdn-cache/[email protected]/lib/codemirror.js");
await import("@cdn-cache/[email protected]/mode/sql/sql.js");
const CodeMirror = cmModule.default || cmModule;
// Use CodeMirror...
});
</script>CSS - svelte:head
<svelte:head>
<link rel="stylesheet" href="http://@cdn-cache/[email protected]/lib/codemirror.css">
</svelte:head>Add packages as peerDependencies (npm 7+ auto-installs them for dev mode, and they can be detected for CDN commissioning).
Use @cdn-cache to import external packages that will be loaded from a CDN at runtime. This keeps your bundle small while allowing access to any npm package.
Usage in main.js
Since IIFE bundles don't support top-level await, wrap your code in an async IIFE and use dynamic import() with Promise.all:
import ConfigForm from './ConfigForm.svelte';
import { FlowBoxSvelteUI } from '@industream/flowmaker-flowbox-ui';
(async () => {
// Load CDN dependencies
const [lodash, dayjs] = await Promise.all([
import("@cdn-cache/[email protected]"),
import("@cdn-cache/[email protected]"),
]);
console.log(lodash.range(1, 10));
console.log(dayjs().format());
register('box-config-frontend', new FlowBoxSvelteUI(ConfigForm));
})();Format
@cdn-cache/package@version[/path]
@cdn-cache/@scope/package@version[/path]- package: npm package name (required)
- version: exact version number (required) - build will error if missing
- path: path within the package (optional)
How it works
| Mode | Behavior |
|------|----------|
| Dev (flowbox-ui start) | Resolves to node_modules/package[/path] for local development |
| Build (npm run build) | Transforms to http://@cdn-cache/... in output |
Setup
Add CDN packages as peerDependencies in your package.json:
{
"peerDependencies": {
"lodash-es": "4.17.21",
"dayjs": "1.11.10"
}
}Then run npm install to install them for dev mode. npm 7+ auto-installs peer dependencies.
Runtime replacement
At runtime, FlowMaker replaces http://@cdn-cache/ with the actual CDN URL before executing the bundle. The exact version in the import path ensures reproducible builds.
Tips
Accessing store values
Use $ prefix to access/modify store values:
$values.myField = 'new value';
console.log($values.myField);Getting raw value from input events
import { getValue } from '@industream/flowmaker-flowbox-ui';
const onChange = (e) => {
$values.field = getValue(e); // extracts e.target.value
};Emitting changes
// Emit updated values
emit('values', $values);
// Emit validation state
emit('validity', $validity);Using Carbon Design System components
The host provides Carbon Design System web components:
<cds-text-input label="Name" value={$values.name} oninput={onNameChange} />
<cds-dropdown label="Type" oninput={onTypeChange}>
<cds-dropdown-item value="a">Option A</cds-dropdown-item>
<cds-dropdown-item value="b">Option B</cds-dropdown-item>
</cds-dropdown>API
FlowBoxSvelteUI
Class that wraps a Svelte component for FlowMaker integration.
import { FlowBoxSvelteUI } from '@industream/flowmaker-flowbox-ui';
const ui = new FlowBoxSvelteUI(MyComponent);
ui.mount('target-element-id');
ui.setProps({ values: {}, context: {} });
ui.on('values', (data) => console.log(data));
ui.destroy();flowMakerBridge(props)
Creates Svelte stores for FlowMaker communication.
const { values, context, validity, emit } = flowMakerBridge(props);Returns:
values- Writable store for form valuescontext- Writable store for flow contextvalidity- Writable store for validation stateemit- Function to emit events to FlowMaker
getValue(event)
Extracts value from input events.
const value = getValue(e); // returns e.target.value