waveterm/pkg/service/workspaceservice/workspaceservice.go
Evan Simkowitz cf578b1d89
Allow empty workspace name while editing workspace (#1592)
Fixes a bug where if you deleted all but one character in the workspace
name, you couldn't delete the final character. To fix this, I have made
the workspace editor save a separate entry from the backend. The backend
will also only update its DB value and notify the frontend if something
was actually edited. If you delete all the characters in the name and
don't put anything new in, though, the name will be whatever the last
character you had was, since the name of a saved workspace cannot be
empty.
2024-12-20 08:55:28 -08:00

274 lines
8.8 KiB
Go

// Copyright 2024, Command Line Inc.
// SPDX-License-Identifier: Apache-2.0
package workspaceservice
import (
"context"
"fmt"
"log"
"time"
"github.com/wavetermdev/waveterm/pkg/blockcontroller"
"github.com/wavetermdev/waveterm/pkg/panichandler"
"github.com/wavetermdev/waveterm/pkg/tsgen/tsgenmeta"
"github.com/wavetermdev/waveterm/pkg/waveobj"
"github.com/wavetermdev/waveterm/pkg/wcore"
"github.com/wavetermdev/waveterm/pkg/wps"
"github.com/wavetermdev/waveterm/pkg/wstore"
)
const DefaultTimeout = 2 * time.Second
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, 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)
_, updated, err := wcore.UpdateWorkspace(ctx, workspaceId, name, icon, color, applyDefaults)
if err != nil {
return nil, fmt.Errorf("error updating workspace: %w", err)
}
if !updated {
return nil, nil
}
wps.Broker.Publish(wps.WaveEvent{
Event: wps.Event_WorkspaceUpdate})
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",
}
}
func (svc *WorkspaceService) GetWorkspace(workspaceId string) (*waveobj.Workspace, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
ws, err := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId)
if err != nil {
return nil, fmt.Errorf("error getting workspace: %w", err)
}
return ws, nil
}
func (svc *WorkspaceService) DeleteWorkspace_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ArgNames: []string{"workspaceId"},
}
}
func (svc *WorkspaceService) DeleteWorkspace(workspaceId string) (waveobj.UpdatesRtnType, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
ctx = waveobj.ContextWithUpdates(ctx)
deleted, err := wcore.DeleteWorkspace(ctx, workspaceId, true)
if err != nil {
return nil, fmt.Errorf("error deleting workspace: %w", err)
}
if !deleted {
return nil, nil
}
updates := waveobj.ContextGetUpdatesRtn(ctx)
go func() {
defer panichandler.PanicHandler("WorkspaceService:DeleteWorkspace:SendUpdateEvents")
wps.Broker.SendUpdateEvents(updates)
}()
return updates, nil
}
func (svc *WorkspaceService) ListWorkspaces() (waveobj.WorkspaceList, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
return wcore.ListWorkspaces(ctx)
}
func (svc *WorkspaceService) CreateTab_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ArgNames: []string{"workspaceId", "tabName", "activateTab", "pinned"},
ReturnDesc: "tabId",
}
}
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()
ctx = waveobj.ContextWithUpdates(ctx)
tabId, err := wcore.CreateTab(ctx, workspaceId, tabName, activateTab, pinned, false)
if err != nil {
return "", nil, fmt.Errorf("error creating tab: %w", err)
}
updates := waveobj.ContextGetUpdatesRtn(ctx)
go func() {
defer panichandler.PanicHandler("WorkspaceService:CreateTab:SendUpdateEvents")
wps.Broker.SendUpdateEvents(updates)
}()
return tabId, updates, nil
}
func (svc *WorkspaceService) ChangeTabPinning_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ArgNames: []string{"ctx", "workspaceId", "tabId", "pinned"},
}
}
func (svc *WorkspaceService) ChangeTabPinning(ctx context.Context, workspaceId string, tabId string, pinned bool) (waveobj.UpdatesRtnType, error) {
log.Printf("ChangeTabPinning %s %s %v\n", workspaceId, tabId, pinned)
ctx = waveobj.ContextWithUpdates(ctx)
err := wcore.ChangeTabPinning(ctx, workspaceId, tabId, pinned)
if err != nil {
return nil, fmt.Errorf("error toggling tab pinning: %w", err)
}
updates := waveobj.ContextGetUpdatesRtn(ctx)
go func() {
defer panichandler.PanicHandler("WorkspaceService:ChangeTabPinning:SendUpdateEvents")
wps.Broker.SendUpdateEvents(updates)
}()
return updates, nil
}
func (svc *WorkspaceService) UpdateTabIds_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ArgNames: []string{"uiContext", "workspaceId", "tabIds", "pinnedTabIds"},
}
}
func (svc *WorkspaceService) UpdateTabIds(uiContext waveobj.UIContext, workspaceId string, tabIds []string, pinnedTabIds []string) (waveobj.UpdatesRtnType, error) {
log.Printf("UpdateTabIds %s %v %v\n", workspaceId, tabIds, pinnedTabIds)
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
ctx = waveobj.ContextWithUpdates(ctx)
err := wcore.UpdateWorkspaceTabIds(ctx, workspaceId, tabIds, pinnedTabIds)
if err != nil {
return nil, fmt.Errorf("error updating workspace tab ids: %w", err)
}
return waveobj.ContextGetUpdatesRtn(ctx), nil
}
func (svc *WorkspaceService) SetActiveTab_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ArgNames: []string{"workspaceId", "tabId"},
}
}
func (svc *WorkspaceService) SetActiveTab(workspaceId string, tabId string) (waveobj.UpdatesRtnType, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
ctx = waveobj.ContextWithUpdates(ctx)
err := wcore.SetActiveTab(ctx, workspaceId, tabId)
if err != nil {
return nil, fmt.Errorf("error setting active tab: %w", err)
}
// check all blocks in tab and start controllers (if necessary)
tab, err := wstore.DBMustGet[*waveobj.Tab](ctx, tabId)
if err != nil {
return nil, fmt.Errorf("error getting tab: %w", err)
}
blockORefs := tab.GetBlockORefs()
blocks, err := wstore.DBSelectORefs(ctx, blockORefs)
if err != nil {
return nil, fmt.Errorf("error getting tab blocks: %w", err)
}
updates := waveobj.ContextGetUpdatesRtn(ctx)
go func() {
defer panichandler.PanicHandler("WorkspaceService:SetActiveTab:SendUpdateEvents")
wps.Broker.SendUpdateEvents(updates)
}()
var extraUpdates waveobj.UpdatesRtnType
extraUpdates = append(extraUpdates, updates...)
extraUpdates = append(extraUpdates, waveobj.MakeUpdate(tab))
extraUpdates = append(extraUpdates, waveobj.MakeUpdates(blocks)...)
return extraUpdates, nil
}
type CloseTabRtnType struct {
CloseWindow bool `json:"closewindow,omitempty"`
NewActiveTabId string `json:"newactivetabid,omitempty"`
}
func (svc *WorkspaceService) CloseTab_Meta() tsgenmeta.MethodMeta {
return tsgenmeta.MethodMeta{
ArgNames: []string{"ctx", "workspaceId", "tabId", "fromElectron"},
ReturnDesc: "CloseTabRtn",
}
}
// returns the new active tabid
func (svc *WorkspaceService) CloseTab(ctx context.Context, workspaceId string, tabId string, fromElectron bool) (*CloseTabRtnType, waveobj.UpdatesRtnType, error) {
ctx = waveobj.ContextWithUpdates(ctx)
tab, err := wstore.DBMustGet[*waveobj.Tab](ctx, tabId)
if err != nil {
return nil, nil, fmt.Errorf("error getting tab: %w", err)
}
go func() {
for _, blockId := range tab.BlockIds {
blockcontroller.StopBlockController(blockId)
}
}()
newActiveTabId, err := wcore.DeleteTab(ctx, workspaceId, tabId, true)
if err != nil {
return nil, nil, fmt.Errorf("error closing tab: %w", err)
}
rtn := &CloseTabRtnType{}
if newActiveTabId == "" {
rtn.CloseWindow = true
} else {
rtn.NewActiveTabId = newActiveTabId
}
updates := waveobj.ContextGetUpdatesRtn(ctx)
go func() {
defer panichandler.PanicHandler("WorkspaceService:CloseTab:SendUpdateEvents")
wps.Broker.SendUpdateEvents(updates)
}()
return rtn, updates, nil
}