diff --git a/db/migrations-wstore/000005_blockparent.down.sql b/db/migrations-wstore/000005_blockparent.down.sql new file mode 100644 index 000000000..5aed013ca --- /dev/null +++ b/db/migrations-wstore/000005_blockparent.down.sql @@ -0,0 +1 @@ +-- we don't need to remove parentoref \ No newline at end of file diff --git a/db/migrations-wstore/000005_blockparent.up.sql b/db/migrations-wstore/000005_blockparent.up.sql new file mode 100644 index 000000000..940d62b32 --- /dev/null +++ b/db/migrations-wstore/000005_blockparent.up.sql @@ -0,0 +1,4 @@ +UPDATE db_block +SET data = json_set(data, '$.parentoref', db_tab.oid) +FROM db_tab +WHERE db_block.oid IN (SELECT value FROM json_each(db_tab.data, '$.blockids')); diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 6a0e57363..89fa65475 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -7,9 +7,11 @@ declare global { // waveobj.Block type Block = WaveObj & { + parentoref?: string; blockdef: BlockDef; runtimeopts?: RuntimeOpts; stickers?: StickerType[]; + subblockids?: string[]; }; // blockcontroller.BlockControllerRuntimeStatus diff --git a/pkg/waveobj/wtype.go b/pkg/waveobj/wtype.go index 2f3e87717..3e1df9e00 100644 --- a/pkg/waveobj/wtype.go +++ b/pkg/waveobj/wtype.go @@ -252,11 +252,13 @@ type WinSize struct { type Block struct { OID string `json:"oid"` + ParentORef string `json:"parentoref,omitempty"` Version int `json:"version"` BlockDef *BlockDef `json:"blockdef"` RuntimeOpts *RuntimeOpts `json:"runtimeopts,omitempty"` Stickers []*StickerType `json:"stickers,omitempty"` Meta MetaMapType `json:"meta"` + SubBlockIds []string `json:"subblockids,omitempty"` } func (*Block) GetOType() string { diff --git a/pkg/wcore/wcore.go b/pkg/wcore/wcore.go index f282b4f53..3e9e1bb4a 100644 --- a/pkg/wcore/wcore.go +++ b/pkg/wcore/wcore.go @@ -27,7 +27,22 @@ const DefaultTimeout = 2 * time.Second const DefaultActivateBlockTimeout = 60 * time.Second func DeleteBlock(ctx context.Context, tabId string, blockId string) error { - err := wstore.DeleteBlock(ctx, tabId, blockId) + block, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId) + if err != nil { + return fmt.Errorf("error getting block: %w", err) + } + if block == nil { + return nil + } + if len(block.SubBlockIds) > 0 { + for _, subBlockId := range block.SubBlockIds { + err := DeleteSubBlock(ctx, blockId, subBlockId) + if err != nil { + return fmt.Errorf("error deleting subblock %s: %w", subBlockId, err) + } + } + } + err = wstore.DeleteBlock(ctx, tabId, blockId) if err != nil { return fmt.Errorf("error deleting block: %w", err) } @@ -36,14 +51,18 @@ func DeleteBlock(ctx context.Context, tabId string, blockId string) error { return nil } +// tabid is optional func sendBlockCloseEvent(tabId string, blockId string) { + scopes := []string{ + waveobj.MakeORef(waveobj.OType_Block, blockId).String(), + } + if tabId != "" { + scopes = append(scopes, waveobj.MakeORef(waveobj.OType_Tab, tabId).String()) + } waveEvent := wps.WaveEvent{ - Event: wps.Event_BlockClose, - Scopes: []string{ - waveobj.MakeORef(waveobj.OType_Tab, tabId).String(), - waveobj.MakeORef(waveobj.OType_Block, blockId).String(), - }, - Data: blockId, + Event: wps.Event_BlockClose, + Scopes: scopes, + Data: blockId, } wps.Broker.Publish(waveEvent) } @@ -205,6 +224,46 @@ func CreateClient(ctx context.Context) (*waveobj.Client, error) { return client, nil } +func DeleteSubBlock(ctx context.Context, parentBlockId string, blockId string) error { + block, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId) + if err != nil { + return fmt.Errorf("error getting block: %w", err) + } + if block == nil { + return nil + } + if len(block.SubBlockIds) > 0 { + // recursively delete sub-blocks + for _, subBlockId := range block.SubBlockIds { + err := DeleteSubBlock(ctx, blockId, subBlockId) + if err != nil { + return fmt.Errorf("error deleting subblock %s: %w", subBlockId, err) + } + } + } + err = wstore.DeleteSubBlock(ctx, parentBlockId, blockId) + if err != nil { + return fmt.Errorf("error deleting block: %w", err) + } + go blockcontroller.StopBlockController(blockId) + sendBlockCloseEvent("", blockId) + return nil +} + +func CreateSubBlock(ctx context.Context, blockId string, blockDef *waveobj.BlockDef) (*waveobj.Block, error) { + if blockDef == nil { + return nil, fmt.Errorf("blockDef is nil") + } + if blockDef.Meta == nil || blockDef.Meta.GetString(waveobj.MetaKey_View, "") == "" { + return nil, fmt.Errorf("no view provided for new block") + } + blockData, err := wstore.CreateSubBlock(ctx, blockId, blockDef) + if err != nil { + return nil, fmt.Errorf("error creating sub block: %w", err) + } + return blockData, nil +} + func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, rtOpts *waveobj.RuntimeOpts) (*waveobj.Block, error) { if blockDef == nil { return nil, fmt.Errorf("blockDef is nil") diff --git a/pkg/wstore/wstore.go b/pkg/wstore/wstore.go index 1c9824350..450e7d587 100644 --- a/pkg/wstore/wstore.go +++ b/pkg/wstore/wstore.go @@ -95,6 +95,27 @@ func UpdateTabName(ctx context.Context, tabId, name string) error { }) } +func CreateSubBlock(ctx context.Context, parentBlockId string, blockDef *waveobj.BlockDef) (*waveobj.Block, error) { + return WithTxRtn(ctx, func(tx *TxWrap) (*waveobj.Block, error) { + parentBlock, _ := DBGet[*waveobj.Block](tx.Context(), parentBlockId) + if parentBlock == nil { + return nil, fmt.Errorf("parent block not found: %q", parentBlockId) + } + blockId := uuid.NewString() + blockData := &waveobj.Block{ + OID: blockId, + ParentORef: waveobj.MakeORef(waveobj.OType_Block, parentBlockId).String(), + BlockDef: blockDef, + RuntimeOpts: nil, + Meta: blockDef.Meta, + } + DBInsert(tx.Context(), blockData) + parentBlock.SubBlockIds = append(parentBlock.SubBlockIds, blockId) + DBUpdate(tx.Context(), parentBlock) + return blockData, nil + }) +} + func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, rtOpts *waveobj.RuntimeOpts) (*waveobj.Block, error) { return WithTxRtn(ctx, func(tx *TxWrap) (*waveobj.Block, error) { tab, _ := DBGet[*waveobj.Tab](tx.Context(), tabId) @@ -104,6 +125,7 @@ func CreateBlock(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, blockId := uuid.NewString() blockData := &waveobj.Block{ OID: blockId, + ParentORef: waveobj.MakeORef(waveobj.OType_Tab, tabId).String(), BlockDef: blockDef, RuntimeOpts: rtOpts, Meta: blockDef.Meta, @@ -124,18 +146,32 @@ func findStringInSlice(slice []string, val string) int { return -1 } +func DeleteSubBlock(ctx context.Context, parentBlockId string, blockId string) error { + return WithTx(ctx, func(tx *TxWrap) error { + parentBlock, _ := DBGet[*waveobj.Block](tx.Context(), parentBlockId) + if parentBlock != nil { + parentBlock.SubBlockIds = utilfn.RemoveElemFromSlice(parentBlock.SubBlockIds, blockId) + DBUpdate(tx.Context(), parentBlock) + } + DBDelete(tx.Context(), waveobj.OType_Block, blockId) + return nil + }) +} + func DeleteBlock(ctx context.Context, tabId string, blockId string) error { return WithTx(ctx, func(tx *TxWrap) error { - tab, _ := DBGet[*waveobj.Tab](tx.Context(), tabId) - if tab == nil { - return fmt.Errorf("tab not found: %q", tabId) - } - blockIdx := findStringInSlice(tab.BlockIds, blockId) - if blockIdx == -1 { + block, _ := DBGet[*waveobj.Block](tx.Context(), blockId) + if block == nil { return nil } - tab.BlockIds = append(tab.BlockIds[:blockIdx], tab.BlockIds[blockIdx+1:]...) - DBUpdate(tx.Context(), tab) + if len(block.SubBlockIds) > 0 { + return fmt.Errorf("block has subblocks, must delete subblocks first") + } + tab, _ := DBGet[*waveobj.Tab](tx.Context(), tabId) + if tab != nil { + tab.BlockIds = utilfn.RemoveElemFromSlice(tab.BlockIds, blockId) + DBUpdate(tx.Context(), tab) + } DBDelete(tx.Context(), waveobj.OType_Block, blockId) return nil }) @@ -145,23 +181,18 @@ func DeleteBlock(ctx context.Context, tabId string, blockId string) error { // also deletes LayoutState func DeleteTab(ctx context.Context, workspaceId string, tabId string) error { return WithTx(ctx, func(tx *TxWrap) error { - ws, _ := DBGet[*waveobj.Workspace](tx.Context(), workspaceId) - if ws == nil { - return fmt.Errorf("workspace not found: %q", workspaceId) - } tab, _ := DBGet[*waveobj.Tab](tx.Context(), tabId) if tab == nil { - return fmt.Errorf("tab not found: %q", tabId) + return nil } if len(tab.BlockIds) != 0 { return fmt.Errorf("tab has blocks, must delete blocks first") } - tabIdx := findStringInSlice(ws.TabIds, tabId) - if tabIdx == -1 { - return nil + ws, _ := DBGet[*waveobj.Workspace](tx.Context(), workspaceId) + if ws != nil { + ws.TabIds = utilfn.RemoveElemFromSlice(ws.TabIds, tabId) + DBUpdate(tx.Context(), ws) } - ws.TabIds = append(ws.TabIds[:tabIdx], ws.TabIds[tabIdx+1:]...) - DBUpdate(tx.Context(), ws) DBDelete(tx.Context(), waveobj.OType_Tab, tabId) DBDelete(tx.Context(), waveobj.OType_LayoutState, tab.LayoutState) return nil @@ -190,6 +221,10 @@ func UpdateObjectMeta(ctx context.Context, oref waveobj.ORef, meta waveobj.MetaM func MoveBlockToTab(ctx context.Context, currentTabId string, newTabId string, blockId string) error { return WithTx(ctx, func(tx *TxWrap) error { + block, _ := DBGet[*waveobj.Block](tx.Context(), blockId) + if block == nil { + return fmt.Errorf("block not found: %q", blockId) + } currentTab, _ := DBGet[*waveobj.Tab](tx.Context(), currentTabId) if currentTab == nil { return fmt.Errorf("current tab not found: %q", currentTabId) @@ -204,6 +239,8 @@ func MoveBlockToTab(ctx context.Context, currentTabId string, newTabId string, b } currentTab.BlockIds = utilfn.RemoveElemFromSlice(currentTab.BlockIds, blockId) newTab.BlockIds = append(newTab.BlockIds, blockId) + block.ParentORef = waveobj.MakeORef(waveobj.OType_Tab, newTabId).String() + DBUpdate(tx.Context(), block) DBUpdate(tx.Context(), currentTab) DBUpdate(tx.Context(), newTab) return nil