mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
merge main into branch
This commit is contained in:
commit
8d19e0de73
1
.github/workflows/build-helper.yml
vendored
1
.github/workflows/build-helper.yml
vendored
@ -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
|
||||
|
1
.github/workflows/testdriver.yml
vendored
1
.github/workflows/testdriver.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
})
|
||||
);
|
||||
|
@ -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();
|
||||
|
@ -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 {
|
||||
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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 (
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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
6
go.mod
@ -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
12
go.sum
@ -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=
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user