From 5e371f1dfb5ae5d38522706c28d3a0c8f5e9ada6 Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 18 Oct 2023 21:18:54 +0800 Subject: [PATCH] [add] block export and media api --- sui/api/api.go | 16 +++++++ sui/api/process.go | 49 ++++++++++++++++++- sui/api/process_test.go | 41 +++++++++++++++- sui/core/block.go | 5 ++ sui/core/interfaces.go | 3 ++ sui/core/types.go | 14 ++++++ sui/storages/local/block.go | 81 ++++++++++++++++++++++++++++++++ sui/storages/local/block_test.go | 46 ++++++++++++++++++ 8 files changed, 251 insertions(+), 4 deletions(-) diff --git a/sui/api/api.go b/sui/api/api.go index a3380993..db72029b 100644 --- a/sui/api/api.go +++ b/sui/api/api.go @@ -44,12 +44,28 @@ var dsl = []byte(` "process": "sui.Block.Get", "in": ["$param.id", "$param.template_id"], "out": { "status": 200, "type": "application/json" } + },{ + "path": "/:id/block/export/:template_id", + "method": "GET", + "process": "sui.Block.Export", + "in": ["$param.id", "$param.template_id"], + "out": { "status": 200, "type": "application/json" } },{ "path": "/:id/block/:template_id/:block_id", "method": "GET", "process": "sui.Block.Find", "in": ["$param.id", "$param.template_id", "$param.block_id"], "out": { "status": 200, "type": "text/javascript" } + },{ + "path": "/:id/block/:template_id/:block_id/media", + "method": "GET", + "process": "sui.Block.Media", + "in": ["$param.id", "$param.template_id", "$param.block_id"], + "out": { + "status": 200, + "body": "?:content", + "headers": { "Content-Type": "?:type"} + } }, { diff --git a/sui/api/process.go b/sui/api/process.go index 16f2d968..b3b43ff9 100644 --- a/sui/api/process.go +++ b/sui/api/process.go @@ -22,8 +22,10 @@ func init() { "locale.get": LocaleGet, "theme.get": ThemeGet, - "block.get": BlockGet, - "block.find": BlockFind, + "block.get": BlockGet, + "block.find": BlockFind, + "block.Media": BlockMedia, + "block.export": BlockExport, "component.get": ComponentGet, "component.find": ComponentFind, @@ -139,6 +141,49 @@ func BlockGet(process *process.Process) interface{} { return blocks } +// BlockExport handle the find Template request +func BlockExport(process *process.Process) interface{} { + process.ValidateArgNums(2) + + sui := get(process) + templateID := process.ArgsString(1) + + tmpl, err := sui.GetTemplate(templateID) + if err != nil { + exception.New(err.Error(), 500).Throw() + } + + items, err := tmpl.BlockLayoutItems() + if err != nil { + exception.New(err.Error(), 500).Throw() + } + return items +} + +// BlockMedia handle the find Template request +func BlockMedia(process *process.Process) interface{} { + process.ValidateArgNums(3) + + sui := get(process) + templateID := process.ArgsString(1) + blockID := strings.TrimRight(process.ArgsString(2), ".js") + + tmpl, err := sui.GetTemplate(templateID) + if err != nil { + exception.New(err.Error(), 500).Throw() + } + + asset, err := tmpl.BlockMedia(blockID) + if err != nil { + exception.New(err.Error(), 500).Throw() + } + + return map[string]interface{}{ + "content": asset.Content, + "type": asset.Type, + } +} + // BlockFind handle the find Template request func BlockFind(process *process.Process) interface{} { process.ValidateArgNums(3) diff --git a/sui/api/process_test.go b/sui/api/process_test.go index 15b1694d..39692af6 100644 --- a/sui/api/process_test.go +++ b/sui/api/process_test.go @@ -115,7 +115,7 @@ func TestTemplateThemeGet(t *testing.T) { assert.Equal(t, "light", res.([]core.SelectOption)[1].Value) } -func TestTemplateBlockGet(t *testing.T) { +func TestBlockGet(t *testing.T) { load(t) defer clean() @@ -138,7 +138,7 @@ func TestTemplateBlockGet(t *testing.T) { assert.Equal(t, "Table", res.([]core.IBlock)[3].(*local.Block).ID) } -func TestTemplateBlockFind(t *testing.T) { +func TestBlockFind(t *testing.T) { load(t) defer clean() @@ -157,6 +157,43 @@ func TestTemplateBlockFind(t *testing.T) { assert.Contains(t, res.(string), "window.block__ColumnsTwo=") } +func TestBlockExport(t *testing.T) { + load(t) + defer clean() + + // test demo + p, err := process.Of("sui.block.export", "demo", "tech-blue") + if err != nil { + t.Fatal(err) + } + + res, err := p.Exec() + if err != nil { + t.Fatal(err) + } + assert.IsType(t, &core.BlockLayoutItems{}, res) + assert.Equal(t, 2, len(res.(*core.BlockLayoutItems).Categories)) +} + +func TestBlockMedia(t *testing.T) { + load(t) + defer clean() + + // test demo + p, err := process.Of("sui.block.media", "demo", "tech-blue", "ColumnsTwo") + if err != nil { + t.Fatal(err) + } + + res, err := p.Exec() + if err != nil { + t.Fatal(err) + } + assert.IsType(t, map[string]interface{}{}, res) + assert.Equal(t, "image/png", res.(map[string]interface{})["type"]) + assert.NotEmpty(t, res.(map[string]interface{})["content"]) +} + func TestTemplateComponentGet(t *testing.T) { load(t) defer clean() diff --git a/sui/core/block.go b/sui/core/block.go index 2f92778d..b9308751 100644 --- a/sui/core/block.go +++ b/sui/core/block.go @@ -60,3 +60,8 @@ func (block *Block) Compile() (string, error) { func (block *Block) Source() string { return block.Compiled } + +// Get get the block +func (block *Block) Get() *Block { + return block +} diff --git a/sui/core/interfaces.go b/sui/core/interfaces.go index c23b6d30..1adc25ce 100644 --- a/sui/core/interfaces.go +++ b/sui/core/interfaces.go @@ -21,6 +21,8 @@ type ITemplate interface { GetPageFromAsset(asset string) (IPage, error) Blocks() ([]IBlock, error) + BlockLayoutItems() (*BlockLayoutItems, error) + BlockMedia(id string) (*Asset, error) Block(name string) (IBlock, error) Components() ([]IComponent, error) @@ -65,6 +67,7 @@ type IBlock interface { Compile() (string, error) Load() error Source() string + Get() *Block } // IComponent is the interface for the component diff --git a/sui/core/types.go b/sui/core/types.go index 85401561..fad00bb4 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -46,6 +46,20 @@ type Block struct { Codes SourceCodes `json:"-"` } +// BlockLayoutItems is the struct for the block layout items +type BlockLayoutItems struct { + Categories []LayoutItem `json:"categories"` + Locals map[string]map[string]string `json:"locals,omitempty"` +} + +// LayoutItem is the struct for the layout it +type LayoutItem struct { + ID string `json:"id"` + Label string `json:"label,omitempty"` + Width int `json:"width,omitempty"` + Blocks []LayoutItem `json:"blocks,omitempty"` +} + // Template is the struct for the template type Template struct { Version int `json:"version"` // Yao Builder version diff --git a/sui/storages/local/block.go b/sui/storages/local/block.go index 9b87410f..a86f51c0 100644 --- a/sui/storages/local/block.go +++ b/sui/storages/local/block.go @@ -4,6 +4,7 @@ import ( "fmt" "path/filepath" + "github.com/yaoapp/gou/application" "github.com/yaoapp/kun/log" "github.com/yaoapp/yao/sui/core" ) @@ -64,6 +65,86 @@ func (tmpl *Template) Block(id string) (core.IBlock, error) { return block, nil } +// BlockMedia get the block media +func (tmpl *Template) BlockMedia(id string) (*core.Asset, error) { + path := filepath.Join(tmpl.Root, "__blocks", id, "media.png") + if exist, _ := tmpl.local.fs.Exists(path); exist { + + content, err := tmpl.local.fs.ReadFile(path) + if err != nil { + return nil, err + } + + return &core.Asset{ + Type: "image/png", + Content: content, + }, nil + } + + path = filepath.Join(tmpl.Root, "__blocks", id, "media.svg") + if exist, _ := tmpl.local.fs.Exists(path); !exist { + return nil, fmt.Errorf("Block %s media not found (media.png / media.png )", id) + } + + content, err := tmpl.local.fs.ReadFile(path) + if err != nil { + return nil, err + } + + return &core.Asset{ + Type: "image/svg+xml", + Content: content, + }, nil +} + +// BlockLayoutItems export the blocks +func (tmpl *Template) BlockLayoutItems() (*core.BlockLayoutItems, error) { + + path := filepath.Join(tmpl.Root, "__blocks", "export.json") + if exist, _ := tmpl.local.fs.Exists(path); !exist { + + blocks, err := tmpl.Blocks() + if err != nil { + return nil, err + } + + // Default layout items + layoutItems := &core.BlockLayoutItems{ + Categories: []core.LayoutItem{{ + ID: "Basic", + Label: "Basic", + Blocks: []core.LayoutItem{}, + }}, + Locals: map[string]map[string]string{ + "zh-CN": {"Basic": "基础"}, + "zh-TW": {"Basic": "基礎"}, + }, + } + + for _, block := range blocks { + layoutItems.Categories[0].Blocks = append( + layoutItems.Categories[0].Blocks, core.LayoutItem{ + ID: block.Get().ID, + Label: block.Get().Name, + }) + } + return layoutItems, nil + } + + data, err := tmpl.local.fs.ReadFile(path) + if err != nil { + return nil, err + } + + layoutItems := core.BlockLayoutItems{} + err = application.Parse(path, data, &layoutItems) + if err != nil { + return nil, err + } + + return &layoutItems, nil +} + // Load get the block from the storage func (block *Block) Load() error { diff --git a/sui/storages/local/block_test.go b/sui/storages/local/block_test.go index ea3027fd..9eaf6460 100644 --- a/sui/storages/local/block_test.go +++ b/sui/storages/local/block_test.go @@ -81,3 +81,49 @@ func TestTemplateBlockTS(t *testing.T) { assert.Contains(t, block.(*Block).Compiled, "window.block__Hero") assert.Contains(t, block.(*Block).Compiled, `
`) } + +func TestBlockLayoutItems(t *testing.T) { + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + items, err := tmpl.BlockLayoutItems() + if err != nil { + t.Fatalf("BlockLayoutItems error: %v", err) + } + + assert.Equal(t, 2, len(items.Categories)) + + tmpl, err = tests.Demo.GetTemplate("website-ai") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + items, err = tmpl.BlockLayoutItems() + if err != nil { + t.Fatalf("BlockLayoutItems error: %v", err) + } + + assert.Equal(t, 1, len(items.Categories)) +} + +func TestBlockMedia(t *testing.T) { + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + media, err := tmpl.BlockMedia("ColumnsTwo") + if err != nil { + t.Fatalf("BlockMedia error: %v", err) + } + + assert.Equal(t, "image/png", media.Type) +}