The IO context is passed around in many areas of LaunchMenu, and is used to provide services that your applet can interact with. These services include:
Below is the exact interface of an IOContext:
export type IIOContext = {
/**
* Retrieves all the UI data opened in this context
* @param hook The hook to subscribe to changes
* @returns All the opened UI layers
*/
getUI(hook?: IDataHook): IUILayer[];
/**
* Opens the given UI layer
* @param layer The layer of UI data to open
* @param config Extra configuration
* @returns A function that can be used to close the opened layer
*/
open(
layer: IUILayer,
config?: {
/** A callback to perform when the layer is closed */
onClose?: () => void | Promise<void>;
/** The index on the stack to open this layer at */
index?: number;
}
): Promise<() => void>;
/**
* Closes the given UI layer
* @param layer The layer of UI data to close
*/
close(layer: IUILayer): Promise<void>;
/**
* Whether LM is running in dev mode
* @param hook The hook to subscribe to changes
* @returns Whether dev mode is enabled
*/
isInDevMode(hook?: IDataHook): boolean;
/** The undo redo facility to undo changes */
readonly undoRedo: IUndoRedoFacility;
/** The application settings */
readonly settings: SettingsContext;
/** The default context menu bindings to add to all context menus */
readonly contextMenuBindings: ISubscribable<IActionBinding[]>;
/** The session that this context is for */
readonly session?: LMSession;
};
There are several situations in which the context becomes of use:
And since all of these are quite common, we're passing the context around to as manu places as possible. Here's a list of common places that you have access to the context:
useIOContext
React hookThis context is passed around explicitely, in order to allow yoou to override parts of the context. For instance, if some component that you want to use makes use of the undo/redo facility, but you don't want to mix the usage history of this component with the global one, you can supply your own undo/redo facility.
Overriding some part of the context is done easily, by using the IOContext
class and specifying what the parent context is:
const context = // ... some existing context
const customUndoRedo = new UndoRedoFacility();
const subContext = new IOContext({
parent: context,
undoRedo: customUndoRedo
});
By specifying an existing context as the parent, the parent's properties are inheretted. Additionally, the newly made sub-context is wrapped in a SubIOContextLayer
ui layer and added to the original context's UI. This way, when any UI is added to the sub-context, it will also become part of the orginal context. When you're done with your custom context, you can call .destroy()
to close all its UI and remove it from its parent.
One can also create an antirely new context. To this context you may pass your own facilities, or facilities from an existing context (possibly by passing the whole context as a shorthand). Then the UI of your own context will be independent of the rest of LaunchMenu, and you can do with it what you want.
In the example below a custom context is created, and used to render a LaunchMenu layout within the section that's usually only contains the content of LaunchMenu. We then add a fake watermark on top of the entire UI, and open an existing applet using our custom context:
const WaterMarked: FC<{context: IOContext}> = ({context}) => {
const [key, setKey] = useState(1);
// Force a refresh after some delay, as this is required for the application layout to properly calculate the menu width (this is a bug)
useEffect(() => {
let id = setTimeout(() => setKey(2), 200);
return () => clearTimeout(id);
}, []);
return (
<FillBox>
<ApplicationLayout key={key} context={context} />
<Box
position="absolute"
right="none"
top="none"
font="header"
zIndex={100}
padding="medium">
Powered by Bob
</Box>
</FillBox>
);
};
export default declare({
info,
settings,
withSession(session) {
const getSettingsApplet = () =>
session.getApplets().find(({info}) => info.name.match(/settings/i));
return {
open({context, onClose}) {
const customContext = new IOContext(context);
const settingsApplet = getSettingsApplet();
if (!settingsApplet || !settingsApplet.open) return;
// Hide the content section by default
customContext.open(
new UILayer(
{contentView: {close: true}},
{
// Prevent transparent overlay from showing
showNodataOverlay: false,
}
)
);
// Open a new layer in the regular context, containing our watermarked UI + opened settings + forward key events
context.open(
new UILayer((ctx, close) => {
settingsApplet!.open!({
context: customContext,
onClose: () => {
close();
onClose();
},
});
return {
fieldView: {close: true},
menuView: {close: true},
contentView: (
<WaterMarked context={customContext} />
),
contentHandler: event =>
emitContextEvent(customContext, event),
};
})
);
},
};
},
});
Now when you search for example
and open it, you will see that you're actually opening the settings, except that a watermark is added on the top right.
This example itself of course isn't too realistic, but it shows what can be done by managing an entirely custom context.