2024-12-02 19:56:56 +01:00
|
|
|
package wcore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/telemetry"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/util/utilfn"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/wstore"
|
|
|
|
)
|
|
|
|
|
|
|
|
func CreateWorkspace(ctx context.Context, name string, icon string, color string) (*waveobj.Workspace, error) {
|
|
|
|
log.Println("CreateWorkspace")
|
|
|
|
ws := &waveobj.Workspace{
|
|
|
|
OID: uuid.NewString(),
|
|
|
|
TabIds: []string{},
|
|
|
|
Name: name,
|
|
|
|
Icon: icon,
|
|
|
|
Color: color,
|
|
|
|
}
|
|
|
|
wstore.DBInsert(ctx, ws)
|
|
|
|
return ws, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func DeleteWorkspace(ctx context.Context, workspaceId string, force bool) (bool, error) {
|
|
|
|
log.Printf("DeleteWorkspace %s\n", workspaceId)
|
|
|
|
workspace, err := wstore.DBMustGet[*waveobj.Workspace](ctx, workspaceId)
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("error getting workspace: %w", err)
|
|
|
|
}
|
|
|
|
if workspace.Name != "" && workspace.Icon != "" && !force {
|
|
|
|
log.Printf("Ignoring DeleteWorkspace for workspace %s as it is named\n", workspaceId)
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
for _, tabId := range workspace.TabIds {
|
|
|
|
log.Printf("deleting tab %s\n", tabId)
|
2024-12-02 21:59:03 +01:00
|
|
|
_, err := DeleteTab(ctx, workspaceId, tabId)
|
2024-12-02 19:56:56 +01:00
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("error closing tab: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = wstore.DBDelete(ctx, waveobj.OType_Workspace, workspaceId)
|
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf("error deleting workspace: %w", err)
|
|
|
|
}
|
|
|
|
log.Printf("deleted workspace %s\n", workspaceId)
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetWorkspace(ctx context.Context, wsID string) (*waveobj.Workspace, error) {
|
|
|
|
return wstore.DBMustGet[*waveobj.Workspace](ctx, wsID)
|
|
|
|
}
|
|
|
|
|
|
|
|
func createTabObj(ctx context.Context, workspaceId string, name string) (*waveobj.Tab, error) {
|
2024-12-02 21:59:03 +01:00
|
|
|
ws, err := GetWorkspace(ctx, workspaceId)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("workspace %s not found: %w", workspaceId, err)
|
2024-12-02 19:56:56 +01:00
|
|
|
}
|
|
|
|
layoutStateId := uuid.NewString()
|
|
|
|
tab := &waveobj.Tab{
|
|
|
|
OID: uuid.NewString(),
|
|
|
|
Name: name,
|
|
|
|
BlockIds: []string{},
|
|
|
|
LayoutState: layoutStateId,
|
|
|
|
}
|
|
|
|
layoutState := &waveobj.LayoutState{
|
|
|
|
OID: layoutStateId,
|
|
|
|
}
|
|
|
|
ws.TabIds = append(ws.TabIds, tab.OID)
|
|
|
|
wstore.DBInsert(ctx, tab)
|
|
|
|
wstore.DBInsert(ctx, layoutState)
|
|
|
|
wstore.DBUpdate(ctx, ws)
|
|
|
|
return tab, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// returns tabid
|
|
|
|
func CreateTab(ctx context.Context, workspaceId string, tabName string, activateTab bool) (string, error) {
|
|
|
|
if tabName == "" {
|
2024-12-02 21:59:03 +01:00
|
|
|
ws, err := GetWorkspace(ctx, workspaceId)
|
2024-12-02 19:56:56 +01:00
|
|
|
if err != nil {
|
2024-12-02 21:59:03 +01:00
|
|
|
return "", fmt.Errorf("workspace %s not found: %w", workspaceId, err)
|
2024-12-02 19:56:56 +01:00
|
|
|
}
|
|
|
|
tabName = "T" + fmt.Sprint(len(ws.TabIds)+1)
|
|
|
|
}
|
|
|
|
tab, err := createTabObj(ctx, workspaceId, tabName)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("error creating tab: %w", err)
|
|
|
|
}
|
|
|
|
if activateTab {
|
|
|
|
err = SetActiveTab(ctx, workspaceId, tab.OID)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("error setting active tab: %w", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
telemetry.GoUpdateActivityWrap(wshrpc.ActivityUpdate{NewTab: 1}, "createtab")
|
|
|
|
return tab.OID, nil
|
|
|
|
}
|
|
|
|
|
2024-12-02 21:59:03 +01:00
|
|
|
// Must delete all blocks individually first.
|
|
|
|
// Also deletes LayoutState.
|
|
|
|
// Returns new active tab id, error.
|
|
|
|
func DeleteTab(ctx context.Context, workspaceId string, tabId string) (string, error) {
|
2024-12-02 19:56:56 +01:00
|
|
|
ws, _ := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId)
|
|
|
|
if ws == nil {
|
2024-12-02 21:59:03 +01:00
|
|
|
return "", fmt.Errorf("workspace not found: %q", workspaceId)
|
2024-12-02 19:56:56 +01:00
|
|
|
}
|
|
|
|
tab, _ := wstore.DBGet[*waveobj.Tab](ctx, tabId)
|
|
|
|
if tab == nil {
|
2024-12-02 21:59:03 +01:00
|
|
|
return "", fmt.Errorf("tab not found: %q", tabId)
|
2024-12-02 19:56:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// close blocks (sends events + stops block controllers)
|
|
|
|
for _, blockId := range tab.BlockIds {
|
|
|
|
err := DeleteBlock(ctx, blockId)
|
|
|
|
if err != nil {
|
2024-12-02 21:59:03 +01:00
|
|
|
return "", fmt.Errorf("error deleting block %s: %w", blockId, err)
|
2024-12-02 19:56:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
tabIdx := utilfn.FindStringInSlice(ws.TabIds, tabId)
|
|
|
|
if tabIdx == -1 {
|
2024-12-02 21:59:03 +01:00
|
|
|
return "", nil
|
2024-12-02 19:56:56 +01:00
|
|
|
}
|
|
|
|
ws.TabIds = append(ws.TabIds[:tabIdx], ws.TabIds[tabIdx+1:]...)
|
2024-12-02 21:59:03 +01:00
|
|
|
newActiveTabId := ws.ActiveTabId
|
|
|
|
if len(ws.TabIds) > 0 {
|
|
|
|
if ws.ActiveTabId == tabId {
|
|
|
|
newActiveTabId = ws.TabIds[max(0, min(tabIdx-1, len(ws.TabIds)-1))]
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
newActiveTabId = ""
|
|
|
|
}
|
|
|
|
ws.ActiveTabId = newActiveTabId
|
|
|
|
|
2024-12-02 19:56:56 +01:00
|
|
|
wstore.DBUpdate(ctx, ws)
|
|
|
|
wstore.DBDelete(ctx, waveobj.OType_Tab, tabId)
|
|
|
|
wstore.DBDelete(ctx, waveobj.OType_LayoutState, tab.LayoutState)
|
2024-12-02 21:59:03 +01:00
|
|
|
return newActiveTabId, nil
|
2024-12-02 19:56:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func SetActiveTab(ctx context.Context, workspaceId string, tabId string) error {
|
|
|
|
if tabId != "" {
|
2024-12-02 21:59:03 +01:00
|
|
|
workspace, err := GetWorkspace(ctx, workspaceId)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("workspace %s not found: %w", workspaceId, err)
|
|
|
|
}
|
2024-12-02 19:56:56 +01:00
|
|
|
tab, _ := wstore.DBGet[*waveobj.Tab](ctx, tabId)
|
|
|
|
if tab == nil {
|
|
|
|
return fmt.Errorf("tab not found: %q", tabId)
|
|
|
|
}
|
2024-12-02 21:59:03 +01:00
|
|
|
workspace.ActiveTabId = tabId
|
|
|
|
wstore.DBUpdate(ctx, workspace)
|
2024-12-02 19:56:56 +01:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func UpdateWorkspaceTabIds(ctx context.Context, workspaceId string, tabIds []string) error {
|
|
|
|
ws, _ := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId)
|
|
|
|
if ws == nil {
|
|
|
|
return fmt.Errorf("workspace not found: %q", workspaceId)
|
|
|
|
}
|
|
|
|
ws.TabIds = tabIds
|
|
|
|
wstore.DBUpdate(ctx, ws)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ListWorkspaces(ctx context.Context) (waveobj.WorkspaceList, error) {
|
|
|
|
workspaces, err := wstore.DBGetAllObjsByType[*waveobj.Workspace](ctx, waveobj.OType_Workspace)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Println("got workspaces")
|
|
|
|
|
|
|
|
windows, err := wstore.DBGetAllObjsByType[*waveobj.Window](ctx, waveobj.OType_Window)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
workspaceToWindow := make(map[string]string)
|
|
|
|
for _, window := range windows {
|
|
|
|
workspaceToWindow[window.WorkspaceId] = window.OID
|
|
|
|
}
|
|
|
|
|
|
|
|
var wl waveobj.WorkspaceList
|
|
|
|
for _, workspace := range workspaces {
|
|
|
|
if workspace.Name == "" || workspace.Icon == "" || workspace.Color == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
windowId, ok := workspaceToWindow[workspace.OID]
|
|
|
|
if !ok {
|
|
|
|
windowId = ""
|
|
|
|
}
|
|
|
|
wl = append(wl, &waveobj.WorkspaceListEntry{
|
|
|
|
WorkspaceId: workspace.OID,
|
|
|
|
WindowId: windowId,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return wl, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetIcon(workspaceId string, icon string) error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
ws, e := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId)
|
|
|
|
if e != nil {
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
if ws == nil {
|
|
|
|
return fmt.Errorf("workspace not found: %q", workspaceId)
|
|
|
|
}
|
|
|
|
ws.Icon = icon
|
|
|
|
wstore.DBUpdate(ctx, ws)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetColor(workspaceId string, color string) error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
ws, e := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId)
|
|
|
|
if e != nil {
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
if ws == nil {
|
|
|
|
return fmt.Errorf("workspace not found: %q", workspaceId)
|
|
|
|
}
|
|
|
|
ws.Color = color
|
|
|
|
wstore.DBUpdate(ctx, ws)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func SetName(workspaceId string, name string) error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
ws, e := wstore.DBGet[*waveobj.Workspace](ctx, workspaceId)
|
|
|
|
if e != nil {
|
|
|
|
return e
|
|
|
|
}
|
|
|
|
if ws == nil {
|
|
|
|
return fmt.Errorf("workspace not found: %q", workspaceId)
|
|
|
|
}
|
|
|
|
ws.Name = name
|
|
|
|
wstore.DBUpdate(ctx, ws)
|
|
|
|
return nil
|
|
|
|
}
|