2024-08-28 03:38:57 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
|
|
|
package wlayout
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"time"
|
|
|
|
|
2024-09-05 23:25:45 +02:00
|
|
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/wcore"
|
|
|
|
"github.com/wavetermdev/waveterm/pkg/wstore"
|
2024-08-28 03:38:57 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
LayoutActionDataType_Insert = "insert"
|
|
|
|
LayoutActionDataType_InsertAtIndex = "insertatindex"
|
|
|
|
LayoutActionDataType_Remove = "delete"
|
2024-09-17 02:26:37 +02:00
|
|
|
LayoutActionDataType_ClearTree = "clear"
|
2024-08-28 03:38:57 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
type PortableLayout []struct {
|
|
|
|
IndexArr []int `json:"indexarr"`
|
|
|
|
Size *uint `json:"size,omitempty"`
|
|
|
|
BlockDef *waveobj.BlockDef `json:"blockdef"`
|
2024-08-28 08:16:07 +02:00
|
|
|
Focused bool `json:"focused"`
|
2024-08-28 03:38:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func GetStarterLayout() PortableLayout {
|
|
|
|
return PortableLayout{
|
|
|
|
{IndexArr: []int{0}, BlockDef: &waveobj.BlockDef{
|
|
|
|
Meta: waveobj.MetaMapType{
|
|
|
|
waveobj.MetaKey_View: "term",
|
|
|
|
waveobj.MetaKey_Controller: "shell",
|
|
|
|
},
|
2024-08-28 08:16:07 +02:00
|
|
|
}, Focused: true},
|
2024-08-28 03:38:57 +02:00
|
|
|
{IndexArr: []int{1}, BlockDef: &waveobj.BlockDef{
|
|
|
|
Meta: waveobj.MetaMapType{
|
2024-10-18 00:19:13 +02:00
|
|
|
waveobj.MetaKey_View: "sysinfo",
|
2024-08-28 03:38:57 +02:00
|
|
|
},
|
|
|
|
}},
|
|
|
|
{IndexArr: []int{1, 1}, BlockDef: &waveobj.BlockDef{
|
|
|
|
Meta: waveobj.MetaMapType{
|
|
|
|
waveobj.MetaKey_View: "web",
|
|
|
|
waveobj.MetaKey_Url: "https://github.com/wavetermdev/waveterm",
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
{IndexArr: []int{1, 2}, BlockDef: &waveobj.BlockDef{
|
|
|
|
Meta: waveobj.MetaMapType{
|
|
|
|
waveobj.MetaKey_View: "preview",
|
|
|
|
waveobj.MetaKey_File: "~",
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
{IndexArr: []int{2}, BlockDef: &waveobj.BlockDef{
|
|
|
|
Meta: waveobj.MetaMapType{
|
2024-09-20 21:17:49 +02:00
|
|
|
waveobj.MetaKey_View: "tips",
|
2024-08-28 03:38:57 +02:00
|
|
|
},
|
|
|
|
}},
|
|
|
|
{IndexArr: []int{2, 1}, BlockDef: &waveobj.BlockDef{
|
2024-09-20 21:17:49 +02:00
|
|
|
Meta: waveobj.MetaMapType{
|
|
|
|
waveobj.MetaKey_View: "help",
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
{IndexArr: []int{2, 2}, BlockDef: &waveobj.BlockDef{
|
2024-08-28 03:38:57 +02:00
|
|
|
Meta: waveobj.MetaMapType{
|
|
|
|
waveobj.MetaKey_View: "waveai",
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
// {IndexArr: []int{2, 2}, BlockDef: &wstore.BlockDef{
|
|
|
|
// Meta: wstore.MetaMapType{
|
|
|
|
// waveobj.MetaKey_View: "web",
|
|
|
|
// waveobj.MetaKey_Url: "https://www.youtube.com/embed/cKqsw_sAsU8",
|
|
|
|
// },
|
|
|
|
// }},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetNewTabLayout() PortableLayout {
|
|
|
|
return PortableLayout{
|
|
|
|
{IndexArr: []int{0}, BlockDef: &waveobj.BlockDef{
|
|
|
|
Meta: waveobj.MetaMapType{
|
|
|
|
waveobj.MetaKey_View: "term",
|
|
|
|
waveobj.MetaKey_Controller: "shell",
|
|
|
|
},
|
2024-08-28 08:16:07 +02:00
|
|
|
}, Focused: true},
|
2024-08-28 03:38:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetLayoutIdForTab(ctx context.Context, tabId string) (string, error) {
|
|
|
|
tabObj, err := wstore.DBGet[*waveobj.Tab](ctx, tabId)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("unable to get layout id for given tab id %s: %w", tabId, err)
|
|
|
|
}
|
|
|
|
return tabObj.LayoutState, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func QueueLayoutAction(ctx context.Context, layoutStateId string, actions ...waveobj.LayoutActionData) error {
|
|
|
|
layoutStateObj, err := wstore.DBGet[*waveobj.LayoutState](ctx, layoutStateId)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to get layout state for given id %s: %w", layoutStateId, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if layoutStateObj.PendingBackendActions == nil {
|
|
|
|
layoutStateObj.PendingBackendActions = &actions
|
|
|
|
} else {
|
|
|
|
*layoutStateObj.PendingBackendActions = append(*layoutStateObj.PendingBackendActions, actions...)
|
|
|
|
}
|
|
|
|
|
|
|
|
err = wstore.DBUpdate(ctx, layoutStateObj)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to update layout state with new actions: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func QueueLayoutActionForTab(ctx context.Context, tabId string, actions ...waveobj.LayoutActionData) error {
|
|
|
|
layoutStateId, err := GetLayoutIdForTab(ctx, tabId)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return QueueLayoutAction(ctx, layoutStateId, actions...)
|
|
|
|
}
|
|
|
|
|
|
|
|
func ApplyPortableLayout(ctx context.Context, tabId string, layout PortableLayout) error {
|
2024-09-19 21:59:09 +02:00
|
|
|
log.Printf("ApplyPortableLayout, tabId: %s, layout: %v\n", tabId, layout)
|
2024-09-17 02:26:37 +02:00
|
|
|
actions := make([]waveobj.LayoutActionData, len(layout)+1)
|
|
|
|
actions[0] = waveobj.LayoutActionData{ActionType: LayoutActionDataType_ClearTree}
|
2024-08-28 03:38:57 +02:00
|
|
|
for i := 0; i < len(layout); i++ {
|
|
|
|
layoutAction := layout[i]
|
|
|
|
|
|
|
|
blockData, err := wcore.CreateBlock(ctx, tabId, layoutAction.BlockDef, &waveobj.RuntimeOpts{})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to create block to apply portable layout to tab %s: %w", tabId, err)
|
|
|
|
}
|
|
|
|
|
2024-09-17 02:26:37 +02:00
|
|
|
actions[i+1] = waveobj.LayoutActionData{
|
2024-08-28 03:38:57 +02:00
|
|
|
ActionType: LayoutActionDataType_InsertAtIndex,
|
|
|
|
BlockId: blockData.OID,
|
|
|
|
IndexArr: &layoutAction.IndexArr,
|
|
|
|
NodeSize: layoutAction.Size,
|
2024-08-28 08:16:07 +02:00
|
|
|
Focused: layoutAction.Focused,
|
2024-08-28 03:38:57 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
err := QueueLayoutActionForTab(ctx, tabId, actions...)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to queue layout actions for portable layout: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-12-02 19:56:56 +01:00
|
|
|
func BootstrapNewWorkspaceLayout(ctx context.Context, workspace *waveobj.Workspace) error {
|
|
|
|
log.Printf("BootstrapNewWorkspaceLayout, workspace: %v\n", workspace)
|
|
|
|
tabId := workspace.ActiveTabId
|
2024-09-12 02:39:08 +02:00
|
|
|
newTabLayout := GetNewTabLayout()
|
|
|
|
|
|
|
|
err := ApplyPortableLayout(ctx, tabId, newTabLayout)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error applying new window layout: %w", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-08-28 03:38:57 +02:00
|
|
|
func BootstrapStarterLayout(ctx context.Context) error {
|
|
|
|
ctx, cancelFn := context.WithTimeout(ctx, 2*time.Second)
|
|
|
|
defer cancelFn()
|
|
|
|
client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("unable to find client: %v\n", err)
|
|
|
|
return fmt.Errorf("unable to find client: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(client.WindowIds) < 1 {
|
|
|
|
return fmt.Errorf("error bootstrapping layout, no windows exist")
|
|
|
|
}
|
|
|
|
|
|
|
|
windowId := client.WindowIds[0]
|
|
|
|
|
|
|
|
window, err := wstore.DBMustGet[*waveobj.Window](ctx, windowId)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error getting window: %w", err)
|
|
|
|
}
|
|
|
|
|
2024-12-02 19:56:56 +01:00
|
|
|
workspace, err := wstore.DBMustGet[*waveobj.Workspace](ctx, window.WorkspaceId)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error getting workspace: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tabId := workspace.ActiveTabId
|
2024-08-28 03:38:57 +02:00
|
|
|
|
|
|
|
starterLayout := GetStarterLayout()
|
|
|
|
|
|
|
|
err = ApplyPortableLayout(ctx, tabId, starterLayout)
|
|
|
|
if err != nil {
|
2024-09-12 02:39:08 +02:00
|
|
|
return fmt.Errorf("error applying starter layout: %w", err)
|
2024-08-28 03:38:57 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|