From 7943453c467b1cd60f5dd9569fc61192a3107fec Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 9 Oct 2023 19:17:35 +0800 Subject: [PATCH] [add] page preview api --- sui/api/api.go | 8 +++ sui/api/process.go | 40 ++++++++++++ sui/api/process_test.go | 19 ++++++ sui/core/compile.go | 64 ++++++++++++++++++++ sui/core/core.go | 2 +- sui/core/data.go | 4 +- sui/core/editor.go | 2 +- sui/core/interfaces.go | 2 + sui/core/preview.go | 84 ++++++++++++++++++++++++++ sui/core/types.go | 32 +++++----- sui/storages/local/page.go | 24 +++++--- sui/storages/local/page_render_test.go | 80 ++++++++++++++++++++++++ sui/storages/local/page_test.go | 40 ------------ 13 files changed, 334 insertions(+), 67 deletions(-) create mode 100644 sui/core/preview.go create mode 100644 sui/storages/local/page_render_test.go diff --git a/sui/api/api.go b/sui/api/api.go index cbb665a7..4840041d 100644 --- a/sui/api/api.go +++ b/sui/api/api.go @@ -156,6 +156,14 @@ var dsl = []byte(` "body": "?:content", "headers": { "Content-Type": "?:type"} } + }, + + { + "path": "/:id/preview/:template_id/*route", + "method": "GET", + "process": "sui.Preview.Render", + "in": ["$param.id", "$param.template_id", "$param.path", "$header.Referer", "$query.r", "$query.t"], + "out": {"status": 200, "type": "text/html; charset=utf-8"} } ], } diff --git a/sui/api/process.go b/sui/api/process.go index 6accaef6..4e3f51ed 100644 --- a/sui/api/process.go +++ b/sui/api/process.go @@ -41,6 +41,8 @@ func init() { "editor.source": EditorSource, "editor.renderaftersavetemp": EditorRenderAfterSaveTemp, "editor.sourceaftersavetemp": EditorSourceAfterSaveTemp, + + "preview.render": PreviewRender, }) } @@ -531,6 +533,44 @@ func EditorSourceAfterSaveTemp(process *process.Process) interface{} { return EditorSource(process) } +// PreviewRender handle the render page request +func PreviewRender(process *process.Process) interface{} { + + process.ValidateArgNums(3) + sui := get(process) + id := process.ArgsString(0) + templateID := process.ArgsString(1) + route := route(process, 2) + referer := process.ArgsString(3, "") + + // reqData := process.ArgsString(4) + // timestamp := process.Args[4].(*timestamp.Timestamp) + + req := &core.Request{ + Method: "GET", + AssetRoot: fmt.Sprintf("/api/__yao/sui/v1/%s/asset/%s/@assets", id, templateID), + Referer: referer, + } + + tmpl, err := sui.GetTemplate(templateID) + if err != nil { + exception.New(err.Error(), 500).Throw() + } + + page, err := tmpl.Page(route) + if err != nil { + exception.New(err.Error(), 500).Throw() + } + + // Request data + html, err := page.PreviewRender(req) + if err != nil { + exception.New(err.Error(), 500).Throw() + } + + return html +} + // get the sui func get(process *process.Process) core.SUI { sui, has := core.SUIs[process.ArgsString(0)] diff --git a/sui/api/process_test.go b/sui/api/process_test.go index dad09d18..51d1c7e4 100644 --- a/sui/api/process_test.go +++ b/sui/api/process_test.go @@ -568,6 +568,25 @@ func TestEditorRenderWithQuery(t *testing.T) { assert.NotEmpty(t, res.(*core.ResponseEditorRender).HTML) } +func TestPreviewRender(t *testing.T) { + load(t) + defer clean() + + // test demo + p, err := process.Of("sui.preview.render", "demo", "tech-blue", "/index") + if err != nil { + t.Fatal(err) + } + + res, err := p.Exec() + if err != nil { + t.Fatal(err) + } + + assert.IsType(t, "", res) + assert.NotEmpty(t, res) +} + func TestEditorPageSource(t *testing.T) { load(t) defer clean() diff --git a/sui/core/compile.go b/sui/core/compile.go index 2de47774..cb2af16f 100644 --- a/sui/core/compile.go +++ b/sui/core/compile.go @@ -1,7 +1,9 @@ package core import ( + "fmt" "regexp" + "strings" "github.com/evanw/esbuild/pkg/api" "github.com/yaoapp/gou/runtime/transform" @@ -46,3 +48,65 @@ func (page *Page) CompileCSS(source []byte, minify bool) ([]byte, error) { } return source, nil } + +// CompileHTML compile the html +func (page *Page) CompileHTML(source []byte, minify bool) ([]byte, error) { + return source, nil +} + +// BuildHTML build the html +func (page *Page) BuildHTML(assetRoot string) (string, error) { + + html := string(page.Document) + if page.Codes.HTML.Code != "" { + html = strings.Replace(html, "{{ __page }}", page.Codes.HTML.Code, 1) + } + + code := strings.ReplaceAll(html, "@assets", assetRoot) + res, err := page.CompileHTML([]byte(code), false) + if err != nil { + return "", err + } + + return string(res), nil +} + +// BuildStyle build the style +func (page *Page) BuildStyle(assetRoot string) (string, error) { + if page.Codes.CSS.Code == "" { + return "", nil + } + + code := strings.ReplaceAll(page.Codes.CSS.Code, "@assets", assetRoot) + res, err := page.CompileCSS([]byte(code), false) + if err != nil { + return "", err + } + + return fmt.Sprintf("\n", res), nil +} + +// BuildScript build the script +func (page *Page) BuildScript(assetRoot string) (string, error) { + + if page.Codes.JS.Code == "" && page.Codes.TS.Code == "" { + return "", nil + } + + if page.Codes.TS.Code != "" { + res, err := page.CompileTS([]byte(page.Codes.TS.Code), false) + if err != nil { + return "", err + } + + return fmt.Sprintf("\n", res), nil + } + + code := strings.ReplaceAll(page.Codes.JS.Code, "@assets", assetRoot) + res, err := page.CompileCSS([]byte(code), false) + if err != nil { + return "", err + } + + return fmt.Sprintf("\n", res), nil +} diff --git a/sui/core/core.go b/sui/core/core.go index e4a2095e..7b86132e 100644 --- a/sui/core/core.go +++ b/sui/core/core.go @@ -12,7 +12,7 @@ func Load(file string, id string) (*DSL, error) { return nil, err } - dsl := DSL{} + dsl := DSL{ID: id} err = application.Parse(file, data, &dsl) if err != nil { return nil, err diff --git a/sui/core/data.go b/sui/core/data.go index db1f09bf..1e2f5d0c 100644 --- a/sui/core/data.go +++ b/sui/core/data.go @@ -22,7 +22,7 @@ func (page *Page) Data(request *Request) (map[string]interface{}, map[string]int return nil, setting, nil } -// RenderHTML render for the html -func (page *Page) renderData(html string, data map[string]interface{}, warnings []string) (string, error) { +// Render render for the html +func (page *Page) Render(html string, data map[string]interface{}, warnings []string) (string, error) { return html, nil } diff --git a/sui/core/editor.go b/sui/core/editor.go index 2b11c9c9..dbe109b0 100644 --- a/sui/core/editor.go +++ b/sui/core/editor.go @@ -61,7 +61,7 @@ func (page *Page) EditorRender(request *Request) (*ResponseEditorRender, error) } if html != "" { - html, err := page.renderData(html, data, res.Warnings) + html, err := page.Render(html, data, res.Warnings) if err != nil { res.Warnings = append(res.Warnings, err.Error()) } diff --git a/sui/core/interfaces.go b/sui/core/interfaces.go index 59caf9ae..66a7dffd 100644 --- a/sui/core/interfaces.go +++ b/sui/core/interfaces.go @@ -49,6 +49,8 @@ type IPage interface { EditorStyleSource() SourceData EditorDataSource() SourceData + PreviewRender(request *Request) (string, error) + AssetScript() (*Asset, error) AssetStyle() (*Asset, error) } diff --git a/sui/core/preview.go b/sui/core/preview.go new file mode 100644 index 00000000..1e8a5ff7 --- /dev/null +++ b/sui/core/preview.go @@ -0,0 +1,84 @@ +package core + +import ( + "fmt" +) + +// PreviewRender render HTML for the preview +func (page *Page) PreviewRender(request *Request) (string, error) { + + warnings := []string{} + html, err := page.BuildHTML(request.AssetRoot) + if err != nil { + warnings = append(warnings, err.Error()) + } + + data, _, err := page.Data(request) + if err != nil { + warnings = append(warnings, err.Error()) + } + + html, err = page.Render(html, data, warnings) + if err != nil { + warnings = append(warnings, err.Error()) + } + + // Add Style & Script & Warning + doc, err := NewDocument([]byte(html)) + if err != nil { + warnings = append(warnings, err.Error()) + } + + // Add Style + style, err := page.BuildStyle(request.AssetRoot) + if err != nil { + warnings = append(warnings, err.Error()) + } + doc.Selection.Find("head").AppendHtml(style) + + // Add Script + script, err := page.BuildScript(request.AssetRoot) + if err != nil { + warnings = append(warnings, err.Error()) + } + doc.Selection.Find("body").AppendHtml(script) + + // Add Frame Height + if request.Referer != "" { + doc.Selection.Find("body").AppendHtml(` + + `) + } + + // Add Warning + if len(warnings) > 0 { + warningHTML := "
" + for _, warning := range warnings { + warningHTML += fmt.Sprintf("
%s
", warning) + } + warningHTML += "
" + doc.Selection.Find("body").AppendHtml(warningHTML) + } + + return doc.Html() +} diff --git a/sui/core/types.go b/sui/core/types.go index 36742150..52ee09a4 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -10,12 +10,14 @@ type DSL struct { // Page is the struct for the page type Page struct { - Route string `json:"route"` - Name string `json:"name,omitempty"` - Config *PageConfig `json:"-"` - Path string `json:"-"` - Codes SourceCodes `json:"-"` - Document []byte `json:"-"` + Route string `json:"route"` + Name string `json:"name,omitempty"` + TemplateID string `json:"-"` + SuiID string `json:"-"` + Config *PageConfig `json:"-"` + Path string `json:"-"` + Codes SourceCodes `json:"-"` + Document []byte `json:"-"` } // PageTreeNode is the struct for the page tree node @@ -76,14 +78,16 @@ type Asset struct { // Request is the struct for the request type Request struct { - Method string `json:"method"` - Payload map[string]interface{} `json:"payload,omitempty"` - Query map[string][]string `json:"query,omitempty"` - Params map[string]string `json:"params,omitempty"` - Headers map[string][]string `json:"headers,omitempty"` - Body interface{} `json:"body,omitempty"` - Theme string `json:"theme,omitempty"` - Locale string `json:"locale,omitempty"` + Method string `json:"method"` + AssetRoot string `json:"asset_root,omitempty"` + Referer string `json:"referer,omitempty"` + Payload map[string]interface{} `json:"payload,omitempty"` + Query map[string][]string `json:"query,omitempty"` + Params map[string]string `json:"params,omitempty"` + Headers map[string][]string `json:"headers,omitempty"` + Body interface{} `json:"body,omitempty"` + Theme string `json:"theme,omitempty"` + Locale string `json:"locale,omitempty"` } // RequestSource is the struct for the request diff --git a/sui/storages/local/page.go b/sui/storages/local/page.go index 08967aab..f171f2bc 100644 --- a/sui/storages/local/page.go +++ b/sui/storages/local/page.go @@ -213,9 +213,11 @@ func (tmpl *Template) CreatePage(route string) (core.IPage, error) { return &Page{ tmpl: tmpl, Page: &core.Page{ - Route: route, - Path: filepath.Join(tmpl.Root, route), - Name: name, + Route: route, + TemplateID: tmpl.ID, + SuiID: tmpl.local.ID, + Path: filepath.Join(tmpl.Root, route), + Name: name, Codes: core.SourceCodes{ HTML: core.Source{File: fmt.Sprintf("%s.html", name)}, CSS: core.Source{File: fmt.Sprintf("%s.css", name)}, @@ -240,9 +242,11 @@ func (tmpl *Template) GetPageFromAsset(file string) (core.IPage, error) { return &Page{ tmpl: tmpl, Page: &core.Page{ - Route: route, - Path: filepath.Join(tmpl.Root, route), - Name: name, + Route: route, + TemplateID: tmpl.ID, + SuiID: tmpl.local.ID, + Path: filepath.Join(tmpl.Root, route), + Name: name, Codes: core.SourceCodes{ CSS: core.Source{File: fmt.Sprintf("%s.css", name)}, JS: core.Source{File: fmt.Sprintf("%s.js", name)}, @@ -264,9 +268,11 @@ func (tmpl *Template) getPage(route, file string) (core.IPage, error) { return &Page{ tmpl: tmpl, Page: &core.Page{ - Route: route, - Path: path, - Name: name, + Route: route, + Path: path, + Name: name, + TemplateID: tmpl.ID, + SuiID: tmpl.local.ID, Codes: core.SourceCodes{ HTML: core.Source{File: fmt.Sprintf("%s%s", name, filepath.Ext(file))}, CSS: core.Source{File: fmt.Sprintf("%s.css", name)}, diff --git a/sui/storages/local/page_render_test.go b/sui/storages/local/page_render_test.go new file mode 100644 index 00000000..96c9381f --- /dev/null +++ b/sui/storages/local/page_render_test.go @@ -0,0 +1,80 @@ +package local + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/yaoapp/yao/sui/core" +) + +func TestPageEditorRender(t *testing.T) { + + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + page, err := tmpl.Page("/index") + if err != nil { + t.Fatalf("Page error: %v", err) + } + + r := &core.Request{Method: "GET"} + res, err := page.EditorRender(r) + if err != nil { + t.Fatalf("EditorRender error: %v", err) + } + + assert.NotEmpty(t, res.HTML) + assert.NotEmpty(t, res.CSS) + assert.NotEmpty(t, res.Scripts) + assert.NotEmpty(t, res.Styles) + assert.Equal(t, 4, len(res.Scripts)) + assert.Equal(t, 5, len(res.Styles)) + + assert.Equal(t, "@assets/libs/tiny-slider/min/tiny-slider.js", res.Scripts[0]) + assert.Equal(t, "@assets/libs/feather-icons/feather.min.js", res.Scripts[1]) + assert.Equal(t, "@assets/js/plugins.init.js", res.Scripts[2]) + assert.Equal(t, "@pages/index/index.js", res.Scripts[3]) + + assert.Equal(t, "@assets/libs/tiny-slider/tiny-slider.css", res.Styles[0]) + assert.Equal(t, "@assets/libs/@iconscout/unicons/css/line.css", res.Styles[1]) + assert.Equal(t, "@assets/libs/@mdi/font/css/materialdesignicons.min.css", res.Styles[2]) + assert.Equal(t, "@assets/css/tailwind.css", res.Styles[3]) + assert.Equal(t, "@pages/index/index.css", res.Styles[4]) +} + +func TestPagePreviewRender(t *testing.T) { + + tests := prepare(t) + defer clean() + + tmpl, err := tests.Demo.GetTemplate("tech-blue") + if err != nil { + t.Fatalf("GetTemplate error: %v", err) + } + + page, err := tmpl.Page("/index") + if err != nil { + t.Fatalf("Page error: %v", err) + } + + r := &core.Request{ + Method: "GET", + AssetRoot: fmt.Sprintf("/api/__yao/sui/v1/%s/asset/%s/@assets", page.Get().SuiID, page.Get().TemplateID), + } + + html, err := page.PreviewRender(r) + if err != nil { + t.Fatalf("PreviewRender error: %v", err) + } + + assert.NotEmpty(t, html) + assert.Contains(t, html, "function Hello()") + assert.Contains(t, html, "color: #2c3e50;") + assert.Contains(t, html, "/api/__yao/sui/v1/demo/asset/tech-blue/@assets") +} diff --git a/sui/storages/local/page_test.go b/sui/storages/local/page_test.go index 0844e746..92799101 100644 --- a/sui/storages/local/page_test.go +++ b/sui/storages/local/page_test.go @@ -149,46 +149,6 @@ func TestTemplatePageJS(t *testing.T) { assert.Contains(t, err.Error(), "Page /the/page/could/not/be/found not found") } -func TestPageRenderEditor(t *testing.T) { - - tests := prepare(t) - defer clean() - - tmpl, err := tests.Demo.GetTemplate("tech-blue") - if err != nil { - t.Fatalf("GetTemplate error: %v", err) - } - - page, err := tmpl.Page("/index") - if err != nil { - t.Fatalf("Page error: %v", err) - } - - r := &core.Request{Method: "GET"} - res, err := page.EditorRender(r) - if err != nil { - t.Fatalf("RenderEditor error: %v", err) - } - - assert.NotEmpty(t, res.HTML) - assert.NotEmpty(t, res.CSS) - assert.NotEmpty(t, res.Scripts) - assert.NotEmpty(t, res.Styles) - assert.Equal(t, 4, len(res.Scripts)) - assert.Equal(t, 5, len(res.Styles)) - - assert.Equal(t, "@assets/libs/tiny-slider/min/tiny-slider.js", res.Scripts[0]) - assert.Equal(t, "@assets/libs/feather-icons/feather.min.js", res.Scripts[1]) - assert.Equal(t, "@assets/js/plugins.init.js", res.Scripts[2]) - assert.Equal(t, "@pages/index/index.js", res.Scripts[3]) - - assert.Equal(t, "@assets/libs/tiny-slider/tiny-slider.css", res.Styles[0]) - assert.Equal(t, "@assets/libs/@iconscout/unicons/css/line.css", res.Styles[1]) - assert.Equal(t, "@assets/libs/@mdi/font/css/materialdesignicons.min.css", res.Styles[2]) - assert.Equal(t, "@assets/css/tailwind.css", res.Styles[3]) - assert.Equal(t, "@pages/index/index.css", res.Styles[4]) -} - func TestPageSaveTempBoard(t *testing.T) { tests := prepare(t)