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.

소스 정보

저장소
BabylonJS/Babylon.js
최근 소스 활동
2026년 6월 27일 05:46
감지된 SKILL.md 언어
영어
스타
26,242
포크
3,712

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
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` |
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기