[add] Page Builder API ( 25% )

This commit is contained in:
Max 2023-09-24 22:09:08 +08:00
parent 572a486f4f
commit 137e8ad971
9 changed files with 241 additions and 66 deletions

23
sui/core/block.go Normal file
View file

@ -0,0 +1,23 @@
package core
import (
"fmt"
"strings"
)
// Compile compile the block
func (block *Block) Compile() (string, error) {
if block.Codes.JS.Code == "" {
return "", fmt.Errorf("Block %s has no JS code", block.ID)
}
js := strings.Replace(block.Codes.JS.Code, "export default", fmt.Sprintf("window.block__%s =", block.ID), 1)
if block.Codes.HTML.Code != "" && !strings.Contains(block.Codes.JS.Code, "content:") {
html := strings.ReplaceAll(block.Codes.HTML.Code, "`", "\\`")
js = strings.Replace(js, "{", fmt.Sprintf("{\n content: `%s`,", html), 1)
}
block.Compiled = js
return js, nil
}

View file

@ -36,12 +36,12 @@ type IPage interface {
// IBlock is the interface for the block
type IBlock interface {
Get() error
Save() error
Compile() (string, error)
Load() error
}
// IComponent is the interface for the component
type IComponent interface {
Get() error
Save() error
Compile() (string, error)
Load() error
}

View file

@ -7,21 +7,20 @@ type SUI interface {
UploadTemplate(src string, dst string) (ITemplate, error)
}
// Page is the struct for the page
type Page struct {
Route string `json:"route"`
Root string `json:"root"`
Files PageFiles `json:"files"`
// DSL the struct for the DSL
type DSL struct {
ID string `json:"-"`
Name string `json:"name,omitempty"`
Storage *Storage `json:"storage,omitempty"`
Public *Public `json:"public,omitempty"`
}
// PageFiles is the struct for the page files
type PageFiles struct {
HTML string `json:"html"`
CSS string `json:"css"`
JS string `json:"js"`
TS string `json:"ts"`
LESS string `json:"less"`
DATA string `json:"data"`
// Page is the struct for the page
type Page struct {
Route string `json:"route"`
Name string `json:"name,omitempty"`
Root string `json:"-"`
Codes SourceCodes `json:"-"`
}
// Component is the struct for the component
@ -32,8 +31,10 @@ type Component struct {
// Block is the struct for the block
type Block struct {
template *Template
name string
ID string `json:"id"`
Name string `json:"name,omitempty"`
Compiled string `json:"-"`
Codes SourceCodes `json:"-"`
}
// Template is the struct for the template
@ -45,12 +46,20 @@ type Template struct {
Screenshots []string `json:"screenshots"`
}
// DSL the struct for the DSL
type DSL struct {
ID string `json:"-"`
Name string `json:"name,omitempty"`
Storage *Storage `json:"storage,omitempty"`
Public *Public `json:"public,omitempty"`
// SourceCodes is the struct for the page codes
type SourceCodes struct {
HTML Source `json:"-"`
CSS Source `json:"-"`
JS Source `json:"-"`
TS Source `json:"-"`
LESS Source `json:"-"`
DATA Source `json:"-"`
}
// Source is the struct for the source
type Source struct {
File string `json:"-"`
Code string `json:"-"`
}
// Public is the struct for the static

View file

@ -1,16 +1,28 @@
package local
// NewBlock create a new local block
func NewBlock(file string) (*Block, error) {
return nil, nil
}
import (
"path/filepath"
)
// Load get the block from the storage
func (block *Block) Load() error {
root := filepath.Join(block.tmpl.Root, "__blocks")
jsFile := filepath.Join(root, block.Codes.JS.File)
jsCode, err := block.tmpl.local.fs.ReadFile(jsFile)
if err != nil {
return err
}
block.Codes.JS.Code = string(jsCode)
htmlFile := filepath.Join(root, block.Codes.HTML.File)
if exist, _ := block.tmpl.local.fs.Exists(htmlFile); exist {
htmlCode, err := block.tmpl.local.fs.ReadFile(htmlFile)
if err != nil {
return err
}
block.Codes.HTML.Code = string(htmlCode)
}
// Get get the block
func (block *Block) Get() error {
return nil
}
// Save save the block
func (block *Block) Save() error {
return nil
}

View file

@ -101,6 +101,10 @@ func (local *Local) getTemplateFrom(path string) (*Template, error) {
// getTemplate get the template
func (local *Local) getTemplate(id string, path string) (*Template, error) {
if !local.fs.IsDir(path) {
return nil, fmt.Errorf("Template %s not found", id)
}
tmpl := Template{
local: local,
Root: path,

View file

@ -1,10 +1,5 @@
package local
// NewPage create a new local page
func NewPage(file string) (*Page, error) {
return nil, nil
}
// Get get the page
func (page *Page) Get() error {
return nil

View file

@ -75,13 +75,13 @@ func (tmpl *Template) getPage(route, file string) (core.IPage, error) {
Page: &core.Page{
Route: route,
Root: root,
Files: core.PageFiles{
HTML: fmt.Sprintf("%s%s", route, filepath.Ext(file)),
CSS: fmt.Sprintf("%s.css", route),
JS: fmt.Sprintf("%s.js", route),
DATA: fmt.Sprintf("%s.json", route),
TS: fmt.Sprintf("%s.ts", route),
LESS: fmt.Sprintf("%s.less", route),
Codes: core.SourceCodes{
HTML: core.Source{File: fmt.Sprintf("%s%s", route, filepath.Ext(file))},
CSS: core.Source{File: fmt.Sprintf("%s.css", route)},
JS: core.Source{File: fmt.Sprintf("%s.js", route)},
DATA: core.Source{File: fmt.Sprintf("%s.json", route)},
TS: core.Source{File: fmt.Sprintf("%s.ts", route)},
LESS: core.Source{File: fmt.Sprintf("%s.less", route)},
},
},
}, nil
@ -93,7 +93,90 @@ func (tmpl *Template) getPageRoute(path string) string {
// Blocks get the blocks
func (tmpl *Template) Blocks() ([]core.IBlock, error) {
return nil, nil
path := filepath.Join(tmpl.Root, "__blocks")
blocks := []core.IBlock{}
if exist, _ := tmpl.local.fs.Exists(path); !exist {
return blocks, nil
}
dirs, err := tmpl.local.fs.ReadDir(path, false)
if err != nil {
return nil, err
}
for _, dir := range dirs {
if !tmpl.local.fs.IsDir(dir) {
continue
}
block, err := tmpl.getBlockFrom(dir)
if err != nil {
log.Error("Get block error: %v", err)
continue
}
blocks = append(blocks, block)
}
return blocks, nil
}
// Block get the block
func (tmpl *Template) Block(id string) (core.IBlock, error) {
path := filepath.Join(tmpl.Root, "__blocks", id)
if exist, _ := tmpl.local.fs.Exists(path); !exist {
return nil, fmt.Errorf("Block %s not found", id)
}
block, err := tmpl.getBlockFrom(path)
if err != nil {
return nil, err
}
err = block.Load()
if err != nil {
return nil, err
}
_, err = block.Compile()
if err != nil {
return nil, err
}
return block, nil
}
func (tmpl *Template) getBlockFrom(path string) (core.IBlock, error) {
id := tmpl.getBlockID(path)
return tmpl.getBlock(id)
}
func (tmpl *Template) getBlock(id string) (core.IBlock, error) {
path := filepath.Join(tmpl.Root, "__blocks", id)
if !tmpl.local.fs.IsDir(path) {
return nil, fmt.Errorf("Block %s not found", id)
}
jsFile := filepath.Join("/", id, "main.js")
htmlFile := filepath.Join("/", id, "main.html")
block := &Block{
tmpl: tmpl,
Block: &core.Block{
ID: id,
Codes: core.SourceCodes{
HTML: core.Source{File: htmlFile},
JS: core.Source{File: jsFile},
},
},
}
return block, nil
}
func (tmpl *Template) getBlockID(path string) string {
return filepath.Base(path)
}
// Components get the components
@ -101,11 +184,6 @@ func (tmpl *Template) Components() ([]core.IComponent, error) {
return nil, nil
}
// Block get the block
func (tmpl *Template) Block(name string) (core.IBlock, error) {
return nil, nil
}
// Component get the component
func (tmpl *Template) Component(name string) (core.IComponent, error) {
return nil, nil

View file

@ -26,15 +26,16 @@ func TestTemplatePages(t *testing.T) {
assert.Equal(t, "/index", pages[0].(*Page).Route)
assert.Equal(t, "/templates/website-ai", pages[0].(*Page).Root)
assert.Equal(t, "/index.css", pages[0].(*Page).Files.CSS)
assert.Equal(t, "/index.html", pages[0].(*Page).Files.HTML)
assert.Equal(t, "/index.js", pages[0].(*Page).Files.JS)
assert.Equal(t, "/index.less", pages[0].(*Page).Files.LESS)
assert.Equal(t, "/index.ts", pages[0].(*Page).Files.TS)
assert.Equal(t, "/index.json", pages[0].(*Page).Files.DATA)
assert.Equal(t, "/index.css", pages[0].(*Page).Codes.CSS.File)
assert.Equal(t, "/index.html", pages[0].(*Page).Codes.HTML.File)
assert.Equal(t, "/index.js", pages[0].(*Page).Codes.JS.File)
assert.Equal(t, "/index.less", pages[0].(*Page).Codes.LESS.File)
assert.Equal(t, "/index.ts", pages[0].(*Page).Codes.TS.File)
assert.Equal(t, "/index.json", pages[0].(*Page).Codes.DATA.File)
}
func TestTemplatePage(t *testing.T) {
tests := prepare(t)
defer clean()
@ -49,13 +50,65 @@ func TestTemplatePage(t *testing.T) {
}
assert.Equal(t, "/index", page.(*Page).Route)
assert.Equal(t, "/templates/website-ai", page.(*Page).Root)
assert.Equal(t, "/index.css", page.(*Page).Files.CSS)
assert.Equal(t, "/index.html", page.(*Page).Files.HTML)
assert.Equal(t, "/index.js", page.(*Page).Files.JS)
assert.Equal(t, "/index.less", page.(*Page).Files.LESS)
assert.Equal(t, "/index.ts", page.(*Page).Files.TS)
assert.Equal(t, "/index.json", page.(*Page).Files.DATA)
assert.Equal(t, "/index.css", page.(*Page).Codes.CSS.File)
assert.Equal(t, "/index.html", page.(*Page).Codes.HTML.File)
assert.Equal(t, "/index.js", page.(*Page).Codes.JS.File)
assert.Equal(t, "/index.less", page.(*Page).Codes.LESS.File)
assert.Equal(t, "/index.ts", page.(*Page).Codes.TS.File)
assert.Equal(t, "/index.json", page.(*Page).Codes.DATA.File)
_, err = tmpl.Page("/the/page/could/not/be/found")
assert.Contains(t, err.Error(), "Page /the/page/could/not/be/found not found")
}
func TestTemplateBlocks(t *testing.T) {
tests := prepare(t)
defer clean()
tmpl, err := tests.Demo.GetTemplate("tech-blue")
if err != nil {
t.Fatalf("GetTemplate error: %v", err)
}
blocks, err := tmpl.Blocks()
if err != nil {
t.Fatalf("Blocks error: %v", err)
}
if len(blocks) < 3 {
t.Fatalf("Blocks error: %v", len(blocks))
}
assert.Equal(t, "columns-two", blocks[0].(*Block).ID)
assert.Equal(t, "/columns-two/main.html", blocks[0].(*Block).Codes.HTML.File)
assert.Equal(t, "/columns-two/main.js", blocks[0].(*Block).Codes.JS.File)
assert.Equal(t, "hero", blocks[1].(*Block).ID)
assert.Equal(t, "/hero/main.html", blocks[1].(*Block).Codes.HTML.File)
assert.Equal(t, "/hero/main.js", blocks[1].(*Block).Codes.JS.File)
assert.Equal(t, "section", blocks[2].(*Block).ID)
assert.Equal(t, "/section/main.html", blocks[2].(*Block).Codes.HTML.File)
assert.Equal(t, "/section/main.js", blocks[2].(*Block).Codes.JS.File)
}
func TestTemplateBlock(t *testing.T) {
tests := prepare(t)
defer clean()
tmpl, err := tests.Demo.GetTemplate("tech-blue")
if err != nil {
t.Fatalf("GetTemplate error: %v", err)
}
block, err := tmpl.Block("columns-two")
if err != nil {
t.Fatalf("Blocks error: %v", err)
}
assert.Equal(t, "columns-two", block.(*Block).ID)
assert.NotEmpty(t, block.(*Block).Codes.HTML.Code)
assert.NotEmpty(t, block.(*Block).Codes.JS.Code)
assert.Contains(t, block.(*Block).Compiled, "window.block__columns-two")
assert.Contains(t, block.(*Block).Compiled, `<div class="columns-two-left"`)
}

View file

@ -27,6 +27,7 @@ type Page struct {
// Block is the struct for the local sui block
type Block struct {
tmpl *Template
*core.Block
}