Skip to main content

porting-tools-to-fluent

Guide for porting Babylon.js tools from legacy shared-ui-components to Fluent UI using MakeModularTool. Use when: port to fluent, migrate to fluent, fluent migration, porting tool UI.

Quellinformationen

Repository
BabylonJS/Babylon.js
Letzte Quellaktivität
27. Juni 2026 um 05:46
Erkannte Sprache von SKILL.md
Englisch
Sterne
26.242
Forks
3.712

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
porting-tools-to-fluent
description
Guide for porting Babylon.js tools from legacy shared-ui-components to Fluent UI using MakeModularTool. Use when: port to fluent, migrate to fluent, fluent migration, porting tool UI.
# Porting Babylon.js Tools to Fluent UI Guide for porting Babylon.js tools (NME, NGE, NPE, NRGE, Playground, etc.) from the legacy shared-ui-components to Fluent UI using the `MakeModularTool` framework from `shared-ui-components/modularTool/`. **Reference implementation:** `packages/tools/viewer-configurator/` (fully ported). **Design guidelines:** For the shared design system these ports target — color schema, shell/panel structure, component and styling conventions — see [`.github/design-guidelines.md`](../../design-guidelines.md). --- ## Overview A Fluent port replaces four layers: 1. **Bootstrapping** — from ad-hoc `createRoot` + `<App />` to `MakeModularTool` (provides theming, settings, shell layout) 2. **Layout** — from hand-rolled split containers / panes to `IShellService` (central content, side panes, toolbars) 3. **Components** — from legacy shared-ui-components to `shared-ui-components/fluent/` primitives and HOCs 4. **Styling** — from raw SCSS/CSS to `makeStyles` from `@fluentui/react-components` 5. **Icons** — from FontAwesome to `@fluentui/react-icons` --- ## 1. Dependencies ### Add ```jsonc // package.json devDependencies "@fluentui/react-components": "^9.x", // for makeStyles, tokens, low-level Fluent components "@fluentui/react-icons": "^2.x" // for all icons ``` Note: `@dev/shared-ui-components` should already be a dependency. It contains both the Fluent primitives (`fluent/`) and the ModularTool framework (`modularTool/`). No dependency on `@dev/inspector` is needed. ### Remove ```jsonc "@fortawesome/fontawesome-svg-core": "...", "@fortawesome/free-solid-svg-icons": "...", "@fortawesome/free-regular-svg-icons": "...", "@fortawesome/react-fontawesome": "...", "sass": "...", "sass-loader": "..." // if no other SCSS remains ``` ### Vite config Ensure the `shared-ui-components` alias is present: ```js commonDevViteConfiguration({ aliases: { "shared-ui-components": path.resolve("../../dev/sharedUiComponents/src"), // ... other aliases as needed }, }); ``` ### tsconfig.json Ensure the `shared-ui-components` path mapping is present (no `inspector` mapping needed): ```jsonc "paths": { "shared-ui-components/*": ["../../dev/sharedUiComponents/src/*"] } ``` --- ## 2. Bootstrapping with MakeModularTool Replace the old entry point: ```tsx // BEFORE const root = createRoot(document.getElementById("root")!); root.render(<App />); // AFTER import { MakeModularTool } from "shared-ui-components/modularTool/modularTool"; MakeModularTool({ namespace: "MyToolName", containerElement: document.getElementById("root")!, serviceDefinitions: [ /* your service definitions */ ], toolbarMode: "compact", // "compact" for minimal toolbar, "full" for full toolbar showThemeSelector: true, // adds theme toggle to toolbar // Do NOT pass extensionFeeds to disable the extensions dialog }); ``` `MakeModularTool` automatically provides: - `FluentProvider` + theme (light/dark) - `SettingsStore` (persisted user preferences) - `ThemeService` + optional `ThemeSelectorService` - `ShellService` (layout: central content, side panes, toolbars) - `ToastProvider` + `IToastService` for toast notifications (consume `ToastServiceIdentity` — do not roll your own container) - `IDialogService` (consume `DialogServiceIdentity`) for modal alert/confirm dialogs — replaces ad-hoc `MessageDialog` ### Cross-window / popup hosting `MakeModularTool` derives `targetDocument` from `containerElement.ownerDocument`. If your tool's entry function (e.g. `Show(options)`) hosts the editor in a popup window, just pass the popup body as `containerElement` — Fluent/Griffel/`Theme` plumb cross-window automatically. - For a fully-Fluent popup, use `OpenPopupWindow` from `shared-ui-components/fluent/hoc/popupWindow`. - If part of your tool still ships traditional CSS/SCSS (e.g. `shared-ui-components/nodeGraphSystem/`'s graph canvas), keep the legacy `CreatePopup` from `shared-ui-components/popupHelper` — it copies stylesheets into the popup. Fluent and `CreatePopup` coexist fine. --- ## 3. Service Architecture Each tool should define its own services that populate the shell. A service is a `ServiceDefinition<Produces, Consumes>` with: - `friendlyName` — human-readable name for debugging - `produces` — array of service identity symbols this service provides - `consumes` — array of service identity symbols this service depends on - `factory(…consumedServices)` — returns an object satisfying the produced contracts + optional `IDisposable` ### Defining a service identity and contract ```tsx export const MyServiceIdentity = Symbol("MyService"); export interface IMyService extends IService<typeof MyServiceIdentity> { readonly someData: SomeType | undefined; readonly onStateChanged: IReadonlyObservable<void>; } ``` ### Service factory pattern ```tsx export const MyServiceDefinition: ServiceDefinition<[IMyService], [IShellService]> = { friendlyName: "My Service", produces: [MyServiceIdentity], consumes: [ShellServiceIdentity], factory: (shellService) => { const onStateChanged = new Observable<void>(); let someData: SomeType | undefined; // Register shell content const registration = shellService.addCentralContent({ key: "MyContent", component: () => <MyComponent />, }); return { get someData() { return someData; }, onStateChanged, dispose: () => { onStateChanged.clear(); registration.dispose(); }, } satisfies IMyService & IDisposable; }, }; ``` ### Shell service APIs - `shellService.addCentralContent({ key, component })` — main content area - `shellService.addSidePane({ key, title, icon, horizontalLocation, verticalLocation, teachingMoment, content })` — side pane - `shellService.addToolbarItem({ key, horizontalLocation, verticalLocation, teachingMoment, component })` — toolbar button All return `IDisposable` — clean up in your service's `dispose()`. ### Reactive state with useObservableState Use the `useObservableState` hook from `shared-ui-components/modularTool/` to subscribe to service state in React components: ```tsx import { useObservableState } from "shared-ui-components/modularTool/hooks/observableHooks"; const myData = useObservableState( () => myService.someData, // getter myService.onStateChanged // observable to subscribe to ); ``` --- ## 4. Component Mapping ### Legacy → Fluent shared component mapping | Legacy Component | Fluent Replacement | Import Path | | -------------------------------------- | -------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | `LineContainerComponent` | `AccordionSection` | `shared-ui-components/fluent/primitives/accordion` | | Side pane container | `Accordion` (or `ExtensibleAccordion`) | `shared-ui-components/fluent/primitives/accordion` | | `CheckBoxLineComponent` | `Switch` (primitive) or `SwitchPropertyLine` (with label) | `shared-ui-components/fluent/primitives/switch` or `.../hoc/propertyLines/switchPropertyLine` | | `SliderLineComponent` | `SyncedSliderInput` (primitive) or `SyncedSliderPropertyLine` (with label) | `shared-ui-components/fluent/primitives/syncedSlider` or `.../hoc/propertyLines/syncedSliderPropertyLine` | | `OptionsLine` | `Dropdown` (primitive) or `StringDropdownPropertyLine` (with label) | `shared-ui-components/fluent/primitives/dropdown` or `.../hoc/propertyLines/dropdownPropertyLine` | | `ButtonLineComponent` | `Button` (primitive) | `shared-ui-components/fluent/primitives/button` | | `TextInputLineComponent` (single-line) | `TextInput` (primitive) or `TextInputPropertyLine` (with label) | `shared-ui-components/fluent/primitives/textInput` or `.../hoc/propertyLines/inputPropertyLine` | | `TextInputLineComponent` (multiline) | Fluent `Textarea` + slot props | `@fluentui/react-components` | | `MessageLineComponent` | `MessageBar` | `shared-ui-components/fluent/primitives/messageBar` | | `Color4LineComponent` | `ColorPickerPopup` (primitive) or `Color4PropertyLine` (with label) | `shared-ui-components/fluent/primitives/colorPicker` or `.../hoc/propertyLines/colorPropertyLine` | | `LockObject` | Not needed (Fluent property lines don't use it) | — | | `FontAwesomeIconButton` | `Button` with `icon` prop | `shared-ui-components/fluent/primitives/button` | | `SplitContainer` / `Splitter` | Shell service layout (side panes) | Handled by `MakeModularTool` | ### When to use PropertyLine vs primitives - **`PropertyLine`** — Use when a row has a label. Renders `InfoLabel` + child content in a standardized layout with hover border. Import from `shared-ui-components/fluent/hoc/propertyLines/propertyLine`. - **`LineContainer`** — Use for rows without a label. Simple wrapper with hover border. Same import path. - **Primitives directly** — Use when you need custom layout, e.g. button groups or compound rows. ### Composing controls within PropertyLine `PropertyLine` constrains its children via an internal `childWrapper` div with `overflow: hidden` and `whiteSpace: nowrap`. For multi-element children, wrap them in a flex container: ```tsx const useStyles = makeStyles({ propertyContent: { display: "flex", flexDirection: "row", alignItems: "center", gap: tokens.spacingHorizontalS, width: "100%", }, fillControl: { flex: 1, minWidth: 0 }, buttonGroup: { display: "flex", flexDirection: "row", alignItems: "center" }, }); // Example: TextInput + action buttons <PropertyLine label="Model URL"> <div className={classes.propertyContent}> <div className={classes.fillControl}> <TextInput value={url} onChange={setUrl} className={classes.fullWidth} /> </div> <div className={classes.buttonGroup}> <Button icon={ArrowUploadRegular} onClick={onUpload} /> <Button icon={ArrowResetRegular} onClick={onReset} /> </div> </div> </PropertyLine>; ``` --- ## 5. Icons Replace all FontAwesome icons with `@fluentui/react-icons`. For general icon conventions (unsized variants, `fontSize` sizing), see [fluent.instructions.md](fluent.instructions.md). Common FontAwesome → Fluent mappings: | FontAwesome | Fluent Icon | | --------------------- | ---------------------------- | | `faQuestionCircle` | `QuestionCircleRegular` | | `faBullseye` | `TargetRegular` | | `faCamera` | `CameraRegular` | | `faCheck` | `CheckmarkRegular` | | `faCopy` | `CopyRegular` | | `faGripVertical` | `ReOrderDotsVerticalRegular` | | `faRotateLeft` | `ArrowResetRegular` | | `faSave` | `SaveRegular` | | `faSquarePlus` | `AddSquareRegular` | | `faTrashCan` | `DeleteRegular` | | `faUpload` | `ArrowUploadRegular` | | `faChevronDown` | `ChevronDownRegular` | | `faChevronUp` | `ChevronUpRegular` | | `faGear` / `faCog` | `SettingsRegular` | | `faEye` | `EyeRegular` | | `faEyeSlash` | `EyeOffRegular` | | `faPlus` | `AddRegular` |
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen