merge main

This commit is contained in:
Red Adaya 2024-12-11 08:45:14 +08:00
commit 9b2a7de8f2
49 changed files with 682 additions and 462 deletions

View File

@ -14,7 +14,7 @@ const config: StorybookConfig = {
"./custom-addons/theme/register", "./custom-addons/theme/register",
], ],
core: {}, core: { builder: "@storybook/builder-vite" },
framework: { framework: {
name: "@storybook/react-vite", name: "@storybook/react-vite",
@ -58,6 +58,7 @@ const config: StorybookConfig = {
<link rel="stylesheet" href="./fontawesome/css/solid.min.css" /> <link rel="stylesheet" href="./fontawesome/css/solid.min.css" />
<link rel="stylesheet" href="./fontawesome/css/sharp-solid.min.css" /> <link rel="stylesheet" href="./fontawesome/css/sharp-solid.min.css" />
<link rel="stylesheet" href="./fontawesome/css/sharp-regular.min.css" /> <link rel="stylesheet" href="./fontawesome/css/sharp-regular.min.css" />
<link rel="stylesheet" href="./fontawesome/css/custom-icons.min.css" />
<style> <style>
#storybook-docs { #storybook-docs {
[id^="anchor--"], [id^="anchor--"],

View File

@ -9,7 +9,6 @@ import "../frontend/app/reset.scss";
import "./global.css"; import "./global.css";
import { light, dark } from "./theme"; import { light, dark } from "./theme";
import { DocsContainer } from "@storybook/addon-docs"; import { DocsContainer } from "@storybook/addon-docs";
import { addons } from "@storybook/preview-api"; import { addons } from "@storybook/preview-api";
import { DARK_MODE_EVENT_NAME } from "storybook-dark-mode"; import { DARK_MODE_EVENT_NAME } from "storybook-dark-mode";

View File

@ -18,7 +18,7 @@ Wave is an open-source terminal that can launch graphical widgets, controlled an
Wave isn't just another terminal emulator; it's a rethink on how terminals are built. For too long there has been a disconnect between the CLI and the web. If you want fast, keyboard-accessible, easy-to-write applications, you use the CLI, but if you want graphical interfaces, native widgets, copy/paste, scrolling, variable font sizes, then you'd have to turn to the web. Wave's goal is to bridge that gap. Wave isn't just another terminal emulator; it's a rethink on how terminals are built. For too long there has been a disconnect between the CLI and the web. If you want fast, keyboard-accessible, easy-to-write applications, you use the CLI, but if you want graphical interfaces, native widgets, copy/paste, scrolling, variable font sizes, then you'd have to turn to the web. Wave's goal is to bridge that gap.
![WaveTerm Screenshot](./assets/wave-screenshot.png) ![WaveTerm Screenshot](./assets/wave-screenshot.webp)
## Installation ## Installation

View File

@ -120,6 +120,7 @@ tasks:
sources: sources:
- "cmd/server/*.go" - "cmd/server/*.go"
- "pkg/**/*.go" - "pkg/**/*.go"
- "pkg/**/*.json"
generates: generates:
- dist/bin/wavesrv.* - dist/bin/wavesrv.*

View File

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.8.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 600 400" style="enable-background:new 0 0 600 400;" xml:space="preserve">
<style type="text/css">
.st0{fill:url(#SVGID_1_);}
.st1{fill:url(#SVGID_00000123405442604648655120000000636425700907447225_);}
.st2{fill:#FFFFFF;}
.st3{fill:#58C142;}
.st4{fill:url(#SVGID_00000100361222533905006500000012948355425597525920_);}
.st5{fill:url(#SVGID_00000039098984540155201650000005728924474174226367_);}
.st6{fill:url(#SVGID_00000065066116668922763670000009055193333651929737_);}
.st7{fill:url(#SVGID_00000046310373342220590360000000833566212213317053_);}
.st8{fill:url(#SVGID_00000067914870925445215340000010520730682335284873_);}
.st9{fill:url(#SVGID_00000139972009352615321800000011748188400644384138_);}
.st10{fill:url(#SVGID_00000137813723102203423940000002281677718566419372_);}
.st11{fill:url(#SVGID_00000117658585713214366330000001759073570098750902_);}
.st12{fill:url(#SVGID_00000075146307645666362690000001517567647835044244_);}
.st13{fill:url(#SVGID_00000169553343881037582000000012883033354483876241_);}
</style>
<g>
<path d="M218.2,133.2c51,0,99.3,21.9,138.2,39.5c21.2,9.6,43.1,19.5,53.4,19.5c8,0,21.5,0,30.5-42.1l4.6-21.3l21.6,3.1l66.7,9.6
c11.2-27.9,18.8-61.6,22.9-101.2L426.4,21.7c-8.6,40.3-24.5,60.5-53.3,60.5c-37.4,0-113.8-59.1-191.6-59.1
C76.4,23.1,23.2,89.4,7.3,226.2l46,6.7c7.6-15.6,16.4-29.3,26.4-41.2C112.6,152.9,159.2,133.2,218.2,133.2z"/>
<path d="M409.8,215.6c-37.4,0-113.8-59.1-191.6-59.1c-105.1,0-158.4,66.3-174.3,203.1l129.6,18.7c8.7-41.8,24.5-60.5,53.3-60.5
c38.9,0,112.3,59.1,191.6,59.1c105.1,0,159.9-66.3,174.3-203.1l-129.6-18.7C454.4,195.4,438.6,215.6,409.8,215.6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 438 KiB

BIN
assets/wave-screenshot.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

View File

@ -34,6 +34,7 @@ var editCmd = &cobra.Command{
func init() { func init() {
viewCmd.Flags().BoolVarP(&viewMagnified, "magnified", "m", false, "open view in magnified mode") viewCmd.Flags().BoolVarP(&viewMagnified, "magnified", "m", false, "open view in magnified mode")
rootCmd.AddCommand(viewCmd) rootCmd.AddCommand(viewCmd)
editCmd.Flags().BoolVarP(&viewMagnified, "magnified", "m", false, "open view in magnified mode")
rootCmd.AddCommand(editCmd) rootCmd.AddCommand(editCmd)
} }

177
docs/docs/ai-presets.mdx Normal file
View File

@ -0,0 +1,177 @@
---
sidebar_position: 3.6
id: "ai-presets"
title: "AI Presets"
---
<img
title="AI Presets Menu"
style={{ float: "right", margin: "0 0 10px 10px" }}
src="./img/ai-presets.png"
width="300"
/>
Wave's AI widget can be configured to work with various AI providers and models through presets. Presets allow you to define multiple AI configurations and easily switch between them using the dropdown menu in the AI widget.
## How AI Presets Work
AI presets are defined in `~/.config/waveterm/presets/ai.json`. You can easily edit this file using:
```bash
wsh editconfig presets/ai.json
```
Each preset defines a complete set of configuration values for the AI widget. When you select a preset from the dropdown menu, those configuration values are applied to the widget. If no preset is selected, the widget uses the default values from `settings.json`.
Here's a basic example using Claude:
```json
{
"ai@claude-sonnet": {
"display:name": "Claude 3 Sonnet",
"display:order": 1,
"ai:*": true,
"ai:apitype": "anthropic",
"ai:model": "claude-3-5-sonnet-latest",
"ai:apitoken": "<your anthropic API key>"
}
}
```
To make a preset your default, add this single line to your `settings.json`:
```json
{
"ai:preset": "ai@claude-sonnet"
}
```
:::info
You can quickly set your default preset using the `setconfig` command:
```bash
wsh setconfig ai:preset=ai@claude-sonnet
```
This is easier than editing settings.json directly!
:::
## Provider-Specific Configurations
### Anthropic (Claude)
To use Claude models, create a preset like this:
```json
{
"ai@claude-sonnet": {
"display:name": "Claude 3 Sonnet",
"display:order": 1,
"ai:*": true,
"ai:apitype": "anthropic",
"ai:model": "claude-3-5-sonnet-latest",
"ai:apitoken": "<your anthropic API key>"
}
}
```
### Local LLMs (Ollama)
To connect to a local Ollama instance:
```json
{
"ai@ollama-llama": {
"display:name": "Ollama - Llama2",
"display:order": 2,
"ai:*": true,
"ai:baseurl": "http://localhost:11434/v1",
"ai:name": "llama2",
"ai:model": "llama2",
"ai:apitoken": "ollama"
}
}
```
Note: The `ai:apitoken` is required but can be any value as Ollama ignores it. See [Ollama OpenAI compatibility docs](https://github.com/ollama/ollama/blob/main/docs/openai.md) for more details.
### Azure OpenAI
To connect to Azure AI services:
```json
{
"ai@azure-gpt4": {
"display:name": "Azure GPT-4",
"display:order": 3,
"ai:*": true,
"ai:apitype": "azure",
"ai:baseurl": "<your Azure AI base URL>",
"ai:model": "<your model deployment name>",
"ai:apitoken": "<your Azure API key>"
}
}
```
Note: Do not include query parameters or `api-version` in the `ai:baseurl`. The `ai:model` should be your model deployment name in Azure.
### Perplexity
To use Perplexity's models:
```json
{
"ai@perplexity-sonar": {
"display:name": "Perplexity Sonar",
"display:order": 4,
"ai:*": true,
"ai:apitype": "perplexity",
"ai:model": "llama-3.1-sonar-small-128k-online",
"ai:apitoken": "<your perplexity API key>"
}
}
```
## Multiple Presets Example
You can define multiple presets in your `ai.json` file:
```json
{
"ai@claude-sonnet": {
"display:name": "Claude 3 Sonnet",
"display:order": 1,
"ai:*": true,
"ai:apitype": "anthropic",
"ai:model": "claude-3-5-sonnet-latest",
"ai:apitoken": "<your anthropic API key>"
},
"ai@ollama-llama": {
"display:name": "Ollama - Llama2",
"display:order": 2,
"ai:*": true,
"ai:baseurl": "http://localhost:11434/v1",
"ai:name": "llama2",
"ai:model": "llama2",
"ai:apitoken": "ollama"
},
"ai@perplexity-sonar": {
"display:name": "Perplexity Sonar",
"display:order": 3,
"ai:*": true,
"ai:apitype": "perplexity",
"ai:model": "llama-3.1-sonar-small-128k-online",
"ai:apitoken": "<your perplexity API key>"
}
}
```
The `display:order` value determines the order in which presets appear in the dropdown menu.
Remember to set your default preset in `settings.json`:
```json
{
"ai:preset": "ai@claude-sonnet"
}
```

View File

@ -54,6 +54,7 @@ wsh editconfig
| autoupdate:intervalms | float64 | time in milliseconds to wait between update checks (requires app restart) | | autoupdate:intervalms | float64 | time in milliseconds to wait between update checks (requires app restart) |
| autoupdate:installonquit | bool | whether to automatically install updates on quit (requires app restart) | | autoupdate:installonquit | bool | whether to automatically install updates on quit (requires app restart) |
| autoupdate:channel | string | the auto update channel "latest" (stable builds), or "beta" (updated more frequently) (requires app restart) | | autoupdate:channel | string | the auto update channel "latest" (stable builds), or "beta" (updated more frequently) (requires app restart) |
| tab:preset | string | a "bg@" preset to automatically apply to new tabs. e.g. `bg@green`. should match the preset key |
| widget:showhelp | bool | whether to show help/tips widgets in right sidebar | | widget:showhelp | bool | whether to show help/tips widgets in right sidebar |
| window:transparent | bool | set to true to enable window transparency (cannot be combined with `window:blur`) (macOS and Windows only, requires app restart, see [note on Windows compatibility](https://www.electronjs.org/docs/latest/tutorial/window-customization#limitations)) | | window:transparent | bool | set to true to enable window transparency (cannot be combined with `window:blur`) (macOS and Windows only, requires app restart, see [note on Windows compatibility](https://www.electronjs.org/docs/latest/tutorial/window-customization#limitations)) |
| window:blur | bool | set to enable window background blurring (cannot be combined with `window:transparent`) (macOS and Windows only, requires app restart, see [note on Windows compatibility](https://www.electronjs.org/docs/latest/tutorial/window-customization#limitations)) | | window:blur | bool | set to enable window background blurring (cannot be combined with `window:transparent`) (macOS and Windows only, requires app restart, see [note on Windows compatibility](https://www.electronjs.org/docs/latest/tutorial/window-customization#limitations)) |

View File

@ -16,7 +16,7 @@ The easiest way to access connections is to click the <i className="fa-sharp fa-
## What are wsh Shell Extensions? ## What are wsh Shell Extensions?
`wsh` is a small program that helps manage waveterm regardless of which machine you are currently connected to. It is always included on your host machine, but you also have the option to install it when connecting to a remote machine. If it is installed on the remote machine, it is installed at `~/.waveterm/bin/wsh`. Then, when wave connects to your connection (and only when wave connects to your connection), `~/.waveterm/bin` is added to your `PATH` for that individual session. For more info on what `wsh` is capable of, see [wsh command](/wsh). And if you wish to view the source code of `wsh`, you can find it [here](https://github.com/wavetermdev/waveterm/tree/main/cmd/wsh). `wsh` is a small program that helps manage waveterm regardless of which machine you are currently connected to. It is always included on your host machine, but you also have the option to install it when connecting to a remote machine. If it is installed on the remote machine, it is installed at `~/.waveterm/bin/wsh`. Then, when wave connects to your connection (and only when wave connects to your connection), `~/.waveterm/bin` is added to your `PATH` for that individual session. If this fails for some reason, Wave will attempt to run without wsh. You will see this indicated by a small **<code><i className="fa-link-slash fa-solid fa-sharp"/></code>** icon in the block header. For more info on what `wsh` is capable of, see [wsh command](/wsh). And if you wish to view the source code of `wsh`, you can find it [here](https://github.com/wavetermdev/waveterm/tree/main/cmd/wsh).
With `wsh` installed, you have the ability to view certain widgets from the remote machine as if it were your host. In addition, `wsh` can be used to influence the widgets across various machines. As a very simple example, you can close a widget on the host machine by using the `wsh` command in a terminal window on a remote machine. For more information on what you can accomplish with `wsh`, take a look [here](/wsh). With `wsh` installed, you have the ability to view certain widgets from the remote machine as if it were your host. In addition, `wsh` can be used to influence the widgets across various machines. As a very simple example, you can close a widget on the host machine by using the `wsh` command in a terminal window on a remote machine. For more information on what you can accomplish with `wsh`, take a look [here](/wsh).

View File

@ -38,6 +38,8 @@ in the [default termthemes.json file](https://github.com/wavetermdev/waveterm/bl
If you add your own termthemes.json file in the config directory, you can also add your own custom terminal themes (just follow the same format). If you add your own termthemes.json file in the config directory, you can also add your own custom terminal themes (just follow the same format).
You can set the key `tab:preset` in your [Wave Config File](/config) to apply a theme to all new tabs.
<div style={{ clear: "both" }} /> <div style={{ clear: "both" }} />
#### Font Size #### Font Size

View File

@ -126,7 +126,7 @@ Suppose I want a widget that will run speedtest-go when opened. Then, I can defi
"view": "term", "view": "term",
"controller": "cmd", "controller": "cmd",
"cmd": "speedtest-go --unix", "cmd": "speedtest-go --unix",
"cmd:clearonstart" "cmd:clearonstart": true
} }
} }
}, },
@ -144,13 +144,13 @@ Now suppose I wanted to run a TUI app, for instance, `dua`. Well, it turns out t
```json ```json
<... other widgets go here ...>, <... other widgets go here ...>,
"dua" : { "dua" : {
"icon": "brands@linux", "icon": "brands@linux",
"label": "dua", "label": "dua",
"blockdef": { "blockdef": {
"meta": { "meta": {
"view": "term", "view": "term",
"controller": "cmd", "controller": "cmd",
"cmd": "dua" "cmd": "dua"
} }
} }
}, },

View File

@ -6,84 +6,11 @@ title: "FAQ"
# FAQ # FAQ
### How do I set up my own LLM? ### How do I configure Wave to use different AI models/providers?
Open your [config file](./config) in Wave using `wsh editconfig` (the config file is normally located Wave supports various AI providers including local LLMs (via Ollama), Azure OpenAI, Anthropic's Claude, and Perplexity. The recommended way to configure these is through AI presets, which let you set up and easily switch between different providers and models.
at `~/.config/waveterm/settings.json`).
| Key Name | Type | Function | See our [AI Presets documentation](/ai-presets) for detailed setup instructions for each provider.
| ------------ | ------ | ----------------------------------------------- |
| ai:baseurl | string | Set the AI Base Url (must be OpenAI compatible) |
| ai:apitoken | string | your AI api token |
| ai:name | string | string to display in the Wave AI block header |
| ai:model | string | model name to pass to API |
| ai:maxtokens | int | max tokens to pass to API |
| ai:timeoutms | int | timeout (in milliseconds) for AI calls |
Here's an example of pointing it to a local Ollama instance. Note that to get the text in the header of the AI block
to update, you'll need to set the "ai:name" key. For ollama, you'll also need to provide something for the
apitoken (even though it is ignored).
Here are the ollma open AI compatibility docs: https://github.com/ollama/ollama/blob/main/docs/openai.md
```json
{
"ai:*": true,
"ai:baseurl": "http://localhost:11434/v1",
"ai:name": "llama3.2",
"ai:model": "llama3.2",
"ai:apitoken": "ollama"
}
```
Note: we set the `ai:*` key to true to clear all the existing "ai" keys so this config is a clean slate.
To switch between multiple models, consider [adding AI Presets](./presets) instead.
### How can I connect to Azure AI?
Open your [config file](./config) in Wave using `wsh editconfig` (the config file is normally located
at `~/.config/waveterm/settings.json`).
You'll need to set your `ai:baseurl` to your Azure AI Base URL (do not include query parameters or `api-version`).
You'll also need to set `ai:apitype` to `azure`. You can then set the `ai:model`, and `ai:apitoken` appropriately
for your setup.
### How can I connect to Claude?
Open your [config file](./config) in Wave using `wsh editconfig`.
Set these keys:
```json
{
"ai:*": true,
"ai:apitype": "anthropic",
"ai:model": "claude-3-5-sonnet-latest",
"ai:apitoken": "<your anthropic API key>"
}
```
Note: we set the `ai:*` key to true to clear all the existing "ai" keys so this config is a clean slate.
### How can I connect to Perplexity?
Open your [config file](./config) in Wave using `wsh editconfig`.
Set these keys:
```json
{
"ai:*": true,
"ai:apitype": "perplexity",
"ai:model": "llama-3.1-sonar-small-128k-online",
"ai:apitoken": "<your perplexity API key>"
}
```
Note: we set the `ai:*` key to true to clear all the existing "ai" keys so this config is a clean slate.
To switch between models, consider [adding AI Presets](./presets) instead.
### How can I see the block numbers? ### How can I see the block numbers?

View File

@ -1,127 +1,94 @@
--- ---
sidebar_position: 3 sidebar_position: 3.5
id: "presets" id: "presets"
title: "Presets" title: "Presets"
--- ---
Presets can be used to apply multiple setting overrides at once to either a tab or a block. They are currently supported in two scenarios: tab backgrounds and AI models. # Presets
You can set presets either by placing them in `~/.config/waveterm/presets.json` or by placing them in a JSON file in the `~/.config/waveterm/presets/` directory. All presets will be aggregated regardless of which file they're placed in so you can use the `presets` directory to organize them as you see fit. Wave's preset system allows you to save and apply multiple configuration settings at once. Presets can be used in two different scenarios:
- AI models: Configure different AI providers and models (see [AI Presets](/ai-presets))
- Tab backgrounds: Apply visual styles to your tabs
## Managing Presets
You can store presets in two locations:
- `~/.config/waveterm/presets.json`: Main presets file
- `~/.config/waveterm/presets/`: Directory for organizing presets into separate files
All presets are aggregated regardless of which file they're in, so you can use the `presets` directory to organize them (e.g., `presets/backgrounds.json`, `presets/ai.json`).
:::info :::info
You can easily edit your presets using the built-in editor:
You can open up the main presets config file in Wave by running: ```bash
wsh editconfig presets.json # Edit main presets file
``` wsh editconfig presets/ai.json # Edit AI presets
wsh editconfig presets.json
``` ```
::: :::
## File format ## File Format
Presets follow the following format: Presets follow this format:
```json ```json
{ {
... "<preset-type>@<preset-key>": {
"<preset-type>@<preset-key>": { "display:name": "<Preset name>",
"display:name": "<Preset name>", "display:order": "<number>", // optional
"display:order": "<number>", // optional "<overridden-config-key-1>": "<overridden-config-value-1>"
"<overridden-config-key-1>": "<overridden-config-value-1>" ...
...
}
}
```
A complete example of a preset for a tab background is the following:
```json
{
"bg@rainbow": {
"display:name": "Rainbow",
"display:order": 2.1,
"bg:*": true,
"bg": "linear-gradient( 226.4deg, rgba(255,26,1,1) 28.9%, rgba(254,155,1,1) 33%, rgba(255,241,0,1) 48.6%, rgba(34,218,1,1) 65.3%, rgba(0,141,254,1) 80.6%, rgba(113,63,254,1) 100.1% )",
"bg:opacity": 0.3
} }
} }
``` ```
A complete example of a preset for an AI model is the following: The `preset-type` determines where the preset appears in Wave's interface:
```json - `ai`: Appears in the models dropdown in the "Wave AI" widget header (see [AI Presets](/ai-presets))
{ - `bg`: Appears in the "Backgrounds" submenu when right-clicking a tab
"ai@wave": {
"display:name": "Ollama - llama3.1",
"display:order": 0,
"ai:baseurl": "http://localhost:11434",
"ai:model": "llama3.1:latest"
}
}
```
## Preset Types ### Common Keys
The type of the preset determines where it can be discovered in the app. Currently, the two types that will be discovered in the app are `bg` and `ai`. | Key Name | Type | Function |
| ------------- | ------ | ----------------------------------------- |
`bg` will be served in the "Backgrounds" submenu of the Tab context menu (which can be found by right-clicking on a tab). | display:name | string | Name shown in the UI menu (required) |
| display:order | float | Controls the order in the menu (optional) |
![screenshot showing the default options in the backgrounds submenu of the tab context menu](./img/backgrounds-menu.png)
`ai` will be served in the models dropdown in the block header of the "Wave AI" widget.
![screenshot showing the default options in the models dropdown in the block header of the "Wave AI" widget](./img/waveai-model-dropdown.png)
### Available configuration keys
The following configuration keys are available for use in presets:
#### Common keys
| Key Name | Type | Function |
| ------------- | ------ | ---------------------------------------------------------------------- |
| display:name | string | the name to use when displaying the preset in a menu (required) |
| display:order | float | the order in which the preset should be displayed in a menu (optional) |
:::info :::info
When a preset is applied, it overrides the default configuration values for that tab or block. Using `bg:*` or `ai:*` will clear any previously overridden values, setting them back to defaults. It's recommended to include these keys in your presets to ensure a clean slate.
Configs in a preset are applied in order to override the default config values, which will persist for the remainder of the tab or block's lifetime. Setting `bg:*` or `ai:*` to `"true"` will clear the values of any previously overridden Background or AI configurations, respectively, setting them back to their defaults. You almost always want to add these keys to your presets in order to create a clean slate and prevent previously set values from leaking in.
::: :::
## AI Configurations ## AI Presets
| Key Name | Type | Function | For configuring AI providers and models, see our dedicated [AI Presets](/ai-presets) documentation. It covers setting up presets for:
| ------------- | ------ | -------------------------------------------------------------------------------------------------- |
| ai:\* | bool | reset all existing ai keys |
| ai:preset | string | the default AI preset to use |
| ai:baseurl | string | Set the AI Base Url (must be OpenAI compatible) |
| ai:apitoken | string | your AI api token |
| ai:apitype | string | defaults to "open_ai", but can also set to "azure" (for special Azure AI handling), or "anthropic" |
| ai:name | string | string to display in the Wave AI block header |
| ai:model | string | model name to pass to API |
| ai:apiversion | string | for Azure AI only (when apitype is "azure", this will default to "2023-05-15") |
| ai:orgid | string | |
| ai:maxtokens | int | max tokens to pass to API |
| ai:timeoutms | int | timeout (in milliseconds) for AI calls |
<a name="background-configurations" /> - Local LLMs via Ollama
- Azure OpenAI
- Anthropic's Claude
- Perplexity
- And more
## Background Configurations ## Background Presets
Wave's background system harnesses the full power of CSS backgrounds, letting you create rich visual effects through the "background" attribute. You can apply solid colors, gradients (both linear and radial), images, and even blend multiple elements together. The system offers preset configurations while maintaining the flexibility of pure CSS, making it both powerful and easy to use. Wave's background system harnesses the full power of CSS backgrounds, letting you create rich visual effects through the "background" attribute. You can apply solid colors, gradients (both linear and radial), images, and even blend multiple elements together.
### Configuration Keys
| Key Name | Type | Function | | Key Name | Type | Function |
| -------------------- | ------ | ------------------------------------------------------------------------------------------------------- | | -------------------- | ------ | ------------------------------------------------------------------------------------------------------- |
| bg:\* | bool | reset all existing bg keys (recommended to prevent any existing background settings from carrying over) | | bg:\* | bool | Reset all existing bg keys (recommended to prevent any existing background settings from carrying over) |
| bg | string | CSS `background` attribute for the tab (supports colors, gradients images, etc.) | | bg | string | CSS `background` attribute for the tab (supports colors, gradients images, etc.) |
| bg:opacity | float | the opacity of the background (defaults to 0.5) | | bg:opacity | float | The opacity of the background (defaults to 0.5) |
| bg:blendmode | string | the [blend mode](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode) of the background | | bg:blendmode | string | The [blend mode](https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode) of the background |
| bg:bordercolor | string | the color of the border when a block is not active (rarely used) | | bg:bordercolor | string | The color of the border when a block is not active (rarely used) |
| bg:activebordercolor | string | the color of the border when a block is active | | bg:activebordercolor | string | The color of the border when a block is active |
### Simple solid color with opacity: ### Examples
#### Simple solid color:
```json ```json
{ {
@ -135,7 +102,7 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo
} }
``` ```
### Complex gradient combining multiple effects: #### Complex gradient:
```json ```json
{ {
@ -149,24 +116,11 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo
} }
``` ```
### Repeating pattern background: #### Background image:
```json ```json
{ {
"bg@pattern": { "bg@ocean": {
"display:name": "Diamond Pattern",
"bg:*": true,
"bg": "url('/path/to/pattern.png') repeat",
"bg:opacity": 0.15
}
}
```
### Full-cover background image:
```json
{
"bg@cover": {
"display:name": "Ocean Scene", "display:name": "Ocean Scene",
"bg:*": true, "bg:*": true,
"bg": "url('/path/to/ocean.jpg') center/cover no-repeat", "bg": "url('/path/to/ocean.jpg') center/cover no-repeat",
@ -175,25 +129,23 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo
} }
``` ```
Replace image URLs with your own assets. All examples use reduced opacity to work well with dark themes.
:::info :::info
Background images can be specified using URLs or local file paths. While URLs are supported, it's recommended to download and serve images locally for better reliability. For local files, you must use absolute paths or paths starting with `~` (e.g. `~/Downloads/background.png`). The system will automatically rewrite local paths to ensure proper access. We support all common web image formats: PNG, JPEG/JPG, WebP, GIF, and SVG. Background images support both URLs and local file paths. For better reliability, we recommend using local files. Local paths must be absolute or start with `~` (e.g., `~/Downloads/background.png`). We support common web formats: PNG, JPEG/JPG, WebP, GIF, and SVG.
::: :::
:::tip :::tip
You can use `wsh setbg --print` to help generate the JSON for your background presets. For example: The `setbg` command can help generate background preset JSON:
```bash ```bash
# Preview the metadata for a gradient background # Preview a solid color preset
wsh setbg --print "#ff0000" wsh setbg --print "#ff0000"
{ {
"bg:*": true, "bg:*": true,
"bg": "#ff0000", "bg": "#ff0000",
"bg:opacity": 0.5 "bg:opacity": 0.5
} }
# Preview a centered logo configuration # Preview a centered image preset
wsh setbg --print --center --opacity 0.3 ~/logo.png wsh setbg --print --center --opacity 0.3 ~/logo.png
{ {
"bg:*": true, "bg:*": true,
@ -202,9 +154,5 @@ wsh setbg --print --center --opacity 0.3 ~/logo.png
} }
``` ```
Copy the output and use it as a starting point for your preset configuration, just add the required `display:name` field! Just add the required `display:name` field to complete your preset!
::: :::
#### Unset a default value
To unset a default value in a preset, add an override that sets it to an empty string, like `""`.

View File

@ -6,6 +6,28 @@ sidebar_position: 200
# Release Notes # Release Notes
### v0.10.0 &mdash; Dec 10, 2024
Wave Terminal v0.10.0 introduces workspaces, making it easier to manage multiple work environments. We've added powerful new command execution capabilities with `wsh run`, allowing you to launch and control commands in dedicated blocks. This release also brings significant improvements to SSH with a new connections configuration system for managing your remote environments.
- **Workspaces**: Organize your work into separate environments, each with their own tabs, layouts, and settings
- **Command Blocks**: New `wsh run` command for launching terminal commands in dedicated blocks, with support for magnification, auto-closing, and execution control ([docs](/wsh#run))
- **Connections**: New configuration system for managing SSH connections, with support for wsh-free operation, per-connection themes, and more ([docs](/connections))
- Improved tab management with better switching behavior and context menus (many bug fixes)
- New tab features including pinned tabs and drag-and-drop improvements
- Create, rename, and delete files/directories directly in directory preview
- Attempt wsh-free connection as a fallback if wsh installation or execution fails
- New `-i` flag to add identity files with the `wsh ssh` command
- Added Perplexity API integration ([docs](/faq#perplexity))
- `wsh setbg` command for background handling ([docs](/wsh#setbg))
- Switched from Less to SCSS for styling
- [bugfix] Fixed tab flickering issues during tab switches
- [bugfix] Corrected WaveAI text area resize behavior
- [bugfix] Fixed concurrent block controller start issues
- [bugfix] Fixed Preview Blocks for uninitialized connections
- Upgraded Go toolchain to 1.23.4
- Other bug fixes, performance improvements, and dependency updates
### v0.9.3 &mdash; Nov 20, 2024 ### v0.9.3 &mdash; Nov 20, 2024
New minor release that introduces Wave's connected computing extensions. We've introduced new `wsh` commands that allow you to store variables and files, and access them across terminal sessions (on both local and remote machines). New minor release that introduces Wave's connected computing extensions. We've introduced new `wsh` commands that allow you to store variables and files, and access them across terminal sessions (on both local and remote machines).

BIN
docs/static/img/ai-presets.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 211 KiB

View File

@ -23,7 +23,10 @@ export type WindowOpts = {
}; };
export const waveWindowMap = new Map<string, WaveBrowserWindow>(); // waveWindowId -> WaveBrowserWindow export const waveWindowMap = new Map<string, WaveBrowserWindow>(); // waveWindowId -> WaveBrowserWindow
export let focusedWaveWindow = null; // on blur we do not set this to null (but on destroy we do)
// on blur we do not set this to null (but on destroy we do), so this tracks the *last* focused window
// e.g. it persists when the app itself is not focused
export let focusedWaveWindow: WaveBrowserWindow = null;
let cachedClientId: string = null; let cachedClientId: string = null;
@ -55,10 +58,13 @@ type WindowActionQueueEntry =
workspaceId: string; workspaceId: string;
}; };
function showCloseConfirmDialog(workspace: Workspace): boolean {
return !workspace.name && !workspace.icon && (workspace.tabids?.length > 1 || workspace.pinnedtabids?.length > 1);
}
export class WaveBrowserWindow extends BaseWindow { export class WaveBrowserWindow extends BaseWindow {
waveWindowId: string; waveWindowId: string;
workspaceId: string; workspaceId: string;
waveReadyPromise: Promise<void>;
allLoadedTabViews: Map<string, WaveTabView>; allLoadedTabViews: Map<string, WaveTabView>;
activeTabView: WaveTabView; activeTabView: WaveTabView;
private canClose: boolean; private canClose: boolean;
@ -207,12 +213,7 @@ export class WaveBrowserWindow extends BaseWindow {
setWasActive(true); setWasActive(true);
}); });
this.on("blur", () => { this.on("blur", () => {
if (this.isDestroyed()) { // nothing for now
return;
}
if (focusedWaveWindow == this) {
focusedWaveWindow = null;
}
}); });
this.on("close", (e) => { this.on("close", (e) => {
if (this.canClose) { if (this.canClose) {
@ -232,14 +233,13 @@ export class WaveBrowserWindow extends BaseWindow {
console.log("numWindows > 1", numWindows); console.log("numWindows > 1", numWindows);
const workspace = await WorkspaceService.GetWorkspace(this.workspaceId); const workspace = await WorkspaceService.GetWorkspace(this.workspaceId);
console.log("workspace", workspace); console.log("workspace", workspace);
if (!workspace.name && !workspace.icon && workspace.tabids.length > 1) { if (showCloseConfirmDialog(workspace)) {
console.log("workspace has no name, icon, and multiple tabs", workspace); console.log("workspace has no name, icon, and multiple tabs", workspace);
const choice = dialog.showMessageBoxSync(this, { const choice = dialog.showMessageBoxSync(this, {
type: "question", type: "question",
buttons: ["Cancel", "Yes"], buttons: ["Cancel", "Close Window"],
title: "Confirm", title: "Confirm",
message: message: "Window has unsaved tabs, closing window will delete existing tabs.\n\nContinue?",
"Are you sure you want to close this window (all tabs and blocks will be deleted)?",
}); });
if (choice === 0) { if (choice === 0) {
console.log("user cancelled close window", this.waveWindowId); console.log("user cancelled close window", this.waveWindowId);
@ -303,16 +303,12 @@ export class WaveBrowserWindow extends BaseWindow {
const workspaceList = await WorkspaceService.ListWorkspaces(); const workspaceList = await WorkspaceService.ListWorkspaces();
if (!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid) { if (!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid) {
const curWorkspace = await WorkspaceService.GetWorkspace(this.workspaceId); const curWorkspace = await WorkspaceService.GetWorkspace(this.workspaceId);
if ( if (showCloseConfirmDialog(curWorkspace)) {
(curWorkspace.tabids?.length || curWorkspace.pinnedtabids?.length) &&
(!curWorkspace.name || !curWorkspace.icon)
) {
const choice = dialog.showMessageBoxSync(this, { const choice = dialog.showMessageBoxSync(this, {
type: "question", type: "question",
buttons: ["Cancel", "Open in New Window", "Yes"], buttons: ["Cancel", "Open in New Window", "Switch Workspace"],
title: "Confirm", title: "Confirm",
message: message: "Window has unsaved tabs, switching workspaces will delete existing tabs.\n\nContinue?",
"This window has unsaved tabs, switching workspaces will delete the existing tabs. Would you like to continue?",
}); });
if (choice === 0) { if (choice === 0) {
console.log("user cancelled switch workspace", this.waveWindowId); console.log("user cancelled switch workspace", this.waveWindowId);
@ -471,6 +467,9 @@ export class WaveBrowserWindow extends BaseWindow {
private async processActionQueue() { private async processActionQueue() {
while (this.actionQueue.length > 0) { while (this.actionQueue.length > 0) {
try { try {
if (this.isDestroyed()) {
break;
}
const entry = this.actionQueue[0]; const entry = this.actionQueue[0];
let tabId: string = null; let tabId: string = null;
// have to use "===" here to get the typechecker to work :/ // have to use "===" here to get the typechecker to work :/
@ -689,6 +688,21 @@ ipcMain.on("delete-workspace", (event, workspaceId) => {
fireAndForget(async () => { fireAndForget(async () => {
const ww = getWaveWindowByWebContentsId(event.sender.id); const ww = getWaveWindowByWebContentsId(event.sender.id);
console.log("delete-workspace", workspaceId, ww?.waveWindowId); console.log("delete-workspace", workspaceId, ww?.waveWindowId);
const workspaceList = await WorkspaceService.ListWorkspaces();
const workspaceHasWindow = !!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid;
const choice = dialog.showMessageBoxSync(this, {
type: "question",
buttons: ["Cancel", "Delete Workspace"],
title: "Confirm",
message: `Deleting workspace will also delete its contents.${workspaceHasWindow ? "\nWorkspace is open in a window, which will be closed." : ""}\n\nContinue?`,
});
if (choice === 0) {
console.log("user cancelled workspace delete", workspaceId, ww?.waveWindowId);
return;
}
await WorkspaceService.DeleteWorkspace(workspaceId); await WorkspaceService.DeleteWorkspace(workspaceId);
console.log("delete-workspace done", workspaceId, ww?.waveWindowId); console.log("delete-workspace done", workspaceId, ww?.waveWindowId);
if (ww?.workspaceId == workspaceId) { if (ww?.workspaceId == workspaceId) {
@ -711,7 +725,6 @@ export async function createNewWaveWindow() {
const existingWindowData = (await ObjectService.GetObject("window:" + existingWindowId)) as WaveWindow; const existingWindowData = (await ObjectService.GetObject("window:" + existingWindowId)) as WaveWindow;
if (existingWindowData != null) { if (existingWindowData != null) {
const win = await createBrowserWindow(existingWindowData, fullConfig, { unamePlatform }); const win = await createBrowserWindow(existingWindowData, fullConfig, { unamePlatform });
await win.waveReadyPromise;
win.show(); win.show();
recreatedWindow = true; recreatedWindow = true;
} }
@ -722,7 +735,6 @@ export async function createNewWaveWindow() {
} }
console.log("creating new window"); console.log("creating new window");
const newBrowserWindow = await createBrowserWindow(null, fullConfig, { unamePlatform }); const newBrowserWindow = await createBrowserWindow(null, fullConfig, { unamePlatform });
await newBrowserWindow.waveReadyPromise;
newBrowserWindow.show(); newBrowserWindow.show();
} }
@ -754,7 +766,6 @@ export async function relaunchBrowserWindows() {
wins.push(win); wins.push(win);
} }
for (const win of wins) { for (const win of wins) {
await win.waveReadyPromise;
console.log("show window", win.waveWindowId); console.log("show window", win.waveWindowId);
win.show(); win.show();
} }

View File

@ -94,7 +94,6 @@ function handleWSEvent(evtMsg: WSEventType) {
} }
const fullConfig = await services.FileService.GetFullConfig(); const fullConfig = await services.FileService.GetFullConfig();
const newWin = await createBrowserWindow(windowData, fullConfig, { unamePlatform }); const newWin = await createBrowserWindow(windowData, fullConfig, { unamePlatform });
await newWin.waveReadyPromise;
newWin.show(); newWin.show();
} else if (evtMsg.eventtype == "electron:closewindow") { } else if (evtMsg.eventtype == "electron:closewindow") {
console.log("electron:closewindow", evtMsg.data); console.log("electron:closewindow", evtMsg.data);
@ -378,6 +377,9 @@ function saveImageFileWithNativeDialog(defaultFileName: string, mimeType: string
defaultFileName = "image"; defaultFileName = "image";
} }
const ww = focusedWaveWindow; const ww = focusedWaveWindow;
if (ww == null) {
return;
}
const mimeToExtension: { [key: string]: string } = { const mimeToExtension: { [key: string]: string } = {
"image/png": "png", "image/png": "png",
"image/jpeg": "jpg", "image/jpeg": "jpg",

View File

@ -164,7 +164,9 @@ export class Updater {
type: "info", type: "info",
message: "There are currently no updates available.", message: "There are currently no updates available.",
}; };
dialog.showMessageBox(focusedWaveWindow, dialogOpts); if (focusedWaveWindow) {
dialog.showMessageBox(focusedWaveWindow, dialogOpts);
}
} }
// Only update the last check time if this is an automatic check. This ensures the interval remains consistent. // Only update the last check time if this is an automatic check. This ensures the interval remains consistent.

View File

@ -637,7 +637,7 @@ const ChangeConnectionBlockModal = React.memo(
const connStatusMap = new Map<string, ConnStatus>(); const connStatusMap = new Map<string, ConnStatus>();
const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom); const fullConfig = jotai.useAtomValue(atoms.fullConfigAtom);
const connectionsConfig = fullConfig.connections; const connectionsConfig = fullConfig.connections;
let filterOutNowsh = util.useAtomValueSafe(viewModel.filterOutNowsh) || true; let filterOutNowsh = util.useAtomValueSafe(viewModel.filterOutNowsh) ?? true;
let maxActiveConnNum = 1; let maxActiveConnNum = 1;
for (const conn of allConnStatus) { for (const conn of allConnStatus) {
@ -706,9 +706,6 @@ const ChangeConnectionBlockModal = React.memo(
} }
const filteredList: Array<string> = []; const filteredList: Array<string> = [];
for (const conn of connList) { for (const conn of connList) {
if (conn === connSelected) {
createNew = false;
}
if ( if (
conn.includes(connSelected) && conn.includes(connSelected) &&
connectionsConfig?.[conn]?.["display:hidden"] != true && connectionsConfig?.[conn]?.["display:hidden"] != true &&
@ -716,13 +713,13 @@ const ChangeConnectionBlockModal = React.memo(
// != false is necessary because of defaults // != false is necessary because of defaults
) { ) {
filteredList.push(conn); filteredList.push(conn);
if (conn === connSelected) {
createNew = false;
}
} }
} }
const filteredWslList: Array<string> = []; const filteredWslList: Array<string> = [];
for (const conn of wslList) { for (const conn of wslList) {
if (conn === connSelected) {
createNew = false;
}
if ( if (
conn.includes(connSelected) && conn.includes(connSelected) &&
connectionsConfig?.[conn]?.["display:hidden"] != true && connectionsConfig?.[conn]?.["display:hidden"] != true &&
@ -730,6 +727,9 @@ const ChangeConnectionBlockModal = React.memo(
// != false is necessary because of defaults // != false is necessary because of defaults
) { ) {
filteredWslList.push(conn); filteredWslList.push(conn);
if (conn === connSelected) {
createNew = false;
}
} }
} }
// priority handles special suggestions when necessary // priority handles special suggestions when necessary

View File

@ -146,6 +146,10 @@
} }
} }
&.bold {
font-weight: bold;
}
&:disabled { &:disabled {
cursor: default; cursor: default;
opacity: 0.5; opacity: 0.5;

View File

@ -67,6 +67,7 @@ interface InputProps {
required?: boolean; required?: boolean;
maxLength?: number; maxLength?: number;
autoFocus?: boolean; autoFocus?: boolean;
autoSelect?: boolean;
disabled?: boolean; disabled?: boolean;
isNumber?: boolean; isNumber?: boolean;
inputRef?: React.MutableRefObject<any>; inputRef?: React.MutableRefObject<any>;
@ -88,6 +89,7 @@ const Input = memo(
required, required,
maxLength, maxLength,
autoFocus, autoFocus,
autoSelect,
disabled, disabled,
isNumber, isNumber,
manageFocus, manageFocus,
@ -114,6 +116,9 @@ const Input = memo(
}; };
const handleFocus = () => { const handleFocus = () => {
if (autoSelect) {
inputRef.current?.select();
}
manageFocus?.(true); manageFocus?.(true);
onFocus?.(); onFocus?.();
}; };

View File

@ -1,6 +1,8 @@
// Copyright 2024, Command Line Inc. // Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
@import url("../../../node_modules/highlight.js/scss/github-dark-dimmed.scss");
.markdown { .markdown {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View File

@ -2,6 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
.window-drag { .window-drag {
-webkit-app-region: drag; -webkit-app-region: drag !important;
z-index: var(--zindex-window-drag); z-index: var(--zindex-window-drag);
} }

View File

@ -5,7 +5,7 @@
position: absolute; position: absolute;
width: 130px; width: 130px;
height: calc(100% - 1px); height: calc(100% - 1px);
padding: 6px 0px 0px; padding: 0 0 0 0;
box-sizing: border-box; box-sizing: border-box;
font-weight: bold; font-weight: bold;
color: var(--secondary-text-color); color: var(--secondary-text-color);

View File

@ -28,17 +28,23 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
width: env(titlebar-area-width); width: env(titlebar-area-width);
-webkit-app-region: drag;
* {
-webkit-app-region: no-drag;
}
.tabs-wrapper { .tabs-wrapper {
transition: var(--tabs-wrapper-transition); transition: var(--tabs-wrapper-transition);
height: 32px; height: 26px;
} }
.tab-bar { .tab-bar {
margin-top: 6px;
position: relative; // Needed for absolute positioning of child tabs position: relative; // Needed for absolute positioning of child tabs
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 33px; height: 27px;
} }
.pinned-tab-spacer { .pinned-tab-spacer {

View File

@ -99,6 +99,43 @@ const ConfigErrorIcon = ({ buttonRef }: { buttonRef: React.RefObject<HTMLElement
); );
}; };
function strArrayIsEqual(a: string[], b: string[]) {
// null check
if (a == null && b == null) {
return true;
}
if (a == null || b == null) {
return false;
}
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
function setIsEqual(a: Set<string> | null, b: Set<string> | null): boolean {
if (a == null && b == null) {
return true;
}
if (a == null || b == null) {
return false;
}
if (a.size !== b.size) {
return false;
}
for (const item of a) {
if (!b.has(item)) {
return false;
}
}
return true;
}
const TabBar = memo(({ workspace }: TabBarProps) => { const TabBar = memo(({ workspace }: TabBarProps) => {
const [tabIds, setTabIds] = useState([]); const [tabIds, setTabIds] = useState([]);
const [pinnedTabIds, setPinnedTabIds] = useState<Set<string>>(new Set()); const [pinnedTabIds, setPinnedTabIds] = useState<Set<string>>(new Set());
@ -149,22 +186,22 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
}, [tabIds]); }, [tabIds]);
useEffect(() => { useEffect(() => {
if (workspace) { if (!workspace) {
// Compare current tabIds with new workspace.tabids return;
const newTabIds = new Set([...(workspace.pinnedtabids ?? []), ...(workspace.tabids ?? [])]); }
const newPinnedTabIds = workspace.pinnedtabids ?? []; // Compare current tabIds with new workspace.tabids
console.log("tabbar workspace", workspace);
const areEqual = const newTabIdsArr = [...(workspace.pinnedtabids ?? []), ...(workspace.tabids ?? [])];
tabIds.length === newTabIds.size && const newPinnedTabSet = new Set(workspace.pinnedtabids ?? []);
tabIds.every((id) => newTabIds.has(id)) &&
newPinnedTabIds.length === pinnedTabIds.size;
if (!areEqual) { const areEqual = strArrayIsEqual(tabIds, newTabIdsArr) && setIsEqual(pinnedTabIds, newPinnedTabSet);
const newPinnedTabIdSet = new Set(newPinnedTabIds);
const newTabIdList = newPinnedTabIds.concat([...newTabIds].filter((id) => !newPinnedTabIdSet.has(id))); // Corrects for any duplicates between the two lists if (!areEqual) {
setTabIds(newTabIdList); console.log("newPinnedTabIds", newPinnedTabSet);
setPinnedTabIds(newPinnedTabIdSet); console.log("newTabIdList", newTabIdsArr);
} setTabIds(newTabIdsArr);
setPinnedTabIds(newPinnedTabSet);
} }
}, [workspace, tabIds, pinnedTabIds]); }, [workspace, tabIds, pinnedTabIds]);

View File

@ -143,14 +143,9 @@
.expandable-menu-item-group-title { .expandable-menu-item-group-title {
height: 29px; height: 29px;
padding: 0; padding: 0;
.left-icon {
font-size: 14px;
width: 16px;
}
} }
.color-icon-selector { .workspace-editor {
width: 100%; width: 100%;
.input { .input {
margin: 5px 0 10px; margin: 5px 0 10px;
@ -223,7 +218,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-top: 5px; margin-top: 10px;
} }
} }

View File

@ -43,7 +43,7 @@ const colors = [
]; ];
const icons = [ const icons = [
"circle", "custom@wave-logo-solid",
"triangle", "triangle",
"star", "star",
"heart", "heart",
@ -93,7 +93,7 @@ const IconSelector = memo(({ icons, selectedIcon, onSelect, className }: IconSel
return ( return (
<div className={clsx("icon-selector", className)}> <div className={clsx("icon-selector", className)}>
{icons.map((icon) => { {icons.map((icon) => {
const iconClass = makeIconClass(icon, false); const iconClass = makeIconClass(icon, true);
return ( return (
<i <i
key={icon} key={icon}
@ -106,7 +106,7 @@ const IconSelector = memo(({ icons, selectedIcon, onSelect, className }: IconSel
); );
}); });
interface ColorAndIconSelectorProps { interface WorkspaceEditorProps {
title: string; title: string;
icon: string; icon: string;
color: string; color: string;
@ -116,7 +116,7 @@ interface ColorAndIconSelectorProps {
onIconChange: (newIcon: string) => void; onIconChange: (newIcon: string) => void;
onDeleteWorkspace: () => void; onDeleteWorkspace: () => void;
} }
const ColorAndIconSelector = memo( const WorkspaceEditor = memo(
({ ({
title, title,
icon, icon,
@ -126,28 +126,30 @@ const ColorAndIconSelector = memo(
onColorChange, onColorChange,
onIconChange, onIconChange,
onDeleteWorkspace, onDeleteWorkspace,
}: ColorAndIconSelectorProps) => { }: WorkspaceEditorProps) => {
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => { useEffect(() => {
if (focusInput && inputRef.current) { if (focusInput && inputRef.current) {
inputRef.current.focus(); inputRef.current.focus();
inputRef.current.select();
} }
}, [focusInput]); }, [focusInput]);
return ( return (
<div className="color-icon-selector"> <div className="workspace-editor">
<Input <Input
ref={inputRef} ref={inputRef}
className={clsx("vertical-padding-3", { error: title === "" })} className={clsx("vertical-padding-3", { error: title === "" })}
onChange={onTitleChange} onChange={onTitleChange}
value={title} value={title}
autoFocus autoFocus
autoSelect
/> />
<ColorSelector selectedColor={color} colors={colors} onSelect={onColorChange} /> <ColorSelector selectedColor={color} colors={colors} onSelect={onColorChange} />
<IconSelector selectedIcon={icon} icons={icons} onSelect={onIconChange} /> <IconSelector selectedIcon={icon} icons={icons} onSelect={onIconChange} />
<div className="delete-ws-btn-wrapper"> <div className="delete-ws-btn-wrapper">
<Button className="ghost red font-size-12" onClick={onDeleteWorkspace}> <Button className="ghost red font-size-12 bold" onClick={onDeleteWorkspace}>
Delete workspace Delete workspace
</Button> </Button>
</div> </div>
@ -221,6 +223,7 @@ const WorkspaceSwitcher = forwardRef<HTMLDivElement, {}>(({}, ref) => {
setTimeout(() => { setTimeout(() => {
fireAndForget(updateWorkspaceList); fireAndForget(updateWorkspaceList);
}, 10); }, 10);
setEditingWorkspace(activeWorkspace.oid);
}; };
return ( return (
@ -335,7 +338,7 @@ const WorkspaceSwitcherItem = ({
> >
<ExpandableMenuItemLeftElement> <ExpandableMenuItemLeftElement>
<i <i
className={clsx("left-icon", makeIconClass(workspace.icon, false))} className={clsx("left-icon", makeIconClass(workspace.icon, true))}
style={{ color: workspace.color }} style={{ color: workspace.color }}
/> />
</ExpandableMenuItemLeftElement> </ExpandableMenuItemLeftElement>
@ -349,7 +352,7 @@ const WorkspaceSwitcherItem = ({
</div> </div>
</ExpandableMenuItemGroupTitle> </ExpandableMenuItemGroupTitle>
<ExpandableMenuItem> <ExpandableMenuItem>
<ColorAndIconSelector <WorkspaceEditor
title={workspace.name} title={workspace.name}
icon={workspace.icon} icon={workspace.icon}
color={workspace.color} color={workspace.color}

View File

@ -2,7 +2,6 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Used for syntax highlighting in markdown // Used for syntax highlighting in markdown
@import url("../../node_modules/highlight.js/styles/github-dark-dimmed.min.css");
:root { :root {
--main-text-color: #f7f7f7; --main-text-color: #f7f7f7;

View File

@ -293,7 +293,8 @@ function DirectoryTable({
newPath = path.replace(fileName, newName); newPath = path.replace(fileName, newName);
console.log(`replacing ${fileName} with ${newName}: ${path}`); console.log(`replacing ${fileName} with ${newName}: ${path}`);
fireAndForget(async () => { fireAndForget(async () => {
await FileService.Rename(globalStore.get(model.connection), path, newPath); const connection = await globalStore.get(model.connection);
await FileService.Rename(connection, path, newPath);
model.refreshCallback(); model.refreshCallback();
}); });
} }
@ -804,7 +805,8 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
onSave: (newName: string) => { onSave: (newName: string) => {
console.log(`newFile: ${newName}`); console.log(`newFile: ${newName}`);
fireAndForget(async () => { fireAndForget(async () => {
await FileService.TouchFile(globalStore.get(model.connection), `${dirPath}/${newName}`); const connection = await globalStore.get(model.connection);
await FileService.TouchFile(connection, `${dirPath}/${newName}`);
model.refreshCallback(); model.refreshCallback();
}); });
setEntryManagerProps(undefined); setEntryManagerProps(undefined);
@ -817,7 +819,8 @@ function DirectoryPreview({ model }: DirectoryPreviewProps) {
onSave: (newName: string) => { onSave: (newName: string) => {
console.log(`newDirectory: ${newName}`); console.log(`newDirectory: ${newName}`);
fireAndForget(async () => { fireAndForget(async () => {
await FileService.Mkdir(globalStore.get(model.connection), `${dirPath}/${newName}`); const connection = await globalStore.get(model.connection);
await FileService.Mkdir(connection, `${dirPath}/${newName}`);
model.refreshCallback(); model.refreshCallback();
}); });
setEntryManagerProps(undefined); setEntryManagerProps(undefined);

View File

@ -128,7 +128,7 @@ export class PreviewModel implements ViewModel {
normFilePath: Atom<Promise<string>>; normFilePath: Atom<Promise<string>>;
loadableStatFilePath: Atom<Loadable<string>>; loadableStatFilePath: Atom<Loadable<string>>;
loadableFileInfo: Atom<Loadable<FileInfo>>; loadableFileInfo: Atom<Loadable<FileInfo>>;
connection: Atom<string>; connection: Atom<Promise<string>>;
statFile: Atom<Promise<FileInfo>>; statFile: Atom<Promise<FileInfo>>;
fullFile: Atom<Promise<FullFile>>; fullFile: Atom<Promise<FullFile>>;
fileMimeType: Atom<Promise<string>>; fileMimeType: Atom<Promise<string>>;
@ -136,6 +136,7 @@ export class PreviewModel implements ViewModel {
fileContentSaved: PrimitiveAtom<string | null>; fileContentSaved: PrimitiveAtom<string | null>;
fileContent: WritableAtom<Promise<string>, [string], void>; fileContent: WritableAtom<Promise<string>, [string], void>;
newFileContent: PrimitiveAtom<string | null>; newFileContent: PrimitiveAtom<string | null>;
connectionError: PrimitiveAtom<string>;
openFileModal: PrimitiveAtom<boolean>; openFileModal: PrimitiveAtom<boolean>;
openFileError: PrimitiveAtom<string>; openFileError: PrimitiveAtom<string>;
@ -167,6 +168,7 @@ export class PreviewModel implements ViewModel {
this.markdownShowToc = atom(false); this.markdownShowToc = atom(false);
this.filterOutNowsh = atom(true); this.filterOutNowsh = atom(true);
this.monacoRef = createRef(); this.monacoRef = createRef();
this.connectionError = atom("");
this.viewIcon = atom((get) => { this.viewIcon = atom((get) => {
const blockData = get(this.blockAtom); const blockData = get(this.blockAtom);
if (blockData?.meta?.icon) { if (blockData?.meta?.icon) {
@ -359,15 +361,22 @@ export class PreviewModel implements ViewModel {
return fileInfo.dir + "/" + fileInfo.name; return fileInfo.dir + "/" + fileInfo.name;
}); });
this.loadableStatFilePath = loadable(this.statFilePath); this.loadableStatFilePath = loadable(this.statFilePath);
this.connection = atom<string>((get) => { this.connection = atom<Promise<string>>(async (get) => {
return get(this.blockAtom)?.meta?.connection; const connName = get(this.blockAtom)?.meta?.connection;
try {
await RpcApi.ConnEnsureCommand(TabRpcClient, connName, { timeout: 60000 });
globalStore.set(this.connectionError, "");
} catch (e) {
globalStore.set(this.connectionError, e as string);
}
return connName;
}); });
this.statFile = atom<Promise<FileInfo>>(async (get) => { this.statFile = atom<Promise<FileInfo>>(async (get) => {
const fileName = get(this.metaFilePath); const fileName = get(this.metaFilePath);
if (fileName == null) { if (fileName == null) {
return null; return null;
} }
const conn = get(this.connection) ?? ""; const conn = (await get(this.connection)) ?? "";
const statFile = await services.FileService.StatFile(conn, fileName); const statFile = await services.FileService.StatFile(conn, fileName);
return statFile; return statFile;
}); });
@ -384,7 +393,7 @@ export class PreviewModel implements ViewModel {
if (fileName == null) { if (fileName == null) {
return null; return null;
} }
const conn = get(this.connection) ?? ""; const conn = (await get(this.connection)) ?? "";
const file = await services.FileService.ReadFile(conn, fileName); const file = await services.FileService.ReadFile(conn, fileName);
return file; return file;
}); });
@ -434,10 +443,14 @@ export class PreviewModel implements ViewModel {
const mimeType = await getFn(this.fileMimeType); const mimeType = await getFn(this.fileMimeType);
const fileInfo = await getFn(this.statFile); const fileInfo = await getFn(this.statFile);
const fileName = await getFn(this.statFilePath); const fileName = await getFn(this.statFilePath);
const connErr = getFn(this.connectionError);
const editMode = getFn(this.editMode); const editMode = getFn(this.editMode);
const parentFileInfo = await this.getParentInfo(fileInfo); const parentFileInfo = await this.getParentInfo(fileInfo);
console.log(parentFileInfo); console.log(parentFileInfo);
if (connErr != "") {
return { errorStr: `Connection Error: ${connErr}` };
}
if (parentFileInfo?.notfound ?? false) { if (parentFileInfo?.notfound ?? false) {
return { errorStr: `Parent Directory Not Found: ${fileInfo.path}` }; return { errorStr: `Parent Directory Not Found: ${fileInfo.path}` };
} }
@ -505,7 +518,7 @@ export class PreviewModel implements ViewModel {
} }
async getParentInfo(fileInfo: FileInfo): Promise<FileInfo | undefined> { async getParentInfo(fileInfo: FileInfo): Promise<FileInfo | undefined> {
const conn = globalStore.get(this.connection); const conn = await globalStore.get(this.connection);
try { try {
const parentFileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [fileInfo.path, ".."], { const parentFileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [fileInfo.path, ".."], {
route: makeConnRoute(conn), route: makeConnRoute(conn),
@ -526,7 +539,7 @@ export class PreviewModel implements ViewModel {
this.updateOpenFileModalAndError(false); this.updateOpenFileModalAndError(false);
return true; return true;
} }
const conn = globalStore.get(this.connection); const conn = await globalStore.get(this.connection);
try { try {
const newFileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [fileInfo.path, ".."], { const newFileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [fileInfo.path, ".."], {
route: makeConnRoute(conn), route: makeConnRoute(conn),
@ -586,7 +599,7 @@ export class PreviewModel implements ViewModel {
console.log("not saving file, newFileContent is null"); console.log("not saving file, newFileContent is null");
return; return;
} }
const conn = globalStore.get(this.connection) ?? ""; const conn = (await globalStore.get(this.connection)) ?? "";
try { try {
await services.FileService.SaveFile(conn, filePath, stringToBase64(newFileContent)); await services.FileService.SaveFile(conn, filePath, stringToBase64(newFileContent));
globalStore.set(this.fileContent, newFileContent); globalStore.set(this.fileContent, newFileContent);
@ -609,7 +622,7 @@ export class PreviewModel implements ViewModel {
this.updateOpenFileModalAndError(false); this.updateOpenFileModalAndError(false);
return true; return true;
} }
const conn = globalStore.get(this.connection); const conn = await globalStore.get(this.connection);
try { try {
const newFileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [fileInfo.dir, filePath], { const newFileInfo = await RpcApi.RemoteFileJoinCommand(TabRpcClient, [fileInfo.dir, filePath], {
route: makeConnRoute(conn), route: makeConnRoute(conn),

View File

@ -103,6 +103,11 @@ function makeIconClass(icon: string, fw: boolean, opts?: { spin?: boolean; defau
icon = icon.replace(/^brands@/, ""); icon = icon.replace(/^brands@/, "");
return clsx(`fa fa-brands fa-${icon}`, fw ? "fa-fw" : null, opts?.spin ? "fa-spin" : null); return clsx(`fa fa-brands fa-${icon}`, fw ? "fa-fw" : null, opts?.spin ? "fa-spin" : null);
} }
if (icon.match(/^custom@[a-z0-9-]+$/)) {
// strip off the "custom@" prefix if it exists
icon = icon.replace(/^custom@/, "");
return clsx(`fa fa-kit fa-${icon}`, fw ? "fa-fw" : null, opts?.spin ? "fa-spin" : null);
}
if (opts?.defaultIcon != null) { if (opts?.defaultIcon != null) {
return makeIconClass(opts.defaultIcon, fw, { spin: opts?.spin }); return makeIconClass(opts.defaultIcon, fw, { spin: opts?.spin });
} }

View File

@ -109,21 +109,27 @@ async function reinitWave() {
} }
function reloadAllWorkspaceTabs(ws: Workspace) { function reloadAllWorkspaceTabs(ws: Workspace) {
if (ws == null || ws.tabids == null) { if (ws == null || (!ws.tabids?.length && !ws.pinnedtabids?.length)) {
return; return;
} }
ws.tabids.forEach((tabid) => { ws?.tabids.forEach((tabid) => {
WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", tabid));
});
ws?.pinnedtabids?.forEach((tabid) => {
WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", tabid)); WOS.reloadWaveObject<Tab>(WOS.makeORef("tab", tabid));
}); });
} }
function loadAllWorkspaceTabs(ws: Workspace) { function loadAllWorkspaceTabs(ws: Workspace) {
if (ws == null || ws.tabids == null) { if (ws == null || (!ws.tabids?.length && !ws.pinnedtabids?.length)) {
return; return;
} }
ws.tabids.forEach((tabid) => { ws.tabids.forEach((tabid) => {
WOS.getObjectValue<Tab>(WOS.makeORef("tab", tabid)); WOS.getObjectValue<Tab>(WOS.makeORef("tab", tabid));
}); });
ws.pinnedtabids.forEach((tabid) => {
WOS.getObjectValue<Tab>(WOS.makeORef("tab", tabid));
});
} }
async function initWave(initOpts: WaveInitOpts) { async function initWave(initOpts: WaveInitOpts) {

View File

@ -10,6 +10,7 @@
<link rel="stylesheet" href="/fontawesome/css/solid.min.css" /> <link rel="stylesheet" href="/fontawesome/css/solid.min.css" />
<link rel="stylesheet" href="/fontawesome/css/sharp-solid.min.css" /> <link rel="stylesheet" href="/fontawesome/css/sharp-solid.min.css" />
<link rel="stylesheet" href="/fontawesome/css/sharp-regular.min.css" /> <link rel="stylesheet" href="/fontawesome/css/sharp-regular.min.css" />
<link rel="stylesheet" href="/fontawesome/css/custom-icons.min.css" />
<script type="module" src="frontend/wave.ts"></script> <script type="module" src="frontend/wave.ts"></script>
</head> </head>
<body class="init"> <body class="init">

View File

@ -7,7 +7,7 @@
"productName": "Wave", "productName": "Wave",
"description": "Open-Source AI-Native Terminal Built for Seamless Workflows", "description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
"license": "Apache-2.0", "license": "Apache-2.0",
"version": "0.10.0-beta.2", "version": "0.10.0-beta.4",
"homepage": "https://waveterm.dev", "homepage": "https://waveterm.dev",
"build": { "build": {
"appId": "dev.commandline.waveterm" "appId": "dev.commandline.waveterm"
@ -30,14 +30,15 @@
"@chromatic-com/storybook": "^3.2.2", "@chromatic-com/storybook": "^3.2.2",
"@eslint/js": "^9.16.0", "@eslint/js": "^9.16.0",
"@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-node-resolve": "^15.3.0",
"@storybook/addon-essentials": "^8.4.6", "@storybook/addon-essentials": "^8.4.7",
"@storybook/addon-interactions": "^8.4.6", "@storybook/addon-interactions": "^8.4.7",
"@storybook/addon-links": "^8.4.6", "@storybook/addon-links": "^8.4.7",
"@storybook/blocks": "^8.4.6", "@storybook/blocks": "^8.4.7",
"@storybook/react": "^8.4.6", "@storybook/builder-vite": "^8.4.7",
"@storybook/react-vite": "^8.4.6", "@storybook/react": "^8.4.7",
"@storybook/test": "^8.4.6", "@storybook/react-vite": "^8.4.7",
"@storybook/theming": "^8.4.6", "@storybook/test": "^8.4.7",
"@storybook/theming": "^8.4.7",
"@types/color": "^4.2.0", "@types/color": "^4.2.0",
"@types/css-tree": "^2", "@types/css-tree": "^2",
"@types/debug": "^4", "@types/debug": "^4",
@ -68,7 +69,7 @@
"rollup-plugin-flow": "^1.1.1", "rollup-plugin-flow": "^1.1.1",
"sass": "^1.82.0", "sass": "^1.82.0",
"semver": "^7.6.3", "semver": "^7.6.3",
"storybook": "^8.4.6", "storybook": "^8.4.7",
"storybook-dark-mode": "^4.0.2", "storybook-dark-mode": "^4.0.2",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"tslib": "^2.8.1", "tslib": "^2.8.1",

View File

@ -733,6 +733,10 @@ func GetConnectionsFromInternalConfig() []string {
var internalNames []string var internalNames []string
config := wconfig.ReadFullConfig() config := wconfig.ReadFullConfig()
for internalName := range config.Connections { for internalName := range config.Connections {
if strings.HasPrefix(internalName, "wsl://") {
// don't add wsl conns to this list
continue
}
internalNames = append(internalNames, internalName) internalNames = append(internalNames, internalName)
} }
return internalNames return internalNames

View File

@ -1,3 +1,6 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package workspaceservice package workspaceservice
import ( import (

View File

@ -96,5 +96,13 @@
"bg": "linear-gradient(120deg,hsla(350, 65%, 57%, 1),hsla(30,60%,60%, .75), hsla(208,69%,50%,.15), hsl(230,60%,40%)),radial-gradient(at top right,hsla(300,60%,70%,0.3),transparent),radial-gradient(at top left,hsla(330,100%,70%,.20),transparent),radial-gradient(at top right,hsla(190,100%,40%,.20),transparent),radial-gradient(at bottom left,hsla(323,54%,50%,.5),transparent),radial-gradient(at bottom left,hsla(144,54%,50%,.25),transparent)", "bg": "linear-gradient(120deg,hsla(350, 65%, 57%, 1),hsla(30,60%,60%, .75), hsla(208,69%,50%,.15), hsl(230,60%,40%)),radial-gradient(at top right,hsla(300,60%,70%,0.3),transparent),radial-gradient(at top left,hsla(330,100%,70%,.20),transparent),radial-gradient(at top right,hsla(190,100%,40%,.20),transparent),radial-gradient(at bottom left,hsla(323,54%,50%,.5),transparent),radial-gradient(at bottom left,hsla(144,54%,50%,.25),transparent)",
"bg:blendmode": "overlay", "bg:blendmode": "overlay",
"bg:text": "rgb(200, 200, 200)" "bg:text": "rgb(200, 200, 200)"
},
"bg@cosmic-tide": {
"display:name": "Cosmic Tide",
"display:order": 3.6,
"bg:activebordercolor": "#ff55aa",
"bg:*": true,
"bg": "linear-gradient(135deg, #00d9d9, #ff55aa, #1e1e2f, #2f3b57, #ff99ff)",
"bg:opacity": 0.6
} }
} }

View File

@ -1,3 +1,6 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package wcore package wcore
import ( import (

View File

@ -54,7 +54,7 @@ func EnsureInitialData() error {
return nil return nil
} }
log.Println("client has no windows, creating starter workspace") log.Println("client has no windows, creating starter workspace")
starterWs, err := CreateWorkspace(ctx, "Starter workspace", "circle", "#58C142", true) starterWs, err := CreateWorkspace(ctx, "Starter workspace", "custom@wave-logo-solid", "#58C142", true)
if err != nil { if err != nil {
return fmt.Errorf("error creating starter workspace: %w", err) return fmt.Errorf("error creating starter workspace: %w", err)
} }

View File

@ -1,3 +1,6 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package wcore package wcore
import ( import (

View File

@ -1,3 +1,6 @@
// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package wcore package wcore
import ( import (

View File

@ -0,0 +1 @@
@charset "utf-8";.fak.fa-wave-logo-solid,.fa-kit.fa-wave-logo-solid{--fa:"";--fa--fa:""}.fak,.fa-kit{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;display:var(--fa-display,inline-block);font-variant:normal;text-rendering:auto;font-family:Font Awesome Kit;font-style:normal;font-weight:400;line-height:1}.fak:before,.fa-kit:before{content:var(--fa)}@font-face{font-family:Font Awesome Kit;font-style:normal;font-display:block;src:url(../webfonts/custom-icons.woff2)format("woff2"),url(../webfonts/custom-icons.ttf)format("truetype")}

File diff suppressed because one or more lines are too long

Binary file not shown.

317
yarn.lock
View File

@ -5167,9 +5167,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-actions@npm:8.4.6": "@storybook/addon-actions@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-actions@npm:8.4.6" resolution: "@storybook/addon-actions@npm:8.4.7"
dependencies: dependencies:
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
"@types/uuid": "npm:^9.0.1" "@types/uuid": "npm:^9.0.1"
@ -5177,164 +5177,164 @@ __metadata:
polished: "npm:^4.2.2" polished: "npm:^4.2.2"
uuid: "npm:^9.0.0" uuid: "npm:^9.0.0"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/80b2feceacb4ebe7f2be06b2fe3f49ded5ee08ca8bd036ff47a65d45d8796d29081ccadd0526984c8022bcfa24348e0ad4ce3f37cee4a60a928bae372bfc8afe checksum: 10c0/411be60f358101291cbd4ff8e5ddbac58fa0583c95338b82b410dc030a73632b654eaf7004b421c7e309cf0bfa709c4f93728b943e1b59dcfff5a249686501c1
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-backgrounds@npm:8.4.6": "@storybook/addon-backgrounds@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-backgrounds@npm:8.4.6" resolution: "@storybook/addon-backgrounds@npm:8.4.7"
dependencies: dependencies:
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
memoizerific: "npm:^1.11.3" memoizerific: "npm:^1.11.3"
ts-dedent: "npm:^2.0.0" ts-dedent: "npm:^2.0.0"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/2125d6905bf44194adf79e92698753d5e4ff75fac1ffbba1fc95ae705ba9ac8dc6ca9249c9a862aa05ea207d916d23142faefa759bb9ce21c6e16f0e329d28d2 checksum: 10c0/d22c4acd1d99f616865dde11c70b444a0aac7fe7623904479a29a0142b504f284ddc2407eacfd1203c3b0856e5497e7902eb86e287516364c7735b90e224bbcb
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-controls@npm:8.4.6": "@storybook/addon-controls@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-controls@npm:8.4.6" resolution: "@storybook/addon-controls@npm:8.4.7"
dependencies: dependencies:
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
dequal: "npm:^2.0.2" dequal: "npm:^2.0.2"
ts-dedent: "npm:^2.0.0" ts-dedent: "npm:^2.0.0"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/f5f0ab2de8de80c8c3726de81802042cc29a6f2ec50de3b8bd463286c9056e87800e4ea9b350c6a41ce4c4175a11cb7d3d490da5cfc20bbf2a2e3549f77a82a7 checksum: 10c0/900c71d172e9f75a1c39a87de1d411890fcea012586be02e3293c705c500a3a62a2bdecb10c11ba9c9f6117706dfbc34aaa40d2ca8e8a9d7b8a6a739d6a73e0c
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-docs@npm:8.4.6": "@storybook/addon-docs@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-docs@npm:8.4.6" resolution: "@storybook/addon-docs@npm:8.4.7"
dependencies: dependencies:
"@mdx-js/react": "npm:^3.0.0" "@mdx-js/react": "npm:^3.0.0"
"@storybook/blocks": "npm:8.4.6" "@storybook/blocks": "npm:8.4.7"
"@storybook/csf-plugin": "npm:8.4.6" "@storybook/csf-plugin": "npm:8.4.7"
"@storybook/react-dom-shim": "npm:8.4.6" "@storybook/react-dom-shim": "npm:8.4.7"
react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0" react: "npm:^16.8.0 || ^17.0.0 || ^18.0.0"
react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0" react-dom: "npm:^16.8.0 || ^17.0.0 || ^18.0.0"
ts-dedent: "npm:^2.0.0" ts-dedent: "npm:^2.0.0"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/ae53bf71048fe7476862ae733f0f765a22d0d1da32457f7ca7e3bdd23bb1cd452c56bc4e1f586cf978599c3f5acb835caeb569ff394eaec09d3259382f4954be checksum: 10c0/0eb1854ddb6dbef1b32f89746944ee7a16db986403fe0a3712f43d39faa6335e0bce4ac21a8c20d09955ae73cccd1962f3b45037ab1144f61c1317d686e8695f
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-essentials@npm:^8.4.6": "@storybook/addon-essentials@npm:^8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-essentials@npm:8.4.6" resolution: "@storybook/addon-essentials@npm:8.4.7"
dependencies: dependencies:
"@storybook/addon-actions": "npm:8.4.6" "@storybook/addon-actions": "npm:8.4.7"
"@storybook/addon-backgrounds": "npm:8.4.6" "@storybook/addon-backgrounds": "npm:8.4.7"
"@storybook/addon-controls": "npm:8.4.6" "@storybook/addon-controls": "npm:8.4.7"
"@storybook/addon-docs": "npm:8.4.6" "@storybook/addon-docs": "npm:8.4.7"
"@storybook/addon-highlight": "npm:8.4.6" "@storybook/addon-highlight": "npm:8.4.7"
"@storybook/addon-measure": "npm:8.4.6" "@storybook/addon-measure": "npm:8.4.7"
"@storybook/addon-outline": "npm:8.4.6" "@storybook/addon-outline": "npm:8.4.7"
"@storybook/addon-toolbars": "npm:8.4.6" "@storybook/addon-toolbars": "npm:8.4.7"
"@storybook/addon-viewport": "npm:8.4.6" "@storybook/addon-viewport": "npm:8.4.7"
ts-dedent: "npm:^2.0.0" ts-dedent: "npm:^2.0.0"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/b8fb83e018fcb1e8cad04b371af5f8ce9933e3a500a78a889715ecfe4efd9faa52acce2d0f97fb04fe9ae0898e661112816c052bfe9b5f01189938b122055a44 checksum: 10c0/82ddd8424dfd5bf0ef44cee6a320f8395c63678bc0d4566307b2c68bd83c39f6bd447fb421681e3ab581c35c9d991207b01bebf20269c083931f581bb4651d6d
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-highlight@npm:8.4.6": "@storybook/addon-highlight@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-highlight@npm:8.4.6" resolution: "@storybook/addon-highlight@npm:8.4.7"
dependencies: dependencies:
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/67a23a5e3b8f7740c7101e8fa886f3f9c6c61b6db3cb3430d2c805231f7ad170d2d926c12e7c9bfc4af327c5abac5b4155f4c0d70ea423b04704fe3def845acc checksum: 10c0/2256b880d1f83c86c64287988bd4f4b76a8e1990f2a2a080a322994a9a8e553013fc21b7503c218ec394a880c1b72b131975e6eeadec6accb7eb35d3cb85a6ce
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-interactions@npm:^8.4.6": "@storybook/addon-interactions@npm:^8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-interactions@npm:8.4.6" resolution: "@storybook/addon-interactions@npm:8.4.7"
dependencies: dependencies:
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
"@storybook/instrumenter": "npm:8.4.6" "@storybook/instrumenter": "npm:8.4.7"
"@storybook/test": "npm:8.4.6" "@storybook/test": "npm:8.4.7"
polished: "npm:^4.2.2" polished: "npm:^4.2.2"
ts-dedent: "npm:^2.2.0" ts-dedent: "npm:^2.2.0"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/42e4bc2df354dba10217385687ac20fb355f4e1a2a7390812081d6b387151b67bca868211794e531c1e112dc4ce50c70dffa55c8f4338b0bd860d59363d58d5b checksum: 10c0/5c35d2f33122f053568a746c36eb99eb1764ee990146ea374b0fc01defd3f0b33674d2758c027c760fe2966f8683193e8c414089c07e1136ffc562e3346ce479
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-links@npm:^8.4.6": "@storybook/addon-links@npm:^8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-links@npm:8.4.6" resolution: "@storybook/addon-links@npm:8.4.7"
dependencies: dependencies:
"@storybook/csf": "npm:^0.1.11" "@storybook/csf": "npm:^0.1.11"
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
ts-dedent: "npm:^2.0.0" ts-dedent: "npm:^2.0.0"
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.6 storybook: ^8.4.7
peerDependenciesMeta: peerDependenciesMeta:
react: react:
optional: true optional: true
checksum: 10c0/9360122d9c5370706a583526fb72efd0901d7e64c7467bfb4d832712cc41928d4fcfa397a53cfa17a1ae3875b8ef92ce6a10fb0bf0ce00149dc0d0eb1d66e27b checksum: 10c0/475d3231ac6c6531cfa5d01e8816b90cbf51e993c1575fa7bf541540bf76af52d7f1087e929b87d771ce41ae4fd7762df1e25c9d8543200630f8618d85b16520
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-measure@npm:8.4.6": "@storybook/addon-measure@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-measure@npm:8.4.6" resolution: "@storybook/addon-measure@npm:8.4.7"
dependencies: dependencies:
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
tiny-invariant: "npm:^1.3.1" tiny-invariant: "npm:^1.3.1"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/fd05b49fdb102a991fc696a0f75fde08d372b692778340ab2abc2c73fbd31a07dfa27a7a9d775dda7baaa9bd8a18972ed0bd86e9ce27948afb0305778f7b5a95 checksum: 10c0/a9e87c91cbcade2d0059cdc471e8ba479ad6d9dee0c2558c3b706e37d58b4cb3d986924ea0ff623aa791300ee2a8d2429e8fb3ef32eeec9d49861f8677815ac2
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-outline@npm:8.4.6": "@storybook/addon-outline@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-outline@npm:8.4.6" resolution: "@storybook/addon-outline@npm:8.4.7"
dependencies: dependencies:
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
ts-dedent: "npm:^2.0.0" ts-dedent: "npm:^2.0.0"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/62600a9f4164a8d91118d37cd7be4f4dd871e849a156ba7728f463bc2cfc5a8a233df09055dd5e5733a042fde7a63b08616cb3c61b26c363c1e2d4ce20d92584 checksum: 10c0/13e8579ad1e9c8e338a66935331764351d9681e177469c7be72bc8383d6ab0441a783b2089ac3a730979d9a97c347800a47769b1f1ab5b4dfd7fc31f29e1709f
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-toolbars@npm:8.4.6": "@storybook/addon-toolbars@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-toolbars@npm:8.4.6" resolution: "@storybook/addon-toolbars@npm:8.4.7"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/6525e71aaa3870ae97d407b662323022ade98859f89975110f5fb4a1d3f34b6c918d47fcc8a6a271f4a77acfcaadc963a846a83ebc6c748b37df50422ad60e7e checksum: 10c0/1c315d5ad07291f35ad780ef69fbd6570a582c008ab911cf14bff84061546b9ea1373d1127213844652d73a47c3011d28c1ad08d465fc120969c133dabfe7638
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/addon-viewport@npm:8.4.6": "@storybook/addon-viewport@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/addon-viewport@npm:8.4.6" resolution: "@storybook/addon-viewport@npm:8.4.7"
dependencies: dependencies:
memoizerific: "npm:^1.11.3" memoizerific: "npm:^1.11.3"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/824438cc44a45f90748ac5f20ac148a36d975a94fa89504a583e0e1188de8c574e042ad3cd537bc16ddb30d4e44e90f5a63263239b13419aec5334e2ece18cd0 checksum: 10c0/4dec3b59be1f3b99d3c9eaab695a7e346d975b772f6691f8286005d78a13a204c5680c6c8733ae83060c7639b56efed9f3580cee7413834ac6595b56345183ef
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/blocks@npm:8.4.6, @storybook/blocks@npm:^8.4.6": "@storybook/blocks@npm:8.4.7, @storybook/blocks@npm:^8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/blocks@npm:8.4.6" resolution: "@storybook/blocks@npm:8.4.7"
dependencies: dependencies:
"@storybook/csf": "npm:^0.1.11" "@storybook/csf": "npm:^0.1.11"
"@storybook/icons": "npm:^1.2.12" "@storybook/icons": "npm:^1.2.12"
@ -5342,36 +5342,36 @@ __metadata:
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.6 storybook: ^8.4.7
peerDependenciesMeta: peerDependenciesMeta:
react: react:
optional: true optional: true
react-dom: react-dom:
optional: true optional: true
checksum: 10c0/36d79c3aeb3d27f4ba966d62302e13fc17fd7b450dbfbcf538adfc6df3cfecb13c92f9d2542871fa747a77d7c770e413b358623049135355fb01454d6eb52d9a checksum: 10c0/1cb87811f9c7bad087dca752fb0d6483c237cb5776abea59cb555d8fce9ca14f4d5487725f5d8679a49f7e3f38bbe84189703498a31f2a9aa306f9fb3c8e65c8
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/builder-vite@npm:8.4.6": "@storybook/builder-vite@npm:8.4.7, @storybook/builder-vite@npm:^8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/builder-vite@npm:8.4.6" resolution: "@storybook/builder-vite@npm:8.4.7"
dependencies: dependencies:
"@storybook/csf-plugin": "npm:8.4.6" "@storybook/csf-plugin": "npm:8.4.7"
browser-assert: "npm:^1.2.1" browser-assert: "npm:^1.2.1"
ts-dedent: "npm:^2.0.0" ts-dedent: "npm:^2.0.0"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
vite: ^4.0.0 || ^5.0.0 || ^6.0.0 vite: ^4.0.0 || ^5.0.0 || ^6.0.0
checksum: 10c0/36998ffea04023a9f634ebbafe0d1ab3bd3e7c7fec8e8e6c4caef3ce0c94ce01fa44f332f40d0053edb788548f95096baf8561cd35c23fe3c9bcfd872f74f631 checksum: 10c0/138651b9042356972580a121eb3116c745f9fbc8b188ae0a5e543070dc54fcb6c1f14d35bc0cd1294ee763993a5f0e3a30cbe92508e74d183fed04a4d4125591
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/components@npm:8.4.6": "@storybook/components@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/components@npm:8.4.6" resolution: "@storybook/components@npm:8.4.7"
peerDependencies: peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/1622b2f12b6d18e5c495a623deb2930888b3e8b173a271cbe42a7cbd6e14e80b736c57792ea97d5269dff0e6c0db40385d3ea80ab6e46d4cb6e104aee6cac6bc checksum: 10c0/7c1eb12fe2310a306f3c2f77a499c3a0caeb4694d4af8dde418f3b2d2ac8a3549b3f56cdc4629b9c15d79177c72e8668dd781a71bf257948f799b0e9cba201fa
languageName: node languageName: node
linkType: hard linkType: hard
@ -5393,9 +5393,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/core@npm:8.4.6": "@storybook/core@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/core@npm:8.4.6" resolution: "@storybook/core@npm:8.4.7"
dependencies: dependencies:
"@storybook/csf": "npm:^0.1.11" "@storybook/csf": "npm:^0.1.11"
better-opn: "npm:^3.0.2" better-opn: "npm:^3.0.2"
@ -5413,18 +5413,18 @@ __metadata:
peerDependenciesMeta: peerDependenciesMeta:
prettier: prettier:
optional: true optional: true
checksum: 10c0/1e30268eec18458dd78ed4b97fb12ac47b2c3cb41ffcbe9e9f5934b3f0c83b0bfcb0c0d508926344779383cc5260f992dcd534ffffab3f05425c7cee8c90687c checksum: 10c0/0943ea7cd092739834ae4347cb46c66aa1c238ee9494af60345364f11568ee60d6290875a593808cd7aeb79715ae27365c2448e6ae5c644e316cd194af184755
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/csf-plugin@npm:8.4.6": "@storybook/csf-plugin@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/csf-plugin@npm:8.4.6" resolution: "@storybook/csf-plugin@npm:8.4.7"
dependencies: dependencies:
unplugin: "npm:^1.3.1" unplugin: "npm:^1.3.1"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/d771f36ee768c6ff62ecd930c6ff64a4ba45bdbb7f7fb41e5f4ffd02204e3f54b17ed091049b265a6d371922bf599bfe749eb9deabfcd7e2b4fb5a5444655241 checksum: 10c0/da38e2422e474e323e237e569b3dd678af77d975a4a08fa36108e66c9228858e510246628e18b013bd859a4e674c1a3d0072952a71dac0d7058e03e7c3417b3f
languageName: node languageName: node
linkType: hard linkType: hard
@ -5454,24 +5454,24 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/instrumenter@npm:8.4.6": "@storybook/instrumenter@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/instrumenter@npm:8.4.6" resolution: "@storybook/instrumenter@npm:8.4.7"
dependencies: dependencies:
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
"@vitest/utils": "npm:^2.1.1" "@vitest/utils": "npm:^2.1.1"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/602017872236124dc9dfa77d6bc2c5987d540063f15c7ace83bf91060d9343fdbe113a61cba44e17cae2247aeeb69875ebf45ff66ce9c28d364d2d3638eb3ec8 checksum: 10c0/bc0865fed7f3c8242cd97978257e3d48f1880ad01e9794cc45122c4bcc7cf4a498c6ff8deebffcc70332b4a096e98b00e695ac152e40d0ef2c23160009c86f5d
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/manager-api@npm:8.4.6": "@storybook/manager-api@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/manager-api@npm:8.4.6" resolution: "@storybook/manager-api@npm:8.4.7"
peerDependencies: peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/5921ec72df0be765bd398aa906186c9b121a8b3415a7e1a10014a8d17c44aec386b59de3d240017bfc925be00c40a4da8d26991b5fa39023f23ba8efe1b0d58e checksum: 10c0/a3aeed441a2cca1a8fac73336a853b389a00a1e7dbbbbcd54492a90f2f12f86e976235fd1272f27a606532fb7e0f82dec3f7ecd1f2b87b03ffa74b667830152a
languageName: node languageName: node
linkType: hard linkType: hard
@ -5484,34 +5484,34 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/preview-api@npm:8.4.6": "@storybook/preview-api@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/preview-api@npm:8.4.6" resolution: "@storybook/preview-api@npm:8.4.7"
peerDependencies: peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/63967f4813c75e410634bff20189b5a670a061cfeeaa601ec07f0de82e2b4955af292836030d5a8432c3c7e48968285e121ed2bb55d2b5c70d17dbb4ada3c051 checksum: 10c0/86e8dd8e46b20a4cab99655ded093a76ae5a2b2b9ab03af57292022c8143d76e0f76a137f8768b8f6847fd1b522abf3dee8504f0ba5ff16b5779120d3875967c
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/react-dom-shim@npm:8.4.6": "@storybook/react-dom-shim@npm:8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/react-dom-shim@npm:8.4.6" resolution: "@storybook/react-dom-shim@npm:8.4.7"
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/b97c6faa3adc3efe1b7b6f5e38476e040c0a988b14db68e368d704c68f3f4d4bf7866b36607c118a0483242921b34944b5f5f72614d9852476476f6ead462e5c checksum: 10c0/5db1306c844a36264587836860d17f3fd44e5981a2417e66ccb0699d2b05364736f29df2ebc605ae19a7f7b9b9d6a19845771c3052b167ce27702e20337cd334
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/react-vite@npm:^8.4.6": "@storybook/react-vite@npm:^8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/react-vite@npm:8.4.6" resolution: "@storybook/react-vite@npm:8.4.7"
dependencies: dependencies:
"@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.4.2" "@joshwooding/vite-plugin-react-docgen-typescript": "npm:0.4.2"
"@rollup/pluginutils": "npm:^5.0.2" "@rollup/pluginutils": "npm:^5.0.2"
"@storybook/builder-vite": "npm:8.4.6" "@storybook/builder-vite": "npm:8.4.7"
"@storybook/react": "npm:8.4.6" "@storybook/react": "npm:8.4.7"
find-up: "npm:^5.0.0" find-up: "npm:^5.0.0"
magic-string: "npm:^0.30.0" magic-string: "npm:^0.30.0"
react-docgen: "npm:^7.0.0" react-docgen: "npm:^7.0.0"
@ -5520,61 +5520,61 @@ __metadata:
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.6 storybook: ^8.4.7
vite: ^4.0.0 || ^5.0.0 || ^6.0.0 vite: ^4.0.0 || ^5.0.0 || ^6.0.0
checksum: 10c0/9f81a19461dbbf11932a13f8fb611dbcd95fbfa695ee5536daf7e078bf0feb5ddda2738606073826131e3fee710e230dce9042e3f7f985203392376aa8407643 checksum: 10c0/105d967cc7aa9168b60723b6325d1d011836a2d6a5b9a4e45a13a64ccae26b7794fb3eb70042ab4b4af2af705078e34250c834bfe305c090b3dd22cf67301978
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/react@npm:8.4.6, @storybook/react@npm:^8.4.6": "@storybook/react@npm:8.4.7, @storybook/react@npm:^8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/react@npm:8.4.6" resolution: "@storybook/react@npm:8.4.7"
dependencies: dependencies:
"@storybook/components": "npm:8.4.6" "@storybook/components": "npm:8.4.7"
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
"@storybook/manager-api": "npm:8.4.6" "@storybook/manager-api": "npm:8.4.7"
"@storybook/preview-api": "npm:8.4.6" "@storybook/preview-api": "npm:8.4.7"
"@storybook/react-dom-shim": "npm:8.4.6" "@storybook/react-dom-shim": "npm:8.4.7"
"@storybook/theming": "npm:8.4.6" "@storybook/theming": "npm:8.4.7"
peerDependencies: peerDependencies:
"@storybook/test": 8.4.6 "@storybook/test": 8.4.7
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta
storybook: ^8.4.6 storybook: ^8.4.7
typescript: ">= 4.2.x" typescript: ">= 4.2.x"
peerDependenciesMeta: peerDependenciesMeta:
"@storybook/test": "@storybook/test":
optional: true optional: true
typescript: typescript:
optional: true optional: true
checksum: 10c0/1441f8ab3be91757647c6b1a05eb1ef0d78a454ffd14b01a14fdde00e92a8be8fc7c8408c4670b46bc20a5a04995514f0890e98ed6ee35c362ff36141da02f02 checksum: 10c0/9ca588446171491458e9adb5f9cf69b17517feddb4edd876da495843a45fa48a9c9272d4823090546e24a78dd7a93f1dcedef96257054383eb5678bfae6ccc09
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/test@npm:8.4.6, @storybook/test@npm:^8.4.6": "@storybook/test@npm:8.4.7, @storybook/test@npm:^8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/test@npm:8.4.6" resolution: "@storybook/test@npm:8.4.7"
dependencies: dependencies:
"@storybook/csf": "npm:^0.1.11" "@storybook/csf": "npm:^0.1.11"
"@storybook/global": "npm:^5.0.0" "@storybook/global": "npm:^5.0.0"
"@storybook/instrumenter": "npm:8.4.6" "@storybook/instrumenter": "npm:8.4.7"
"@testing-library/dom": "npm:10.4.0" "@testing-library/dom": "npm:10.4.0"
"@testing-library/jest-dom": "npm:6.5.0" "@testing-library/jest-dom": "npm:6.5.0"
"@testing-library/user-event": "npm:14.5.2" "@testing-library/user-event": "npm:14.5.2"
"@vitest/expect": "npm:2.0.5" "@vitest/expect": "npm:2.0.5"
"@vitest/spy": "npm:2.0.5" "@vitest/spy": "npm:2.0.5"
peerDependencies: peerDependencies:
storybook: ^8.4.6 storybook: ^8.4.7
checksum: 10c0/fbf7c2ac7773a7fe18145876eb67491ce90b000ba5f8e364a319569e56d56e706fdd1c7ef24d3ab2ffa3dfcdb92377d8050c8ffbd457d2d8b613aba2a4845a04 checksum: 10c0/4b100eacdca6d016a08358b1bf4c17f36450dffc9005557e0184814e546e71d200afccfb652fd2d45404fbd15e75f61fb4b93d869694249769ca919a0a2111f1
languageName: node languageName: node
linkType: hard linkType: hard
"@storybook/theming@npm:8.4.6, @storybook/theming@npm:^8.4.6": "@storybook/theming@npm:8.4.7, @storybook/theming@npm:^8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "@storybook/theming@npm:8.4.6" resolution: "@storybook/theming@npm:8.4.7"
peerDependencies: peerDependencies:
storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0
checksum: 10c0/7d9c8e5ef2c1d974cd5258301350a2345890326e7be7a5ed6bdd0db70fd1648c0bbb8ee1d905f8e66fa57b75c47aefe7ec9772ec0bfb9691d127dcc19286e4c9 checksum: 10c0/20a4975478063cea616ce6ab6b1e9ec181af1424280678ed74dc5afc15b828c043e843696a1643601331c4fd266169ec4bcc5bb43fd2f1f3c01c0e21443a658a
languageName: node languageName: node
linkType: hard linkType: hard
@ -20174,11 +20174,11 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"storybook@npm:^8.4.6": "storybook@npm:^8.4.7":
version: 8.4.6 version: 8.4.7
resolution: "storybook@npm:8.4.6" resolution: "storybook@npm:8.4.7"
dependencies: dependencies:
"@storybook/core": "npm:8.4.6" "@storybook/core": "npm:8.4.7"
peerDependencies: peerDependencies:
prettier: ^2 || ^3 prettier: ^2 || ^3
peerDependenciesMeta: peerDependenciesMeta:
@ -20188,7 +20188,7 @@ __metadata:
getstorybook: ./bin/index.cjs getstorybook: ./bin/index.cjs
sb: ./bin/index.cjs sb: ./bin/index.cjs
storybook: ./bin/index.cjs storybook: ./bin/index.cjs
checksum: 10c0/e15249718c1efab3d3d05f3152df28fc8f7e2e988bf7414cd4abf2adfb5d6c3b802f05dad5be0521c30d0ba43e55abf516e6f874b0671e0d1e84a7096cb47d3d checksum: 10c0/795b79950b88b41ee0158fe2e2583a8ce97ff843c054f91e3c55310967b9e5c4e4d72814773380b543c33bd6d57ce6b5f377ce93ce73962e803b250a751be37c
languageName: node languageName: node
linkType: hard linkType: hard
@ -21954,14 +21954,15 @@ __metadata:
"@observablehq/plot": "npm:^0.6.16" "@observablehq/plot": "npm:^0.6.16"
"@react-hook/resize-observer": "npm:^2.0.2" "@react-hook/resize-observer": "npm:^2.0.2"
"@rollup/plugin-node-resolve": "npm:^15.3.0" "@rollup/plugin-node-resolve": "npm:^15.3.0"
"@storybook/addon-essentials": "npm:^8.4.6" "@storybook/addon-essentials": "npm:^8.4.7"
"@storybook/addon-interactions": "npm:^8.4.6" "@storybook/addon-interactions": "npm:^8.4.7"
"@storybook/addon-links": "npm:^8.4.6" "@storybook/addon-links": "npm:^8.4.7"
"@storybook/blocks": "npm:^8.4.6" "@storybook/blocks": "npm:^8.4.7"
"@storybook/react": "npm:^8.4.6" "@storybook/builder-vite": "npm:^8.4.7"
"@storybook/react-vite": "npm:^8.4.6" "@storybook/react": "npm:^8.4.7"
"@storybook/test": "npm:^8.4.6" "@storybook/react-vite": "npm:^8.4.7"
"@storybook/theming": "npm:^8.4.6" "@storybook/test": "npm:^8.4.7"
"@storybook/theming": "npm:^8.4.7"
"@table-nav/core": "npm:^0.0.7" "@table-nav/core": "npm:^0.0.7"
"@table-nav/react": "npm:^0.0.7" "@table-nav/react": "npm:^0.0.7"
"@tanstack/react-table": "npm:^8.20.5" "@tanstack/react-table": "npm:^8.20.5"
@ -22039,7 +22040,7 @@ __metadata:
sharp: "npm:^0.33.5" sharp: "npm:^0.33.5"
shell-quote: "npm:^1.8.2" shell-quote: "npm:^1.8.2"
sprintf-js: "npm:^1.1.3" sprintf-js: "npm:^1.1.3"
storybook: "npm:^8.4.6" storybook: "npm:^8.4.7"
storybook-dark-mode: "npm:^4.0.2" storybook-dark-mode: "npm:^4.0.2"
throttle-debounce: "npm:^5.0.2" throttle-debounce: "npm:^5.0.2"
tinycolor2: "npm:^1.6.0" tinycolor2: "npm:^1.6.0"