mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
Merge branch 'main' into feature/auto-hide-tab-bar
This commit is contained in:
commit
2af5c024f0
2
.github/workflows/merge-gatekeeper.yml
vendored
2
.github/workflows/merge-gatekeeper.yml
vendored
@ -23,4 +23,4 @@ jobs:
|
|||||||
uses: upsidr/merge-gatekeeper@v1
|
uses: upsidr/merge-gatekeeper@v1
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
ignored: Test Onboarding, Analyze (go), Analyze (javascript-typescript), License Compliance
|
ignored: Test Onboarding, Analyze (go), Analyze (javascript-typescript), License Compliance, CodeRabbit
|
||||||
|
@ -58,5 +58,5 @@ func shellCmdInner() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// none found
|
// none found
|
||||||
return "bin/bash\n"
|
return "/bin/bash\n"
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,8 @@ wsh editconfig
|
|||||||
| term:localshellopts | string[] | set to pass additional parameters to the term:localshellpath (example: `["-NoLogo"]` for PowerShell will remove the copyright notice) |
|
| term:localshellopts | string[] | set to pass additional parameters to the term:localshellpath (example: `["-NoLogo"]` for PowerShell will remove the copyright notice) |
|
||||||
| term:copyonselect | bool | set to false to disable terminal copy-on-select |
|
| term:copyonselect | bool | set to false to disable terminal copy-on-select |
|
||||||
| term:scrollback | int | size of terminal scrollback buffer, max is 10000 |
|
| term:scrollback | int | size of terminal scrollback buffer, max is 10000 |
|
||||||
|
| term:theme | string | preset name of terminal theme to apply by default (default is "default-dark") |
|
||||||
|
| term:transparency | float64 | set the background transparency of terminal theme (default 0.5, 0 = not transparent, 1.0 = fully transparent) |
|
||||||
| editor:minimapenabled | bool | set to false to disable editor minimap |
|
| editor:minimapenabled | bool | set to false to disable editor minimap |
|
||||||
| editor:stickyscrollenabled | bool | enables monaco editor's stickyScroll feature (pinning headers of current context, e.g. class names, method names, etc.), defaults to false |
|
| editor:stickyscrollenabled | bool | enables monaco editor's stickyScroll feature (pinning headers of current context, e.g. class names, method names, etc.), defaults to false |
|
||||||
| editor:wordwrap | bool | set to true to enable word wrapping in the editor (defaults to false) |
|
| editor:wordwrap | bool | set to true to enable word wrapping in the editor (defaults to false) |
|
||||||
@ -75,9 +77,11 @@ wsh editconfig
|
|||||||
| window:autohidetabbar | bool | show and hide the tab bar automatically when the mouse moves near the top of the window
|
| window:autohidetabbar | bool | show and hide the tab bar automatically when the mouse moves near the top of the window
|
||||||
| window:nativetitlebar | bool | set to use the OS-native title bar, rather than the overlay (Windows and Linux only, requires app restart) |
|
| window:nativetitlebar | bool | set to use the OS-native title bar, rather than the overlay (Windows and Linux only, requires app restart) |
|
||||||
| window:disablehardwareacceleration | bool | set to disable Chromium hardware acceleration to resolve graphical bugs (requires app restart) |
|
| window:disablehardwareacceleration | bool | set to disable Chromium hardware acceleration to resolve graphical bugs (requires app restart) |
|
||||||
|
| window:savelastwindow | bool | when `true`, the last window that is closed is preserved and is reopened the next time the app is launched (defaults to `true`) |
|
||||||
|
| window:confirmonclose | bool | when `true`, a prompt will ask a user to confirm that they want to close a window if it has an unsaved workspace with more than one tab (defaults to `true`) |
|
||||||
| telemetry:enabled | bool | set to enable/disable telemetry |
|
| telemetry:enabled | bool | set to enable/disable telemetry |
|
||||||
|
|
||||||
For reference this is the current default configuration (v0.9.3):
|
For reference, this is the current default configuration (v0.10.4):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@ -89,6 +93,7 @@ For reference this is the current default configuration (v0.9.3):
|
|||||||
"autoupdate:installonquit": true,
|
"autoupdate:installonquit": true,
|
||||||
"autoupdate:intervalms": 3600000,
|
"autoupdate:intervalms": 3600000,
|
||||||
"conn:askbeforewshinstall": true,
|
"conn:askbeforewshinstall": true,
|
||||||
|
"conn:wshenabled": true,
|
||||||
"editor:minimapenabled": true,
|
"editor:minimapenabled": true,
|
||||||
"web:defaulturl": "https://github.com/wavetermdev/waveterm",
|
"web:defaulturl": "https://github.com/wavetermdev/waveterm",
|
||||||
"web:defaultsearch": "https://www.google.com/search?q={query}",
|
"web:defaultsearch": "https://www.google.com/search?q={query}",
|
||||||
@ -99,6 +104,8 @@ For reference this is the current default configuration (v0.9.3):
|
|||||||
"window:magnifiedblocksize": 0.9,
|
"window:magnifiedblocksize": 0.9,
|
||||||
"window:magnifiedblockblurprimarypx": 10,
|
"window:magnifiedblockblurprimarypx": 10,
|
||||||
"window:magnifiedblockblursecondarypx": 2,
|
"window:magnifiedblockblursecondarypx": 2,
|
||||||
|
"window:confirmclose": true,
|
||||||
|
"window:savelastwindow": true,
|
||||||
"telemetry:enabled": true,
|
"telemetry:enabled": true,
|
||||||
"term:copyonselect": true
|
"term:copyonselect": true
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,14 @@ replace "Cmd" with "Alt" (note that "Ctrl" is "Ctrl" on both Mac, Windows, and L
|
|||||||
| ---------------- | ------------- |
|
| ---------------- | ------------- |
|
||||||
| <Kbd k="Cmd:l"/> | Clear AI Chat |
|
| <Kbd k="Cmd:l"/> | Clear AI Chat |
|
||||||
|
|
||||||
|
## Terminal Keybindings
|
||||||
|
|
||||||
|
| Key | Function |
|
||||||
|
| ----------------------- | -------------- |
|
||||||
|
| <Kbd k="Ctrl:Shift:c"/> | Copy |
|
||||||
|
| <Kbd k="Ctrl:Shift:v"/> | Paste |
|
||||||
|
| <Kbd k="Cmd:k"/> | Clear Terminal |
|
||||||
|
|
||||||
## Customizeable Systemwide Global Hotkey
|
## Customizeable Systemwide Global Hotkey
|
||||||
|
|
||||||
Wave allows setting a custom global hotkey to focus your most recent window from anywhere in your computer. For more information on this, see [the config docs](./config#customizable-systemwide-global-hotkey).
|
Wave allows setting a custom global hotkey to focus your most recent window from anywhere in your computer. For more information on this, see [the config docs](./config#customizable-systemwide-global-hotkey).
|
||||||
|
@ -6,6 +6,36 @@ sidebar_position: 200
|
|||||||
|
|
||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
|
### v0.10.4 — Dec 20, 2024
|
||||||
|
|
||||||
|
Quick update with bug fixes and new configuration options
|
||||||
|
|
||||||
|
- Added "window:confirmclose" and "window:savelastwindow" configuration options
|
||||||
|
- [bugfix] Fixed broken scroll bar in the AI widget
|
||||||
|
- [bugfix] Fixed default path for wsh shell detection (used in remote connections)
|
||||||
|
- Dependency updates
|
||||||
|
|
||||||
|
### v0.10.3 — Dec 19, 2024
|
||||||
|
|
||||||
|
Quick update to v0.10 with new features and bug fixes.
|
||||||
|
|
||||||
|
- Global hotkey support [docs](https://docs.waveterm.dev/config#customizable-systemwide-global-hotkey)
|
||||||
|
- Added configuration to override the font size for markdown, AI-chat, and preview editor [docs](https://docs.waveterm.dev/config)
|
||||||
|
- Added ability to set independent zoom level for the web view (right click block header)
|
||||||
|
- New `wsh wavepath` command to open the config directory, data directory, and log file
|
||||||
|
- [bugfix] Fixed crash when /etc/sshd_config contained an unsupported Match directive (most common on Fedora)
|
||||||
|
- [bugfix] Workspaces are now more consistent across windows, closes associated window when Workspaces are deleted
|
||||||
|
- [bugfix] Fixed zsh on WSL
|
||||||
|
- [bugfix] Fixed long-standing bug around control sequences sometimes showing up in terminal output when switching tabs
|
||||||
|
- Lots of new examples in the docs for shell overrides, presets, widgets, and connections
|
||||||
|
- Other bug fixes and UI updates
|
||||||
|
|
||||||
|
(note, v0.10.2 and v0.10.3's release notes have been merged together)
|
||||||
|
|
||||||
|
### v0.10.1 — Dec 12, 2024
|
||||||
|
|
||||||
|
Quick update to fix the workspace app menu actions. Also fixes workspace switching to always open a new window when invoked from a non-workspace window. This reduces the chance of losing a non-workspace window's tabs accidentally.
|
||||||
|
|
||||||
### v0.10.0 — Dec 11, 2024
|
### v0.10.0 — Dec 11, 2024
|
||||||
|
|
||||||
Wave Terminal v0.10.0 introduces workspaces, making it easier to manage multiple work environments. We've added powerful new command execution capabilities with `wsh run`, allowing you to launch and control commands in dedicated blocks. This release also brings significant improvements to SSH with a new connections configuration system for managing your remote environments.
|
Wave Terminal v0.10.0 introduces workspaces, making it easier to manage multiple work environments. We've added powerful new command execution capabilities with `wsh run`, allowing you to launch and control commands in dedicated blocks. This release also brings significant improvements to SSH with a new connections configuration system for managing your remote environments.
|
||||||
|
@ -38,9 +38,9 @@ function convertKey(platform: Platform, key: string): [any, string, boolean] {
|
|||||||
return ["⇧", "Shift", true];
|
return ["⇧", "Shift", true];
|
||||||
}
|
}
|
||||||
if (key == "Escape") {
|
if (key == "Escape") {
|
||||||
return ["Esc", null, false];
|
return ["Esc", "Escape", false];
|
||||||
}
|
}
|
||||||
return [key, null, false];
|
return [key.length > 1 ? key : key.toUpperCase(), key, false];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Custom KBD component
|
// Custom KBD component
|
||||||
@ -50,7 +50,7 @@ const KbdInternal = ({ k }: { k: string }) => {
|
|||||||
const keyElems = keys.map((key, i) => {
|
const keyElems = keys.map((key, i) => {
|
||||||
const [displayKey, title, symbol] = convertKey(platform, key);
|
const [displayKey, title, symbol] = convertKey(platform, key);
|
||||||
return (
|
return (
|
||||||
<kbd key={i} title={title} className={symbol ? "symbol" : null}>
|
<kbd key={i} title={title} aria-label={title} className={symbol ? "symbol" : null}>
|
||||||
{displayKey}
|
{displayKey}
|
||||||
</kbd>
|
</kbd>
|
||||||
);
|
);
|
||||||
|
@ -229,21 +229,26 @@ export class WaveBrowserWindow extends BaseWindow {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
fireAndForget(async () => {
|
fireAndForget(async () => {
|
||||||
const numWindows = waveWindowMap.size;
|
const numWindows = waveWindowMap.size;
|
||||||
if (numWindows > 1) {
|
const fullConfig = await FileService.GetFullConfig();
|
||||||
console.log("numWindows > 1", numWindows);
|
if (numWindows > 1 || !fullConfig.settings["window:savelastwindow"]) {
|
||||||
const workspace = await WorkspaceService.GetWorkspace(this.workspaceId);
|
console.log("numWindows > 1 or user does not want last window saved", numWindows);
|
||||||
console.log("workspace", workspace);
|
if (fullConfig.settings["window:confirmclose"]) {
|
||||||
if (isNonEmptyUnsavedWorkspace(workspace)) {
|
console.log("confirmclose", this.waveWindowId);
|
||||||
console.log("workspace has no name, icon, and multiple tabs", workspace);
|
const workspace = await WorkspaceService.GetWorkspace(this.workspaceId);
|
||||||
const choice = dialog.showMessageBoxSync(this, {
|
console.log("workspace", workspace);
|
||||||
type: "question",
|
if (isNonEmptyUnsavedWorkspace(workspace)) {
|
||||||
buttons: ["Cancel", "Close Window"],
|
console.log("workspace has no name, icon, and multiple tabs", workspace);
|
||||||
title: "Confirm",
|
const choice = dialog.showMessageBoxSync(this, {
|
||||||
message: "Window has unsaved tabs, closing window will delete existing tabs.\n\nContinue?",
|
type: "question",
|
||||||
});
|
buttons: ["Cancel", "Close Window"],
|
||||||
if (choice === 0) {
|
title: "Confirm",
|
||||||
console.log("user cancelled close window", this.waveWindowId);
|
message:
|
||||||
return;
|
"Window has unsaved tabs, closing window will delete existing tabs.\n\nContinue?",
|
||||||
|
});
|
||||||
|
if (choice === 0) {
|
||||||
|
console.log("user cancelled close window", this.waveWindowId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("deleteAllowed = true", this.waveWindowId);
|
console.log("deleteAllowed = true", this.waveWindowId);
|
||||||
@ -270,11 +275,6 @@ export class WaveBrowserWindow extends BaseWindow {
|
|||||||
this.destroy();
|
this.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const numWindows = waveWindowMap.size;
|
|
||||||
if (numWindows == 0) {
|
|
||||||
console.log("win no windows left", this.waveWindowId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.deleteAllowed) {
|
if (this.deleteAllowed) {
|
||||||
console.log("win removing window from backend DB", this.waveWindowId);
|
console.log("win removing window from backend DB", this.waveWindowId);
|
||||||
fireAndForget(() => WindowService.CloseWindow(this.waveWindowId, true));
|
fireAndForget(() => WindowService.CloseWindow(this.waveWindowId, true));
|
||||||
|
@ -24,6 +24,10 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*:last-child {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
.heading:not(.heading ~ .heading) {
|
.heading:not(.heading ~ .heading) {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
resolveSrcSet,
|
resolveSrcSet,
|
||||||
transformBlocks,
|
transformBlocks,
|
||||||
} from "@/app/element/markdown-util";
|
} from "@/app/element/markdown-util";
|
||||||
import { useAtomValueSafe } from "@/util/util";
|
import { boundNumber, useAtomValueSafe } from "@/util/util";
|
||||||
import { clsx } from "clsx";
|
import { clsx } from "clsx";
|
||||||
import { Atom } from "jotai";
|
import { Atom } from "jotai";
|
||||||
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
||||||
@ -380,10 +380,10 @@ const Markdown = ({
|
|||||||
|
|
||||||
const mergedStyle = { ...style };
|
const mergedStyle = { ...style };
|
||||||
if (fontSizeOverride != null) {
|
if (fontSizeOverride != null) {
|
||||||
mergedStyle["--markdown-font-size"] = `${fontSizeOverride}px`;
|
mergedStyle["--markdown-font-size"] = `${boundNumber(fontSizeOverride, 6, 64)}px`;
|
||||||
}
|
}
|
||||||
if (fixedFontSizeOverride != null) {
|
if (fixedFontSizeOverride != null) {
|
||||||
mergedStyle["--markdown-fixed-font-size"] = `${fixedFontSizeOverride}px`;
|
mergedStyle["--markdown-fixed-font-size"] = `${boundNumber(fixedFontSizeOverride, 6, 64)}px`;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className={clsx("markdown", className)} style={mergedStyle}>
|
<div className={clsx("markdown", className)} style={mergedStyle}>
|
||||||
|
43
frontend/app/element/search.scss
Normal file
43
frontend/app/element/search.scss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.search-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: var(--modal-bg-color);
|
||||||
|
border: 1px solid var(--accent-color);
|
||||||
|
border-radius: var(--modal-border-radius);
|
||||||
|
box-shadow: var(--modal-box-shadow);
|
||||||
|
color: var(--main-text-color);
|
||||||
|
padding: 5px 5px 5px 10px;
|
||||||
|
gap: 5px;
|
||||||
|
width: 50%;
|
||||||
|
max-width: 300px;
|
||||||
|
min-width: 200px;
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
border: none;
|
||||||
|
font-size: 14px;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-results {
|
||||||
|
font-size: 12px;
|
||||||
|
margin: auto 0;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
border-left: 1px solid var(--modal-border-color);
|
||||||
|
button {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
frontend/app/element/search.stories.tsx
Normal file
127
frontend/app/element/search.stories.tsx
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import type { Meta, StoryObj } from "@storybook/react";
|
||||||
|
import { useSetAtom } from "jotai";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { Popover } from "./popover";
|
||||||
|
import { Search, useSearch } from "./search";
|
||||||
|
|
||||||
|
const meta: Meta<typeof Search> = {
|
||||||
|
title: "Elements/Search",
|
||||||
|
component: Search,
|
||||||
|
args: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof Popover>;
|
||||||
|
|
||||||
|
export const DefaultSearch: Story = {
|
||||||
|
render: (args) => {
|
||||||
|
const props = useSearch();
|
||||||
|
const setIsOpen = useSetAtom(props.isOpenAtom);
|
||||||
|
useEffect(() => {
|
||||||
|
setIsOpen(true);
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="viewbox"
|
||||||
|
ref={props.anchorRef as React.RefObject<HTMLDivElement>}
|
||||||
|
style={{
|
||||||
|
border: "2px solid black",
|
||||||
|
width: "100%",
|
||||||
|
height: "200px",
|
||||||
|
background: "var(--main-bg-color)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Search {...args} {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
args: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Results10: Story = {
|
||||||
|
render: (args) => {
|
||||||
|
const props = useSearch();
|
||||||
|
const setIsOpen = useSetAtom(props.isOpenAtom);
|
||||||
|
const setNumResults = useSetAtom(props.numResultsAtom);
|
||||||
|
useEffect(() => {
|
||||||
|
setIsOpen(true);
|
||||||
|
setNumResults(10);
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="viewbox"
|
||||||
|
ref={props.anchorRef as React.RefObject<HTMLDivElement>}
|
||||||
|
style={{
|
||||||
|
border: "2px solid black",
|
||||||
|
width: "100%",
|
||||||
|
height: "200px",
|
||||||
|
background: "var(--main-bg-color)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Search {...args} {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
args: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InputAndResults10: Story = {
|
||||||
|
render: (args) => {
|
||||||
|
const props = useSearch();
|
||||||
|
const setIsOpen = useSetAtom(props.isOpenAtom);
|
||||||
|
const setNumResults = useSetAtom(props.numResultsAtom);
|
||||||
|
const setSearch = useSetAtom(props.searchAtom);
|
||||||
|
useEffect(() => {
|
||||||
|
setIsOpen(true);
|
||||||
|
setNumResults(10);
|
||||||
|
setSearch("search term");
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="viewbox"
|
||||||
|
ref={props.anchorRef as React.RefObject<HTMLDivElement>}
|
||||||
|
style={{
|
||||||
|
border: "2px solid black",
|
||||||
|
width: "100%",
|
||||||
|
height: "200px",
|
||||||
|
background: "var(--main-bg-color)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Search {...args} {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
args: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LongInputAndResults10: Story = {
|
||||||
|
render: (args) => {
|
||||||
|
const props = useSearch();
|
||||||
|
const setIsOpen = useSetAtom(props.isOpenAtom);
|
||||||
|
const setNumResults = useSetAtom(props.numResultsAtom);
|
||||||
|
const setSearch = useSetAtom(props.searchAtom);
|
||||||
|
useEffect(() => {
|
||||||
|
setIsOpen(true);
|
||||||
|
setNumResults(10);
|
||||||
|
setSearch("search term ".repeat(10).trimEnd());
|
||||||
|
}, []);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="viewbox"
|
||||||
|
ref={props.anchorRef as React.RefObject<HTMLDivElement>}
|
||||||
|
style={{
|
||||||
|
border: "2px solid black",
|
||||||
|
width: "100%",
|
||||||
|
height: "200px",
|
||||||
|
background: "var(--main-bg-color)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Search {...args} {...props} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
args: {},
|
||||||
|
};
|
115
frontend/app/element/search.tsx
Normal file
115
frontend/app/element/search.tsx
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { autoUpdate, FloatingPortal, Middleware, offset, useDismiss, useFloating } from "@floating-ui/react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import { atom, PrimitiveAtom, useAtom, useAtomValue } from "jotai";
|
||||||
|
import { memo, useCallback, useRef, useState } from "react";
|
||||||
|
import { IconButton } from "./iconbutton";
|
||||||
|
import { Input } from "./input";
|
||||||
|
import "./search.scss";
|
||||||
|
|
||||||
|
type SearchProps = {
|
||||||
|
searchAtom: PrimitiveAtom<string>;
|
||||||
|
indexAtom: PrimitiveAtom<number>;
|
||||||
|
numResultsAtom: PrimitiveAtom<number>;
|
||||||
|
isOpenAtom: PrimitiveAtom<boolean>;
|
||||||
|
anchorRef?: React.RefObject<HTMLElement>;
|
||||||
|
offsetX?: number;
|
||||||
|
offsetY?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const SearchComponent = ({
|
||||||
|
searchAtom,
|
||||||
|
indexAtom,
|
||||||
|
numResultsAtom,
|
||||||
|
isOpenAtom,
|
||||||
|
anchorRef,
|
||||||
|
offsetX = 10,
|
||||||
|
offsetY = 10,
|
||||||
|
}: SearchProps) => {
|
||||||
|
const [isOpen, setIsOpen] = useAtom(isOpenAtom);
|
||||||
|
const [search, setSearch] = useAtom(searchAtom);
|
||||||
|
const [index, setIndex] = useAtom(indexAtom);
|
||||||
|
const numResults = useAtomValue(numResultsAtom);
|
||||||
|
|
||||||
|
const handleOpenChange = useCallback((open: boolean) => {
|
||||||
|
setIsOpen(open);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const middleware: Middleware[] = [];
|
||||||
|
middleware.push(
|
||||||
|
offset(({ rects }) => ({
|
||||||
|
mainAxis: -rects.floating.height - offsetY,
|
||||||
|
crossAxis: -offsetX,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const { refs, floatingStyles, context } = useFloating({
|
||||||
|
placement: "top-end",
|
||||||
|
open: isOpen,
|
||||||
|
onOpenChange: handleOpenChange,
|
||||||
|
whileElementsMounted: autoUpdate,
|
||||||
|
middleware,
|
||||||
|
elements: {
|
||||||
|
reference: anchorRef!.current,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const dismiss = useDismiss(context);
|
||||||
|
|
||||||
|
const prevDecl: IconButtonDecl = {
|
||||||
|
elemtype: "iconbutton",
|
||||||
|
icon: "chevron-up",
|
||||||
|
title: "Previous Result",
|
||||||
|
disabled: index === 0,
|
||||||
|
click: () => setIndex(index - 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextDecl: IconButtonDecl = {
|
||||||
|
elemtype: "iconbutton",
|
||||||
|
icon: "chevron-down",
|
||||||
|
title: "Next Result",
|
||||||
|
disabled: !numResults || index === numResults - 1,
|
||||||
|
click: () => setIndex(index + 1),
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDecl: IconButtonDecl = {
|
||||||
|
elemtype: "iconbutton",
|
||||||
|
icon: "xmark-large",
|
||||||
|
title: "Close",
|
||||||
|
click: () => setIsOpen(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isOpen && (
|
||||||
|
<FloatingPortal>
|
||||||
|
<div className="search-container" style={{ ...floatingStyles }} {...dismiss} ref={refs.setFloating}>
|
||||||
|
<Input placeholder="Search" value={search} onChange={setSearch} />
|
||||||
|
<div
|
||||||
|
className={clsx("search-results", { hidden: numResults === 0 })}
|
||||||
|
aria-live="polite"
|
||||||
|
aria-label="Search Results"
|
||||||
|
>
|
||||||
|
{index + 1}/{numResults}
|
||||||
|
</div>
|
||||||
|
<div className="right-buttons">
|
||||||
|
<IconButton decl={prevDecl} />
|
||||||
|
<IconButton decl={nextDecl} />
|
||||||
|
<IconButton decl={closeDecl} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</FloatingPortal>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Search = memo(SearchComponent) as typeof SearchComponent;
|
||||||
|
|
||||||
|
export function useSearch(anchorRef?: React.RefObject<HTMLElement>): SearchProps {
|
||||||
|
const [searchAtom] = useState(atom(""));
|
||||||
|
const [indexAtom] = useState(atom(0));
|
||||||
|
const [numResultsAtom] = useState(atom(0));
|
||||||
|
const [isOpenAtom] = useState(atom(false));
|
||||||
|
anchorRef ??= useRef(null);
|
||||||
|
return { searchAtom, indexAtom, numResultsAtom, isOpenAtom, anchorRef };
|
||||||
|
}
|
@ -1,32 +0,0 @@
|
|||||||
// Copyright 2024, Command Line Inc.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
import type { Meta, StoryObj } from "@storybook/react";
|
|
||||||
import { SearchInput } from "./searchinput";
|
|
||||||
|
|
||||||
const meta: Meta<typeof SearchInput> = {
|
|
||||||
title: "Elements/SearchInput",
|
|
||||||
component: SearchInput,
|
|
||||||
argTypes: {
|
|
||||||
className: {
|
|
||||||
description: "Custom class for styling the input group",
|
|
||||||
control: { type: "text" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof SearchInput>;
|
|
||||||
|
|
||||||
export const DefaultSearchInput: Story = {
|
|
||||||
render: (args) => {
|
|
||||||
const handleSearch = () => {
|
|
||||||
console.log("Search triggered");
|
|
||||||
};
|
|
||||||
|
|
||||||
return <SearchInput />;
|
|
||||||
},
|
|
||||||
args: {
|
|
||||||
className: "custom-search-input",
|
|
||||||
},
|
|
||||||
};
|
|
@ -1,20 +0,0 @@
|
|||||||
// Copyright 2024, Command Line Inc.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
import { Button } from "./button";
|
|
||||||
import { Input, InputGroup, InputRightElement } from "./input";
|
|
||||||
|
|
||||||
const SearchInput = () => {
|
|
||||||
return (
|
|
||||||
<InputGroup className="search-input-group">
|
|
||||||
<Input placeholder="Search..." />
|
|
||||||
<InputRightElement>
|
|
||||||
<Button className="search-button ghost grey">
|
|
||||||
<i className="fa-sharp fa-solid fa-magnifying-glass"></i>
|
|
||||||
</Button>
|
|
||||||
</InputRightElement>
|
|
||||||
</InputGroup>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { SearchInput };
|
|
@ -230,6 +230,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
button {
|
button {
|
||||||
|
padding: 8px 20px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,6 +153,7 @@ const WorkspaceSwitcherItem = ({
|
|||||||
const isCurrentWorkspace = activeWorkspace.oid === workspace.oid;
|
const isCurrentWorkspace = activeWorkspace.oid === workspace.oid;
|
||||||
|
|
||||||
const setWorkspace = useCallback((newWorkspace: Workspace) => {
|
const setWorkspace = useCallback((newWorkspace: Workspace) => {
|
||||||
|
setWorkspaceEntry({ ...workspaceEntry, workspace: newWorkspace });
|
||||||
if (newWorkspace.name != "") {
|
if (newWorkspace.name != "") {
|
||||||
fireAndForget(() =>
|
fireAndForget(() =>
|
||||||
WorkspaceService.UpdateWorkspace(
|
WorkspaceService.UpdateWorkspace(
|
||||||
|
@ -83,8 +83,10 @@
|
|||||||
--modal-bg-color: #232323;
|
--modal-bg-color: #232323;
|
||||||
--modal-header-bottom-border-color: rgba(241, 246, 243, 0.15);
|
--modal-header-bottom-border-color: rgba(241, 246, 243, 0.15);
|
||||||
--modal-border-color: rgba(255, 255, 255, 0.12); /* toggle colors */
|
--modal-border-color: rgba(255, 255, 255, 0.12); /* toggle colors */
|
||||||
|
--modal-border-radius: 6px;
|
||||||
--toggle-bg-color: var(--border-color);
|
--toggle-bg-color: var(--border-color);
|
||||||
--modal-shadow-color: rgba(0, 0, 0, 0.8);
|
--modal-shadow-color: rgba(0, 0, 0, 0.8);
|
||||||
|
--modal-box-shadow: box-shadow: 0px 8px 24px 0px var(--modal-shadow-color);
|
||||||
|
|
||||||
--toggle-thumb-color: var(--main-text-color);
|
--toggle-thumb-color: var(--main-text-color);
|
||||||
--toggle-checked-bg-color: var(--accent-color);
|
--toggle-checked-bg-color: var(--accent-color);
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
import { useOverrideConfigAtom } from "@/app/store/global";
|
import { useOverrideConfigAtom } from "@/app/store/global";
|
||||||
|
import { boundNumber } from "@/util/util";
|
||||||
import loader from "@monaco-editor/loader";
|
import loader from "@monaco-editor/loader";
|
||||||
import { Editor, Monaco } from "@monaco-editor/react";
|
import { Editor, Monaco } from "@monaco-editor/react";
|
||||||
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
import type * as MonacoTypes from "monaco-editor/esm/vs/editor/editor.api";
|
||||||
@ -122,7 +123,7 @@ export function CodeEditor({ blockId, text, language, filename, meta, onChange,
|
|||||||
const minimapEnabled = useOverrideConfigAtom(blockId, "editor:minimapenabled") ?? false;
|
const minimapEnabled = useOverrideConfigAtom(blockId, "editor:minimapenabled") ?? false;
|
||||||
const stickyScrollEnabled = useOverrideConfigAtom(blockId, "editor:stickyscrollenabled") ?? false;
|
const stickyScrollEnabled = useOverrideConfigAtom(blockId, "editor:stickyscrollenabled") ?? false;
|
||||||
const wordWrap = useOverrideConfigAtom(blockId, "editor:wordwrap") ?? false;
|
const wordWrap = useOverrideConfigAtom(blockId, "editor:wordwrap") ?? false;
|
||||||
const fontSize = useOverrideConfigAtom(blockId, "editor:fontsize");
|
const fontSize = boundNumber(useOverrideConfigAtom(blockId, "editor:fontsize"), 6, 64);
|
||||||
const theme = "wave-theme-dark";
|
const theme = "wave-theme-dark";
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -24,6 +24,7 @@ import {
|
|||||||
} from "@/store/global";
|
} from "@/store/global";
|
||||||
import * as services from "@/store/services";
|
import * as services from "@/store/services";
|
||||||
import * as keyutil from "@/util/keyutil";
|
import * as keyutil from "@/util/keyutil";
|
||||||
|
import { boundNumber } from "@/util/util";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
import * as jotai from "jotai";
|
import * as jotai from "jotai";
|
||||||
@ -62,6 +63,7 @@ class TermViewModel implements ViewModel {
|
|||||||
vdomToolbarTarget: jotai.PrimitiveAtom<VDomTargetToolbar>;
|
vdomToolbarTarget: jotai.PrimitiveAtom<VDomTargetToolbar>;
|
||||||
fontSizeAtom: jotai.Atom<number>;
|
fontSizeAtom: jotai.Atom<number>;
|
||||||
termThemeNameAtom: jotai.Atom<string>;
|
termThemeNameAtom: jotai.Atom<string>;
|
||||||
|
termTransparencyAtom: jotai.Atom<number>;
|
||||||
noPadding: jotai.PrimitiveAtom<boolean>;
|
noPadding: jotai.PrimitiveAtom<boolean>;
|
||||||
endIconButtons: jotai.Atom<IconButtonDecl[]>;
|
endIconButtons: jotai.Atom<IconButtonDecl[]>;
|
||||||
shellProcFullStatus: jotai.PrimitiveAtom<BlockControllerRuntimeStatus>;
|
shellProcFullStatus: jotai.PrimitiveAtom<BlockControllerRuntimeStatus>;
|
||||||
@ -203,10 +205,17 @@ class TermViewModel implements ViewModel {
|
|||||||
return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme;
|
return get(getOverrideConfigAtom(this.blockId, "term:theme")) ?? DefaultTermTheme;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
this.termTransparencyAtom = useBlockAtom(blockId, "termtransparencyatom", () => {
|
||||||
|
return jotai.atom<number>((get) => {
|
||||||
|
let value = get(getOverrideConfigAtom(this.blockId, "term:transparency")) ?? 0.5;
|
||||||
|
return boundNumber(value, 0, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
this.blockBg = jotai.atom((get) => {
|
this.blockBg = jotai.atom((get) => {
|
||||||
const fullConfig = get(atoms.fullConfigAtom);
|
const fullConfig = get(atoms.fullConfigAtom);
|
||||||
const themeName = get(this.termThemeNameAtom);
|
const themeName = get(this.termThemeNameAtom);
|
||||||
const [_, bgcolor] = computeTheme(fullConfig, themeName);
|
const termTransparency = get(this.termTransparencyAtom);
|
||||||
|
const [_, bgcolor] = computeTheme(fullConfig, themeName, termTransparency);
|
||||||
if (bgcolor != null) {
|
if (bgcolor != null) {
|
||||||
return { bg: bgcolor };
|
return { bg: bgcolor };
|
||||||
}
|
}
|
||||||
@ -407,6 +416,11 @@ class TermViewModel implements ViewModel {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
return false;
|
return false;
|
||||||
|
} else if (keyutil.checkKeyPressed(waveEvent, "Cmd:k")) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
this.termRef.current?.terminal?.clear();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
const shellProcStatus = globalStore.get(this.shellProcStatus);
|
const shellProcStatus = globalStore.get(this.shellProcStatus);
|
||||||
if ((shellProcStatus == "done" || shellProcStatus == "init") && keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
if ((shellProcStatus == "done" || shellProcStatus == "init") && keyutil.checkKeyPressed(waveEvent, "Enter")) {
|
||||||
@ -453,6 +467,7 @@ class TermViewModel implements ViewModel {
|
|||||||
const termThemeKeys = Object.keys(termThemes);
|
const termThemeKeys = Object.keys(termThemes);
|
||||||
const curThemeName = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:theme"));
|
const curThemeName = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:theme"));
|
||||||
const defaultFontSize = globalStore.get(getSettingsKeyAtom("term:fontsize")) ?? 12;
|
const defaultFontSize = globalStore.get(getSettingsKeyAtom("term:fontsize")) ?? 12;
|
||||||
|
const transparencyMeta = globalStore.get(getBlockMetaKeyAtom(this.blockId, "term:transparency"));
|
||||||
const blockData = globalStore.get(this.blockAtom);
|
const blockData = globalStore.get(this.blockAtom);
|
||||||
const overrideFontSize = blockData?.meta?.["term:fontsize"];
|
const overrideFontSize = blockData?.meta?.["term:fontsize"];
|
||||||
|
|
||||||
@ -474,6 +489,41 @@ class TermViewModel implements ViewModel {
|
|||||||
checked: curThemeName == null,
|
checked: curThemeName == null,
|
||||||
click: () => this.setTerminalTheme(null),
|
click: () => this.setTerminalTheme(null),
|
||||||
});
|
});
|
||||||
|
const transparencySubMenu: ContextMenuItem[] = [];
|
||||||
|
transparencySubMenu.push({
|
||||||
|
label: "Default",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: transparencyMeta == null,
|
||||||
|
click: () => {
|
||||||
|
RpcApi.SetMetaCommand(TabRpcClient, {
|
||||||
|
oref: WOS.makeORef("block", this.blockId),
|
||||||
|
meta: { "term:transparency": null },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
transparencySubMenu.push({
|
||||||
|
label: "Transparent Background",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: transparencyMeta == 0.5,
|
||||||
|
click: () => {
|
||||||
|
RpcApi.SetMetaCommand(TabRpcClient, {
|
||||||
|
oref: WOS.makeORef("block", this.blockId),
|
||||||
|
meta: { "term:transparency": 0.5 },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
transparencySubMenu.push({
|
||||||
|
label: "No Transparency",
|
||||||
|
type: "checkbox",
|
||||||
|
checked: transparencyMeta == 0,
|
||||||
|
click: () => {
|
||||||
|
RpcApi.SetMetaCommand(TabRpcClient, {
|
||||||
|
oref: WOS.makeORef("block", this.blockId),
|
||||||
|
meta: { "term:transparency": 0 },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const fontSizeSubMenu: ContextMenuItem[] = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18].map(
|
const fontSizeSubMenu: ContextMenuItem[] = [6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18].map(
|
||||||
(fontSize: number) => {
|
(fontSize: number) => {
|
||||||
return {
|
return {
|
||||||
@ -508,6 +558,10 @@ class TermViewModel implements ViewModel {
|
|||||||
label: "Font Size",
|
label: "Font Size",
|
||||||
submenu: fontSizeSubMenu,
|
submenu: fontSizeSubMenu,
|
||||||
});
|
});
|
||||||
|
fullMenu.push({
|
||||||
|
label: "Transparency",
|
||||||
|
submenu: transparencySubMenu,
|
||||||
|
});
|
||||||
fullMenu.push({ type: "separator" });
|
fullMenu.push({ type: "separator" });
|
||||||
fullMenu.push({
|
fullMenu.push({
|
||||||
label: "Force Restart Controller",
|
label: "Force Restart Controller",
|
||||||
@ -734,7 +788,8 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const fullConfig = globalStore.get(atoms.fullConfigAtom);
|
const fullConfig = globalStore.get(atoms.fullConfigAtom);
|
||||||
const termThemeName = globalStore.get(model.termThemeNameAtom);
|
const termThemeName = globalStore.get(model.termThemeNameAtom);
|
||||||
const [termTheme, _] = computeTheme(fullConfig, termThemeName);
|
const termTransparency = globalStore.get(model.termTransparencyAtom);
|
||||||
|
const [termTheme, _] = computeTheme(fullConfig, termThemeName, termTransparency);
|
||||||
let termScrollback = 1000;
|
let termScrollback = 1000;
|
||||||
if (termSettings?.["term:scrollback"]) {
|
if (termSettings?.["term:scrollback"]) {
|
||||||
termScrollback = Math.floor(termSettings["term:scrollback"]);
|
termScrollback = Math.floor(termSettings["term:scrollback"]);
|
||||||
|
@ -17,7 +17,8 @@ interface TermThemeProps {
|
|||||||
const TermThemeUpdater = ({ blockId, model, termRef }: TermThemeProps) => {
|
const TermThemeUpdater = ({ blockId, model, termRef }: TermThemeProps) => {
|
||||||
const fullConfig = useAtomValue(atoms.fullConfigAtom);
|
const fullConfig = useAtomValue(atoms.fullConfigAtom);
|
||||||
const blockTermTheme = useAtomValue(model.termThemeNameAtom);
|
const blockTermTheme = useAtomValue(model.termThemeNameAtom);
|
||||||
const [theme, _] = computeTheme(fullConfig, blockTermTheme);
|
const transparency = useAtomValue(model.termTransparencyAtom);
|
||||||
|
const [theme, _] = computeTheme(fullConfig, blockTermTheme, transparency);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (termRef.current?.terminal) {
|
if (termRef.current?.terminal) {
|
||||||
termRef.current.terminal.options.theme = theme;
|
termRef.current.terminal.options.theme = theme;
|
||||||
|
@ -2,14 +2,32 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
export const DefaultTermTheme = "default-dark";
|
export const DefaultTermTheme = "default-dark";
|
||||||
|
import { colord } from "colord";
|
||||||
|
|
||||||
// returns (theme, bgcolor)
|
function applyTransparencyToColor(hexColor: string, transparency: number): string {
|
||||||
function computeTheme(fullConfig: FullConfigType, themeName: string): [TermThemeType, string] {
|
const alpha = 1 - transparency; // transparency is already 0-1
|
||||||
|
return colord(hexColor).alpha(alpha).toHex();
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns (theme, bgcolor, transparency (0 - 1.0))
|
||||||
|
function computeTheme(
|
||||||
|
fullConfig: FullConfigType,
|
||||||
|
themeName: string,
|
||||||
|
termTransparency: number
|
||||||
|
): [TermThemeType, string] {
|
||||||
let theme: TermThemeType = fullConfig?.termthemes?.[themeName];
|
let theme: TermThemeType = fullConfig?.termthemes?.[themeName];
|
||||||
if (theme == null) {
|
if (theme == null) {
|
||||||
theme = fullConfig?.termthemes?.[DefaultTermTheme] || ({} as any);
|
theme = fullConfig?.termthemes?.[DefaultTermTheme] || ({} as any);
|
||||||
}
|
}
|
||||||
const themeCopy = { ...theme };
|
const themeCopy = { ...theme };
|
||||||
|
if (termTransparency != null && termTransparency > 0) {
|
||||||
|
if (themeCopy.background) {
|
||||||
|
themeCopy.background = applyTransparencyToColor(themeCopy.background, termTransparency);
|
||||||
|
}
|
||||||
|
if (themeCopy.selectionBackground) {
|
||||||
|
themeCopy.selectionBackground = applyTransparencyToColor(themeCopy.selectionBackground, termTransparency);
|
||||||
|
}
|
||||||
|
}
|
||||||
let bgcolor = themeCopy.background;
|
let bgcolor = themeCopy.background;
|
||||||
themeCopy.background = "#00000000";
|
themeCopy.background = "#00000000";
|
||||||
return [themeCopy, bgcolor];
|
return [themeCopy, bgcolor];
|
||||||
|
@ -4,100 +4,99 @@
|
|||||||
.waveai {
|
.waveai {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.waveai-chat {
|
.waveai-chat {
|
||||||
flex-grow: 1;
|
flex: 1 1 auto;
|
||||||
> .scrollable {
|
overflow: hidden;
|
||||||
flex-flow: column nowrap;
|
.chat-window-container {
|
||||||
margin-bottom: 0;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
min-height: 100%;
|
margin-bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
.chat-window {
|
.chat-window {
|
||||||
|
flex-flow: column nowrap;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
// This is the filler that will push the chat messages to the bottom until the chat window is full
|
// This is the filler that will push the chat messages to the bottom until the chat window is full
|
||||||
.filler {
|
.filler {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.chat-msg-container {
|
.chat-msg-container {
|
||||||
display: flex;
|
|
||||||
gap: 8px;
|
|
||||||
.chat-msg {
|
|
||||||
margin: 10px 0;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
gap: 8px;
|
||||||
border-radius: 8px;
|
.chat-msg {
|
||||||
|
margin: 10px 0;
|
||||||
&.chat-msg-header {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
align-items: flex-start;
|
||||||
justify-content: flex-start;
|
border-radius: 8px;
|
||||||
|
|
||||||
.icon-box {
|
&.chat-msg-header {
|
||||||
padding-top: 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: rgb(from var(--highlight-bg-color) r g b / 0.05);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 6px;
|
flex-direction: column;
|
||||||
}
|
justify-content: flex-start;
|
||||||
}
|
|
||||||
|
|
||||||
&.chat-msg-assistant {
|
.icon-box {
|
||||||
color: var(--main-text-color);
|
padding-top: 0;
|
||||||
background-color: rgb(from var(--highlight-bg-color) r g b / 0.1);
|
border-radius: 4px;
|
||||||
margin-right: auto;
|
background-color: rgb(from var(--highlight-bg-color) r g b / 0.05);
|
||||||
padding: 10px;
|
display: flex;
|
||||||
max-width: 85%;
|
padding: 6px;
|
||||||
|
|
||||||
.markdown {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-break: break-word;
|
|
||||||
max-width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
&.chat-msg-user {
|
|
||||||
margin-left: auto;
|
|
||||||
padding: 10px;
|
|
||||||
max-width: 85%;
|
|
||||||
background-color: rgb(from var(--accent-color) r g b / 0.15);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.chat-msg-error {
|
&.chat-msg-assistant {
|
||||||
color: var(--main-text-color);
|
color: var(--main-text-color);
|
||||||
background-color: rgb(from var(--error-color) r g b / 0.25);
|
background-color: rgb(from var(--highlight-bg-color) r g b / 0.1);
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
max-width: 85%;
|
max-width: 85%;
|
||||||
|
|
||||||
.markdown {
|
.markdown {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
&.chat-msg-user {
|
||||||
|
margin-left: auto;
|
||||||
|
padding: 10px;
|
||||||
|
max-width: 85%;
|
||||||
|
background-color: rgb(from var(--accent-color) r g b / 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
&.typing-indicator {
|
&.chat-msg-error {
|
||||||
margin-top: 4px;
|
color: var(--main-text-color);
|
||||||
|
background-color: rgb(from var(--error-color) r g b / 0.25);
|
||||||
|
margin-right: auto;
|
||||||
|
padding: 10px;
|
||||||
|
max-width: 85%;
|
||||||
|
|
||||||
|
.markdown {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
max-width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.typing-indicator {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -105,6 +104,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.waveai-controls {
|
.waveai-controls {
|
||||||
|
flex: 0 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -13,9 +13,11 @@ import { BlockService, ObjectService } from "@/store/services";
|
|||||||
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
|
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
|
||||||
import { fireAndForget, isBlank, makeIconClass } from "@/util/util";
|
import { fireAndForget, isBlank, makeIconClass } from "@/util/util";
|
||||||
import { atom, Atom, PrimitiveAtom, useAtomValue, WritableAtom } from "jotai";
|
import { atom, Atom, PrimitiveAtom, useAtomValue, WritableAtom } from "jotai";
|
||||||
|
import { splitAtom } from "jotai/utils";
|
||||||
import type { OverlayScrollbars } from "overlayscrollbars";
|
import type { OverlayScrollbars } from "overlayscrollbars";
|
||||||
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
import { OverlayScrollbarsComponent, OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
||||||
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
||||||
|
import { debounce, throttle } from "throttle-debounce";
|
||||||
import "./waveai.scss";
|
import "./waveai.scss";
|
||||||
|
|
||||||
interface ChatMessageType {
|
interface ChatMessageType {
|
||||||
@ -29,7 +31,7 @@ const outline = "2px solid var(--accent-color)";
|
|||||||
const slidingWindowSize = 30;
|
const slidingWindowSize = 30;
|
||||||
|
|
||||||
interface ChatItemProps {
|
interface ChatItemProps {
|
||||||
chatItem: ChatMessageType;
|
chatItemAtom: Atom<ChatMessageType>;
|
||||||
model: WaveAiModel;
|
model: WaveAiModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +74,8 @@ export class WaveAiModel implements ViewModel {
|
|||||||
preIconButton?: Atom<IconButtonDecl>;
|
preIconButton?: Atom<IconButtonDecl>;
|
||||||
endIconButtons?: Atom<IconButtonDecl[]>;
|
endIconButtons?: Atom<IconButtonDecl[]>;
|
||||||
messagesAtom: PrimitiveAtom<Array<ChatMessageType>>;
|
messagesAtom: PrimitiveAtom<Array<ChatMessageType>>;
|
||||||
|
messagesSplitAtom: SplitAtom<Array<ChatMessageType>>;
|
||||||
|
latestMessageAtom: Atom<ChatMessageType>;
|
||||||
addMessageAtom: WritableAtom<unknown, [message: ChatMessageType], void>;
|
addMessageAtom: WritableAtom<unknown, [message: ChatMessageType], void>;
|
||||||
updateLastMessageAtom: WritableAtom<unknown, [text: string, isUpdating: boolean], void>;
|
updateLastMessageAtom: WritableAtom<unknown, [text: string, isUpdating: boolean], void>;
|
||||||
removeLastMessageAtom: WritableAtom<unknown, [], void>;
|
removeLastMessageAtom: WritableAtom<unknown, [], void>;
|
||||||
@ -92,6 +96,8 @@ export class WaveAiModel implements ViewModel {
|
|||||||
this.viewIcon = atom("sparkles");
|
this.viewIcon = atom("sparkles");
|
||||||
this.viewName = atom("Wave AI");
|
this.viewName = atom("Wave AI");
|
||||||
this.messagesAtom = atom([]);
|
this.messagesAtom = atom([]);
|
||||||
|
this.messagesSplitAtom = splitAtom(this.messagesAtom);
|
||||||
|
this.latestMessageAtom = atom((get) => get(this.messagesAtom).slice(-1)[0]);
|
||||||
this.presetKey = atom((get) => {
|
this.presetKey = atom((get) => {
|
||||||
const metaPresetKey = get(this.blockAtom).meta["ai:preset"];
|
const metaPresetKey = get(this.blockAtom).meta["ai:preset"];
|
||||||
const globalPresetKey = get(atoms.settingsAtom)["ai:preset"];
|
const globalPresetKey = get(atoms.settingsAtom)["ai:preset"];
|
||||||
@ -405,10 +411,8 @@ export class WaveAiModel implements ViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useWaveAi() {
|
useWaveAi() {
|
||||||
const messages = useAtomValue(this.messagesAtom);
|
|
||||||
return {
|
return {
|
||||||
messages,
|
sendMessage: this.sendMessage.bind(this) as (text: string) => void,
|
||||||
sendMessage: this.sendMessage.bind(this),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,10 +435,9 @@ function makeWaveAiViewModel(blockId: string): WaveAiModel {
|
|||||||
return waveAiModel;
|
return waveAiModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatItem = ({ chatItem, model }: ChatItemProps) => {
|
const ChatItem = ({ chatItemAtom, model }: ChatItemProps) => {
|
||||||
|
const chatItem = useAtomValue(chatItemAtom);
|
||||||
const { user, text } = chatItem;
|
const { user, text } = chatItem;
|
||||||
const cssVar = "--panel-bg-color";
|
|
||||||
const panelBgColor = getComputedStyle(document.documentElement).getPropertyValue(cssVar).trim();
|
|
||||||
const fontSize = useOverrideConfigAtom(model.blockId, "ai:fontsize");
|
const fontSize = useOverrideConfigAtom(model.blockId, "ai:fontsize");
|
||||||
const fixedFontSize = useOverrideConfigAtom(model.blockId, "ai:fixedfontsize");
|
const fixedFontSize = useOverrideConfigAtom(model.blockId, "ai:fixedfontsize");
|
||||||
const renderContent = useMemo(() => {
|
const renderContent = useMemo(() => {
|
||||||
@ -503,43 +506,65 @@ const ChatItem = ({ chatItem, model }: ChatItemProps) => {
|
|||||||
|
|
||||||
interface ChatWindowProps {
|
interface ChatWindowProps {
|
||||||
chatWindowRef: React.RefObject<HTMLDivElement>;
|
chatWindowRef: React.RefObject<HTMLDivElement>;
|
||||||
messages: ChatMessageType[];
|
|
||||||
msgWidths: Object;
|
msgWidths: Object;
|
||||||
model: WaveAiModel;
|
model: WaveAiModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatWindow = memo(
|
const ChatWindow = memo(
|
||||||
forwardRef<OverlayScrollbarsComponentRef, ChatWindowProps>(({ chatWindowRef, messages, msgWidths, model }, ref) => {
|
forwardRef<OverlayScrollbarsComponentRef, ChatWindowProps>(({ chatWindowRef, msgWidths, model }, ref) => {
|
||||||
const [isUserScrolling, setIsUserScrolling] = useState(false);
|
const isUserScrolling = useRef(false);
|
||||||
|
|
||||||
const osRef = useRef<OverlayScrollbarsComponentRef>(null);
|
const osRef = useRef<OverlayScrollbarsComponentRef>(null);
|
||||||
const prevMessagesLenRef = useRef(messages.length);
|
const splitMessages = useAtomValue(model.messagesSplitAtom) as Atom<ChatMessageType>[];
|
||||||
|
const latestMessage = useAtomValue(model.latestMessageAtom);
|
||||||
|
const prevMessagesLenRef = useRef(splitMessages.length);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => osRef.current as OverlayScrollbarsComponentRef);
|
useImperativeHandle(ref, () => osRef.current as OverlayScrollbarsComponentRef);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleNewMessage = useCallback(
|
||||||
if (osRef.current && osRef.current.osInstance()) {
|
throttle(100, (messagesLen: number) => {
|
||||||
const { viewport } = osRef.current.osInstance().elements();
|
if (osRef.current?.osInstance()) {
|
||||||
const curMessagesLen = messages.length;
|
console.log("handleNewMessage", messagesLen, isUserScrolling.current);
|
||||||
if (prevMessagesLenRef.current !== curMessagesLen || !isUserScrolling) {
|
const { viewport } = osRef.current.osInstance().elements();
|
||||||
setIsUserScrolling(false);
|
if (prevMessagesLenRef.current !== messagesLen || !isUserScrolling.current) {
|
||||||
viewport.scrollTo({
|
viewport.scrollTo({
|
||||||
behavior: "auto",
|
behavior: "auto",
|
||||||
top: chatWindowRef.current?.scrollHeight || 0,
|
top: chatWindowRef.current?.scrollHeight || 0,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
prevMessagesLenRef.current = messagesLen;
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
prevMessagesLenRef.current = curMessagesLen;
|
[]
|
||||||
}
|
);
|
||||||
}, [messages, isUserScrolling]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (osRef.current && osRef.current.osInstance()) {
|
handleNewMessage(splitMessages.length);
|
||||||
const { viewport } = osRef.current.osInstance().elements();
|
}, [splitMessages, latestMessage]);
|
||||||
|
|
||||||
const handleUserScroll = () => {
|
// Wait 300 ms after the user stops scrolling to determine if the user is within 300px of the bottom of the chat window.
|
||||||
setIsUserScrolling(true);
|
// If so, unset the user scrolling flag.
|
||||||
};
|
const determineUnsetScroll = useCallback(
|
||||||
|
debounce(300, () => {
|
||||||
|
const { viewport } = osRef.current.osInstance().elements();
|
||||||
|
if (viewport.scrollTop > chatWindowRef.current?.clientHeight - viewport.clientHeight - 100) {
|
||||||
|
isUserScrolling.current = false;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleUserScroll = useCallback(
|
||||||
|
throttle(100, () => {
|
||||||
|
isUserScrolling.current = true;
|
||||||
|
determineUnsetScroll();
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (osRef.current?.osInstance()) {
|
||||||
|
const { viewport } = osRef.current.osInstance().elements();
|
||||||
|
|
||||||
viewport.addEventListener("wheel", handleUserScroll, { passive: true });
|
viewport.addEventListener("wheel", handleUserScroll, { passive: true });
|
||||||
viewport.addEventListener("touchmove", handleUserScroll, { passive: true });
|
viewport.addEventListener("touchmove", handleUserScroll, { passive: true });
|
||||||
@ -571,14 +596,14 @@ const ChatWindow = memo(
|
|||||||
return (
|
return (
|
||||||
<OverlayScrollbarsComponent
|
<OverlayScrollbarsComponent
|
||||||
ref={osRef}
|
ref={osRef}
|
||||||
className="scrollable"
|
className="chat-window-container"
|
||||||
options={{ scrollbars: { autoHide: "leave" } }}
|
options={{ scrollbars: { autoHide: "leave" } }}
|
||||||
events={{ initialized: handleScrollbarInitialized, updated: handleScrollbarUpdated }}
|
events={{ initialized: handleScrollbarInitialized, updated: handleScrollbarUpdated }}
|
||||||
>
|
>
|
||||||
<div ref={chatWindowRef} className="chat-window" style={msgWidths}>
|
<div ref={chatWindowRef} className="chat-window" style={msgWidths}>
|
||||||
<div className="filler"></div>
|
<div className="filler"></div>
|
||||||
{messages.map((chitem, idx) => (
|
{splitMessages.map((chitem, idx) => (
|
||||||
<ChatItem key={idx} chatItem={chitem} model={model} />
|
<ChatItem key={idx} chatItemAtom={chitem} model={model} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
@ -652,7 +677,7 @@ const ChatInput = forwardRef<HTMLTextAreaElement, ChatInputProps>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
||||||
const { messages, sendMessage } = model.useWaveAi();
|
const { sendMessage } = model.useWaveAi();
|
||||||
const waveaiRef = useRef<HTMLDivElement>(null);
|
const waveaiRef = useRef<HTMLDivElement>(null);
|
||||||
const chatWindowRef = useRef<HTMLDivElement>(null);
|
const chatWindowRef = useRef<HTMLDivElement>(null);
|
||||||
const osRef = useRef<OverlayScrollbarsComponentRef>(null);
|
const osRef = useRef<OverlayScrollbarsComponentRef>(null);
|
||||||
@ -716,7 +741,7 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
|||||||
sendMessage(value);
|
sendMessage(value);
|
||||||
setValue("");
|
setValue("");
|
||||||
setSelectedBlockIdx(null);
|
setSelectedBlockIdx(null);
|
||||||
}, [messages, value]);
|
}, [value]);
|
||||||
|
|
||||||
const updateScrollTop = () => {
|
const updateScrollTop = () => {
|
||||||
const pres = chatWindowRef.current?.querySelectorAll("pre");
|
const pres = chatWindowRef.current?.querySelectorAll("pre");
|
||||||
@ -823,13 +848,7 @@ const WaveAi = ({ model }: { model: WaveAiModel; blockId: string }) => {
|
|||||||
return (
|
return (
|
||||||
<div ref={waveaiRef} className="waveai">
|
<div ref={waveaiRef} className="waveai">
|
||||||
<div className="waveai-chat">
|
<div className="waveai-chat">
|
||||||
<ChatWindow
|
<ChatWindow ref={osRef} chatWindowRef={chatWindowRef} msgWidths={msgWidths} model={model} />
|
||||||
ref={osRef}
|
|
||||||
chatWindowRef={chatWindowRef}
|
|
||||||
messages={messages}
|
|
||||||
msgWidths={msgWidths}
|
|
||||||
model={model}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="waveai-controls">
|
<div className="waveai-controls">
|
||||||
<div className="waveai-input-wrapper">
|
<div className="waveai-input-wrapper">
|
||||||
|
4
frontend/types/gotypes.d.ts
vendored
4
frontend/types/gotypes.d.ts
vendored
@ -491,6 +491,7 @@ declare global {
|
|||||||
"term:scrollback"?: number;
|
"term:scrollback"?: number;
|
||||||
"term:vdomblockid"?: string;
|
"term:vdomblockid"?: string;
|
||||||
"term:vdomtoolbarblockid"?: string;
|
"term:vdomtoolbarblockid"?: string;
|
||||||
|
"term:transparency"?: number;
|
||||||
"web:zoom"?: number;
|
"web:zoom"?: number;
|
||||||
"markdown:fontsize"?: number;
|
"markdown:fontsize"?: number;
|
||||||
"markdown:fixedfontsize"?: number;
|
"markdown:fixedfontsize"?: number;
|
||||||
@ -641,6 +642,7 @@ declare global {
|
|||||||
"term:localshellopts"?: string[];
|
"term:localshellopts"?: string[];
|
||||||
"term:scrollback"?: number;
|
"term:scrollback"?: number;
|
||||||
"term:copyonselect"?: boolean;
|
"term:copyonselect"?: boolean;
|
||||||
|
"term:transparency"?: number;
|
||||||
"editor:minimapenabled"?: boolean;
|
"editor:minimapenabled"?: boolean;
|
||||||
"editor:stickyscrollenabled"?: boolean;
|
"editor:stickyscrollenabled"?: boolean;
|
||||||
"editor:wordwrap"?: boolean;
|
"editor:wordwrap"?: boolean;
|
||||||
@ -678,6 +680,8 @@ declare global {
|
|||||||
"window:magnifiedblocksize"?: number;
|
"window:magnifiedblocksize"?: number;
|
||||||
"window:magnifiedblockblurprimarypx"?: number;
|
"window:magnifiedblockblurprimarypx"?: number;
|
||||||
"window:magnifiedblockblursecondarypx"?: number;
|
"window:magnifiedblockblursecondarypx"?: number;
|
||||||
|
"window:confirmclose"?: boolean;
|
||||||
|
"window:savelastwindow"?: boolean;
|
||||||
"telemetry:*"?: boolean;
|
"telemetry:*"?: boolean;
|
||||||
"telemetry:enabled"?: boolean;
|
"telemetry:enabled"?: boolean;
|
||||||
"conn:*"?: boolean;
|
"conn:*"?: boolean;
|
||||||
|
4
go.mod
4
go.mod
@ -45,9 +45,9 @@ require (
|
|||||||
github.com/ubuntu/decorate v0.0.0-20230125165522-2d5b0a9bb117 // indirect
|
github.com/ubuntu/decorate v0.0.0-20230125165522-2d5b0a9bb117 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
golang.org/x/net v0.29.0 // indirect
|
golang.org/x/net v0.33.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
replace github.com/kevinburke/ssh_config => github.com/wavetermdev/ssh_config v0.0.0-20241027232332-ed124367682d
|
replace github.com/kevinburke/ssh_config => github.com/wavetermdev/ssh_config v0.0.0-20241219203747-6409e4292f34
|
||||||
|
|
||||||
replace github.com/creack/pty => github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b
|
replace github.com/creack/pty => github.com/photostorm/pty v1.1.19-0.20230903182454-31354506054b
|
||||||
|
8
go.sum
8
go.sum
@ -90,16 +90,16 @@ github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b h1:wFBKF5k5xbJQU8bYgc
|
|||||||
github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b/go.mod h1:N1CYNinssZru+ikvYTgVbVeSi21thHUTCoJ9xMvWe+s=
|
github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b/go.mod h1:N1CYNinssZru+ikvYTgVbVeSi21thHUTCoJ9xMvWe+s=
|
||||||
github.com/wavetermdev/htmltoken v0.2.0 h1:sFVPPemlDv7/jg7n4Hx1AEF2m9MVAFjFpELWfhi/DlM=
|
github.com/wavetermdev/htmltoken v0.2.0 h1:sFVPPemlDv7/jg7n4Hx1AEF2m9MVAFjFpELWfhi/DlM=
|
||||||
github.com/wavetermdev/htmltoken v0.2.0/go.mod h1:5FM0XV6zNYiNza2iaTcFGj+hnMtgqumFHO31Z8euquk=
|
github.com/wavetermdev/htmltoken v0.2.0/go.mod h1:5FM0XV6zNYiNza2iaTcFGj+hnMtgqumFHO31Z8euquk=
|
||||||
github.com/wavetermdev/ssh_config v0.0.0-20241027232332-ed124367682d h1:ArHaUBaiQWUqBzM2G/oLlm3Be0kwUMDt9vTNOWIfOd0=
|
github.com/wavetermdev/ssh_config v0.0.0-20241219203747-6409e4292f34 h1:I8VZVTZEXhnzfN7jB9a7TZYpzNO48sCUWMRXHM9XWSA=
|
||||||
github.com/wavetermdev/ssh_config v0.0.0-20241027232332-ed124367682d/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
github.com/wavetermdev/ssh_config v0.0.0-20241219203747-6409e4292f34/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
|
||||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"productName": "Wave",
|
"productName": "Wave",
|
||||||
"description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
|
"description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"version": "0.10.2-beta.0",
|
"version": "0.10.4",
|
||||||
"homepage": "https://waveterm.dev",
|
"homepage": "https://waveterm.dev",
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "dev.commandline.waveterm"
|
"appId": "dev.commandline.waveterm"
|
||||||
|
@ -227,6 +227,9 @@ func (entry *CacheEntry) readAt(ctx context.Context, offset int64, size int64, r
|
|||||||
offset += truncateAmt
|
offset += truncateAmt
|
||||||
size -= truncateAmt
|
size -= truncateAmt
|
||||||
}
|
}
|
||||||
|
if size <= 0 {
|
||||||
|
return realDataOffset, nil, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
partMap := file.computePartMap(offset, size)
|
partMap := file.computePartMap(offset, size)
|
||||||
dataEntryMap, err := entry.loadDataPartsForRead(ctx, getPartIdxsFromMap(partMap))
|
dataEntryMap, err := entry.loadDataPartsForRead(ctx, getPartIdxsFromMap(partMap))
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
|
|
||||||
"github.com/kevinburke/ssh_config"
|
"github.com/kevinburke/ssh_config"
|
||||||
"github.com/skeema/knownhosts"
|
"github.com/skeema/knownhosts"
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/panichandler"
|
||||||
"github.com/wavetermdev/waveterm/pkg/trimquotes"
|
"github.com/wavetermdev/waveterm/pkg/trimquotes"
|
||||||
"github.com/wavetermdev/waveterm/pkg/userinput"
|
"github.com/wavetermdev/waveterm/pkg/userinput"
|
||||||
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
|
"github.com/wavetermdev/waveterm/pkg/util/shellutil"
|
||||||
@ -750,7 +751,13 @@ func combineSshKeywords(userProvidedOpts *wshrpc.ConnKeywords, configKeywords *w
|
|||||||
// note that a `var == "yes"` will default to false
|
// note that a `var == "yes"` will default to false
|
||||||
// but `var != "no"` will default to true
|
// but `var != "no"` will default to true
|
||||||
// when given unexpected strings
|
// when given unexpected strings
|
||||||
func findSshConfigKeywords(hostPattern string) (*wshrpc.ConnKeywords, error) {
|
func findSshConfigKeywords(hostPattern string) (connKeywords *wshrpc.ConnKeywords, outErr error) {
|
||||||
|
defer func() {
|
||||||
|
err := panichandler.PanicHandler("sshclient:find-ssh-config-keywords")
|
||||||
|
if err != nil {
|
||||||
|
outErr = err
|
||||||
|
}
|
||||||
|
}()
|
||||||
WaveSshConfigUserSettings().ReloadConfigs()
|
WaveSshConfigUserSettings().ReloadConfigs()
|
||||||
sshKeywords := &wshrpc.ConnKeywords{}
|
sshKeywords := &wshrpc.ConnKeywords{}
|
||||||
var err error
|
var err error
|
||||||
|
@ -45,10 +45,13 @@ func (svc *WorkspaceService) UpdateWorkspace_Meta() tsgenmeta.MethodMeta {
|
|||||||
|
|
||||||
func (svc *WorkspaceService) UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon string, color string, applyDefaults bool) (waveobj.UpdatesRtnType, error) {
|
func (svc *WorkspaceService) UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon string, color string, applyDefaults bool) (waveobj.UpdatesRtnType, error) {
|
||||||
ctx = waveobj.ContextWithUpdates(ctx)
|
ctx = waveobj.ContextWithUpdates(ctx)
|
||||||
_, err := wcore.UpdateWorkspace(ctx, workspaceId, name, icon, color, applyDefaults)
|
_, updated, err := wcore.UpdateWorkspace(ctx, workspaceId, name, icon, color, applyDefaults)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error updating workspace: %w", err)
|
return nil, fmt.Errorf("error updating workspace: %w", err)
|
||||||
}
|
}
|
||||||
|
if !updated {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
wps.Broker.Publish(wps.WaveEvent{
|
wps.Broker.Publish(wps.WaveEvent{
|
||||||
Event: wps.Event_WorkspaceUpdate})
|
Event: wps.Event_WorkspaceUpdate})
|
||||||
|
@ -215,7 +215,7 @@ func StartWslShellProc(ctx context.Context, termSize waveobj.TermSize, cmdStr st
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isZshShell(shellPath) {
|
if isZshShell(shellPath) {
|
||||||
shellOpts = append(shellOpts, fmt.Sprintf(`ZDOTDIR="%s/.waveterm/%s"`, homeDir, shellutil.ZshIntegrationDir))
|
shellOpts = append(shellOpts, fmt.Sprintf(`ZDOTDIR=%s/.waveterm/%s`, homeDir, shellutil.ZshIntegrationDir))
|
||||||
}
|
}
|
||||||
shellOpts = append(shellOpts, shellPath)
|
shellOpts = append(shellOpts, shellPath)
|
||||||
shellOpts = append(shellOpts, subShellOpts...)
|
shellOpts = append(shellOpts, subShellOpts...)
|
||||||
|
@ -93,6 +93,7 @@ const (
|
|||||||
MetaKey_TermScrollback = "term:scrollback"
|
MetaKey_TermScrollback = "term:scrollback"
|
||||||
MetaKey_TermVDomSubBlockId = "term:vdomblockid"
|
MetaKey_TermVDomSubBlockId = "term:vdomblockid"
|
||||||
MetaKey_TermVDomToolbarBlockId = "term:vdomtoolbarblockid"
|
MetaKey_TermVDomToolbarBlockId = "term:vdomtoolbarblockid"
|
||||||
|
MetaKey_TermTransparency = "term:transparency"
|
||||||
|
|
||||||
MetaKey_WebZoom = "web:zoom"
|
MetaKey_WebZoom = "web:zoom"
|
||||||
|
|
||||||
|
@ -94,6 +94,7 @@ type MetaTSType struct {
|
|||||||
TermScrollback *int `json:"term:scrollback,omitempty"`
|
TermScrollback *int `json:"term:scrollback,omitempty"`
|
||||||
TermVDomSubBlockId string `json:"term:vdomblockid,omitempty"`
|
TermVDomSubBlockId string `json:"term:vdomblockid,omitempty"`
|
||||||
TermVDomToolbarBlockId string `json:"term:vdomtoolbarblockid,omitempty"`
|
TermVDomToolbarBlockId string `json:"term:vdomtoolbarblockid,omitempty"`
|
||||||
|
TermTransparency *float64 `json:"term:transparency,omitempty"` // default 0.5
|
||||||
|
|
||||||
WebZoom float64 `json:"web:zoom,omitempty"`
|
WebZoom float64 `json:"web:zoom,omitempty"`
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"autoupdate:installonquit": true,
|
"autoupdate:installonquit": true,
|
||||||
"autoupdate:intervalms": 3600000,
|
"autoupdate:intervalms": 3600000,
|
||||||
"conn:askbeforewshinstall": true,
|
"conn:askbeforewshinstall": true,
|
||||||
"conn:wshenabled": true,
|
"conn:wshenabled": true,
|
||||||
"editor:minimapenabled": true,
|
"editor:minimapenabled": true,
|
||||||
"web:defaulturl": "https://github.com/wavetermdev/waveterm",
|
"web:defaulturl": "https://github.com/wavetermdev/waveterm",
|
||||||
"web:defaultsearch": "https://www.google.com/search?q={query}",
|
"web:defaultsearch": "https://www.google.com/search?q={query}",
|
||||||
@ -18,6 +18,8 @@
|
|||||||
"window:magnifiedblocksize": 0.9,
|
"window:magnifiedblocksize": 0.9,
|
||||||
"window:magnifiedblockblurprimarypx": 10,
|
"window:magnifiedblockblurprimarypx": 10,
|
||||||
"window:magnifiedblockblursecondarypx": 2,
|
"window:magnifiedblockblursecondarypx": 2,
|
||||||
|
"window:confirmclose": true,
|
||||||
|
"window:savelastwindow": true,
|
||||||
"telemetry:enabled": true,
|
"telemetry:enabled": true,
|
||||||
"term:copyonselect": true
|
"term:copyonselect": true
|
||||||
}
|
}
|
||||||
|
@ -22,12 +22,37 @@
|
|||||||
"cmdtext": "#f0f0f0",
|
"cmdtext": "#f0f0f0",
|
||||||
"foreground": "#c1c1c1",
|
"foreground": "#c1c1c1",
|
||||||
"selectionBackground": "",
|
"selectionBackground": "",
|
||||||
"background": "#00000077",
|
"background": "#000000",
|
||||||
"cursor": ""
|
"cursor": ""
|
||||||
},
|
},
|
||||||
|
"onedarkpro": {
|
||||||
|
"display:name": "One Dark Pro",
|
||||||
|
"display:order": 2,
|
||||||
|
"background": "#21252B",
|
||||||
|
"foreground": "#ABB2BF",
|
||||||
|
"cursor": "#D7DAE0",
|
||||||
|
"black": "#3F4451",
|
||||||
|
"red": "#E06C75",
|
||||||
|
"green": "#98C379",
|
||||||
|
"yellow": "#D18F52",
|
||||||
|
"blue": "#61AFEF",
|
||||||
|
"magenta": "#C678DD",
|
||||||
|
"cyan": "#42B3C2",
|
||||||
|
"white": "#D7DAE0",
|
||||||
|
"brightBlack": "#4F5666",
|
||||||
|
"brightRed": "#FF616E",
|
||||||
|
"brightGreen": "#A5E075",
|
||||||
|
"brightYellow": "#F0A45D",
|
||||||
|
"brightBlue": "#4DC4FF",
|
||||||
|
"brightMagenta": "#DE73FF",
|
||||||
|
"brightCyan": "#4CD1E0",
|
||||||
|
"brightWhite": "#E6E6E6",
|
||||||
|
"gray": "#495162",
|
||||||
|
"cmdtext": "#ABB2BF"
|
||||||
|
},
|
||||||
"dracula": {
|
"dracula": {
|
||||||
"display:name": "Dracula",
|
"display:name": "Dracula",
|
||||||
"display:order": 2,
|
"display:order": 3,
|
||||||
"black": "#21222C",
|
"black": "#21222C",
|
||||||
"red": "#FF5555",
|
"red": "#FF5555",
|
||||||
"green": "#50FA7B",
|
"green": "#50FA7B",
|
||||||
@ -47,13 +72,12 @@
|
|||||||
"gray": "#6272A4",
|
"gray": "#6272A4",
|
||||||
"cmdtext": "#F8F8F2",
|
"cmdtext": "#F8F8F2",
|
||||||
"foreground": "#F8F8F2",
|
"foreground": "#F8F8F2",
|
||||||
"selectionBackground": "#44475a",
|
|
||||||
"background": "#282a36",
|
"background": "#282a36",
|
||||||
"cursor": "#f8f8f2"
|
"cursor": "#f8f8f2"
|
||||||
},
|
},
|
||||||
"monokai": {
|
"monokai": {
|
||||||
"display:name": "Monokai",
|
"display:name": "Monokai",
|
||||||
"display:order": 3,
|
"display:order": 4,
|
||||||
"black": "#1B1D1E",
|
"black": "#1B1D1E",
|
||||||
"red": "#F92672",
|
"red": "#F92672",
|
||||||
"green": "#A6E22E",
|
"green": "#A6E22E",
|
||||||
@ -73,13 +97,12 @@
|
|||||||
"gray": "#75715E",
|
"gray": "#75715E",
|
||||||
"cmdtext": "#F8F8F2",
|
"cmdtext": "#F8F8F2",
|
||||||
"foreground": "#F8F8F2",
|
"foreground": "#F8F8F2",
|
||||||
"selectionBackground": "#49483E",
|
|
||||||
"background": "#272822",
|
"background": "#272822",
|
||||||
"cursor": "#F8F8F2"
|
"cursor": "#F8F8F2"
|
||||||
},
|
},
|
||||||
"campbell": {
|
"campbell": {
|
||||||
"display:name": "Campbell",
|
"display:name": "Campbell",
|
||||||
"display:order": 4,
|
"display:order": 5,
|
||||||
"black": "#0C0C0C",
|
"black": "#0C0C0C",
|
||||||
"red": "#C50F1F",
|
"red": "#C50F1F",
|
||||||
"green": "#13A10E",
|
"green": "#13A10E",
|
||||||
@ -99,13 +122,13 @@
|
|||||||
"gray": "#767676",
|
"gray": "#767676",
|
||||||
"cmdtext": "#CCCCCC",
|
"cmdtext": "#CCCCCC",
|
||||||
"foreground": "#CCCCCC",
|
"foreground": "#CCCCCC",
|
||||||
"selectionBackground": "#3A96DD",
|
"selectionBackground": "#3A96DD77",
|
||||||
"background": "#0C0C0C",
|
"background": "#0C0C0C",
|
||||||
"cursor": "#CCCCCC"
|
"cursor": "#CCCCCC"
|
||||||
},
|
},
|
||||||
"warmyellow": {
|
"warmyellow": {
|
||||||
"display:name": "Warm Yellow",
|
"display:name": "Warm Yellow",
|
||||||
"display:order": 4,
|
"display:order": 6,
|
||||||
"black": "#3C3228",
|
"black": "#3C3228",
|
||||||
"red": "#E67E22",
|
"red": "#E67E22",
|
||||||
"green": "#A5D6A7",
|
"green": "#A5D6A7",
|
||||||
@ -124,33 +147,32 @@
|
|||||||
"brightWhite": "#FFFFFF",
|
"brightWhite": "#FFFFFF",
|
||||||
"background": "#2B2620",
|
"background": "#2B2620",
|
||||||
"foreground": "#F2E6D4",
|
"foreground": "#F2E6D4",
|
||||||
"selectionBackground": "#B7950B",
|
"selectionBackground": "#B7950B77",
|
||||||
"cursor": "#F9D784"
|
"cursor": "#F9D784"
|
||||||
},
|
},
|
||||||
"onedarkpro": {
|
"rosepine": {
|
||||||
"display:name": "One Dark Pro",
|
"display:name": "Rose Pine",
|
||||||
"display:order": 1.5,
|
"display:order": 7,
|
||||||
"background": "#282C34",
|
"black": "#26233a",
|
||||||
"foreground": "#ABB2BF",
|
"red": "#eb6f92",
|
||||||
"cursor": "#D7DAE0",
|
"green": "#3e8fb0",
|
||||||
"selectionBackground": "#528BFF",
|
"yellow": "#f6c177",
|
||||||
"black": "#3F4451",
|
"blue": "#9ccfd8",
|
||||||
"red": "#E05561",
|
"magenta": "#c4a7e7",
|
||||||
"green": "#8CC265",
|
"cyan": "#ebbcba",
|
||||||
"yellow": "#D18F52",
|
"white": "#e0def4",
|
||||||
"blue": "#4AA5F0",
|
"brightBlack": "#908caa",
|
||||||
"magenta": "#C162DE",
|
"brightRed": "#ff8cab",
|
||||||
"cyan": "#42B3C2",
|
"brightGreen": "#9ccfb0",
|
||||||
"white": "#D7DAE0",
|
"brightYellow": "#ffd196",
|
||||||
"brightBlack": "#4F5666",
|
"brightBlue": "#bee6e0",
|
||||||
"brightRed": "#FF616E",
|
"brightMagenta": "#e2c4ff",
|
||||||
"brightGreen": "#A5E075",
|
"brightCyan": "#ffd1d0",
|
||||||
"brightYellow": "#F0A45D",
|
"brightWhite": "#fffaf3",
|
||||||
"brightBlue": "#4DC4FF",
|
"gray": "#908caa",
|
||||||
"brightMagenta": "#DE73FF",
|
"cmdtext": "#e0def4",
|
||||||
"brightCyan": "#4CD1E0",
|
"foreground": "#e0def4",
|
||||||
"brightWhite": "#E6E6E6",
|
"background": "#191724",
|
||||||
"gray": "#495162",
|
"cursor": "#524f67"
|
||||||
"cmdtext": "#ABB2BF"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ const (
|
|||||||
ConfigKey_TermLocalShellOpts = "term:localshellopts"
|
ConfigKey_TermLocalShellOpts = "term:localshellopts"
|
||||||
ConfigKey_TermScrollback = "term:scrollback"
|
ConfigKey_TermScrollback = "term:scrollback"
|
||||||
ConfigKey_TermCopyOnSelect = "term:copyonselect"
|
ConfigKey_TermCopyOnSelect = "term:copyonselect"
|
||||||
|
ConfigKey_TermTransparency = "term:transparency"
|
||||||
|
|
||||||
ConfigKey_EditorMinimapEnabled = "editor:minimapenabled"
|
ConfigKey_EditorMinimapEnabled = "editor:minimapenabled"
|
||||||
ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled"
|
ConfigKey_EditorStickyScrollEnabled = "editor:stickyscrollenabled"
|
||||||
@ -79,6 +80,8 @@ const (
|
|||||||
ConfigKey_WindowMagnifiedBlockSize = "window:magnifiedblocksize"
|
ConfigKey_WindowMagnifiedBlockSize = "window:magnifiedblocksize"
|
||||||
ConfigKey_WindowMagnifiedBlockBlurPrimaryPx = "window:magnifiedblockblurprimarypx"
|
ConfigKey_WindowMagnifiedBlockBlurPrimaryPx = "window:magnifiedblockblurprimarypx"
|
||||||
ConfigKey_WindowMagnifiedBlockBlurSecondaryPx = "window:magnifiedblockblursecondarypx"
|
ConfigKey_WindowMagnifiedBlockBlurSecondaryPx = "window:magnifiedblockblursecondarypx"
|
||||||
|
ConfigKey_WindowConfirmClose = "window:confirmclose"
|
||||||
|
ConfigKey_WindowSaveLastWindow = "window:savelastwindow"
|
||||||
|
|
||||||
ConfigKey_TelemetryClear = "telemetry:*"
|
ConfigKey_TelemetryClear = "telemetry:*"
|
||||||
ConfigKey_TelemetryEnabled = "telemetry:enabled"
|
ConfigKey_TelemetryEnabled = "telemetry:enabled"
|
||||||
|
@ -60,6 +60,7 @@ type SettingsType struct {
|
|||||||
TermLocalShellOpts []string `json:"term:localshellopts,omitempty"`
|
TermLocalShellOpts []string `json:"term:localshellopts,omitempty"`
|
||||||
TermScrollback *int64 `json:"term:scrollback,omitempty"`
|
TermScrollback *int64 `json:"term:scrollback,omitempty"`
|
||||||
TermCopyOnSelect *bool `json:"term:copyonselect,omitempty"`
|
TermCopyOnSelect *bool `json:"term:copyonselect,omitempty"`
|
||||||
|
TermTransparency *float64 `json:"term:transparency,omitempty"`
|
||||||
|
|
||||||
EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"`
|
EditorMinimapEnabled bool `json:"editor:minimapenabled,omitempty"`
|
||||||
EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"`
|
EditorStickyScrollEnabled bool `json:"editor:stickyscrollenabled,omitempty"`
|
||||||
@ -106,6 +107,8 @@ type SettingsType struct {
|
|||||||
WindowMagnifiedBlockSize *float64 `json:"window:magnifiedblocksize,omitempty"`
|
WindowMagnifiedBlockSize *float64 `json:"window:magnifiedblocksize,omitempty"`
|
||||||
WindowMagnifiedBlockBlurPrimaryPx *int64 `json:"window:magnifiedblockblurprimarypx,omitempty"`
|
WindowMagnifiedBlockBlurPrimaryPx *int64 `json:"window:magnifiedblockblurprimarypx,omitempty"`
|
||||||
WindowMagnifiedBlockBlurSecondaryPx *int64 `json:"window:magnifiedblockblursecondarypx,omitempty"`
|
WindowMagnifiedBlockBlurSecondaryPx *int64 `json:"window:magnifiedblockblursecondarypx,omitempty"`
|
||||||
|
WindowConfirmClose bool `json:"window:confirmclose,omitempty"`
|
||||||
|
WindowSaveLastWindow bool `json:"window:savelastwindow,omitempty"`
|
||||||
|
|
||||||
TelemetryClear bool `json:"telemetry:*,omitempty"`
|
TelemetryClear bool `json:"telemetry:*,omitempty"`
|
||||||
TelemetryEnabled bool `json:"telemetry:enabled,omitempty"`
|
TelemetryEnabled bool `json:"telemetry:enabled,omitempty"`
|
||||||
|
@ -25,6 +25,7 @@ func EnsureInitialData() error {
|
|||||||
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
|
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
defer cancelFn()
|
defer cancelFn()
|
||||||
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
|
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
|
||||||
|
firstLaunch := false
|
||||||
if err == wstore.ErrNotFound {
|
if err == wstore.ErrNotFound {
|
||||||
client, err = CreateClient(ctx)
|
client, err = CreateClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -34,6 +35,7 @@ func EnsureInitialData() error {
|
|||||||
if migrateErr != nil {
|
if migrateErr != nil {
|
||||||
log.Printf("error migrating old history: %v\n", migrateErr)
|
log.Printf("error migrating old history: %v\n", migrateErr)
|
||||||
}
|
}
|
||||||
|
firstLaunch = true
|
||||||
}
|
}
|
||||||
if client.TempOID == "" {
|
if client.TempOID == "" {
|
||||||
log.Println("client.TempOID is empty")
|
log.Println("client.TempOID is empty")
|
||||||
@ -53,12 +55,16 @@ func EnsureInitialData() error {
|
|||||||
log.Println("client has windows")
|
log.Println("client has windows")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
log.Println("client has no windows, creating starter workspace")
|
wsId := ""
|
||||||
starterWs, err := CreateWorkspace(ctx, "Starter workspace", "custom@wave-logo-solid", "#58C142", false, true)
|
if firstLaunch {
|
||||||
if err != nil {
|
log.Println("client has no windows and first launch, creating starter workspace")
|
||||||
return fmt.Errorf("error creating starter workspace: %w", err)
|
starterWs, err := CreateWorkspace(ctx, "Starter workspace", "custom@wave-logo-solid", "#58C142", false, true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error creating starter workspace: %w", err)
|
||||||
|
}
|
||||||
|
wsId = starterWs.OID
|
||||||
}
|
}
|
||||||
_, err = CreateWindow(ctx, nil, starterWs.OID)
|
_, err = CreateWindow(ctx, nil, wsId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error creating window: %w", err)
|
return fmt.Errorf("error creating window: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -68,26 +68,34 @@ func CreateWorkspace(ctx context.Context, name string, icon string, color string
|
|||||||
wps.Broker.Publish(wps.WaveEvent{
|
wps.Broker.Publish(wps.WaveEvent{
|
||||||
Event: wps.Event_WorkspaceUpdate})
|
Event: wps.Event_WorkspaceUpdate})
|
||||||
|
|
||||||
return UpdateWorkspace(ctx, ws.OID, name, icon, color, applyDefaults)
|
ws, _, err = UpdateWorkspace(ctx, ws.OID, name, icon, color, applyDefaults)
|
||||||
|
return ws, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon string, color string, applyDefaults bool) (*waveobj.Workspace, error) {
|
// Returns updated workspace, whether it was updated, error.
|
||||||
|
func UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon string, color string, applyDefaults bool) (*waveobj.Workspace, bool, error) {
|
||||||
ws, err := GetWorkspace(ctx, workspaceId)
|
ws, err := GetWorkspace(ctx, workspaceId)
|
||||||
|
updated := false
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("workspace %s not found: %w", workspaceId, err)
|
return nil, updated, fmt.Errorf("workspace %s not found: %w", workspaceId, err)
|
||||||
}
|
}
|
||||||
if name != "" {
|
if name != "" {
|
||||||
ws.Name = name
|
ws.Name = name
|
||||||
|
updated = true
|
||||||
} else if applyDefaults && ws.Name == "" {
|
} else if applyDefaults && ws.Name == "" {
|
||||||
ws.Name = fmt.Sprintf("New Workspace (%s)", ws.OID[0:5])
|
ws.Name = fmt.Sprintf("New Workspace (%s)", ws.OID[0:5])
|
||||||
|
updated = true
|
||||||
}
|
}
|
||||||
if icon != "" {
|
if icon != "" {
|
||||||
ws.Icon = icon
|
ws.Icon = icon
|
||||||
|
updated = true
|
||||||
} else if applyDefaults && ws.Icon == "" {
|
} else if applyDefaults && ws.Icon == "" {
|
||||||
ws.Icon = WorkspaceIcons[0]
|
ws.Icon = WorkspaceIcons[0]
|
||||||
|
updated = true
|
||||||
}
|
}
|
||||||
if color != "" {
|
if color != "" {
|
||||||
ws.Color = color
|
ws.Color = color
|
||||||
|
updated = true
|
||||||
} else if applyDefaults && ws.Color == "" {
|
} else if applyDefaults && ws.Color == "" {
|
||||||
wsList, err := ListWorkspaces(ctx)
|
wsList, err := ListWorkspaces(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -95,9 +103,12 @@ func UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon
|
|||||||
wsList = waveobj.WorkspaceList{}
|
wsList = waveobj.WorkspaceList{}
|
||||||
}
|
}
|
||||||
ws.Color = WorkspaceColors[len(wsList)%len(WorkspaceColors)]
|
ws.Color = WorkspaceColors[len(wsList)%len(WorkspaceColors)]
|
||||||
|
updated = true
|
||||||
}
|
}
|
||||||
wstore.DBUpdate(ctx, ws)
|
if updated {
|
||||||
return ws, nil
|
wstore.DBUpdate(ctx, ws)
|
||||||
|
}
|
||||||
|
return ws, updated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If force is true, it will delete even if workspace is named.
|
// If force is true, it will delete even if workspace is named.
|
||||||
|
@ -342,7 +342,10 @@ func (w *WshRpc) runServer() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if msg.IsRpcRequest() {
|
if msg.IsRpcRequest() {
|
||||||
go w.handleRequest(&msg)
|
go func() {
|
||||||
|
defer panichandler.PanicHandler("handleRequest:goroutine")
|
||||||
|
w.handleRequest(&msg)
|
||||||
|
}()
|
||||||
} else {
|
} else {
|
||||||
respCh := w.getResponseCh(msg.ResId)
|
respCh := w.getResponseCh(msg.ResId)
|
||||||
if respCh == nil {
|
if respCh == nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user