Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions aiprompts/contextmenu.md
Original file line number Diff line number Diff line change
@@ -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.
50 changes: 50 additions & 0 deletions aiprompts/getsetconfigvar.md
Original file line number Diff line number Diff line change
@@ -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.
64 changes: 57 additions & 7 deletions frontend/app/workspace/workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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-]+$/;

Expand Down Expand Up @@ -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 (
<div className="flex flex-col w-12 overflow-hidden py-1 -ml-1 select-none">
<div
className="flex flex-col w-12 overflow-hidden py-1 -ml-1 select-none"
onContextMenu={handleWidgetsBarContextMenu}
>
{widgets?.map((data, idx) => <Widget key={`widget-${idx}`} widget={data} />)}
<div className="flex-grow" />
{showHelp ? (
<>
<div className="flex-grow" />
<Widget key="tips" widget={tipsWidget} />
<Widget key="help" widget={helpWidget} />
</>
Expand Down
Loading