merge main into branch

This commit is contained in:
Sylvia Crowe 2024-12-16 12:28:00 -08:00
commit 8d19e0de73
26 changed files with 283 additions and 200 deletions

View File

@ -69,7 +69,6 @@ jobs:
corepack enable
yarn install
timeout_minutes: 5
retry_on: error
max_attempts: 3
- name: Install Task
uses: arduino/setup-task@v2

View File

@ -53,7 +53,6 @@ jobs:
corepack enable
yarn install
timeout_minutes: 5
retry_on: error
max_attempts: 3
- name: Install Task
uses: arduino/setup-task@v2

View File

@ -290,7 +290,7 @@ tasks:
vars:
UP_VERSION: '{{ replace "v" "" (index .MATCH 0)}}'
cmd: |
wingetcreate update {{.WINGET_PACKAGE}} -s -v {{.UP_VERSION}} -u "https://{{.RELEASES_BUCKET}}/{{.APP_NAME}}-win32-x64-{{.UP_VERSION}}.msi" -t $env:GITHUB_TOKEN
wingetcreate update {{.WINGET_PACKAGE}} -s -v {{.UP_VERSION}} -u "https://{{.RELEASES_BUCKET}}/{{.APP_NAME}}-win32-x64-{{.UP_VERSION}}.msi" -t {{.GITHUB_TOKEN}}
dev:installwsh:
desc: quick shortcut to rebuild wsh and install for macos arm64

View File

@ -25,6 +25,8 @@ Wave Terminal v0.10.0 introduces workspaces, making it easier to manage multiple
- [bugfix] Corrected WaveAI text area resize behavior
- [bugfix] Fixed concurrent block controller start issues
- [bugfix] Fixed Preview Blocks for uninitialized connections
- [bugfix] Fixed unresponsive context menus
- [bugfix] Fixed connection errors in Help block
- Upgraded Go toolchain to 1.23.4
- Lots of new documentation, including new pages for [Getting Started](https://docs.waveterm.dev/gettingstarted), [AI Presets](https://docs.waveterm.dev/ai-presets), and [wsh overview](https://docs.waveterm.dev/wsh).
- Other bug fixes, performance improvements, and dependency updates
@ -226,7 +228,7 @@ Minor cleanup release.
### v0.8.0 — Sep 20, 2024
**Major New Relase of Wave Terminal**
**Major New Release of Wave Terminal**
The new build is a fresh start, and a clean break from the current version. As such, your history, settings, and configuration will not be carried over. If you'd like to continue to run the legacy version, you will need to download it separately.

View File

@ -58,7 +58,7 @@ type WindowActionQueueEntry =
workspaceId: string;
};
function showCloseConfirmDialog(workspace: Workspace): boolean {
function isNonEmptyUnsavedWorkspace(workspace: Workspace): boolean {
return !workspace.name && !workspace.icon && (workspace.tabids?.length > 1 || workspace.pinnedtabids?.length > 1);
}
@ -233,7 +233,7 @@ export class WaveBrowserWindow extends BaseWindow {
console.log("numWindows > 1", numWindows);
const workspace = await WorkspaceService.GetWorkspace(this.workspaceId);
console.log("workspace", workspace);
if (showCloseConfirmDialog(workspace)) {
if (isNonEmptyUnsavedWorkspace(workspace)) {
console.log("workspace has no name, icon, and multiple tabs", workspace);
const choice = dialog.showMessageBoxSync(this, {
type: "question",
@ -301,30 +301,14 @@ export class WaveBrowserWindow extends BaseWindow {
// If the workspace is already owned by a window, then we can just call SwitchWorkspace without first prompting the user, since it'll just focus to the other window.
const workspaceList = await WorkspaceService.ListWorkspaces();
if (!workspaceList.find((wse) => wse.workspaceid === workspaceId)?.windowid) {
if (!workspaceList?.find((wse) => wse.workspaceid === workspaceId)?.windowid) {
const curWorkspace = await WorkspaceService.GetWorkspace(this.workspaceId);
if (showCloseConfirmDialog(curWorkspace)) {
const choice = dialog.showMessageBoxSync(this, {
type: "question",
buttons: ["Cancel", "Open in New Window", "Switch Workspace"],
title: "Confirm",
message: "Window has unsaved tabs, switching workspaces will delete existing tabs.\n\nContinue?",
});
if (choice === 0) {
console.log("user cancelled switch workspace", this.waveWindowId);
if (isNonEmptyUnsavedWorkspace(curWorkspace)) {
console.log(
`existing unsaved workspace ${this.workspaceId} has content, opening workspace ${workspaceId} in new window`
);
await createWindowForWorkspace(workspaceId);
return;
} else if (choice === 1) {
console.log("user chose open in new window", this.waveWindowId);
const newWin = await WindowService.CreateWindow(null, workspaceId);
if (!newWin) {
console.log("error creating new window", this.waveWindowId);
}
const newBwin = await createBrowserWindow(newWin, await FileService.GetFullConfig(), {
unamePlatform,
});
newBwin.show();
return;
}
}
}
await this._queueActionInternal({ op: "switchworkspace", workspaceId });
@ -605,6 +589,17 @@ export function getAllWaveWindows(): WaveBrowserWindow[] {
return Array.from(waveWindowMap.values());
}
export async function createWindowForWorkspace(workspaceId: string) {
const newWin = await WindowService.CreateWindow(null, workspaceId);
if (!newWin) {
console.log("error creating new window", this.waveWindowId);
}
const newBwin = await createBrowserWindow(newWin, await FileService.GetFullConfig(), {
unamePlatform,
});
newBwin.show();
}
// note, this does not *show* the window.
// to show, await win.readyPromise and then win.show()
export async function createBrowserWindow(
@ -667,12 +662,13 @@ ipcMain.on("switch-workspace", (event, workspaceId) => {
});
export async function createWorkspace(window: WaveBrowserWindow) {
if (!window) {
return;
}
const newWsId = await WorkspaceService.CreateWorkspace();
const newWsId = await WorkspaceService.CreateWorkspace("", "", "", true);
if (newWsId) {
if (window) {
await window.switchWorkspace(newWsId);
} else {
await createWindowForWorkspace(newWsId);
}
}
}

View File

@ -42,15 +42,13 @@ async function getWorkspaceMenu(ww?: WaveBrowserWindow): Promise<Electron.MenuIt
console.log("workspaceList:", workspaceList);
const workspaceMenu: Electron.MenuItemConstructorOptions[] = [
{
label: "Create New Workspace",
click: (_, window) => {
fireAndForget(() => createWorkspace((window as WaveBrowserWindow) ?? ww));
},
label: "Create Workspace",
click: (_, window) => fireAndForget(() => createWorkspace((window as WaveBrowserWindow) ?? ww)),
},
];
function getWorkspaceSwitchAccelerator(i: number): string {
if (i < 9) {
return unamePlatform == "darwin" ? `Command+Control+${i}` : `Alt+Control+${i + 1}`;
return unamePlatform == "darwin" ? `Command+Control+${i + 1}` : `Alt+Control+${i + 1}`;
}
}
workspaceList?.length &&
@ -58,7 +56,7 @@ async function getWorkspaceMenu(ww?: WaveBrowserWindow): Promise<Electron.MenuIt
{ type: "separator" },
...workspaceList.map<Electron.MenuItemConstructorOptions>((workspace, i) => {
return {
label: `Switch to ${workspace.workspacedata.name}`,
label: `${workspace.workspacedata.name}`,
click: (_, window) => {
((window as WaveBrowserWindow) ?? ww)?.switchWorkspace(workspace.workspacedata.oid);
},

View File

@ -1,7 +1,13 @@
.iconbutton {
display: flex;
cursor: pointer;
opacity: 0.7;
align-items: center;
background: none;
border: none;
padding: 0;
font: inherit;
outline: inherit;
&.bulb {
color: var(--bulb-color);

View File

@ -4,16 +4,18 @@
import { useLongClick } from "@/app/hook/useLongClick";
import { makeIconClass } from "@/util/util";
import clsx from "clsx";
import { memo, useRef } from "react";
import { forwardRef, memo, useRef } from "react";
import "./iconbutton.scss";
export const IconButton = memo(({ decl, className }: { decl: IconButtonDecl; className?: string }) => {
const buttonRef = useRef<HTMLDivElement>(null);
type IconButtonProps = { decl: IconButtonDecl; className?: string };
export const IconButton = memo(
forwardRef<HTMLButtonElement, IconButtonProps>(({ decl, className }, ref) => {
ref = ref ?? useRef<HTMLButtonElement>(null);
const spin = decl.iconSpin ?? false;
useLongClick(buttonRef, decl.click, decl.longClick, decl.disabled);
useLongClick(ref, decl.click, decl.longClick, decl.disabled);
return (
<div
ref={buttonRef}
<button
ref={ref}
className={clsx("iconbutton", className, decl.className, {
disabled: decl.disabled,
"no-action": decl.noAction,
@ -22,6 +24,7 @@ export const IconButton = memo(({ decl, className }: { decl: IconButtonDecl; cla
style={{ color: decl.iconColor ?? "inherit" }}
>
{typeof decl.icon === "string" ? <i className={makeIconClass(decl.icon, true, { spin })} /> : decl.icon}
</div>
</button>
);
});
})
);

View File

@ -173,7 +173,7 @@ class WorkspaceServiceType {
return WOS.callBackendService("workspace", "ChangeTabPinning", Array.from(arguments))
}
// @returns object updates
// @returns CloseTabRtn (and object updates)
CloseTab(workspaceId: string, tabId: string, fromElectron: boolean): Promise<CloseTabRtnType> {
return WOS.callBackendService("workspace", "CloseTab", Array.from(arguments))
}
@ -184,7 +184,7 @@ class WorkspaceServiceType {
}
// @returns workspaceId
CreateWorkspace(): Promise<string> {
CreateWorkspace(name: string, icon: string, color: string, applyDefaults: boolean): Promise<string> {
return WOS.callBackendService("workspace", "CreateWorkspace", Array.from(arguments))
}
@ -192,6 +192,18 @@ class WorkspaceServiceType {
DeleteWorkspace(workspaceId: string): Promise<void> {
return WOS.callBackendService("workspace", "DeleteWorkspace", Array.from(arguments))
}
// @returns colors
GetColors(): Promise<string[]> {
return WOS.callBackendService("workspace", "GetColors", Array.from(arguments))
}
// @returns icons
GetIcons(): Promise<string[]> {
return WOS.callBackendService("workspace", "GetIcons", Array.from(arguments))
}
// @returns workspace
GetWorkspace(workspaceId: string): Promise<Workspace> {
return WOS.callBackendService("workspace", "GetWorkspace", Array.from(arguments))
}
@ -208,6 +220,11 @@ class WorkspaceServiceType {
UpdateTabIds(workspaceId: string, tabIds: string[], pinnedTabIds: string[]): Promise<void> {
return WOS.callBackendService("workspace", "UpdateTabIds", Array.from(arguments))
}
// @returns object updates
UpdateWorkspace(workspaceId: string, name: string, icon: string, color: string, applyDefaults: boolean): Promise<void> {
return WOS.callBackendService("workspace", "UpdateWorkspace", Array.from(arguments))
}
}
export const WorkspaceService = new WorkspaceServiceType();

View File

@ -23,6 +23,7 @@
}
.tab-bar-wrapper {
padding-top: 6px;
position: relative;
user-select: none;
display: flex;
@ -30,7 +31,7 @@
width: 100vw;
-webkit-app-region: drag;
* {
button {
-webkit-app-region: no-drag;
}
@ -40,11 +41,11 @@
}
.tab-bar {
margin-top: 6px;
position: relative; // Needed for absolute positioning of child tabs
display: flex;
flex-direction: row;
height: 27px;
-webkit-app-region: no-drag;
}
.pinned-tab-spacer {
@ -61,7 +62,7 @@
display: flex;
align-items: center;
justify-content: center;
padding: 6px 6px 0 0;
padding: 0 6px 0 0;
}
.app-menu-button {
@ -77,34 +78,24 @@
color: var(--accent-color);
}
.tab-bar-right {
display: flex;
flex-direction: row;
gap: 6px;
&:not(:empty) {
margin-left: auto;
margin-right: 6px;
}
}
.config-error-button {
height: 80%;
margin: auto 4px;
color: black;
padding: 0 6px;
flex: 0 0 fit-content;
}
.add-tab-btn {
width: 22px;
height: 100%;
cursor: pointer;
font-size: 14px;
text-align: center;
user-select: none;
display: flex;
align-items: center;
justify-content: center;
opacity: 0.5;
&:hover {
opacity: 1;
}
i {
overflow: hidden;
margin-top: 5px;
font-size: 11px;
}
.add-tab {
padding: 0 10px;
}
.window-drag {

View File

@ -11,6 +11,7 @@ import { useAtomValue } from "jotai";
import { OverlayScrollbars } from "overlayscrollbars";
import { createRef, memo, useCallback, useEffect, useRef, useState } from "react";
import { debounce } from "throttle-debounce";
import { IconButton } from "../element/iconbutton";
import { WorkspaceService } from "../store/services";
import { Tab } from "./tab";
import "./tabbar.scss";
@ -147,7 +148,7 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
const tabBarRef = useRef<HTMLDivElement>(null);
const tabsWrapperRef = useRef<HTMLDivElement>(null);
const tabRefs = useRef<React.RefObject<HTMLDivElement>[]>([]);
const addBtnRef = useRef<HTMLDivElement>(null);
const addBtnRef = useRef<HTMLButtonElement>(null);
const draggingRemovedRef = useRef(false);
const draggingTabDataRef = useRef({
tabId: "",
@ -160,14 +161,13 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
dragged: false,
});
const osInstanceRef = useRef<OverlayScrollbars>(null);
const draggerRightRef = useRef<HTMLDivElement>(null);
const draggerLeftRef = useRef<HTMLDivElement>(null);
const workspaceSwitcherRef = useRef<HTMLDivElement>(null);
const devLabelRef = useRef<HTMLDivElement>(null);
const appMenuButtonRef = useRef<HTMLDivElement>(null);
const tabWidthRef = useRef<number>(TAB_DEFAULT_WIDTH);
const scrollableRef = useRef<boolean>(false);
const updateStatusBannerRef = useRef<HTMLDivElement>(null);
const updateStatusBannerRef = useRef<HTMLButtonElement>(null);
const configErrorButtonRef = useRef<HTMLElement>(null);
const prevAllLoadedRef = useRef<boolean>(false);
const activeTabId = useAtomValue(atoms.staticTabId);
@ -226,7 +226,6 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
const tabbarWrapperWidth = tabbarWrapperRef.current.getBoundingClientRect().width;
const windowDragLeftWidth = draggerLeftRef.current.getBoundingClientRect().width;
const windowDragRightWidth = draggerRightRef.current.getBoundingClientRect().width;
const addBtnWidth = addBtnRef.current.getBoundingClientRect().width;
const updateStatusLabelWidth = updateStatusBannerRef.current?.getBoundingClientRect().width ?? 0;
const configErrorWidth = configErrorButtonRef.current?.getBoundingClientRect().width ?? 0;
@ -236,7 +235,6 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
const nonTabElementsWidth =
windowDragLeftWidth +
windowDragRightWidth +
addBtnWidth +
updateStatusLabelWidth +
configErrorWidth +
@ -648,6 +646,13 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
<i className="fa fa-ellipsis" />
</div>
) : undefined;
const addtabButtonDecl: IconButtonDecl = {
elemtype: "iconbutton",
icon: "plus",
click: handleAddTab,
title: "Add Tab",
};
return (
<div ref={tabbarWrapperRef} className="tab-bar-wrapper">
<WindowDrag ref={draggerLeftRef} className="left" />
@ -680,13 +685,12 @@ const TabBar = memo(({ workspace }: TabBarProps) => {
})}
</div>
</div>
<div ref={addBtnRef} className="add-tab-btn" onClick={handleAddTab}>
<i className="fa fa-solid fa-plus fa-fw" />
</div>
<WindowDrag ref={draggerRightRef} className="right" />
<IconButton className="add-tab" ref={addBtnRef} decl={addtabButtonDecl} />
<div className="tab-bar-right">
<UpdateStatusBanner ref={updateStatusBannerRef} />
<ConfigErrorIcon buttonRef={configErrorButtonRef} />
</div>
</div>
);
});

View File

@ -1,15 +1,10 @@
.update-available-banner {
display: flex;
height: 100%;
.button {
.button {
color: black;
height: 80%;
margin: auto 4px;
background-color: var(--accent-color);
flex: 0 0 fit-content;
line-height: unset !important;
padding: 0 6px;
&:disabled {
opacity: unset !important;
}
}
}

View File

@ -4,7 +4,7 @@ import { useAtomValue } from "jotai";
import { forwardRef, memo, useEffect, useState } from "react";
import "./updatebanner.scss";
const UpdateStatusBannerComponent = forwardRef<HTMLDivElement>((_, ref) => {
const UpdateStatusBannerComponent = forwardRef<HTMLButtonElement>((_, ref) => {
const appUpdateStatus = useAtomValue(atoms.updaterStatusAtom);
let [updateStatusMessage, setUpdateStatusMessage] = useState<string>();
const [dismissBannerTimeout, setDismissBannerTimeout] = useState<NodeJS.Timeout>();
@ -54,16 +54,15 @@ const UpdateStatusBannerComponent = forwardRef<HTMLDivElement>((_, ref) => {
}
if (updateStatusMessage) {
return (
<div className="update-available-banner" ref={ref}>
<Button
className="update-available-button"
className="update-available-banner"
title={appUpdateStatus === "ready" ? "Click to Install Update" : updateStatusMessage}
onClick={onClick}
disabled={appUpdateStatus !== "ready"}
ref={ref}
>
{updateStatusMessage}
</Button>
</div>
);
}
});

View File

@ -9,7 +9,6 @@
align-items: center;
gap: 12px;
border-radius: 6px;
margin-top: 6px;
margin-right: 13px;
box-sizing: border-box;
background-color: rgb(from var(--main-text-color) r g b / 0.1) !important;

View File

@ -17,7 +17,7 @@ import clsx from "clsx";
import { atom, PrimitiveAtom, useAtom, useAtomValue, useSetAtom } from "jotai";
import { splitAtom } from "jotai/utils";
import { OverlayScrollbarsComponent } from "overlayscrollbars-react";
import { CSSProperties, forwardRef, memo, useCallback, useEffect, useRef } from "react";
import { CSSProperties, forwardRef, memo, useCallback, useEffect, useRef, useState } from "react";
import WorkspaceSVG from "../asset/workspace.svg";
import { IconButton } from "../element/iconbutton";
import { atoms, getApi } from "../store/global";
@ -32,33 +32,6 @@ interface ColorSelectorProps {
className?: string;
}
const colors = [
"#58C142", // Green (accent)
"#00FFDB", // Teal
"#429DFF", // Blue
"#BF55EC", // Purple
"#FF453A", // Red
"#FF9500", // Orange
"#FFE900", // Yellow
];
const icons = [
"custom@wave-logo-solid",
"triangle",
"star",
"heart",
"bolt",
"solid@cloud",
"moon",
"layer-group",
"rocket",
"flask",
"paperclip",
"chart-line",
"graduation-cap",
"mug-hot",
];
const ColorSelector = memo(({ colors, selectedColor, onSelect, className }: ColorSelectorProps) => {
const handleColorClick = (color: string) => {
onSelect(color);
@ -129,6 +102,18 @@ const WorkspaceEditor = memo(
}: WorkspaceEditorProps) => {
const inputRef = useRef<HTMLInputElement>(null);
const [colors, setColors] = useState<string[]>([]);
const [icons, setIcons] = useState<string[]>([]);
useEffect(() => {
fireAndForget(async () => {
const colors = await WorkspaceService.GetColors();
const icons = await WorkspaceService.GetIcons();
setColors(colors);
setIcons(icons);
});
}, []);
useEffect(() => {
if (focusInput && inputRef.current) {
inputRef.current.focus();
@ -210,20 +195,11 @@ const WorkspaceSwitcher = forwardRef<HTMLDivElement>((_, ref) => {
);
const saveWorkspace = () => {
setObjectValue(
{
...activeWorkspace,
name: `New Workspace (${activeWorkspace.oid.slice(0, 5)})`,
icon: icons[0],
color: colors[0],
},
undefined,
true
);
setTimeout(() => {
fireAndForget(updateWorkspaceList);
}, 10);
fireAndForget(async () => {
await WorkspaceService.UpdateWorkspace(activeWorkspace.oid, "", "", "", true);
await updateWorkspaceList();
setEditingWorkspace(activeWorkspace.oid);
});
};
return (

View File

@ -54,6 +54,7 @@ class HelpViewModel extends WebViewModel {
const strippedDocsiteUrl = getApi().getDocsiteUrl().replace(baseUrlRegex, "");
const strippedCurUrl = url.replace(baseUrlRegex, "").replace(strippedDocsiteUrl, "");
const newUrl = docsiteWebUrl + strippedCurUrl;
console.log("modify-external-url", url, newUrl);
return newUrl;
};
}
@ -97,7 +98,6 @@ function HelpView({ model }: { model: HelpViewModel }) {
// Correct the base URL of the current page, if necessary
const newBaseUrl = baseUrlRegex.exec(newDocsiteUrl)?.[0];
const curBaseUrl = baseUrlRegex.exec(url)?.[0];
console.log("fix-docsite-url", url, newDocsiteUrl, homepageUrl, curBaseUrl, newBaseUrl);
if (curBaseUrl && newBaseUrl && curBaseUrl !== newBaseUrl) {
model.loadUrl(url.replace(curBaseUrl, newBaseUrl), "fix-fail-load");
}

View File

@ -142,6 +142,7 @@ export class WebViewModel implements ViewModel {
icon: "arrow-up-right-from-square",
title: "Open in External Browser",
click: () => {
console.log("open external", url);
if (url != null && url != "") {
const externalUrl = this.modifyExternalUrl?.(url) ?? url;
return getApi().openExternal(externalUrl);
@ -339,14 +340,14 @@ export class WebViewModel implements ViewModel {
const searchTemplate = globalStore.get(defaultSearchAtom);
const nextUrl = this.ensureUrlScheme(newUrl, searchTemplate);
console.log("webview loadUrl", reason, nextUrl, "cur=", this.webviewRef?.current.getURL());
if (newUrl != nextUrl) {
globalStore.set(this.url, nextUrl);
}
if (!this.webviewRef.current) {
return;
}
if (this.webviewRef.current.getURL() != nextUrl) {
this.webviewRef.current.loadURL(nextUrl);
fireAndForget(() => this.webviewRef.current.loadURL(nextUrl));
}
if (newUrl != nextUrl) {
globalStore.set(this.url, nextUrl);
}
}
@ -531,8 +532,8 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
return;
}
const navigateListener = (e: any) => {
if (e.isMainFrame) {
setErrorText("");
if (e.isMainFrame) {
model.handleNavigate(e.url);
}
};
@ -582,6 +583,7 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
model.setMediaPlaying(false);
};
webview.addEventListener("did-frame-navigate", navigateListener);
webview.addEventListener("did-navigate-in-page", navigateListener);
webview.addEventListener("did-navigate", navigateListener);
webview.addEventListener("did-start-loading", startLoadingHandler);
@ -596,6 +598,7 @@ const WebView = memo(({ model, onFailLoad }: WebViewProps) => {
// Clean up event listeners on component unmount
return () => {
webview.removeEventListener("did-frame-navigate", navigateListener);
webview.removeEventListener("did-navigate", navigateListener);
webview.removeEventListener("did-navigate-in-page", navigateListener);
webview.removeEventListener("new-window", newWindowHandler);

6
go.mod
View File

@ -23,9 +23,9 @@ require (
github.com/spf13/cobra v1.8.1
github.com/ubuntu/gowsl v0.0.0-20240906163211-049fd49bd93b
github.com/wavetermdev/htmltoken v0.2.0
golang.org/x/crypto v0.29.0
golang.org/x/sys v0.27.0
golang.org/x/term v0.26.0
golang.org/x/crypto v0.31.0
golang.org/x/sys v0.28.0
golang.org/x/term v0.27.0
)
require (

12
go.sum
View File

@ -94,8 +94,8 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
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/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
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/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -105,10 +105,10 @@ golang.org/x/sys v0.0.0-20220721230656-c6bc011c0c49/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View File

@ -7,7 +7,7 @@
"productName": "Wave",
"description": "Open-Source AI-Native Terminal Built for Seamless Workflows",
"license": "Apache-2.0",
"version": "0.10.0-beta.6",
"version": "0.10.1",
"homepage": "https://waveterm.dev",
"build": {
"appId": "dev.commandline.waveterm"

View File

@ -145,7 +145,7 @@ func (svc *ObjectService) UpdateObjectMeta(uiContext waveobj.UIContext, orefStr
}
err = wstore.UpdateObjectMeta(ctx, *oref, meta, false)
if err != nil {
return nil, fmt.Errorf("error updateing %q meta: %w", orefStr, err)
return nil, fmt.Errorf("error updating %q meta: %w", orefStr, err)
}
return waveobj.ContextGetUpdatesRtn(ctx), nil
}

View File

@ -24,21 +24,44 @@ type WorkspaceService struct{}
func (svc *WorkspaceService) CreateWorkspace_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ArgNames: []string{"ctx", "name", "icon", "color", "applyDefaults"},
ReturnDesc: "workspaceId",
}
}
func (svc *WorkspaceService) CreateWorkspace(ctx context.Context) (string, error) {
newWS, err := wcore.CreateWorkspace(ctx, "", "", "", false)
func (svc *WorkspaceService) CreateWorkspace(ctx context.Context, name string, icon string, color string, applyDefaults bool) (string, error) {
newWS, err := wcore.CreateWorkspace(ctx, name, icon, color, applyDefaults, false)
if err != nil {
return "", fmt.Errorf("error creating workspace: %w", err)
}
return newWS.OID, nil
}
func (svc *WorkspaceService) UpdateWorkspace_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ArgNames: []string{"ctx", "workspaceId", "name", "icon", "color", "applyDefaults"},
}
}
func (svc *WorkspaceService) UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon string, color string, applyDefaults bool) (waveobj.UpdatesRtnType, error) {
ctx = waveobj.ContextWithUpdates(ctx)
_, err := wcore.UpdateWorkspace(ctx, workspaceId, name, icon, color, applyDefaults)
if err != nil {
return nil, fmt.Errorf("error updating workspace: %w", err)
}
updates := waveobj.ContextGetUpdatesRtn(ctx)
go func() {
defer panichandler.PanicHandler("WorkspaceService:UpdateWorkspace:SendUpdateEvents")
wps.Broker.SendUpdateEvents(updates)
}()
return updates, nil
}
func (svc *WorkspaceService) GetWorkspace_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ArgNames: []string{"workspaceId"},
ReturnDesc: "workspace",
}
}
@ -77,7 +100,7 @@ func (svc *WorkspaceService) DeleteWorkspace(workspaceId string) (waveobj.Update
return updates, nil
}
func (svg *WorkspaceService) ListWorkspaces() (waveobj.WorkspaceList, error) {
func (svc *WorkspaceService) ListWorkspaces() (waveobj.WorkspaceList, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
return wcore.ListWorkspaces(ctx)
@ -90,6 +113,26 @@ func (svc *WorkspaceService) CreateTab_Meta() tsgenmeta.MethodMeta {
}
}
func (svc *WorkspaceService) GetColors_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ReturnDesc: "colors",
}
}
func (svc *WorkspaceService) GetColors() []string {
return wcore.WorkspaceColors[:]
}
func (svc *WorkspaceService) GetIcons_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ReturnDesc: "icons",
}
}
func (svc *WorkspaceService) GetIcons() []string {
return wcore.WorkspaceIcons[:]
}
func (svc *WorkspaceService) CreateTab(workspaceId string, tabName string, activateTab bool, pinned bool) (string, waveobj.UpdatesRtnType, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
@ -189,6 +232,7 @@ type CloseTabRtnType struct {
func (svc *WorkspaceService) CloseTab_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ArgNames: []string{"ctx", "workspaceId", "tabId", "fromElectron"},
ReturnDesc: "CloseTabRtn",
}
}

View File

@ -54,7 +54,7 @@ func EnsureInitialData() error {
return nil
}
log.Println("client has no windows, creating starter workspace")
starterWs, err := CreateWorkspace(ctx, "Starter workspace", "custom@wave-logo-solid", "#58C142", true)
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)
}

View File

@ -78,7 +78,7 @@ func CreateWindow(ctx context.Context, winSize *waveobj.WinSize, workspaceId str
log.Printf("CreateWindow %v %v\n", winSize, workspaceId)
var ws *waveobj.Workspace
if workspaceId == "" {
ws1, err := CreateWorkspace(ctx, "", "", "", false)
ws1, err := CreateWorkspace(ctx, "", "", "", false, false)
if err != nil {
return nil, fmt.Errorf("error creating workspace: %w", err)
}

View File

@ -20,14 +20,41 @@ import (
"github.com/wavetermdev/waveterm/pkg/wstore"
)
func CreateWorkspace(ctx context.Context, name string, icon string, color string, isInitialLaunch bool) (*waveobj.Workspace, error) {
var WorkspaceColors = [...]string{
"#58C142", // Green (accent)
"#00FFDB", // Teal
"#429DFF", // Blue
"#BF55EC", // Purple
"#FF453A", // Red
"#FF9500", // Orange
"#FFE900", // Yellow
}
var WorkspaceIcons = [...]string{
"custom@wave-logo-solid",
"triangle",
"star",
"heart",
"bolt",
"solid@cloud",
"moon",
"layer-group",
"rocket",
"flask",
"paperclip",
"chart-line",
"graduation-cap",
"mug-hot",
}
func CreateWorkspace(ctx context.Context, name string, icon string, color string, applyDefaults bool, isInitialLaunch bool) (*waveobj.Workspace, error) {
ws := &waveobj.Workspace{
OID: uuid.NewString(),
TabIds: []string{},
PinnedTabIds: []string{},
Name: name,
Icon: icon,
Color: color,
Name: "",
Icon: "",
Color: "",
}
err := wstore.DBInsert(ctx, ws)
if err != nil {
@ -41,10 +68,35 @@ func CreateWorkspace(ctx context.Context, name string, icon string, color string
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_WorkspaceUpdate})
ws, err = GetWorkspace(ctx, ws.OID)
return UpdateWorkspace(ctx, ws.OID, name, icon, color, applyDefaults)
}
func UpdateWorkspace(ctx context.Context, workspaceId string, name string, icon string, color string, applyDefaults bool) (*waveobj.Workspace, error) {
ws, err := GetWorkspace(ctx, workspaceId)
if err != nil {
return nil, fmt.Errorf("error getting updated workspace: %w", err)
return nil, fmt.Errorf("workspace %s not found: %w", workspaceId, err)
}
if name != "" {
ws.Name = name
} else if applyDefaults && ws.Name == "" {
ws.Name = fmt.Sprintf("New Workspace (%s)", ws.OID[0:5])
}
if icon != "" {
ws.Icon = icon
} else if applyDefaults && ws.Icon == "" {
ws.Icon = WorkspaceIcons[0]
}
if color != "" {
ws.Color = color
} else if applyDefaults && ws.Color == "" {
wsList, err := ListWorkspaces(ctx)
if err != nil {
log.Printf("error listing workspaces: %v", err)
wsList = waveobj.WorkspaceList{}
}
ws.Color = WorkspaceColors[len(wsList)%len(WorkspaceColors)]
}
wstore.DBUpdate(ctx, ws)
return ws, nil
}

View File

@ -15615,11 +15615,11 @@ __metadata:
linkType: hard
"nanoid@npm:^3.3.7":
version: 3.3.7
resolution: "nanoid@npm:3.3.7"
version: 3.3.8
resolution: "nanoid@npm:3.3.8"
bin:
nanoid: bin/nanoid.cjs
checksum: 10c0/e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3
checksum: 10c0/4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120
languageName: node
linkType: hard