mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
Create workspace will always create a saved workspace (#1509)
Also moves icon and color definitions to the backend and makes it so the new workspaces get created with a different icon color for each index. If an ephemeral window has more than one tab, always create a new window when switching workspaces Also fixes workspace accelerators on macOS
This commit is contained in:
parent
c34f2da0c0
commit
ec4f6c01de
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
|
||||
|
@ -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",
|
||||
@ -303,29 +303,12 @@ export class WaveBrowserWindow extends BaseWindow {
|
||||
const workspaceList = await WorkspaceService.ListWorkspaces();
|
||||
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);
|
||||
await WorkspaceService.DeleteWorkspace(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;
|
||||
}
|
||||
if (isNonEmptyUnsavedWorkspace(curWorkspace)) {
|
||||
console.log(
|
||||
`existing unsaved workspace ${this.workspaceId} has content, opening workspace ${workspaceId} in new window`
|
||||
);
|
||||
await createWindowForWorkspace(workspaceId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await this._queueActionInternal({ op: "switchworkspace", workspaceId });
|
||||
@ -606,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(
|
||||
@ -668,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) {
|
||||
await window.switchWorkspace(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);
|
||||
},
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
setEditingWorkspace(activeWorkspace.oid);
|
||||
fireAndForget(async () => {
|
||||
await WorkspaceService.UpdateWorkspace(activeWorkspace.oid, "", "", "", true);
|
||||
await updateWorkspaceList();
|
||||
setEditingWorkspace(activeWorkspace.oid);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -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"},
|
||||
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()
|
||||
@ -188,7 +231,8 @@ type CloseTabRtnType struct {
|
||||
|
||||
func (svc *WorkspaceService) CloseTab_Meta() tsgenmeta.MethodMeta {
|
||||
return tsgenmeta.MethodMeta{
|
||||
ArgNames: []string{"ctx", "workspaceId", "tabId", "fromElectron"},
|
||||
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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user