diff --git a/aiprompts/contextmenu.md b/aiprompts/contextmenu.md new file mode 100644 index 000000000..7447c4f1d --- /dev/null +++ b/aiprompts/contextmenu.md @@ -0,0 +1,141 @@ +# Context Menu Quick Reference + +This guide provides a quick overview of how to create and display a context menu using our system. + +--- + +## ContextMenuItem Type + +Define each menu item using the `ContextMenuItem` type: + +```ts +type ContextMenuItem = { + label?: string; + type?: "separator" | "normal" | "submenu" | "checkbox" | "radio"; + role?: string; // Electron role (optional) + click?: () => void; // Callback for item selection (not needed if role is set) + submenu?: ContextMenuItem[]; // For nested menus + checked?: boolean; // For checkbox or radio items + visible?: boolean; + enabled?: boolean; + sublabel?: string; +}; +``` + +--- + +## Import and Show the Menu + +Import the context menu module: + +```ts +import { ContextMenuModel } from "@/app/store/contextmenu"; +``` + +To display the context menu, call: + +```ts +ContextMenuModel.showContextMenu(menu, event); +``` + +- **menu**: An array of `ContextMenuItem`. +- **event**: The mouse event that triggered the context menu (typically from an onContextMenu handler). + +--- + +## Basic Example + +A simple context menu with a separator: + +```ts +const menu: ContextMenuItem[] = [ + { + label: "New File", + click: () => { + /* create a new file */ + }, + }, + { + label: "New Folder", + click: () => { + /* create a new folder */ + }, + }, + { type: "separator" }, + { + label: "Rename", + click: () => { + /* rename item */ + }, + }, +]; + +ContextMenuModel.showContextMenu(menu, e); +``` + +--- + +## Example with Submenu and Checkboxes + +Toggle settings using a submenu with checkbox items: + +```ts +const isClearOnStart = true; // Example setting + +const menu: ContextMenuItem[] = [ + { + label: "Clear Output On Restart", + submenu: [ + { + label: "On", + type: "checkbox", + checked: isClearOnStart, + click: () => { + // Set the config to enable clear on restart + }, + }, + { + label: "Off", + type: "checkbox", + checked: !isClearOnStart, + click: () => { + // Set the config to disable clear on restart + }, + }, + ], + }, +]; + +ContextMenuModel.showContextMenu(menu, e); +``` + +--- + +## Editing a Config File Example + +Open a configuration file (e.g., `widgets.json`) in preview mode: + +```ts +{ + label: "Edit widgets.json", + click: () => { + fireAndForget(async () => { + const path = `${getApi().getConfigDir()}/widgets.json`; + const blockDef: BlockDef = { + meta: { view: "preview", file: path }, + }; + await createBlock(blockDef, false, true); + }); + }, +} +``` + +--- + +## Summary + +- **Menu Definition**: Use the `ContextMenuItem` type. +- **Actions**: Use `click` for actions; use `submenu` for nested options. +- **Separators**: Use `type: "separator"` to group items. +- **Toggles**: Use `type: "checkbox"` or `"radio"` with the `checked` property. +- **Displaying**: Use `ContextMenuModel.showContextMenu(menu, event)` to render the menu. diff --git a/aiprompts/getsetconfigvar.md b/aiprompts/getsetconfigvar.md new file mode 100644 index 000000000..c2ac24411 --- /dev/null +++ b/aiprompts/getsetconfigvar.md @@ -0,0 +1,50 @@ +# Setting and Reading Config Variables + +This document provides a quick reference for updating and reading configuration values in our system. + +--- + +## Setting a Config Variable + +To update a configuration, use the `RpcApi.SetConfigCommand` function. The command takes an object with a key/value pair where the key is the config variable and the value is the new setting. + +**Example:** + +```ts +await RpcApi.SetConfigCommand(TabRpcClient, { "web:defaulturl": url }); +``` + +In this example, `"web:defaulturl"` is the key and `url` is the new value. Use this approach for any config key. + +--- + +## Reading a Config Value + +To read a configuration value, retrieve the corresponding atom using `getSettingsKeyAtom` and then use `globalStore.get` to access its current value. getSettingsKeyAtom returns a jotai Atom. + +**Example:** + +```ts +const configAtom = getSettingsKeyAtom("app:defaultnewblock"); +const configValue = globalStore.get(configAtom) ?? "default value"; +``` + +Here, `"app:defaultnewblock"` is the config key and `"default value"` serves as a fallback if the key isn't set. + +Inside of a react componet we should not use globalStore, instead we use useSettingsKeyAtom (this is just a jotai useAtomValue call wrapped around the getSettingsKeyAtom call) + +```tsx +const configValue = useSettingsKeyAtom("app:defaultnewblock") ?? "default value"; +``` + +--- + +## Relevant Imports + +```ts +import { RpcApi } from "@/app/store/wshclientapi"; +import { TabRpcClient } from "@/app/store/wshrpcutil"; +import { getSettingsKeyAtom, useSettingsKeyAtom, globalStore } from "@/app/store/global"; +``` + +Keep this guide handy for a quick reference when working with configuration values. diff --git a/frontend/app/workspace/workspace.tsx b/frontend/app/workspace/workspace.tsx index 49cc02f04..6a03e3b95 100644 --- a/frontend/app/workspace/workspace.tsx +++ b/frontend/app/workspace/workspace.tsx @@ -4,15 +4,17 @@ import { ErrorBoundary } from "@/app/element/errorboundary"; import { CenteredDiv } from "@/app/element/quickelems"; import { ModalsRenderer } from "@/app/modals/modalsrenderer"; +import { NotificationPopover } from "@/app/notification/notificationpopover"; +import { ContextMenuModel } from "@/app/store/contextmenu"; +import { RpcApi } from "@/app/store/wshclientapi"; +import { TabRpcClient } from "@/app/store/wshrpcutil"; import { TabBar } from "@/app/tab/tabbar"; import { TabContent } from "@/app/tab/tabcontent"; -import { atoms, createBlock, isDev } from "@/store/global"; -import { isBlank, makeIconClass } from "@/util/util"; +import { atoms, createBlock, getApi, isDev } from "@/store/global"; +import { fireAndForget, isBlank, makeIconClass } from "@/util/util"; +import clsx from "clsx"; import { useAtomValue } from "jotai"; import { memo } from "react"; -import { NotificationPopover } from "../notification/notificationpopover"; - -import clsx from "clsx"; const iconRegex = /^[a-z0-9-]+$/; @@ -56,12 +58,60 @@ const Widgets = memo(() => { }; const showHelp = fullConfig?.settings?.["widget:showhelp"] ?? true; const widgets = sortByDisplayOrder(fullConfig?.widgets); + + const handleWidgetsBarContextMenu = (e: React.MouseEvent) => { + e.preventDefault(); + const menu: ContextMenuItem[] = [ + { + label: "Edit widgets.json", + click: () => { + fireAndForget(async () => { + const path = `${getApi().getConfigDir()}/widgets.json`; + const blockDef: BlockDef = { + meta: { view: "preview", file: path }, + }; + await createBlock(blockDef, false, true); + }); + }, + }, + { + label: "Show Help Widgets", + submenu: [ + { + label: "On", + type: "checkbox", + checked: showHelp, + click: () => { + fireAndForget(async () => { + await RpcApi.SetConfigCommand(TabRpcClient, { "widget:showhelp": true }); + }); + }, + }, + { + label: "Off", + type: "checkbox", + checked: !showHelp, + click: () => { + fireAndForget(async () => { + await RpcApi.SetConfigCommand(TabRpcClient, { "widget:showhelp": false }); + }); + }, + }, + ], + }, + ]; + ContextMenuModel.showContextMenu(menu, e); + }; + return ( -