소스 정보
- 저장소
- tomevault-io/skills-registry
- 최근 소스 활동
- 2026년 7월 3일 19:45
- 감지된 SKILL.md 언어
- 영어
- 스타
- 0
- 포크
- 0
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill babylon-js명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
SKILL.md 표시 중
SOC 직업 분류 기준
| name | babylon-js |
| description | name: porting-tools-to-fluent Use when this capability is needed. |
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).
A Fluent port replaces four layers:
createRoot + <App /> to MakeModularTool (provides theming, settings, shell layout)IShellService (central content, side panes, toolbars)shared-ui-components/fluent/ primitives and HOCsmakeStyles from @fluentui/react-components@fluentui/react-icons// 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.
"@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
Ensure the shared-ui-components alias is present:
commonDevViteConfiguration({
aliases: {
"shared-ui-components": path.resolve("../../dev/sharedUiComponents/src"),
// ... other aliases as needed
},
});
Ensure the shared-ui-components path mapping is present (no inspector mapping needed):
"paths": {
"shared-ui-components/*": ["../../dev/sharedUiComponents/src/*"]
}
Replace the old entry point:
// 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 ThemeSelectorServiceShellService (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 MessageDialogMakeModularTool 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.
OpenPopupWindow from shared-ui-components/fluent/hoc/popupWindow.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.Each tool should define its own services that populate the shell. A service is a ServiceDefinition<Produces, Consumes> with:
friendlyName — human-readable name for debuggingproduces — array of service identity symbols this service providesconsumes — array of service identity symbols this service depends onfactory(…consumedServices) — returns an object satisfying the produced contracts + optional IDisposableexport const MyServiceIdentity = Symbol("MyService");
export interface IMyService extends IService<typeof MyServiceIdentity> {
readonly someData: SomeType | undefined;
readonly onStateChanged: IReadonlyObservable<void>;
}
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;
},
};
shellService.addCentralContent({ key, component }) — main content areashellService.addSidePane({ key, title, icon, horizontalLocation, verticalLocation, teachingMoment, content }) — side paneshellService.addToolbarItem({ key, horizontalLocation, verticalLocation, teachingMoment, component }) — toolbar buttonAll return IDisposable — clean up in your service's dispose().
Use the useObservableState hook from shared-ui-components/modularTool/ to subscribe to service state in React components:
import { useObservableState } from "shared-ui-components/modularTool/hooks/observableHooks";
const myData = useObservableState(
() => myService.someData, // getter
myService.onStateChanged // observable to subscribe to
);
| 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 |
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.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:
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= = />
;
Replace all FontAwesome icons with @fluentui/react-icons. For general icon conventions (unsized variants, fontSize sizing), see 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 |
faMinus | SubtractRegular |
faPencil / faEdit | EditRegular |
faClose / faTimes | DismissRegular |
faSearch |
Use createFluentIcon for custom icons (e.g. Babylon logo):
import { createFluentIcon } from "@fluentui/react-icons";
export const MyIcon = createFluentIcon(
"MyIcon",
"1em", // width — "1em" sizes with font-size
// Single string for complex SVG (supports fill colors):
'<g transform="...">' + '<path fill="#e0684b" d="..."/>' + "</g>"
);
The default viewBox is 0 0 20 20. If your SVG source has a different viewBox, compute a transform to map the content bounds into 20×20 space:
min(20 / contentWidth, 20 / contentHeight)translate((20 - scaledWidth) / 2, (20 - scaledHeight) / 2)translate(centerX, centerY) scale(s) translate(-minX, -minY)For general makeStyles, spacing tokens, and inline style rules, see fluent.instructions.md. This section covers migration-specific steps.
.scss files and scssDeclaration.d.ts.style={} for truly dynamic values (e.g. drag-and-drop transforms).import { makeStyles, tokens } from "@fluentui/react-components";
const useStyles = makeStyles({
root: {
display: "flex",
flexDirection: "column",
height: "100%",
overflow: "hidden",
},
header: {
display: "flex",
alignItems: "center",
gap: tokens.spacingHorizontalS,
},
});
const MyComponent = () => {
const classes = useStyles();
return <div className={classes.root}>...</div>;
};
Wrap your tool's root content in a ToolContext.Provider with size: "medium" to ensure consistent control sizing:
import { ToolContext } from "shared-ui-components/fluent/hoc/fluentToolWrapper";
<ToolContext.Provider value={{ useFluent: true, disableCopy: false, toolName: "MyTool", size: "medium" }}>{/* tool content */}</ToolContext.Provider>;
Not all shared primitives forward className to the outermost DOM element. When a primitive does NOT forward className, wrap it in a <div>:
| Primitive | Forwards className? | Workaround |
|---|---|---|
Button | ✅ Yes | — |
Dropdown | ✅ Yes | — |
TextInput | ✅ Yes (via mergeClasses) | — |
SyncedSliderInput | ❌ No | Wrap in <div className={...}> |
ColorPickerPopup | ❌ No | Wrap in <div className={...}> |
TextInput has a hardcoded width: 150px from Fluent's UniformWidthStyling. To make it fill available space:
<div> with flex: 1; minWidth: 0 (the fillControl pattern)className={classes.fullWidth} (with fullWidth: { width: "100%" }) to the TextInput — Griffel's deduplication ensures the external className wins over the internal 150pxclassName on Fluent's Textarea applies to the outer wrapper span, not the inner <textarea> element. To style the actual textarea (e.g. monospace font, no-wrap):
<Textarea className={classes.outerStyles} textarea={{ className: classes.innerStyles }} />
Where:
outerStyles: { minHeight: "160px" },
innerStyles: { fontFamily: "monospace", whiteSpace: "pre", overflowX: "auto" },
React's className doesn't work on HTML custom elements (e.g. <babylon-viewer>). Use class= instead:
<babylon-viewer class={classes.myViewer} />
You'll need a JSX IntrinsicElements declaration with class?: string.
Do not nest ButtonLine inside PropertyLine — this creates a LineContainer inside PropertyLine resulting in double borders. Use the Button primitive directly instead.
When multiple action buttons appear together (e.g. upload + reset), wrap them in a gapless flex row to avoid unwanted spacing between buttons:
buttonGroup: { display: "flex", flexDirection: "row", alignItems: "center" },
The gap should be between the left content (e.g. a text input) and the button group, not between individual buttons.
The ModularTool framework and Fluent components live in shared-ui-components:
// Service framework
import { MakeModularTool } from "shared-ui-components/modularTool/modularTool";
import { type ServiceDefinition, type IService } from "shared-ui-components/modularTool/modularity/serviceDefinition";
import { type WeaklyTypedServiceDefinition } from "shared-ui-components/modularTool/modularity/serviceContainer";
// Shell service
import { type IShellService, ShellServiceIdentity } from "shared-ui-components/modularTool/services/shellService";
// Hooks
import { useObservableState } from "shared-ui-components/modularTool/hooks/observableHooks";
// Fluent primitives and HOCs
import { Accordion, AccordionSection } from "shared-ui-components/fluent/primitives/accordion";
import { Button } from "shared-ui-components/fluent/primitives/button";
// ... etc.
No inspector/ imports are needed for tools — everything comes from shared-ui-components/.
After porting, delete:
.scss / .css filesscssDeclaration.d.ts (SCSS module type declarations)FontAwesomeIconButton.tsx or similar FA wrapper componentsExpandableMessageLineComponent.tsx or similar legacy message componentsobservableHooks.ts (use shared-ui-components/modularTool/hooks/observableHooks instead)App.tsx / App.scss if the root component is replaced by shell service contentsass, scss, fontawesome, or legacy shared-ui-componentsThe Fluent Dropdown uses DropdownOption<T> instead of the old IInspectableOptions:
import type { DropdownOption } from "shared-ui-components/fluent/primitives/dropdown";
const options: DropdownOption<string>[] = [
{ key: "option1", text: "Option 1" },
{ key: "option2", text: "Option 2" },
];
<Dropdown options={options} value={currentValue} onChange={onValueChanged} />;
For satisfies clauses in const option arrays, use satisfies DropdownOption<string>[] instead of the old satisfies IInspectableOptions[].
inspector path mappingcreateRoot with MakeModularToolmakeStyles@fluentui/react-iconsuseObservableState from shared-ui-components/modularTool/ instead of local hooksDistilled from the Flow Graph Editor port (packages/tools/flowGraphEditor/). See its port-to-fluent.md for the full plan.
For tools too large to port in one go (NME-class), use phases that each leave the build green:
GlobalState service, wrap the existing class component as a single addCentralContent passthrough, switch entry point to MakeModularTool.SplitContainer/Splitter once the shell owns the layout.makeStyles. One component at a time.shared-ui-components/lines/* with Fluent property-line HOCs.sharedComponents/, all .scss, obsolete package.json devDeps; run lint/format/build/e2e.MakeXService(options) factory patternWhen a service needs instance-specific inputs from the tool's entry function (e.g. Show(options)), export a factory rather than a static ServiceDefinition:
export function MakeGlobalStateService(options: IMyToolOptions, hostElement: HTMLElement): ServiceDefinition<[IGlobalStateService], []> {
return {
friendlyName: "Global State Service",
produces: [GlobalStateServiceIdentity],
factory: () => {
const globalState = new GlobalState(options.scene);
// wire options into globalState...
return { globalState, dispose: () => { /* cleanup */ } };
},
};
}
// In Show(options):
MakeModularTool({
serviceDefinitions: [MakeGlobalStateService(options, hostElement), CentralServiceDefinition, ...],
/* ... */
});
Prefer this over a parentContainer for instance-scoped data.
When the existing codebase uses globalState.on*Observable to trigger UI (toasts, dialogs, etc.), don't rewrite all call sites. Add a small "bridge" service that consumes the framework service and forwards observable events:
export const ToastBridgeServiceDefinition: ServiceDefinition<[], [IGlobalStateService, IToastService]> = {
friendlyName: "Toast Bridge Service",
consumes: [GlobalStateServiceIdentity, ToastServiceIdentity],
factory: (gs, toast) => {
const observer = gs.globalState.onToastNotification.add((d) => toast.showToast(d.message, { intent: d.severity }));
return { dispose: () => observer?.remove() };
},
};
Same pattern works for DialogBridge → IDialogService, etc. Delete the legacy renderer (ToastContainerComponent, MessageDialog) once bridged.
ToolContext override per paneDense property panels often want size: "small" independent of the user's tool-wide setting. Override ToolContext inside the pane's content function (spread the parent first so other fields are inherited):
content: () => {
const parent = useContext(ToolContext);
const ctx = useMemo(() => ({ ...parent, size: "small" as const }), [parent]);
return <ToolContext.Provider value={ctx}><PropertyTab .../></ToolContext.Provider>;
},
Convert global buttons (Help, How-to-use, documentation links) to shellService.addToolbarItem({ horizontalLocation: "right", verticalLocation: "bottom" }). Existing top-of-canvas control bars (play/pause/undo/redo) fit naturally in { horizontalLocation: "left", verticalLocation: "top" }, removing the need for a custom bar above the canvas.
Buttons typically just notify an existing globalState.on*Requested observable so the dialog overlay logic stays where it is.
ExtensibleAccordion, title + iconExtensibleAccordion (from shared-ui-components/modularTool/components/extensibleAccordion) for node lists and property tabs — gives filtering and pinning for free.title and icon on addSidePane so the shell renders the tool name/logo in the pane header (mirrors viewer-configurator/configuratorService.tsx). Use Fluent icons first; createFluentIcon only for Babylon-specific glyphs (logo, port markers).FileUploadLine for file inputsReplace local FileButtonLineComponent-style components with FileUploadLine from shared-ui-components/fluent/hoc/fileUploadLine. Its callback receives a FileList — read files[0] if you previously took a single File.
Dialog primitiveUse Dialog from shared-ui-components/fluent/primitives/dialog (open + title + children + actions) for ad-hoc dialogs instead of composing FluentDialog + DialogSurface + DialogBody directly.
GraphCanvasComponent out of scopeThe shared shared-ui-components/nodeGraphSystem/ graph canvas is consumed by every node-graph editor and still ships SCSS. Don't try to port it during a tool-level Fluent migration — only port the surrounding shell, panes, dialogs, and property panels.
Source: BabylonJS/Babylon.js — distributed by TomeVault.
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 |
SearchRegular |
faLink | LinkRegular |