mirror of
https://github.com/wavetermdev/waveterm
synced 2026-04-21 14:37:16 +00:00
Change presets/bg.json => backgrounds.json, migrate, change tab background to tab:background key (#3108)
also fixes aipanel's border colors
This commit is contained in:
parent
2b110433d0
commit
645424a8be
43 changed files with 738 additions and 477 deletions
|
|
@ -30,7 +30,7 @@ Create a narrowing whenever you are writing a component (or group of components)
|
|||
|
||||
```ts
|
||||
import {
|
||||
BlockMetaKeyAtomFnType, // only if you use getBlockMetaKeyAtom
|
||||
MetaKeyAtomFnType, // only if you use getBlockMetaKeyAtom or getTabMetaKeyAtom
|
||||
ConnConfigKeyAtomFnType, // only if you use getConnConfigKeyAtom
|
||||
SettingsKeyAtomFnType, // only if you use getSettingsKeyAtom
|
||||
WaveEnv,
|
||||
|
|
@ -77,12 +77,14 @@ export type MyEnv = WaveEnvSubset<{
|
|||
|
||||
// --- key-parameterized atom factories: enumerate the keys you use ---
|
||||
getSettingsKeyAtom: SettingsKeyAtomFnType<"app:focusfollowscursor" | "window:magnifiedblockopacity">;
|
||||
getBlockMetaKeyAtom: BlockMetaKeyAtomFnType<"view" | "frame:title" | "connection">;
|
||||
getBlockMetaKeyAtom: MetaKeyAtomFnType<"view" | "frame:title" | "connection">;
|
||||
getTabMetaKeyAtom: MetaKeyAtomFnType<"tabid" | "name">;
|
||||
getConnConfigKeyAtom: ConnConfigKeyAtomFnType<"conn:wshenabled">;
|
||||
|
||||
// --- other atom helpers: copy verbatim ---
|
||||
getConnStatusAtom: WaveEnv["getConnStatusAtom"];
|
||||
getLocalHostDisplayNameAtom: WaveEnv["getLocalHostDisplayNameAtom"];
|
||||
getConfigBackgroundAtom: WaveEnv["getConfigBackgroundAtom"];
|
||||
}>;
|
||||
```
|
||||
|
||||
|
|
@ -104,7 +106,8 @@ Every `WaveEnvSubset<T>` automatically includes the mock fields — you never ne
|
|||
| `wos` | `wos: WaveEnv["wos"]` | Take the whole `wos` object (no sub-typing needed), but **only add it if `wos` is actually used**. |
|
||||
| `services` | `services: { svc: WaveEnv["services"]["svc"]; }` | List each service used; take the whole service object (no method-level narrowing). |
|
||||
| `getSettingsKeyAtom` | `SettingsKeyAtomFnType<"key1" \| "key2">` | Union all settings keys accessed. |
|
||||
| `getBlockMetaKeyAtom` | `BlockMetaKeyAtomFnType<"key1" \| "key2">` | Union all block meta keys accessed. |
|
||||
| `getBlockMetaKeyAtom` | `MetaKeyAtomFnType<"key1" \| "key2">` | Union all block meta keys accessed. |
|
||||
| `getTabMetaKeyAtom` | `MetaKeyAtomFnType<"key1" \| "key2">` | Union all tab meta keys accessed. |
|
||||
| `getConnConfigKeyAtom` | `ConnConfigKeyAtomFnType<"key1">` | Union all conn config keys accessed. |
|
||||
| All other `WaveEnv` fields | `WaveEnv["fieldName"]` | Copy type verbatim. |
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ const WaveSchemaSettingsFileName = "schema/settings.json"
|
|||
const WaveSchemaConnectionsFileName = "schema/connections.json"
|
||||
const WaveSchemaAiPresetsFileName = "schema/aipresets.json"
|
||||
const WaveSchemaWidgetsFileName = "schema/widgets.json"
|
||||
const WaveSchemaBgPresetsFileName = "schema/bgpresets.json"
|
||||
const WaveSchemaBackgroundsFileName = "schema/backgrounds.json"
|
||||
const WaveSchemaWaveAIFileName = "schema/waveai.json"
|
||||
|
||||
// ViewNameType is a string type whose JSON Schema offers enum suggestions for the most
|
||||
|
|
@ -105,8 +105,26 @@ type WidgetsMetaSchemaHints struct {
|
|||
TermDurable *bool `json:"term:durable,omitempty"`
|
||||
}
|
||||
|
||||
func generateSchema(template any, dir string) error {
|
||||
// allowNullValues wraps the top-level additionalProperties of a map schema with
|
||||
// anyOf: [originalSchema, {type: "null"}] so that setting a key to null is valid
|
||||
// (e.g. "bg@foo": null to remove a default entry).
|
||||
func allowNullValues(schema *jsonschema.Schema) {
|
||||
if schema.AdditionalProperties != nil && schema.AdditionalProperties != jsonschema.TrueSchema && schema.AdditionalProperties != jsonschema.FalseSchema {
|
||||
original := schema.AdditionalProperties
|
||||
schema.AdditionalProperties = &jsonschema.Schema{
|
||||
AnyOf: []*jsonschema.Schema{
|
||||
original,
|
||||
{Type: "null"},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generateSchema(template any, dir string, allowNull bool) error {
|
||||
settingsSchema := jsonschema.Reflect(template)
|
||||
if allowNull {
|
||||
allowNullValues(settingsSchema)
|
||||
}
|
||||
|
||||
jsonSettingsSchema, err := json.MarshalIndent(settingsSchema, "", " ")
|
||||
if err != nil {
|
||||
|
|
@ -147,6 +165,7 @@ func generateWidgetsSchema(dir string) error {
|
|||
|
||||
widgetsTemplate := make(map[string]wconfig.WidgetConfigType)
|
||||
widgetsSchema := r.Reflect(&widgetsTemplate)
|
||||
allowNullValues(widgetsSchema)
|
||||
|
||||
jsonWidgetsSchema, err := json.MarshalIndent(widgetsSchema, "", " ")
|
||||
if err != nil {
|
||||
|
|
@ -163,19 +182,19 @@ func generateWidgetsSchema(dir string) error {
|
|||
}
|
||||
|
||||
func main() {
|
||||
err := generateSchema(&wconfig.SettingsType{}, WaveSchemaSettingsFileName)
|
||||
err := generateSchema(&wconfig.SettingsType{}, WaveSchemaSettingsFileName, false)
|
||||
if err != nil {
|
||||
log.Fatalf("settings schema error: %v", err)
|
||||
}
|
||||
|
||||
connectionTemplate := make(map[string]wconfig.ConnKeywords)
|
||||
err = generateSchema(&connectionTemplate, WaveSchemaConnectionsFileName)
|
||||
err = generateSchema(&connectionTemplate, WaveSchemaConnectionsFileName, false)
|
||||
if err != nil {
|
||||
log.Fatalf("connections schema error: %v", err)
|
||||
}
|
||||
|
||||
aiPresetsTemplate := make(map[string]wconfig.AiSettingsType)
|
||||
err = generateSchema(&aiPresetsTemplate, WaveSchemaAiPresetsFileName)
|
||||
err = generateSchema(&aiPresetsTemplate, WaveSchemaAiPresetsFileName, false)
|
||||
if err != nil {
|
||||
log.Fatalf("ai presets schema error: %v", err)
|
||||
}
|
||||
|
|
@ -185,14 +204,14 @@ func main() {
|
|||
log.Fatalf("widgets schema error: %v", err)
|
||||
}
|
||||
|
||||
bgPresetsTemplate := make(map[string]wconfig.BgPresetsType)
|
||||
err = generateSchema(&bgPresetsTemplate, WaveSchemaBgPresetsFileName)
|
||||
backgroundsTemplate := make(map[string]wconfig.BackgroundConfigType)
|
||||
err = generateSchema(&backgroundsTemplate, WaveSchemaBackgroundsFileName, true)
|
||||
if err != nil {
|
||||
log.Fatalf("bg presets schema error: %v", err)
|
||||
log.Fatalf("backgrounds schema error: %v", err)
|
||||
}
|
||||
|
||||
waveAITemplate := make(map[string]wconfig.AIModeConfigType)
|
||||
err = generateSchema(&waveAITemplate, WaveSchemaWaveAIFileName)
|
||||
err = generateSchema(&waveAITemplate, WaveSchemaWaveAIFileName, false)
|
||||
if err != nil {
|
||||
log.Fatalf("waveai schema error: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -560,6 +560,7 @@ func main() {
|
|||
createMainWshClient()
|
||||
sigutil.InstallShutdownSignalHandlers(doShutdown)
|
||||
sigutil.InstallSIGUSR1Handler()
|
||||
wconfig.MigratePresetsBackgrounds()
|
||||
startConfigWatcher()
|
||||
aiusechat.InitAIModeConfigWatcher()
|
||||
maybeStartPprofServer()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
var setBgCmd = &cobra.Command{
|
||||
Use: "setbg [--opacity value] [--tile|--center] [--scale value] (image-path|\"#color\"|color-name)",
|
||||
Use: "setbg [--opacity value] [--tile|--center] [--scale value] [--border-color color] [--active-border-color color] (image-path|\"#color\"|color-name)",
|
||||
Short: "set background image or color for a tab",
|
||||
Long: `Set a background image or color for a tab. Colors can be specified as:
|
||||
- A quoted hex value like "#ff0000" (quotes required to prevent # being interpreted as a shell comment)
|
||||
|
|
@ -31,18 +31,22 @@ You can also:
|
|||
- Use --opacity without other arguments to change just the opacity
|
||||
- Use --center for centered images without scaling (good for logos)
|
||||
- Use --scale with --center to control image size
|
||||
- Use --border-color to set the block frame border color
|
||||
- Use --active-border-color to set the block frame focused border color
|
||||
- Use --print to see the metadata without applying it`,
|
||||
RunE: setBgRun,
|
||||
PreRunE: preRunSetupRpcClient,
|
||||
}
|
||||
|
||||
var (
|
||||
setBgOpacity float64
|
||||
setBgTile bool
|
||||
setBgCenter bool
|
||||
setBgSize string
|
||||
setBgClear bool
|
||||
setBgPrint bool
|
||||
setBgOpacity float64
|
||||
setBgTile bool
|
||||
setBgCenter bool
|
||||
setBgSize string
|
||||
setBgClear bool
|
||||
setBgPrint bool
|
||||
setBgBorderColor string
|
||||
setBgActiveBorderColor string
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -53,8 +57,9 @@ func init() {
|
|||
setBgCmd.Flags().StringVar(&setBgSize, "size", "auto", "size for centered images (px, %, or auto)")
|
||||
setBgCmd.Flags().BoolVar(&setBgClear, "clear", false, "clear the background")
|
||||
setBgCmd.Flags().BoolVar(&setBgPrint, "print", false, "print the metadata without applying it")
|
||||
setBgCmd.Flags().StringVar(&setBgBorderColor, "border-color", "", "block frame border color (#RRGGBB, #RRGGBBAA, or CSS color name)")
|
||||
setBgCmd.Flags().StringVar(&setBgActiveBorderColor, "active-border-color", "", "block frame focused border color (#RRGGBB, #RRGGBBAA, or CSS color name)")
|
||||
|
||||
// Make tile and center mutually exclusive
|
||||
setBgCmd.MarkFlagsMutuallyExclusive("tile", "center")
|
||||
}
|
||||
|
||||
|
|
@ -73,17 +78,41 @@ func validateHexColor(color string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateColor(color string) error {
|
||||
if strings.HasPrefix(color, "#") {
|
||||
return validateHexColor(color)
|
||||
}
|
||||
if !CssColorNames[strings.ToLower(color)] {
|
||||
return fmt.Errorf("invalid color %q: must be a hex color (#RRGGBB or #RRGGBBAA) or a CSS color name", color)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) {
|
||||
defer func() {
|
||||
sendActivity("setbg", rtnErr == nil)
|
||||
}()
|
||||
|
||||
borderColorChanged := cmd.Flags().Changed("border-color")
|
||||
activeBorderColorChanged := cmd.Flags().Changed("active-border-color")
|
||||
|
||||
if borderColorChanged {
|
||||
if err := validateColor(setBgBorderColor); err != nil {
|
||||
return fmt.Errorf("--border-color: %v", err)
|
||||
}
|
||||
}
|
||||
if activeBorderColorChanged {
|
||||
if err := validateColor(setBgActiveBorderColor); err != nil {
|
||||
return fmt.Errorf("--active-border-color: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Create base metadata
|
||||
meta := map[string]interface{}{}
|
||||
|
||||
// Handle opacity-only change or clear
|
||||
if len(args) == 0 {
|
||||
if !cmd.Flags().Changed("opacity") && !setBgClear {
|
||||
if !cmd.Flags().Changed("opacity") && !setBgClear && !borderColorChanged && !activeBorderColorChanged {
|
||||
OutputHelpMessage(cmd)
|
||||
return fmt.Errorf("setbg requires an image path or color value")
|
||||
}
|
||||
|
|
@ -92,7 +121,7 @@ func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) {
|
|||
}
|
||||
if setBgClear {
|
||||
meta["bg:*"] = true
|
||||
} else {
|
||||
} else if cmd.Flags().Changed("opacity") {
|
||||
meta["bg:opacity"] = setBgOpacity
|
||||
}
|
||||
} else if len(args) > 1 {
|
||||
|
|
@ -101,6 +130,7 @@ func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) {
|
|||
} else {
|
||||
// Handle background setting
|
||||
meta["bg:*"] = true
|
||||
meta["tab:background"] = nil
|
||||
if setBgOpacity < 0 || setBgOpacity > 1 {
|
||||
return fmt.Errorf("opacity must be between 0.0 and 1.0")
|
||||
}
|
||||
|
|
@ -159,6 +189,13 @@ func setBgRun(cmd *cobra.Command, args []string) (rtnErr error) {
|
|||
meta["bg"] = bgStyle
|
||||
}
|
||||
|
||||
if borderColorChanged {
|
||||
meta["bg:bordercolor"] = setBgBorderColor
|
||||
}
|
||||
if activeBorderColorChanged {
|
||||
meta["bg:activebordercolor"] = setBgActiveBorderColor
|
||||
}
|
||||
|
||||
if setBgPrint {
|
||||
jsonBytes, err := json.MarshalIndent(meta, "", " ")
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ title: "Configuration"
|
|||
|
||||
import { Kbd } from "@site/src/components/kbd";
|
||||
import { PlatformProvider, PlatformSelectorButton } from "@site/src/components/platformcontext";
|
||||
import { VersionBadge } from "@site/src/components/versionbadge";
|
||||
import { VersionBadge, DeprecatedBadge } from "@site/src/components/versionbadge";
|
||||
|
||||
<PlatformProvider>
|
||||
|
||||
|
|
@ -92,7 +92,8 @@ wsh editconfig
|
|||
| 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: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 |
|
||||
| tab:preset <DeprecatedBadge /> | string | a "bg@" preset to automatically apply to new tabs. e.g. `bg@green`. should match the preset key. deprecated in favor of `tab:background` |
|
||||
| tab:background <VersionBadge version="v0.14.4" /> | string | a "bg@" preset to automatically apply to new tabs. e.g. `bg@green`. should match the preset key |
|
||||
| tab:confirmclose | bool | if set to true, a confirmation dialog will be shown before closing a tab (defaults to false) |
|
||||
| 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/custom-window-styles#limitations)) |
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ title: "Customization"
|
|||
|
||||
Right click on any tab to bring up a menu which allows you to rename the tab and select different backgrounds.
|
||||
|
||||
It is also possible to create your own themes using custom colors, gradients, images and more by editing your presets.json config file. To see how Wave's built in tab themes are defined, you can check out our [default presets file](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/presets.json).
|
||||
It is also possible to create your own background themes using custom colors, gradients, images and more by editing your backgrounds.json config file. To see how Wave's built-in tab backgrounds are defined, you can check out the [default backgrounds.json file](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/backgrounds.json).
|
||||
|
||||
To apply a tab background to all new tabs by default, set the key `tab:background` in your [Wave Config File](/config) to one of the background preset keys (e.g. `"bg@ocean-depths"`). The available built-in background keys can be found in the [default backgrounds.json file](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/backgrounds.json).
|
||||
|
||||
## Terminal Customization
|
||||
|
||||
|
|
@ -26,8 +28,6 @@ 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).
|
||||
|
||||
You can set the key `tab:preset` in your [Wave Config File](/config) to apply a theme to all new tabs.
|
||||
|
||||
#### Font Size
|
||||
|
||||
From the same context menu you can also change the font-size of the terminal. To change the default font size across all of your (non-overridden) terminals, you can set the config key `term:fontsize` to the size you want. e.g. `{ "term:fontsize": 14}`.
|
||||
|
|
@ -79,6 +79,6 @@ To preview the metadata for any background without applying it, use the `--print
|
|||
wsh setbg --print "#ff0000"
|
||||
```
|
||||
|
||||
For more advanced customization options including gradients, colors, and saving your own background presets, check out our [Background Configuration](/presets#background-configurations) documentation.
|
||||
For more advanced customization options including gradients, colors, and saving your own custom backgrounds, check out our [Tab Backgrounds](/tab-backgrounds) documentation.
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -479,7 +479,7 @@ New minor release that introduces Wave's connected computing extensions. We've i
|
|||
|
||||
### v0.9.2 — Nov 11, 2024
|
||||
|
||||
New minor release with bug fixes and new features! Fixed the bug around making Wave fullscreen (also affecting certain window managers like Hyprland). We've also put a lot of work into the doc site (https://docs.waveterm.dev), including documenting how [Widgets](./widgets) and [Presets](./presets) work!
|
||||
New minor release with bug fixes and new features! Fixed the bug around making Wave fullscreen (also affecting certain window managers like Hyprland). We've also put a lot of work into the doc site (https://docs.waveterm.dev), including documenting how [Widgets](./widgets) and Presets work!
|
||||
|
||||
- Updated documentation
|
||||
- Wave AI now supports the Anthropic API! Checkout the [FAQ](./faq) for how to use the Claude models with Wave AI.
|
||||
|
|
|
|||
|
|
@ -1,80 +1,57 @@
|
|||
---
|
||||
sidebar_position: 3.5
|
||||
id: "presets"
|
||||
title: "Presets"
|
||||
id: "tab-backgrounds"
|
||||
title: "Tab Backgrounds"
|
||||
---
|
||||
|
||||
# Presets
|
||||
# Tab Backgrounds
|
||||
|
||||
Wave's preset system allows you to save and apply multiple configuration settings at once. Presets are used for:
|
||||
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.
|
||||
|
||||
- Tab backgrounds: Apply visual styles to your tabs
|
||||
## Managing Backgrounds
|
||||
|
||||
## Managing Presets
|
||||
Custom backgrounds are stored in `~/.config/waveterm/backgrounds.json`.
|
||||
|
||||
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/bg.json`).
|
||||
|
||||
:::info
|
||||
You can easily edit your presets using the built-in editor:
|
||||
**To edit using the UI:**
|
||||
1. Click the settings (gear) icon in the widget bar
|
||||
2. Select "Settings" from the menu
|
||||
3. Choose "Tab Backgrounds" from the settings sidebar
|
||||
|
||||
**Or launch from the command line:**
|
||||
```bash
|
||||
wsh editconfig presets.json # Edit main presets file
|
||||
wsh editconfig presets/bg.json # Edit background presets
|
||||
wsh editconfig backgrounds.json
|
||||
```
|
||||
|
||||
:::
|
||||
|
||||
## File Format
|
||||
|
||||
Presets follow this format:
|
||||
Backgrounds follow this format:
|
||||
|
||||
```json
|
||||
{
|
||||
"<preset-type>@<preset-key>": {
|
||||
"display:name": "<Preset name>",
|
||||
"display:order": "<number>", // optional
|
||||
"<overridden-config-key-1>": "<overridden-config-value-1>"
|
||||
...
|
||||
"bg@<key>": {
|
||||
"display:name": "<Background name>",
|
||||
"display:order": <number>,
|
||||
"bg": "<CSS background value>",
|
||||
"bg:opacity": <float>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `preset-type` determines where the preset appears in Wave's interface:
|
||||
To see how Wave's built-in backgrounds are defined, check out the [default backgrounds.json file](https://github.com/wavetermdev/waveterm/blob/main/pkg/wconfig/defaultconfig/backgrounds.json).
|
||||
|
||||
- `bg`: Appears in the "Backgrounds" submenu when right-clicking a tab
|
||||
|
||||
### Common Keys
|
||||
|
||||
| Key Name | Type | Function |
|
||||
| ------------- | ------ | ----------------------------------------- |
|
||||
| display:name | string | Name shown in the UI menu (required) |
|
||||
| display:order | float | Controls the order in the menu (optional) |
|
||||
|
||||
:::info
|
||||
When a preset is applied, it overrides the default configuration values for that tab or block. Using `bg:*` will clear any previously overridden values, setting them back to defaults. It's recommended to include this key in your presets to ensure a clean slate.
|
||||
:::
|
||||
|
||||
## 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.
|
||||
|
||||
### Configuration Keys
|
||||
## Configuration Keys
|
||||
|
||||
| Key Name | Type | Function |
|
||||
| -------------------- | ------ | ------------------------------------------------------------------------------------------------------- |
|
||||
| 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.) |
|
||||
| display:name | string | Name shown in the UI menu (required) |
|
||||
| display:order | float | Controls the order in the menu (optional) |
|
||||
| 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: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:activebordercolor | string | The color of the border when a block is active |
|
||||
|
||||
### Examples
|
||||
## Examples
|
||||
|
||||
#### Simple solid color:
|
||||
|
||||
|
|
@ -82,7 +59,6 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo
|
|||
{
|
||||
"bg@blue": {
|
||||
"display:name": "Blue",
|
||||
"bg:*": true,
|
||||
"bg": "blue",
|
||||
"bg:opacity": 0.3,
|
||||
"bg:activebordercolor": "rgba(0, 0, 255, 1.0)"
|
||||
|
|
@ -96,7 +72,6 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo
|
|||
{
|
||||
"bg@duskhorizon": {
|
||||
"display:name": "Dusk Horizon",
|
||||
"bg:*": true,
|
||||
"bg": "linear-gradient(0deg, rgba(128,0,0,1) 0%, rgba(204,85,0,0.7) 20%, rgba(255,140,0,0.6) 45%, rgba(160,90,160,0.5) 65%, rgba(60,60,120,1) 100%), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)",
|
||||
"bg:opacity": 0.9,
|
||||
"bg:blendmode": "overlay"
|
||||
|
|
@ -110,7 +85,6 @@ Wave's background system harnesses the full power of CSS backgrounds, letting yo
|
|||
{
|
||||
"bg@ocean": {
|
||||
"display:name": "Ocean Scene",
|
||||
"bg:*": true,
|
||||
"bg": "url('/path/to/ocean.jpg') center/cover no-repeat",
|
||||
"bg:opacity": 0.2
|
||||
}
|
||||
|
|
@ -122,10 +96,10 @@ Background images support both URLs and local file paths. For better reliability
|
|||
:::
|
||||
|
||||
:::tip
|
||||
The `setbg` command can help generate background preset JSON:
|
||||
The `setbg` command can help generate background JSON:
|
||||
|
||||
```bash
|
||||
# Preview a solid color preset
|
||||
# Preview a solid color background
|
||||
wsh setbg --print "#ff0000"
|
||||
{
|
||||
"bg:*": true,
|
||||
|
|
@ -133,7 +107,7 @@ wsh setbg --print "#ff0000"
|
|||
"bg:opacity": 0.5
|
||||
}
|
||||
|
||||
# Preview a centered image preset
|
||||
# Preview a centered image background
|
||||
wsh setbg --print --center --opacity 0.3 ~/logo.png
|
||||
{
|
||||
"bg:*": true,
|
||||
|
|
@ -142,5 +116,5 @@ wsh setbg --print --center --opacity 0.3 ~/logo.png
|
|||
}
|
||||
```
|
||||
|
||||
Just add the required `display:name` field to complete your preset!
|
||||
Just add the required `display:name` field and a `bg@<key>` wrapper to complete your background entry!
|
||||
:::
|
||||
|
|
@ -200,7 +200,7 @@ wsh editconfig presets/ai.json
|
|||
The `setbg` command allows you to set a background image or color for the current tab with various customization options.
|
||||
|
||||
```sh
|
||||
wsh setbg [--opacity value] [--tile|--center] [--size value] (image-path|"#color"|color-name)
|
||||
wsh setbg [--opacity value] [--tile|--center] [--size value] [--border-color color] [--active-border-color color] (image-path|"#color"|color-name)
|
||||
```
|
||||
|
||||
You can set a background using:
|
||||
|
|
@ -216,6 +216,8 @@ Flags:
|
|||
- `--center` - center the image without scaling (good for logos)
|
||||
- `--size` - size for centered images (px, %, or auto)
|
||||
- `--clear` - remove the background
|
||||
- `--border-color color` - set the block frame border color (hex or CSS color name)
|
||||
- `--active-border-color color` - set the block frame focused border color (hex or CSS color name)
|
||||
- `--print` - show the metadata without applying it
|
||||
|
||||
Supported image formats: JPEG, PNG, GIF, WebP, and SVG.
|
||||
|
|
@ -243,6 +245,10 @@ wsh setbg forestgreen # CSS color name
|
|||
# Change just the opacity of current background
|
||||
wsh setbg --opacity 0.7
|
||||
|
||||
# Set border colors alongside a background
|
||||
wsh setbg --border-color "#ff0000" --active-border-color "#00ff00" ~/pictures/background.jpg
|
||||
wsh setbg --border-color steelblue forestgreen
|
||||
|
||||
# Remove background
|
||||
wsh setbg --clear
|
||||
|
||||
|
|
@ -258,7 +264,7 @@ The command validates that:
|
|||
- The center and tile options are not used together
|
||||
|
||||
:::tip
|
||||
Use `--print` to preview the metadata for any background configuration without applying it. You can then copy this JSON representation to use as a [Background Preset](/presets#background-configurations)
|
||||
Use `--print` to preview the metadata for any background configuration without applying it. You can then copy this JSON representation to use as a [Background entry](/tab-backgrounds)
|
||||
:::
|
||||
|
||||
---
|
||||
|
|
|
|||
|
|
@ -20,3 +20,22 @@
|
|||
background-color: var(--ifm-color-primary-dark);
|
||||
color: var(--ifm-background-color);
|
||||
}
|
||||
|
||||
.deprecated-badge {
|
||||
display: inline-block;
|
||||
padding: 0.125rem 0.5rem;
|
||||
margin-left: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
border-radius: 0.25rem;
|
||||
background-color: #9e9e9e;
|
||||
color: #fff;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .deprecated-badge {
|
||||
background-color: #616161;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,4 +7,8 @@ interface VersionBadgeProps {
|
|||
|
||||
export function VersionBadge({ version, noLeftMargin }: VersionBadgeProps) {
|
||||
return <span className={`version-badge${noLeftMargin ? " no-left-margin" : ""}`}>{version}</span>;
|
||||
}
|
||||
|
||||
export function DeprecatedBadge() {
|
||||
return <span className="deprecated-badge">deprecated</span>;
|
||||
}
|
||||
|
|
@ -3,11 +3,13 @@
|
|||
|
||||
import { handleWaveAIContextMenu } from "@/app/aipanel/aipanel-contextmenu";
|
||||
import { waveAIHasSelection } from "@/app/aipanel/waveai-focus-utils";
|
||||
import { useTabBackground } from "@/app/block/blockutil";
|
||||
import { ErrorBoundary } from "@/app/element/errorboundary";
|
||||
import { atoms, getSettingsKeyAtom } from "@/app/store/global";
|
||||
import { globalStore } from "@/app/store/jotaiStore";
|
||||
import { useTabModelMaybe } from "@/app/store/tab-model";
|
||||
import { isBuilderWindow } from "@/app/store/windowtype";
|
||||
import { useWaveEnv } from "@/app/waveenv/waveenv";
|
||||
import { checkKeyPressed, keydownWrapper } from "@/util/keyutil";
|
||||
import { isMacOS, isWindows } from "@/util/platformutil";
|
||||
import { cn } from "@/util/util";
|
||||
|
|
@ -255,6 +257,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps
|
|||
const [initialLoadDone, setInitialLoadDone] = useState(false);
|
||||
const model = WaveAIModel.getInstance();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const waveEnv = useWaveEnv();
|
||||
const isLayoutMode = jotai.useAtomValue(atoms.controlShiftDelayAtom);
|
||||
const showOverlayBlockNums = jotai.useAtomValue(getSettingsKeyAtom("app:showoverlayblocknums")) ?? true;
|
||||
const isFocused = jotai.useAtomValue(model.isWaveAIFocusedAtom);
|
||||
|
|
@ -262,6 +265,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps
|
|||
const telemetryEnabled = jotai.useAtomValue(getSettingsKeyAtom("telemetry:enabled")) ?? false;
|
||||
const isPanelVisible = jotai.useAtomValue(model.getPanelVisibleAtom());
|
||||
const tabModel = useTabModelMaybe();
|
||||
const [tabBorderColor, tabActiveBorderColor] = useTabBackground(waveEnv, tabModel?.tabId);
|
||||
const defaultMode = jotai.useAtomValue(getSettingsKeyAtom("waveai:defaultmode")) ?? "waveai@balanced";
|
||||
const aiModeConfigs = jotai.useAtomValue(model.aiModeConfigs);
|
||||
|
||||
|
|
@ -546,6 +550,7 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps
|
|||
};
|
||||
|
||||
const showBlockMask = isLayoutMode && showOverlayBlockNums;
|
||||
const borderColor = isFocused ? (tabActiveBorderColor ?? null) : (tabBorderColor ?? null);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -555,13 +560,14 @@ const AIPanelComponentInner = memo(({ roundTopLeft }: AIPanelComponentInnerProps
|
|||
"@container bg-zinc-900/70 flex flex-col relative",
|
||||
model.inBuilder ? "mt-0 h-full" : "mt-1 h-[calc(100%-4px)]",
|
||||
(isDragOver || isReactDndDragOver) && "bg-zinc-800 border-accent",
|
||||
isFocused ? "border-2 border-accent" : "border-2 border-transparent"
|
||||
isFocused && !borderColor ? "border-2 border-accent" : "border-2 border-transparent"
|
||||
)}
|
||||
style={{
|
||||
borderTopLeftRadius: roundTopLeft ? 10 : 0,
|
||||
borderTopRightRadius: model.inBuilder ? 0 : 10,
|
||||
borderBottomRightRadius: model.inBuilder ? 0 : 10,
|
||||
borderBottomLeftRadius: 10,
|
||||
borderColor: borderColor ?? undefined,
|
||||
}}
|
||||
onFocusCapture={handleFocusCapture}
|
||||
onPointerEnter={handlePointerEnter}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { MetaKeyAtomFnType, useWaveEnv, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
|
||||
import { PLATFORM, PlatformMacOS } from "@/util/platformutil";
|
||||
import { computeBgStyleFromMeta } from "@/util/waveutil";
|
||||
import useResizeObserver from "@react-hook/resize-observer";
|
||||
|
|
@ -10,11 +11,20 @@ import { debounce } from "throttle-debounce";
|
|||
import { atoms, getApi, WOS } from "./store/global";
|
||||
import { useWaveObjectValue } from "./store/wos";
|
||||
|
||||
type AppBgEnv = WaveEnvSubset<{
|
||||
getTabMetaKeyAtom: MetaKeyAtomFnType<"tab:background">;
|
||||
getConfigBackgroundAtom: WaveEnv["getConfigBackgroundAtom"];
|
||||
}>;
|
||||
|
||||
export function AppBackground() {
|
||||
const bgRef = useRef<HTMLDivElement>(null);
|
||||
const tabId = useAtomValue(atoms.staticTabId);
|
||||
const [tabData] = useWaveObjectValue<Tab>(WOS.makeORef("tab", tabId));
|
||||
const style: CSSProperties = computeBgStyleFromMeta(tabData?.meta, 0.5) ?? {};
|
||||
const env = useWaveEnv<AppBgEnv>();
|
||||
const tabBg = useAtomValue(env.getTabMetaKeyAtom(tabId, "tab:background"));
|
||||
const configBg = useAtomValue(env.getConfigBackgroundAtom(tabBg));
|
||||
const resolvedMeta: Omit<BackgroundConfigType, "display:name"> = tabBg && configBg ? configBg : tabData?.meta;
|
||||
const style: CSSProperties = computeBgStyleFromMeta(resolvedMeta, 0.5) ?? {};
|
||||
const getAvgColor = useCallback(
|
||||
debounce(30, () => {
|
||||
if (
|
||||
|
|
@ -42,5 +52,11 @@ export function AppBackground() {
|
|||
useLayoutEffect(getAvgColor, [getAvgColor]);
|
||||
useResizeObserver(bgRef, getAvgColor);
|
||||
|
||||
return <div ref={bgRef} className="pointer-events-none absolute top-0 left-0 w-full h-full z-[var(--zindex-app-background)]" style={style} />;
|
||||
return (
|
||||
<div
|
||||
ref={bgRef}
|
||||
className="pointer-events-none absolute top-0 left-0 w-full h-full z-[var(--zindex-app-background)]"
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import {
|
||||
BlockMetaKeyAtomFnType,
|
||||
ConnConfigKeyAtomFnType,
|
||||
MetaKeyAtomFnType,
|
||||
SettingsKeyAtomFnType,
|
||||
WaveEnv,
|
||||
WaveEnvSubset,
|
||||
|
|
@ -36,7 +36,7 @@ export type BlockEnv = WaveEnvSubset<{
|
|||
getConnStatusAtom: WaveEnv["getConnStatusAtom"];
|
||||
getLocalHostDisplayNameAtom: WaveEnv["getLocalHostDisplayNameAtom"];
|
||||
getConnConfigKeyAtom: ConnConfigKeyAtomFnType<"conn:wshenabled">;
|
||||
getBlockMetaKeyAtom: BlockMetaKeyAtomFnType<
|
||||
getBlockMetaKeyAtom: MetaKeyAtomFnType<
|
||||
| "frame:text"
|
||||
| "frame:activebordercolor"
|
||||
| "frame:bordercolor"
|
||||
|
|
@ -46,4 +46,6 @@ export type BlockEnv = WaveEnvSubset<{
|
|||
| "frame:title"
|
||||
| "frame:icon"
|
||||
>;
|
||||
getTabMetaKeyAtom: MetaKeyAtomFnType<"bg:activebordercolor" | "bg:bordercolor" | "tab:background">;
|
||||
getConfigBackgroundAtom: WaveEnv["getConfigBackgroundAtom"];
|
||||
}>;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import { BlockModel } from "@/app/block/block-model";
|
||||
import { BlockFrame_Header } from "@/app/block/blockframe-header";
|
||||
import { blockViewToIcon, getViewIconElem } from "@/app/block/blockutil";
|
||||
import { blockViewToIcon, getViewIconElem, useTabBackground } from "@/app/block/blockutil";
|
||||
import { ConnStatusOverlay } from "@/app/block/connstatusoverlay";
|
||||
import { ChangeConnectionBlockModal } from "@/app/modals/conntypeahead";
|
||||
import { getBlockComponentModel, globalStore, useBlockAtom } from "@/app/store/global";
|
||||
|
|
@ -36,8 +36,7 @@ const BlockMask = React.memo(({ nodeModel }: { nodeModel: NodeModel }) => {
|
|||
waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "frame:activebordercolor")
|
||||
);
|
||||
const frameBorderColor = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "frame:bordercolor"));
|
||||
const tabActiveBorderColor = jotai.useAtomValue(tabModel.getTabMetaAtom("bg:activebordercolor"));
|
||||
const tabBorderColor = jotai.useAtomValue(tabModel.getTabMetaAtom("bg:bordercolor"));
|
||||
const [tabBorderColor, tabActiveBorderColor] = useTabBackground(waveEnv, tabModel.tabId);
|
||||
const style: React.CSSProperties = {};
|
||||
let showBlockMask = false;
|
||||
|
||||
|
|
@ -107,9 +106,13 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => {
|
|||
const connModalOpen = jotai.useAtomValue(changeConnModalAtom);
|
||||
const isMagnified = jotai.useAtomValue(nodeModel.isMagnified);
|
||||
const isEphemeral = jotai.useAtomValue(nodeModel.isEphemeral);
|
||||
const [magnifiedBlockBlurAtom] = React.useState(() => waveEnv.getSettingsKeyAtom("window:magnifiedblockblurprimarypx"));
|
||||
const [magnifiedBlockBlurAtom] = React.useState(() =>
|
||||
waveEnv.getSettingsKeyAtom("window:magnifiedblockblurprimarypx")
|
||||
);
|
||||
const magnifiedBlockBlur = jotai.useAtomValue(magnifiedBlockBlurAtom);
|
||||
const [magnifiedBlockOpacityAtom] = React.useState(() => waveEnv.getSettingsKeyAtom("window:magnifiedblockopacity"));
|
||||
const [magnifiedBlockOpacityAtom] = React.useState(() =>
|
||||
waveEnv.getSettingsKeyAtom("window:magnifiedblockopacity")
|
||||
);
|
||||
const magnifiedBlockOpacity = jotai.useAtomValue(magnifiedBlockOpacityAtom);
|
||||
const connBtnRef = React.useRef<HTMLDivElement>(null);
|
||||
const connName = jotai.useAtomValue(waveEnv.getBlockMetaKeyAtom(nodeModel.blockId, "connection"));
|
||||
|
|
@ -141,7 +144,11 @@ const BlockFrame_Default_Component = (props: BlockFrameProps) => {
|
|||
if (!util.isLocalConnName(connName)) {
|
||||
console.log("ensure conn", nodeModel.blockId, connName);
|
||||
waveEnv.rpc
|
||||
.ConnEnsureCommand(TabRpcClient, { connname: connName, logblockid: nodeModel.blockId }, { timeout: 60000 })
|
||||
.ConnEnsureCommand(
|
||||
TabRpcClient,
|
||||
{ connname: connName, logblockid: nodeModel.blockId },
|
||||
{ timeout: 60000 }
|
||||
)
|
||||
.catch((e) => {
|
||||
console.log("error ensuring connection", nodeModel.blockId, connName, e);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,13 +2,24 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { Button } from "@/app/element/button";
|
||||
import {
|
||||
MetaKeyAtomFnType,
|
||||
WaveEnv,
|
||||
WaveEnvSubset,
|
||||
} from "@/app/waveenv/waveenv";
|
||||
import { IconButton, ToggleIconButton } from "@/element/iconbutton";
|
||||
import { MagnifyIcon } from "@/element/magnify";
|
||||
import { MenuButton } from "@/element/menubutton";
|
||||
import * as util from "@/util/util";
|
||||
import clsx from "clsx";
|
||||
import * as jotai from "jotai";
|
||||
import * as React from "react";
|
||||
|
||||
export type TabBackgroundEnv = WaveEnvSubset<{
|
||||
getTabMetaKeyAtom: MetaKeyAtomFnType<"bg:activebordercolor" | "bg:bordercolor" | "tab:background">;
|
||||
getConfigBackgroundAtom: WaveEnv["getConfigBackgroundAtom"];
|
||||
}>;
|
||||
|
||||
export const colorRegex = /^((#[0-9a-f]{6,8})|([a-z]+))$/;
|
||||
export const NumActiveConnColors = 8;
|
||||
|
||||
|
|
@ -155,6 +166,19 @@ export function getViewIconElem(
|
|||
}
|
||||
}
|
||||
|
||||
export function useTabBackground(
|
||||
waveEnv: TabBackgroundEnv,
|
||||
tabId: string | null
|
||||
): [string, string, BackgroundConfigType] {
|
||||
const tabActiveBorderColorDirect = jotai.useAtomValue(waveEnv.getTabMetaKeyAtom(tabId, "bg:activebordercolor"));
|
||||
const tabBorderColorDirect = jotai.useAtomValue(waveEnv.getTabMetaKeyAtom(tabId, "bg:bordercolor"));
|
||||
const tabBg = jotai.useAtomValue(waveEnv.getTabMetaKeyAtom(tabId, "tab:background"));
|
||||
const configBg = jotai.useAtomValue(waveEnv.getConfigBackgroundAtom(tabBg));
|
||||
const tabActiveBorderColor = tabActiveBorderColorDirect ?? configBg?.["bg:activebordercolor"];
|
||||
const tabBorderColor = tabBorderColorDirect ?? configBg?.["bg:bordercolor"];
|
||||
return [tabBorderColor, tabActiveBorderColor, configBg];
|
||||
}
|
||||
|
||||
export const Input = React.memo(
|
||||
({ decl, className, preview }: { decl: HeaderInput; className: string; preview: boolean }) => {
|
||||
const { value, ref, isDisabled, onChange, onKeyDown, onFocus, onBlur } = decl;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
// Copyright 2025, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import settingsSchema from "../../../schema/settings.json";
|
||||
import connectionsSchema from "../../../schema/connections.json";
|
||||
import aipresetsSchema from "../../../schema/aipresets.json";
|
||||
import bgpresetsSchema from "../../../schema/bgpresets.json";
|
||||
import backgroundsSchema from "../../../schema/backgrounds.json";
|
||||
import connectionsSchema from "../../../schema/connections.json";
|
||||
import settingsSchema from "../../../schema/settings.json";
|
||||
import waveaiSchema from "../../../schema/waveai.json";
|
||||
import widgetsSchema from "../../../schema/widgets.json";
|
||||
|
||||
|
|
@ -31,9 +31,9 @@ const MonacoSchemas: SchemaInfo[] = [
|
|||
schema: aipresetsSchema,
|
||||
},
|
||||
{
|
||||
uri: "wave://schema/bgpresets.json",
|
||||
fileMatch: ["*/WAVECONFIGPATH/presets/bg.json"],
|
||||
schema: bgpresetsSchema,
|
||||
uri: "wave://schema/backgrounds.json",
|
||||
fileMatch: ["*/WAVECONFIGPATH/backgrounds.json"],
|
||||
schema: backgroundsSchema,
|
||||
},
|
||||
{
|
||||
uri: "wave://schema/waveai.json",
|
||||
|
|
|
|||
|
|
@ -132,6 +132,10 @@ function getBlockMetaKeyAtom<T extends keyof MetaType>(blockId: string, key: T):
|
|||
return metaAtom;
|
||||
}
|
||||
|
||||
function getTabMetaKeyAtom<T extends keyof MetaType>(tabId: string, key: T): Atom<MetaType[T]> {
|
||||
return getOrefMetaKeyAtom(WOS.makeORef("tab", tabId), key);
|
||||
}
|
||||
|
||||
function getOrefMetaKeyAtom<T extends keyof MetaType>(oref: string, key: T): Atom<MetaType[T]> {
|
||||
const orefCache = getSingleOrefAtomCache(oref);
|
||||
const metaAtomName = "#meta-" + key;
|
||||
|
|
@ -229,6 +233,21 @@ function useSettingsKeyAtom<T extends keyof SettingsType>(key: T): SettingsType[
|
|||
return useAtomValue(getSettingsKeyAtom(key));
|
||||
}
|
||||
|
||||
const configBackgroundAtomCache = new Map<string, Atom<BackgroundConfigType>>();
|
||||
|
||||
function getConfigBackgroundAtom(bgKey: string | null): Atom<BackgroundConfigType> {
|
||||
if (isPreviewWindow() || bgKey == null) return NullAtom as Atom<BackgroundConfigType>;
|
||||
let bgAtom = configBackgroundAtomCache.get(bgKey);
|
||||
if (bgAtom == null) {
|
||||
bgAtom = atom((get) => {
|
||||
const fullConfig = get(atoms.fullConfigAtom);
|
||||
return fullConfig.backgrounds?.[bgKey];
|
||||
});
|
||||
configBackgroundAtomCache.set(bgKey, bgAtom);
|
||||
}
|
||||
return bgAtom;
|
||||
}
|
||||
|
||||
function getSettingsPrefixAtom(prefix: string): Atom<SettingsType> {
|
||||
if (isPreviewWindow()) return NullAtom as Atom<SettingsType>;
|
||||
let settingsPrefixAtom = settingsAtomCache.get(prefix + ":");
|
||||
|
|
@ -666,6 +685,8 @@ export {
|
|||
getBlockComponentModel,
|
||||
getBlockMetaKeyAtom,
|
||||
getBlockTermDurableAtom,
|
||||
getTabMetaKeyAtom,
|
||||
getConfigBackgroundAtom,
|
||||
getConnConfigKeyAtom,
|
||||
getConnStatusAtom,
|
||||
getFocusedBlockId,
|
||||
|
|
|
|||
|
|
@ -75,28 +75,38 @@ export function buildTabContextMenu(
|
|||
];
|
||||
menu.push({ label: "Flag Tab", type: "submenu", submenu: flagSubmenu }, { type: "separator" });
|
||||
const fullConfig = globalStore.get(env.atoms.fullConfigAtom);
|
||||
const bgPresets: string[] = [];
|
||||
for (const key in fullConfig?.presets ?? {}) {
|
||||
if (key.startsWith("bg@") && fullConfig.presets[key] != null) {
|
||||
bgPresets.push(key);
|
||||
}
|
||||
}
|
||||
bgPresets.sort((a, b) => {
|
||||
const aOrder = fullConfig.presets[a]["display:order"] ?? 0;
|
||||
const bOrder = fullConfig.presets[b]["display:order"] ?? 0;
|
||||
const backgrounds = fullConfig?.backgrounds ?? {};
|
||||
const bgKeys = Object.keys(backgrounds).filter((k) => backgrounds[k] != null);
|
||||
bgKeys.sort((a, b) => {
|
||||
const aOrder = backgrounds[a]["display:order"] ?? 0;
|
||||
const bOrder = backgrounds[b]["display:order"] ?? 0;
|
||||
return aOrder - bOrder;
|
||||
});
|
||||
if (bgPresets.length > 0) {
|
||||
if (bgKeys.length > 0) {
|
||||
const submenu: ContextMenuItem[] = [];
|
||||
const oref = makeORef("tab", id);
|
||||
for (const presetName of bgPresets) {
|
||||
// preset cannot be null (filtered above)
|
||||
const preset = fullConfig.presets[presetName];
|
||||
submenu.push({
|
||||
label: "Default",
|
||||
click: () =>
|
||||
fireAndForget(async () => {
|
||||
await env.rpc.SetMetaCommand(TabRpcClient, {
|
||||
oref,
|
||||
meta: { "bg:*": true, "tab:background": null },
|
||||
});
|
||||
env.rpc.ActivityCommand(TabRpcClient, { settabtheme: 1 }, { noresponse: true });
|
||||
recordTEvent("action:settabtheme");
|
||||
}),
|
||||
});
|
||||
for (const bgKey of bgKeys) {
|
||||
const bg = backgrounds[bgKey];
|
||||
submenu.push({
|
||||
label: preset["display:name"] ?? presetName,
|
||||
label: bg["display:name"] ?? bgKey,
|
||||
click: () =>
|
||||
fireAndForget(async () => {
|
||||
await env.rpc.SetMetaCommand(TabRpcClient, { oref, meta: preset });
|
||||
await env.rpc.SetMetaCommand(TabRpcClient, {
|
||||
oref,
|
||||
meta: { "bg:*": true, "tab:background": bgKey },
|
||||
});
|
||||
env.rpc.ActivityCommand(TabRpcClient, { settabtheme: 1 }, { noresponse: true });
|
||||
recordTEvent("action:settabtheme");
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ export function CodeEditor({ blockId, text, language, fileName, readonly, onChan
|
|||
}, [minimapEnabled, stickyScrollEnabled, wordWrap, fontSize, readonly]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-full overflow-hidden items-center justify-center">
|
||||
<div className="flex flex-col w-full h-full items-center justify-center">
|
||||
<div className="flex flex-col h-full w-full" ref={divRef}>
|
||||
<MonacoCodeEditor
|
||||
readonly={readonly}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import * as React from "react";
|
|||
import { useDimensionsWithExistingRef } from "@/app/hook/useDimensions";
|
||||
import { waveEventSubscribeSingle } from "@/app/store/wps";
|
||||
import { TabRpcClient } from "@/app/store/wshrpcutil";
|
||||
import type { BlockMetaKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
|
||||
import type { MetaKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
|
||||
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
||||
|
||||
export type SysinfoEnv = WaveEnvSubset<{
|
||||
|
|
@ -26,7 +26,7 @@ export type SysinfoEnv = WaveEnvSubset<{
|
|||
fullConfigAtom: WaveEnv["atoms"]["fullConfigAtom"];
|
||||
};
|
||||
getConnStatusAtom: WaveEnv["getConnStatusAtom"];
|
||||
getBlockMetaKeyAtom: BlockMetaKeyAtomFnType<"graph:numpoints" | "sysinfo:type" | "connection" | "count">;
|
||||
getBlockMetaKeyAtom: MetaKeyAtomFnType<"graph:numpoints" | "sysinfo:type" | "connection" | "count">;
|
||||
}>;
|
||||
|
||||
const DefaultNumPoints = 120;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,22 @@
|
|||
|
||||
import type { FitAddon as IFitApi } from "@xterm/addon-fit";
|
||||
import type { ITerminalAddon, Terminal } from "@xterm/xterm";
|
||||
import { IRenderDimensions } from "@xterm/xterm/src/browser/renderer/shared/Types";
|
||||
|
||||
interface IDimensions {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
interface IRenderDimensions {
|
||||
css: {
|
||||
canvas: IDimensions;
|
||||
cell: IDimensions;
|
||||
};
|
||||
device: {
|
||||
canvas: IDimensions;
|
||||
cell: IDimensions;
|
||||
};
|
||||
}
|
||||
|
||||
interface ITerminalDimensions {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -32,16 +32,6 @@ export type ConfigFile = {
|
|||
|
||||
export const SecretNameRegex = /^[A-Za-z][A-Za-z0-9_]*$/;
|
||||
|
||||
function validateBgJson(parsed: any): ValidationResult {
|
||||
const keys = Object.keys(parsed);
|
||||
for (const key of keys) {
|
||||
if (!key.startsWith("bg@")) {
|
||||
return { error: `Invalid key "${key}": all top-level keys must start with "bg@"` };
|
||||
}
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
function validateAiJson(parsed: any): ValidationResult {
|
||||
const keys = Object.keys(parsed);
|
||||
for (const key of keys) {
|
||||
|
|
@ -101,10 +91,9 @@ function makeConfigFiles(isWindows: boolean): ConfigFile[] {
|
|||
},
|
||||
{
|
||||
name: "Tab Backgrounds",
|
||||
path: "presets/bg.json",
|
||||
path: "backgrounds.json",
|
||||
language: "json",
|
||||
docsUrl: "https://docs.waveterm.dev/presets#background-configurations",
|
||||
validator: validateBgJson,
|
||||
docsUrl: "https://docs.waveterm.dev/tab-backgrounds",
|
||||
hasJsonView: true,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import type { WaveConfigEnv } from "@/app/view/waveconfig/waveconfigenv";
|
|||
import { useWaveEnv } from "@/app/waveenv/waveenv";
|
||||
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed, keydownWrapper } from "@/util/keyutil";
|
||||
import { cn } from "@/util/util";
|
||||
import { useAtom, useAtomValue } from "jotai";
|
||||
import { useAtom, useAtomValue, useSetAtom } from "jotai";
|
||||
import type * as MonacoTypes from "monaco-editor";
|
||||
import { memo, useCallback, useEffect } from "react";
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ interface ConfigSidebarProps {
|
|||
|
||||
const ConfigSidebar = memo(({ model }: ConfigSidebarProps) => {
|
||||
const selectedFile = useAtomValue(model.selectedFileAtom);
|
||||
const [isMenuOpen, setIsMenuOpen] = useAtom(model.isMenuOpenAtom);
|
||||
const setIsMenuOpen = useSetAtom(model.isMenuOpenAtom);
|
||||
const configFiles = model.getConfigFiles();
|
||||
const deprecatedConfigFiles = model.getDeprecatedConfigFiles();
|
||||
const configErrorFiles = useAtomValue(model.configErrorFilesAtom);
|
||||
|
|
@ -164,141 +164,144 @@ const WaveConfigView = memo(({ blockId, model }: ViewComponentProps<WaveConfigVi
|
|||
return (
|
||||
<div className="@container flex flex-col w-full h-full">
|
||||
<div className="flex flex-row flex-1 min-h-0">
|
||||
{isMenuOpen && (
|
||||
<div className="absolute inset-0 bg-black/50 z-5 @w600:hidden" onClick={() => setIsMenuOpen(false)} />
|
||||
)}
|
||||
<div className={`h-full ${isMenuOpen ? "" : "@max-w600:hidden"}`}>
|
||||
<ConfigSidebar model={model} />
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 min-w-0">
|
||||
{selectedFile && (
|
||||
<>
|
||||
<div className="flex flex-row items-center justify-between px-4 py-2 border-b border-border">
|
||||
<div className="flex items-baseline gap-2 min-w-0">
|
||||
<button
|
||||
onClick={() => setIsMenuOpen(true)}
|
||||
className="@w600:hidden hover:bg-secondary/50 rounded p-1 cursor-pointer transition-colors mr-2 shrink-0"
|
||||
>
|
||||
<i className="fa fa-bars" />
|
||||
</button>
|
||||
<div className="text-lg font-semibold whitespace-nowrap shrink-0">
|
||||
{selectedFile.name}
|
||||
</div>
|
||||
{selectedFile.docsUrl && (
|
||||
<Tooltip content="View documentation">
|
||||
<a
|
||||
href={`${selectedFile.docsUrl}?ref=waveconfig`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="!text-muted-foreground hover:!text-primary transition-colors ml-1 shrink-0 cursor-pointer"
|
||||
>
|
||||
<i className="fa fa-book text-sm" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div className="text-xs text-muted-foreground font-mono pb-0.5 ml-1 truncate @max-w450:hidden">
|
||||
{selectedFile.path}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 items-baseline shrink-0">
|
||||
{selectedFile.hasJsonView && (
|
||||
<>
|
||||
{hasChanges && (
|
||||
<span className="text-xs text-warning pb-0.5 @max-w450:hidden">
|
||||
Unsaved changes
|
||||
</span>
|
||||
)}
|
||||
<Tooltip content={saveTooltip} placement="bottom" divClassName="shrink-0">
|
||||
<button
|
||||
onClick={() => model.saveFile()}
|
||||
disabled={!hasChanges || isSaving}
|
||||
className={`px-3 py-1 rounded transition-colors text-sm ${
|
||||
!hasChanges || isSaving
|
||||
? "border border-border text-muted-foreground opacity-50"
|
||||
: "bg-accent/80 text-primary hover:bg-accent cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
{isSaving ? "Saving..." : "Save"}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{selectedFile.visualComponent && selectedFile.hasJsonView && (
|
||||
<div className="flex gap-0 border-b border-border">
|
||||
<button
|
||||
onClick={() => setActiveTab("visual")}
|
||||
className={cn(
|
||||
"px-4 pt-1 pb-1.5 cursor-pointer transition-colors text-secondary",
|
||||
activeTab === "visual"
|
||||
? "bg-highlightbg text-primary"
|
||||
: "bg-transparent hover:bg-hover"
|
||||
)}
|
||||
>
|
||||
Visual
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("json")}
|
||||
className={cn(
|
||||
"px-4 pt-1 pb-1.5 cursor-pointer transition-colors text-secondary",
|
||||
activeTab === "json"
|
||||
? "bg-highlightbg text-primary"
|
||||
: "bg-transparent hover:bg-hover"
|
||||
)}
|
||||
>
|
||||
Raw JSON
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{errorMessage && (
|
||||
<div className="bg-error text-primary px-4 py-2 border-b border-error flex items-center justify-between">
|
||||
<span>{errorMessage}</span>
|
||||
<button
|
||||
onClick={() => model.clearError()}
|
||||
className="ml-2 hover:bg-black/20 rounded p-1 cursor-pointer transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{validationError && (
|
||||
<div className="bg-error text-primary px-4 py-2 border-b border-error flex items-center justify-between">
|
||||
<span>{validationError}</span>
|
||||
<button
|
||||
onClick={() => model.clearValidationError()}
|
||||
className="ml-2 hover:bg-black/20 rounded p-1 cursor-pointer transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||
Loading...
|
||||
</div>
|
||||
) : selectedFile.visualComponent &&
|
||||
(!selectedFile.hasJsonView || activeTab === "visual") ? (
|
||||
(() => {
|
||||
const VisualComponent = selectedFile.visualComponent;
|
||||
return <VisualComponent model={model} />;
|
||||
})()
|
||||
) : (
|
||||
<CodeEditor
|
||||
blockId={blockId}
|
||||
text={fileContent}
|
||||
fileName={`WAVECONFIGPATH/${selectedFile.path}`}
|
||||
language={selectedFile.language}
|
||||
readonly={false}
|
||||
onChange={handleContentChange}
|
||||
onMount={handleEditorMount}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
{isMenuOpen && (
|
||||
<div
|
||||
className="absolute inset-0 bg-black/50 z-5 @w600:hidden"
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className={`h-full ${isMenuOpen ? "" : "@max-w600:hidden"}`}>
|
||||
<ConfigSidebar model={model} />
|
||||
</div>
|
||||
<div className="flex flex-col flex-1 min-w-0">
|
||||
{selectedFile && (
|
||||
<>
|
||||
<div className="flex flex-row items-center justify-between px-4 py-2 border-b border-border">
|
||||
<div className="flex items-baseline gap-2 min-w-0">
|
||||
<button
|
||||
onClick={() => setIsMenuOpen(true)}
|
||||
className="@w600:hidden hover:bg-secondary/50 rounded p-1 cursor-pointer transition-colors mr-2 shrink-0"
|
||||
>
|
||||
<i className="fa fa-bars" />
|
||||
</button>
|
||||
<div className="text-lg font-semibold whitespace-nowrap shrink-0">
|
||||
{selectedFile.name}
|
||||
</div>
|
||||
{selectedFile.docsUrl && (
|
||||
<Tooltip content="View documentation">
|
||||
<a
|
||||
href={`${selectedFile.docsUrl}?ref=waveconfig`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="!text-muted-foreground hover:!text-primary transition-colors ml-1 shrink-0 cursor-pointer"
|
||||
>
|
||||
<i className="fa fa-book text-sm" />
|
||||
</a>
|
||||
</Tooltip>
|
||||
)}
|
||||
<div className="text-xs text-muted-foreground font-mono pb-0.5 ml-1 truncate @max-w450:hidden">
|
||||
{selectedFile.path}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2 items-baseline shrink-0">
|
||||
{selectedFile.hasJsonView && (
|
||||
<>
|
||||
{hasChanges && (
|
||||
<span className="text-xs text-warning pb-0.5 @max-w450:hidden">
|
||||
Unsaved changes
|
||||
</span>
|
||||
)}
|
||||
<Tooltip content={saveTooltip} placement="bottom" divClassName="shrink-0">
|
||||
<button
|
||||
onClick={() => model.saveFile()}
|
||||
disabled={!hasChanges || isSaving}
|
||||
className={`px-3 py-1 rounded transition-colors text-sm ${
|
||||
!hasChanges || isSaving
|
||||
? "border border-border text-muted-foreground opacity-50"
|
||||
: "bg-accent/80 text-primary hover:bg-accent cursor-pointer"
|
||||
}`}
|
||||
>
|
||||
{isSaving ? "Saving..." : "Save"}
|
||||
</button>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{selectedFile.visualComponent && selectedFile.hasJsonView && (
|
||||
<div className="flex gap-0 border-b border-border">
|
||||
<button
|
||||
onClick={() => setActiveTab("visual")}
|
||||
className={cn(
|
||||
"px-4 pt-1 pb-1.5 cursor-pointer transition-colors text-secondary",
|
||||
activeTab === "visual"
|
||||
? "bg-highlightbg text-primary"
|
||||
: "bg-transparent hover:bg-hover"
|
||||
)}
|
||||
>
|
||||
Visual
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab("json")}
|
||||
className={cn(
|
||||
"px-4 pt-1 pb-1.5 cursor-pointer transition-colors text-secondary",
|
||||
activeTab === "json"
|
||||
? "bg-highlightbg text-primary"
|
||||
: "bg-transparent hover:bg-hover"
|
||||
)}
|
||||
>
|
||||
Raw JSON
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{errorMessage && (
|
||||
<div className="bg-error text-primary px-4 py-2 border-b border-error flex items-center justify-between">
|
||||
<span>{errorMessage}</span>
|
||||
<button
|
||||
onClick={() => model.clearError()}
|
||||
className="ml-2 hover:bg-black/20 rounded p-1 cursor-pointer transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{validationError && (
|
||||
<div className="bg-error text-primary px-4 py-2 border-b border-error flex items-center justify-between">
|
||||
<span>{validationError}</span>
|
||||
<button
|
||||
onClick={() => model.clearValidationError()}
|
||||
className="ml-2 hover:bg-black/20 rounded p-1 cursor-pointer transition-colors"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex-1 min-h-0">
|
||||
{isLoading ? (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground">
|
||||
Loading...
|
||||
</div>
|
||||
) : selectedFile.visualComponent &&
|
||||
(!selectedFile.hasJsonView || activeTab === "visual") ? (
|
||||
(() => {
|
||||
const VisualComponent = selectedFile.visualComponent;
|
||||
return <VisualComponent model={model} />;
|
||||
})()
|
||||
) : (
|
||||
<CodeEditor
|
||||
blockId={blockId}
|
||||
text={fileContent}
|
||||
fileName={`WAVECONFIGPATH/${selectedFile.path}`}
|
||||
language={selectedFile.language}
|
||||
readonly={false}
|
||||
onChange={handleContentChange}
|
||||
onMount={handleEditorMount}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{configErrors?.length > 0 && (
|
||||
<div className="bg-error text-primary px-4 py-1 max-h-12 overflow-y-auto border-t border-error/50 shrink-0">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BlockMetaKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
|
||||
import type { MetaKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
|
||||
|
||||
export type WaveConfigEnv = WaveEnvSubset<{
|
||||
electron: {
|
||||
|
|
@ -22,6 +22,6 @@ export type WaveConfigEnv = WaveEnvSubset<{
|
|||
atoms: {
|
||||
fullConfigAtom: WaveEnv["atoms"]["fullConfigAtom"];
|
||||
};
|
||||
getBlockMetaKeyAtom: BlockMetaKeyAtomFnType<"file">;
|
||||
getBlockMetaKeyAtom: MetaKeyAtomFnType<"file">;
|
||||
isWindows: WaveEnv["isWindows"];
|
||||
}>;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BlockMetaKeyAtomFnType, SettingsKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
|
||||
import type { MetaKeyAtomFnType, SettingsKeyAtomFnType, WaveEnv, WaveEnvSubset } from "@/app/waveenv/waveenv";
|
||||
|
||||
export type WebViewEnv = WaveEnvSubset<{
|
||||
electron: {
|
||||
|
|
@ -19,7 +19,7 @@ export type WebViewEnv = WaveEnvSubset<{
|
|||
wos: WaveEnv["wos"];
|
||||
createBlock: WaveEnv["createBlock"];
|
||||
getSettingsKeyAtom: SettingsKeyAtomFnType<"web:defaulturl" | "web:defaultsearch">;
|
||||
getBlockMetaKeyAtom: BlockMetaKeyAtomFnType<
|
||||
getBlockMetaKeyAtom: MetaKeyAtomFnType<
|
||||
"web:hidenav" | "web:useragenttype" | "web:zoom" | "web:partition"
|
||||
>;
|
||||
}>;
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { RpcApiType } from "@/app/store/wshclientapi";
|
|||
import { Atom, PrimitiveAtom } from "jotai";
|
||||
import React from "react";
|
||||
|
||||
export type BlockMetaKeyAtomFnType<Keys extends keyof MetaType = keyof MetaType> = <T extends Keys>(
|
||||
blockId: string,
|
||||
export type MetaKeyAtomFnType<Keys extends keyof MetaType = keyof MetaType> = <T extends Keys>(
|
||||
id: string,
|
||||
key: T
|
||||
) => Atom<MetaType[T]>;
|
||||
|
||||
|
|
@ -74,8 +74,10 @@ export type WaveEnv = {
|
|||
useWaveObjectValue: <T extends WaveObj>(oref: string) => [T, boolean];
|
||||
};
|
||||
getSettingsKeyAtom: SettingsKeyAtomFnType;
|
||||
getBlockMetaKeyAtom: BlockMetaKeyAtomFnType;
|
||||
getBlockMetaKeyAtom: MetaKeyAtomFnType;
|
||||
getTabMetaKeyAtom: MetaKeyAtomFnType;
|
||||
getConnConfigKeyAtom: ConnConfigKeyAtomFnType;
|
||||
getConfigBackgroundAtom: (bgKey: string | null) => Atom<BackgroundConfigType>;
|
||||
|
||||
// the mock fields are only usable in the preview server (may be be null or throw errors in production)
|
||||
mockSetWaveObj: <T extends WaveObj>(oref: string, obj: T) => void;
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ import {
|
|||
atoms,
|
||||
createBlock,
|
||||
getBlockMetaKeyAtom,
|
||||
getConfigBackgroundAtom,
|
||||
getConnConfigKeyAtom,
|
||||
getConnStatusAtom,
|
||||
getLocalHostDisplayNameAtom,
|
||||
getSettingsKeyAtom,
|
||||
getTabMetaKeyAtom,
|
||||
isDev,
|
||||
WOS,
|
||||
} from "@/app/store/global";
|
||||
|
|
@ -44,6 +46,8 @@ export function makeWaveEnvImpl(): WaveEnv {
|
|||
useWaveObjectValue: WOS.useWaveObjectValue,
|
||||
},
|
||||
getBlockMetaKeyAtom,
|
||||
getTabMetaKeyAtom,
|
||||
getConfigBackgroundAtom,
|
||||
getConnConfigKeyAtom,
|
||||
|
||||
mockSetWaveObj: <T extends WaveObj>(_oref: string, _obj: T) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright 2026, Command Line Inc.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import backgroundsJson from "../../../pkg/wconfig/defaultconfig/backgrounds.json";
|
||||
import mimetypesJson from "../../../pkg/wconfig/defaultconfig/mimetypes.json";
|
||||
import presetsJson from "../../../pkg/wconfig/defaultconfig/presets.json";
|
||||
import settingsJson from "../../../pkg/wconfig/defaultconfig/settings.json";
|
||||
|
|
@ -18,5 +19,6 @@ export const DefaultFullConfig: FullConfigType = {
|
|||
connections: {},
|
||||
bookmarks: {},
|
||||
waveai: waveaiJson as unknown as { [key: string]: AIModeConfigType },
|
||||
backgrounds: backgroundsJson as { [key: string]: BackgroundConfigType },
|
||||
configerrors: [],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { handleWaveEvent } from "@/app/store/wps";
|
|||
import { RpcApiType } from "@/app/store/wshclientapi";
|
||||
import { WaveEnv } from "@/app/waveenv/waveenv";
|
||||
import { PlatformLinux, PlatformMacOS, PlatformWindows } from "@/util/platformutil";
|
||||
import { NullAtom } from "@/util/util";
|
||||
import { Atom, atom, PrimitiveAtom, useAtomValue } from "jotai";
|
||||
import { showPreviewContextMenu } from "../preview-contextmenu";
|
||||
import { MockSysinfoConnection } from "../previews/sysinfo.preview-util";
|
||||
|
|
@ -428,8 +429,9 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv {
|
|||
const connStatusAtomCache = new Map<string, PrimitiveAtom<ConnStatus>>();
|
||||
const waveObjectValueAtomCache = new Map<string, PrimitiveAtom<any>>();
|
||||
const waveObjectDerivedAtomCache = new Map<string, Atom<any>>();
|
||||
const blockMetaKeyAtomCache = new Map<string, Atom<any>>();
|
||||
const orefMetaKeyAtomCache = new Map<string, Atom<any>>();
|
||||
const connConfigKeyAtomCache = new Map<string, Atom<any>>();
|
||||
const configBackgroundAtomCache = new Map<string, Atom<BackgroundConfigType>>();
|
||||
const getWaveObjectAtom = <T extends WaveObj>(oref: string): PrimitiveAtom<T> => {
|
||||
if (!waveObjectValueAtomCache.has(oref)) {
|
||||
const obj = (mergedOverrides.mockWaveObjs?.[oref] ?? null) as T;
|
||||
|
|
@ -461,7 +463,11 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv {
|
|||
globalStore.set(waveObjectValueAtomCache.get(oref), obj);
|
||||
},
|
||||
};
|
||||
const { rpc, setRpcHandler, setRpcStreamHandler } = makeMockRpc(mergedOverrides.rpc, mergedOverrides.rpcStreaming, mockWosFns);
|
||||
const { rpc, setRpcHandler, setRpcStreamHandler } = makeMockRpc(
|
||||
mergedOverrides.rpc,
|
||||
mergedOverrides.rpcStreaming,
|
||||
mockWosFns
|
||||
);
|
||||
const env = {
|
||||
isMock: true,
|
||||
mockEnv: mergedOverrides,
|
||||
|
|
@ -539,17 +545,36 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv {
|
|||
},
|
||||
},
|
||||
getBlockMetaKeyAtom: <T extends keyof MetaType>(blockId: string, key: T) => {
|
||||
const cacheKey = blockId + "#meta-" + key;
|
||||
if (!blockMetaKeyAtomCache.has(cacheKey)) {
|
||||
if (blockId == null) {
|
||||
return NullAtom as Atom<MetaType[T]>;
|
||||
}
|
||||
const oref = "block:" + blockId;
|
||||
const cacheKey = oref + "#meta-" + key;
|
||||
if (!orefMetaKeyAtomCache.has(cacheKey)) {
|
||||
const metaAtom = atom<MetaType[T]>((get) => {
|
||||
const blockORef = "block:" + blockId;
|
||||
const blockAtom = env.wos.getWaveObjectAtom<Block>(blockORef);
|
||||
const blockAtom = env.wos.getWaveObjectAtom<Block>(oref);
|
||||
const blockData = get(blockAtom);
|
||||
return blockData?.meta?.[key] as MetaType[T];
|
||||
});
|
||||
blockMetaKeyAtomCache.set(cacheKey, metaAtom);
|
||||
orefMetaKeyAtomCache.set(cacheKey, metaAtom);
|
||||
}
|
||||
return blockMetaKeyAtomCache.get(cacheKey) as Atom<MetaType[T]>;
|
||||
return orefMetaKeyAtomCache.get(cacheKey) as Atom<MetaType[T]>;
|
||||
},
|
||||
getTabMetaKeyAtom: <T extends keyof MetaType>(tabId: string, key: T) => {
|
||||
if (tabId == null) {
|
||||
return NullAtom as Atom<MetaType[T]>;
|
||||
}
|
||||
const oref = "tab:" + tabId;
|
||||
const cacheKey = oref + "#meta-" + key;
|
||||
if (!orefMetaKeyAtomCache.has(cacheKey)) {
|
||||
const metaAtom = atom<MetaType[T]>((get) => {
|
||||
const tabAtom = env.wos.getWaveObjectAtom<Tab>(oref);
|
||||
const tabData = get(tabAtom);
|
||||
return tabData?.meta?.[key] as MetaType[T];
|
||||
});
|
||||
orefMetaKeyAtomCache.set(cacheKey, metaAtom);
|
||||
}
|
||||
return orefMetaKeyAtomCache.get(cacheKey) as Atom<MetaType[T]>;
|
||||
},
|
||||
getConnConfigKeyAtom: <T extends keyof ConnKeywords>(connName: string, key: T) => {
|
||||
const cacheKey = connName + "#conn-" + key;
|
||||
|
|
@ -562,6 +587,19 @@ export function makeMockWaveEnv(mockEnv?: MockEnv): MockWaveEnv {
|
|||
}
|
||||
return connConfigKeyAtomCache.get(cacheKey) as Atom<ConnKeywords[T]>;
|
||||
},
|
||||
getConfigBackgroundAtom: (bgKey: string | null) => {
|
||||
if (bgKey == null) return NullAtom as Atom<BackgroundConfigType>;
|
||||
if (!configBackgroundAtomCache.has(bgKey)) {
|
||||
configBackgroundAtomCache.set(
|
||||
bgKey,
|
||||
atom((get) => {
|
||||
const fullConfig = get(atoms.fullConfigAtom);
|
||||
return fullConfig.backgrounds?.[bgKey];
|
||||
})
|
||||
);
|
||||
}
|
||||
return configBackgroundAtomCache.get(bgKey);
|
||||
},
|
||||
services: null as any,
|
||||
callBackendService: (service: string, method: string, args: any[], noUIContext?: boolean) => {
|
||||
const fn = mergedOverrides.services?.[service]?.[method];
|
||||
|
|
|
|||
14
frontend/types/gotypes.d.ts
vendored
14
frontend/types/gotypes.d.ts
vendored
|
|
@ -108,6 +108,17 @@ declare global {
|
|||
iconcolor: string;
|
||||
};
|
||||
|
||||
// wconfig.BackgroundConfigType
|
||||
type BackgroundConfigType = {
|
||||
bg?: string;
|
||||
"bg:opacity"?: number;
|
||||
"bg:blendmode"?: string;
|
||||
"bg:bordercolor"?: string;
|
||||
"bg:activebordercolor"?: string;
|
||||
"display:name": string;
|
||||
"display:order"?: number;
|
||||
};
|
||||
|
||||
// baseds.Badge
|
||||
type Badge = {
|
||||
badgeid: string;
|
||||
|
|
@ -991,6 +1002,7 @@ declare global {
|
|||
defaultwidgets: {[key: string]: WidgetConfigType};
|
||||
widgets: {[key: string]: WidgetConfigType};
|
||||
presets: {[key: string]: MetaType};
|
||||
backgrounds: {[key: string]: BackgroundConfigType};
|
||||
termthemes: {[key: string]: TermThemeType};
|
||||
connections: {[key: string]: ConnKeywords};
|
||||
bookmarks: {[key: string]: WebBookmark};
|
||||
|
|
@ -1129,6 +1141,7 @@ declare global {
|
|||
"graph:metrics"?: string[];
|
||||
"sysinfo:type"?: string;
|
||||
"tab:flagcolor"?: string;
|
||||
"tab:background"?: string;
|
||||
"bg:*"?: boolean;
|
||||
bg?: string;
|
||||
"bg:opacity"?: number;
|
||||
|
|
@ -1378,6 +1391,7 @@ declare global {
|
|||
"preview:defaultsort"?: string;
|
||||
"tab:preset"?: string;
|
||||
"tab:confirmclose"?: boolean;
|
||||
"tab:background"?: string;
|
||||
"widget:*"?: boolean;
|
||||
"widget:showhelp"?: boolean;
|
||||
"window:*"?: boolean;
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export function processBackgroundUrls(cssText: string): string {
|
|||
return rtnStyle.replace(/^background:\s*/, "");
|
||||
}
|
||||
|
||||
export function computeBgStyleFromMeta(meta: MetaType, defaultOpacity: number = null): React.CSSProperties {
|
||||
export function computeBgStyleFromMeta(meta: Omit<BackgroundConfigType, "display:name">, defaultOpacity: number = null): React.CSSProperties {
|
||||
const bgAttr = meta?.["bg"];
|
||||
if (isBlank(bgAttr)) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -90,6 +90,7 @@ const (
|
|||
MetaKey_SysinfoType = "sysinfo:type"
|
||||
|
||||
MetaKey_TabFlagColor = "tab:flagcolor"
|
||||
MetaKey_TabBackground = "tab:background"
|
||||
|
||||
MetaKey_BgClear = "bg:*"
|
||||
MetaKey_Bg = "bg"
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ type MetaTSType struct {
|
|||
|
||||
// for tabs
|
||||
TabFlagColor string `json:"tab:flagcolor,omitempty"`
|
||||
TabBackground string `json:"tab:background,omitempty"`
|
||||
BgClear bool `json:"bg:*,omitempty"`
|
||||
Bg string `json:"bg,omitempty"`
|
||||
BgOpacity float64 `json:"bg:opacity,omitempty"`
|
||||
|
|
|
|||
90
pkg/wconfig/defaultconfig/backgrounds.json
Normal file
90
pkg/wconfig/defaultconfig/backgrounds.json
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
{
|
||||
"bg@rainbow": {
|
||||
"display:name": "Rainbow",
|
||||
"display:order": 2.1,
|
||||
"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
|
||||
},
|
||||
"bg@green": {
|
||||
"display:name": "Green",
|
||||
"display:order": 1.2,
|
||||
"bg": "green",
|
||||
"bg:opacity": 0.3
|
||||
},
|
||||
"bg@blue": {
|
||||
"display:name": "Blue",
|
||||
"display:order": 1.1,
|
||||
"bg": "blue",
|
||||
"bg:opacity": 0.3,
|
||||
"bg:activebordercolor": "rgba(0, 0, 255, 1.0)"
|
||||
},
|
||||
"bg@red": {
|
||||
"display:name": "Red",
|
||||
"display:order": 1.3,
|
||||
"bg": "red",
|
||||
"bg:opacity": 0.3,
|
||||
"bg:activebordercolor": "rgba(255, 0, 0, 1.0)"
|
||||
},
|
||||
"bg@ocean-depths": {
|
||||
"display:name": "Ocean Depths",
|
||||
"display:order": 2.2,
|
||||
"bg": "linear-gradient(135deg, purple, blue, teal)",
|
||||
"bg:opacity": 0.7
|
||||
},
|
||||
"bg@aqua-horizon": {
|
||||
"display:name": "Aqua Horizon",
|
||||
"display:order": 2.3,
|
||||
"bg": "linear-gradient(135deg, rgba(15, 30, 50, 1) 0%, rgba(40, 90, 130, 0.85) 30%, rgba(20, 100, 150, 0.75) 60%, rgba(0, 120, 160, 0.65) 80%, rgba(0, 140, 180, 0.55) 100%), linear-gradient(135deg, rgba(100, 80, 255, 0.4), rgba(0, 180, 220, 0.4)), radial-gradient(circle at 70% 70%, rgba(255, 255, 255, 0.05), transparent 70%)",
|
||||
"bg:opacity": 0.85,
|
||||
"bg:blendmode": "overlay"
|
||||
},
|
||||
"bg@sunset": {
|
||||
"display:name": "Sunset",
|
||||
"display:order": 2.4,
|
||||
"bg": "linear-gradient(135deg, rgba(128, 0, 0, 1), rgba(255, 69, 0, 0.8), rgba(75, 0, 130, 1))",
|
||||
"bg:opacity": 0.8,
|
||||
"bg:blendmode": "normal"
|
||||
},
|
||||
"bg@enchantedforest": {
|
||||
"display:name": "Enchanted Forest",
|
||||
"display:order": 2.7,
|
||||
"bg": "linear-gradient(145deg, rgba(0,50,0,1), rgba(34,139,34,0.7) 20%, rgba(0,100,0,0.5) 40%, rgba(0,200,100,0.3) 60%, rgba(34,139,34,0.8) 80%, rgba(0,50,0,1)), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 80%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 80%)",
|
||||
"bg:opacity": 0.8,
|
||||
"bg:blendmode": "soft-light"
|
||||
},
|
||||
"bg@twilight-mist": {
|
||||
"display:name": "Twilight Mist",
|
||||
"display:order": 2.9,
|
||||
"bg": "linear-gradient(180deg, rgba(60,60,90,1) 0%, rgba(90,110,140,0.8) 40%, rgba(120,140,160,0.6) 70%, rgba(60,60,90,1) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.15), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 70%)",
|
||||
"bg:opacity": 0.9,
|
||||
"bg:blendmode": "soft-light"
|
||||
},
|
||||
"bg@duskhorizon": {
|
||||
"display:name": "Dusk Horizon",
|
||||
"display:order": 3.1,
|
||||
"bg": "linear-gradient(0deg, rgba(128,0,0,1) 0%, rgba(204,85,0,0.7) 20%, rgba(255,140,0,0.6) 45%, rgba(160,90,160,0.5) 65%, rgba(60,60,120,1) 100%), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)",
|
||||
"bg:opacity": 0.9,
|
||||
"bg:blendmode": "overlay"
|
||||
},
|
||||
"bg@tropical-radiance": {
|
||||
"display:name": "Tropical Radiance",
|
||||
"display:order": 3.3,
|
||||
"bg": "linear-gradient(135deg, rgba(204, 51, 255, 0.9) 0%, rgba(255, 85, 153, 0.75) 30%, rgba(255, 51, 153, 0.65) 60%, rgba(204, 51, 255, 0.6) 80%, rgba(51, 102, 255, 0.5) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)",
|
||||
"bg:opacity": 0.9,
|
||||
"bg:blendmode": "overlay"
|
||||
},
|
||||
"bg@twilight-ember": {
|
||||
"display:name": "Twilight Ember",
|
||||
"display:order": 3.5,
|
||||
"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:text": "rgb(200, 200, 200)"
|
||||
},
|
||||
"bg@cosmic-tide": {
|
||||
"display:name": "Cosmic Tide",
|
||||
"display:order": 3.6,
|
||||
"bg:activebordercolor": "#ff55aa",
|
||||
"bg": "linear-gradient(135deg, #00d9d9, #ff55aa, #1e1e2f, #2f3b57, #ff99ff)",
|
||||
"bg:opacity": 0.6
|
||||
}
|
||||
}
|
||||
|
|
@ -1,108 +1 @@
|
|||
{
|
||||
"bg@default": {
|
||||
"display:name": "Default",
|
||||
"display:order": -1,
|
||||
"bg:*": true
|
||||
},
|
||||
"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
|
||||
},
|
||||
"bg@green": {
|
||||
"display:name": "Green",
|
||||
"display:order": 1.2,
|
||||
"bg:*": true,
|
||||
"bg": "green",
|
||||
"bg:opacity": 0.3
|
||||
},
|
||||
"bg@blue": {
|
||||
"display:name": "Blue",
|
||||
"display:order": 1.1,
|
||||
"bg:*": true,
|
||||
"bg": "blue",
|
||||
"bg:opacity": 0.3,
|
||||
"bg:activebordercolor": "rgba(0, 0, 255, 1.0)"
|
||||
},
|
||||
"bg@red": {
|
||||
"display:name": "Red",
|
||||
"display:order": 1.3,
|
||||
"bg:*": true,
|
||||
"bg": "red",
|
||||
"bg:opacity": 0.3,
|
||||
"bg:activebordercolor": "rgba(255, 0, 0, 1.0)"
|
||||
},
|
||||
"bg@ocean-depths": {
|
||||
"display:name": "Ocean Depths",
|
||||
"display:order": 2.2,
|
||||
"bg:*": true,
|
||||
"bg": "linear-gradient(135deg, purple, blue, teal)",
|
||||
"bg:opacity": 0.7
|
||||
},
|
||||
"bg@aqua-horizon": {
|
||||
"display:name": "Aqua Horizon",
|
||||
"display:order": 2.3,
|
||||
"bg:*": true,
|
||||
"bg": "linear-gradient(135deg, rgba(15, 30, 50, 1) 0%, rgba(40, 90, 130, 0.85) 30%, rgba(20, 100, 150, 0.75) 60%, rgba(0, 120, 160, 0.65) 80%, rgba(0, 140, 180, 0.55) 100%), linear-gradient(135deg, rgba(100, 80, 255, 0.4), rgba(0, 180, 220, 0.4)), radial-gradient(circle at 70% 70%, rgba(255, 255, 255, 0.05), transparent 70%)",
|
||||
"bg:opacity": 0.85,
|
||||
"bg:blendmode": "overlay"
|
||||
},
|
||||
"bg@sunset": {
|
||||
"display:name": "Sunset",
|
||||
"display:order": 2.4,
|
||||
"bg:*": true,
|
||||
"bg": "linear-gradient(135deg, rgba(128, 0, 0, 1), rgba(255, 69, 0, 0.8), rgba(75, 0, 130, 1))",
|
||||
"bg:opacity": 0.8,
|
||||
"bg:blendmode": "normal"
|
||||
},
|
||||
"bg@enchantedforest": {
|
||||
"display:name": "Enchanted Forest",
|
||||
"display:order": 2.7,
|
||||
"bg:*": true,
|
||||
"bg": "linear-gradient(145deg, rgba(0,50,0,1), rgba(34,139,34,0.7) 20%, rgba(0,100,0,0.5) 40%, rgba(0,200,100,0.3) 60%, rgba(34,139,34,0.8) 80%, rgba(0,50,0,1)), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 80%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 80%)",
|
||||
"bg:opacity": 0.8,
|
||||
"bg:blendmode": "soft-light"
|
||||
},
|
||||
"bg@twilight-mist": {
|
||||
"display:name": "Twilight Mist",
|
||||
"display:order": 2.9,
|
||||
"bg:*": true,
|
||||
"bg": "linear-gradient(180deg, rgba(60,60,90,1) 0%, rgba(90,110,140,0.8) 40%, rgba(120,140,160,0.6) 70%, rgba(60,60,90,1) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.15), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.1), transparent 70%)",
|
||||
"bg:opacity": 0.9,
|
||||
"bg:blendmode": "soft-light"
|
||||
},
|
||||
"bg@duskhorizon": {
|
||||
"display:name": "Dusk Horizon",
|
||||
"display:order": 3.1,
|
||||
"bg:*": true,
|
||||
"bg": "linear-gradient(0deg, rgba(128,0,0,1) 0%, rgba(204,85,0,0.7) 20%, rgba(255,140,0,0.6) 45%, rgba(160,90,160,0.5) 65%, rgba(60,60,120,1) 100%), radial-gradient(circle at 30% 30%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)",
|
||||
"bg:opacity": 0.9,
|
||||
"bg:blendmode": "overlay"
|
||||
},
|
||||
"bg@tropical-radiance": {
|
||||
"display:name": "Tropical Radiance",
|
||||
"display:order": 3.3,
|
||||
"bg:*": true,
|
||||
"bg": "linear-gradient(135deg, rgba(204, 51, 255, 0.9) 0%, rgba(255, 85, 153, 0.75) 30%, rgba(255, 51, 153, 0.65) 60%, rgba(204, 51, 255, 0.6) 80%, rgba(51, 102, 255, 0.5) 100%), radial-gradient(circle at 30% 40%, rgba(255,255,255,0.1), transparent 60%), radial-gradient(circle at 70% 70%, rgba(255,255,255,0.05), transparent 70%)",
|
||||
"bg:opacity": 0.9,
|
||||
"bg:blendmode": "overlay"
|
||||
},
|
||||
"bg@twilight-ember": {
|
||||
"display:name": "Twilight Ember",
|
||||
"display:order": 3.5,
|
||||
"bg:*": true,
|
||||
"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: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
|
||||
}
|
||||
}
|
||||
{}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ const (
|
|||
|
||||
ConfigKey_TabPreset = "tab:preset"
|
||||
ConfigKey_TabConfirmClose = "tab:confirmclose"
|
||||
ConfigKey_TabBackground = "tab:background"
|
||||
|
||||
ConfigKey_WidgetClear = "widget:*"
|
||||
ConfigKey_WidgetShowHelp = "widget:showhelp"
|
||||
|
|
|
|||
|
|
@ -136,6 +136,7 @@ type SettingsType struct {
|
|||
|
||||
TabPreset string `json:"tab:preset,omitempty"`
|
||||
TabConfirmClose bool `json:"tab:confirmclose,omitempty"`
|
||||
TabBackground string `json:"tab:background,omitempty"`
|
||||
|
||||
WidgetClear bool `json:"widget:*,omitempty"`
|
||||
WidgetShowHelp *bool `json:"widget:showhelp,omitempty"`
|
||||
|
|
@ -308,17 +309,72 @@ type AIModeConfigUpdate struct {
|
|||
Configs map[string]AIModeConfigType `json:"configs"`
|
||||
}
|
||||
|
||||
type WidgetConfigType struct {
|
||||
DisplayOrder float64 `json:"display:order,omitempty"`
|
||||
DisplayHidden bool `json:"display:hidden,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Color string `json:"color,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Workspaces []string `json:"workspaces,omitempty"`
|
||||
Magnified bool `json:"magnified,omitempty"`
|
||||
BlockDef waveobj.BlockDef `json:"blockdef"`
|
||||
}
|
||||
|
||||
type BackgroundConfigType struct {
|
||||
Bg string `json:"bg,omitempty" jsonschema_description:"CSS background property value"`
|
||||
BgOpacity float64 `json:"bg:opacity,omitempty" jsonschema_description:"Background opacity (0.0-1.0)"`
|
||||
BgBlendMode string `json:"bg:blendmode,omitempty" jsonschema_description:"CSS background-blend-mode property value"`
|
||||
BgBorderColor string `json:"bg:bordercolor,omitempty" jsonschema_description:"Block frame border color"`
|
||||
BgActiveBorderColor string `json:"bg:activebordercolor,omitempty" jsonschema_description:"Block frame focused border color"`
|
||||
DisplayName string `json:"display:name" jsonschema_description:"The name shown in the context menu"`
|
||||
DisplayOrder float64 `json:"display:order,omitempty" jsonschema_description:"Determines the order of the background in the context menu"`
|
||||
}
|
||||
|
||||
type MimeTypeConfigType struct {
|
||||
Icon string `json:"icon"`
|
||||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
type TermThemeType struct {
|
||||
DisplayName string `json:"display:name"`
|
||||
DisplayOrder float64 `json:"display:order"`
|
||||
Black string `json:"black"`
|
||||
Red string `json:"red"`
|
||||
Green string `json:"green"`
|
||||
Yellow string `json:"yellow"`
|
||||
Blue string `json:"blue"`
|
||||
Magenta string `json:"magenta"`
|
||||
Cyan string `json:"cyan"`
|
||||
White string `json:"white"`
|
||||
BrightBlack string `json:"brightBlack"`
|
||||
BrightRed string `json:"brightRed"`
|
||||
BrightGreen string `json:"brightGreen"`
|
||||
BrightYellow string `json:"brightYellow"`
|
||||
BrightBlue string `json:"brightBlue"`
|
||||
BrightMagenta string `json:"brightMagenta"`
|
||||
BrightCyan string `json:"brightCyan"`
|
||||
BrightWhite string `json:"brightWhite"`
|
||||
Gray string `json:"gray"`
|
||||
CmdText string `json:"cmdtext"`
|
||||
Foreground string `json:"foreground"`
|
||||
SelectionBackground string `json:"selectionBackground"`
|
||||
Background string `json:"background"`
|
||||
Cursor string `json:"cursor"`
|
||||
}
|
||||
|
||||
type FullConfigType struct {
|
||||
Settings SettingsType `json:"settings" merge:"meta"`
|
||||
MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"`
|
||||
DefaultWidgets map[string]WidgetConfigType `json:"defaultwidgets"`
|
||||
Widgets map[string]WidgetConfigType `json:"widgets"`
|
||||
Presets map[string]waveobj.MetaMapType `json:"presets"`
|
||||
TermThemes map[string]TermThemeType `json:"termthemes"`
|
||||
Connections map[string]ConnKeywords `json:"connections"`
|
||||
Bookmarks map[string]WebBookmark `json:"bookmarks"`
|
||||
WaveAIModes map[string]AIModeConfigType `json:"waveai"`
|
||||
ConfigErrors []ConfigError `json:"configerrors" configfile:"-"`
|
||||
Settings SettingsType `json:"settings" merge:"meta"`
|
||||
MimeTypes map[string]MimeTypeConfigType `json:"mimetypes"`
|
||||
DefaultWidgets map[string]WidgetConfigType `json:"defaultwidgets"`
|
||||
Widgets map[string]WidgetConfigType `json:"widgets"`
|
||||
Presets map[string]waveobj.MetaMapType `json:"presets"`
|
||||
Backgrounds map[string]BackgroundConfigType `json:"backgrounds"`
|
||||
TermThemes map[string]TermThemeType `json:"termthemes"`
|
||||
Connections map[string]ConnKeywords `json:"connections"`
|
||||
Bookmarks map[string]WebBookmark `json:"bookmarks"`
|
||||
WaveAIModes map[string]AIModeConfigType `json:"waveai"`
|
||||
ConfigErrors []ConfigError `json:"configerrors" configfile:"-"`
|
||||
}
|
||||
|
||||
type ConnKeywords struct {
|
||||
|
|
@ -837,59 +893,47 @@ func SetConnectionsConfigValue(connName string, toMerge waveobj.MetaMapType) err
|
|||
return WriteWaveHomeConfigFile(ConnectionsFile, m)
|
||||
}
|
||||
|
||||
type WidgetConfigType struct {
|
||||
DisplayOrder float64 `json:"display:order,omitempty"`
|
||||
DisplayHidden bool `json:"display:hidden,omitempty"`
|
||||
Icon string `json:"icon,omitempty"`
|
||||
Color string `json:"color,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Workspaces []string `json:"workspaces,omitempty"`
|
||||
Magnified bool `json:"magnified,omitempty"`
|
||||
BlockDef waveobj.BlockDef `json:"blockdef"`
|
||||
}
|
||||
|
||||
type BgPresetsType struct {
|
||||
BgClear bool `json:"bg:*,omitempty"`
|
||||
Bg string `json:"bg,omitempty" jsonschema_description:"CSS background property value"`
|
||||
BgOpacity float64 `json:"bg:opacity,omitempty" jsonschema_description:"Background opacity (0.0-1.0)"`
|
||||
BgBlendMode string `json:"bg:blendmode,omitempty" jsonschema_description:"CSS background-blend-mode property value"`
|
||||
BgBorderColor string `json:"bg:bordercolor,omitempty" jsonschema_description:"Block frame border color"`
|
||||
BgActiveBorderColor string `json:"bg:activebordercolor,omitempty" jsonschema_description:"Block frame focused border color"`
|
||||
DisplayName string `json:"display:name,omitempty" jsonschema_description:"The name shown in the context menu"`
|
||||
DisplayOrder float64 `json:"display:order,omitempty" jsonschema_description:"Determines the order of the background in the context menu"`
|
||||
}
|
||||
|
||||
type MimeTypeConfigType struct {
|
||||
Icon string `json:"icon"`
|
||||
Color string `json:"color"`
|
||||
}
|
||||
|
||||
type TermThemeType struct {
|
||||
DisplayName string `json:"display:name"`
|
||||
DisplayOrder float64 `json:"display:order"`
|
||||
Black string `json:"black"`
|
||||
Red string `json:"red"`
|
||||
Green string `json:"green"`
|
||||
Yellow string `json:"yellow"`
|
||||
Blue string `json:"blue"`
|
||||
Magenta string `json:"magenta"`
|
||||
Cyan string `json:"cyan"`
|
||||
White string `json:"white"`
|
||||
BrightBlack string `json:"brightBlack"`
|
||||
BrightRed string `json:"brightRed"`
|
||||
BrightGreen string `json:"brightGreen"`
|
||||
BrightYellow string `json:"brightYellow"`
|
||||
BrightBlue string `json:"brightBlue"`
|
||||
BrightMagenta string `json:"brightMagenta"`
|
||||
BrightCyan string `json:"brightCyan"`
|
||||
BrightWhite string `json:"brightWhite"`
|
||||
Gray string `json:"gray"`
|
||||
CmdText string `json:"cmdtext"`
|
||||
Foreground string `json:"foreground"`
|
||||
SelectionBackground string `json:"selectionBackground"`
|
||||
Background string `json:"background"`
|
||||
Cursor string `json:"cursor"`
|
||||
func MigratePresetsBackgrounds() {
|
||||
configDirAbsPath := wavebase.GetWaveConfigDir()
|
||||
backgroundsFile := filepath.Join(configDirAbsPath, "backgrounds.json")
|
||||
if _, err := os.Stat(backgroundsFile); err == nil {
|
||||
return
|
||||
} else if !os.IsNotExist(err) {
|
||||
log.Printf("error checking backgrounds.json during migration: %v\n", err)
|
||||
return
|
||||
}
|
||||
bgFile := filepath.Join(configDirAbsPath, "presets", "bg.json")
|
||||
bgData, err := os.ReadFile(bgFile)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Printf("error reading presets/bg.json for migration: %v\n", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
var rawMap map[string]json.RawMessage
|
||||
if err := json.Unmarshal(bgData, &rawMap); err != nil {
|
||||
log.Printf("error parsing presets/bg.json for migration: %v\n", err)
|
||||
return
|
||||
}
|
||||
filtered := make(map[string]json.RawMessage)
|
||||
for k, v := range rawMap {
|
||||
if strings.HasPrefix(k, "bg@") {
|
||||
filtered[k] = v
|
||||
}
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return
|
||||
}
|
||||
outBarr, err := json.MarshalIndent(filtered, "", " ")
|
||||
if err != nil {
|
||||
log.Printf("error marshaling backgrounds.json during migration: %v\n", err)
|
||||
return
|
||||
}
|
||||
if err := fileutil.AtomicWriteFile(backgroundsFile, outBarr, 0644); err != nil {
|
||||
log.Printf("error writing backgrounds.json during migration: %v\n", err)
|
||||
return
|
||||
}
|
||||
log.Printf("migrated %d background presets from presets/bg.json to backgrounds.json\n", len(filtered))
|
||||
}
|
||||
|
||||
// CountCustomWidgets returns the number of custom widgets the user has defined.
|
||||
|
|
|
|||
|
|
@ -187,14 +187,12 @@ func GetWorkspace(ctx context.Context, wsID string) (*waveobj.Workspace, error)
|
|||
return wstore.DBMustGet[*waveobj.Workspace](ctx, wsID)
|
||||
}
|
||||
|
||||
func getTabPresetMeta() (waveobj.MetaMapType, error) {
|
||||
settings := wconfig.GetWatcher().GetFullConfig()
|
||||
tabPreset := settings.Settings.TabPreset
|
||||
if tabPreset == "" {
|
||||
return nil, nil
|
||||
func getTabBackground() string {
|
||||
config := wconfig.GetWatcher().GetFullConfig()
|
||||
if config.Settings.TabBackground != "" {
|
||||
return config.Settings.TabBackground
|
||||
}
|
||||
presetMeta := settings.Presets[tabPreset]
|
||||
return presetMeta, nil
|
||||
return config.Settings.TabPreset
|
||||
}
|
||||
|
||||
var tabNameRe = regexp.MustCompile(`^T(\d+)$`)
|
||||
|
|
@ -256,12 +254,10 @@ func CreateTab(ctx context.Context, workspaceId string, tabName string, activate
|
|||
if err != nil {
|
||||
return tab.OID, fmt.Errorf("error applying new tab layout: %w", err)
|
||||
}
|
||||
presetMeta, presetErr := getTabPresetMeta()
|
||||
if presetErr != nil {
|
||||
log.Printf("error getting tab preset meta: %v\n", presetErr)
|
||||
} else if len(presetMeta) > 0 {
|
||||
tabBg := getTabBackground()
|
||||
if tabBg != "" {
|
||||
tabORef := waveobj.ORefFromWaveObj(tab)
|
||||
wstore.UpdateObjectMeta(ctx, *tabORef, presetMeta, true)
|
||||
wstore.UpdateObjectMeta(ctx, *tabORef, waveobj.MetaMapType{waveobj.MetaKey_TabBackground: tabBg}, false)
|
||||
}
|
||||
}
|
||||
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{NewTab: 1}, "createtab")
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$defs": {
|
||||
"BgPresetsType": {
|
||||
"BackgroundConfigType": {
|
||||
"properties": {
|
||||
"bg:*": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"bg": {
|
||||
"type": "string",
|
||||
"description": "CSS background property value"
|
||||
|
|
@ -36,11 +33,21 @@
|
|||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object"
|
||||
"type": "object",
|
||||
"required": [
|
||||
"display:name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/BgPresetsType"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/BackgroundConfigType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
|
|
@ -232,6 +232,9 @@
|
|||
"tab:confirmclose": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"tab:background": {
|
||||
"type": "string"
|
||||
},
|
||||
"widget:*": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -224,7 +224,14 @@
|
|||
}
|
||||
},
|
||||
"additionalProperties": {
|
||||
"$ref": "#/$defs/WidgetConfigType"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/$defs/WidgetConfigType"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": "object"
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"include": ["frontend/**/*", "emain/**/*"],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"module": "es2020",
|
||||
|
|
|
|||
Loading…
Reference in a new issue