Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .github/workflows/build-helper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion .github/workflows/testdriver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 25 additions & 30 deletions emain/emain-window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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);
}
}
}

Expand Down
10 changes: 4 additions & 6 deletions emain/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,23 +42,21 @@ 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 &&
workspaceMenu.push(
{ 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);
},
Expand Down
21 changes: 19 additions & 2 deletions frontend/app/store/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand All @@ -184,14 +184,26 @@ 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))
}

// @returns object updates
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))
}
Expand All @@ -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();
Expand Down
60 changes: 18 additions & 42 deletions frontend/app/tab/workspaceswitcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 (
Expand Down
54 changes: 49 additions & 5 deletions pkg/service/workspaceservice/workspaceservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
}
}

Expand Down Expand Up @@ -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)
Expand All @@ -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()
Expand Down Expand Up @@ -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",
}
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/wcore/wcore.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/wcore/window.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
Loading