commit
1c1b551715
23 changed files with 1463 additions and 7 deletions
2
.github/workflows/pr-test.yml
vendored
2
.github/workflows/pr-test.yml
vendored
|
|
@ -140,7 +140,7 @@ jobs:
|
||||||
- name: Checkout Demo App
|
- name: Checkout Demo App
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: yaoapp/demo-app
|
repository: yaoapp/yao-dev-app
|
||||||
path: app
|
path: app
|
||||||
|
|
||||||
- name: Move Kun, Xun, Gou, V8Go
|
- name: Move Kun, Xun, Gou, V8Go
|
||||||
|
|
|
||||||
2
.github/workflows/release-linux.yml
vendored
2
.github/workflows/release-linux.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: [1.19.2]
|
go: [1.19.2]
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-20.04"
|
||||||
steps:
|
steps:
|
||||||
- name: Arm Build
|
- name: Arm Build
|
||||||
run: |
|
run: |
|
||||||
|
|
|
||||||
2
.github/workflows/unit-test.yml
vendored
2
.github/workflows/unit-test.yml
vendored
|
|
@ -97,7 +97,7 @@ jobs:
|
||||||
- name: Checkout Demo App
|
- name: Checkout Demo App
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
with:
|
with:
|
||||||
repository: yaoapp/demo-app
|
repository: yaoapp/yao-dev-app
|
||||||
path: app
|
path: app
|
||||||
|
|
||||||
- name: Move Kun, Xun, Gou, V8Go
|
- name: Move Kun, Xun, Gou, V8Go
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/yaoapp/yao/widgets/chart"
|
"github.com/yaoapp/yao/widgets/chart"
|
||||||
"github.com/yaoapp/yao/widgets/form"
|
"github.com/yaoapp/yao/widgets/form"
|
||||||
|
"github.com/yaoapp/yao/widgets/list"
|
||||||
"github.com/yaoapp/yao/widgets/table"
|
"github.com/yaoapp/yao/widgets/table"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -18,6 +19,7 @@ var Guards = map[string]gin.HandlerFunc{
|
||||||
"cross-origin": guardCrossOrigin, // Cross-Origin Resource Sharing
|
"cross-origin": guardCrossOrigin, // Cross-Origin Resource Sharing
|
||||||
"table-guard": table_v0.Guard, // Table Guard ( v0.9 table)
|
"table-guard": table_v0.Guard, // Table Guard ( v0.9 table)
|
||||||
"widget-table": table.Guard, // Widget Table Guard
|
"widget-table": table.Guard, // Widget Table Guard
|
||||||
|
"widget-list": list.Guard, // Widget List Guard
|
||||||
"widget-form": form.Guard, // Widget Form Guard
|
"widget-form": form.Guard, // Widget Form Guard
|
||||||
"widget-chart": chart.Guard, // Widget Chart Guard
|
"widget-chart": chart.Guard, // Widget Chart Guard
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/yaoapp/yao/i18n"
|
"github.com/yaoapp/yao/i18n"
|
||||||
"github.com/yaoapp/yao/runtime"
|
"github.com/yaoapp/yao/runtime"
|
||||||
"github.com/yaoapp/yao/script"
|
"github.com/yaoapp/yao/script"
|
||||||
|
_ "github.com/yaoapp/yao/utils"
|
||||||
"github.com/yaoapp/yao/widgets/login"
|
"github.com/yaoapp/yao/widgets/login"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -224,7 +225,10 @@ func TestProcessMenu(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Len(t, res, 3)
|
|
||||||
|
data := any.Of(res).MapStr()
|
||||||
|
assert.True(t, data.Has("items"))
|
||||||
|
assert.True(t, data.Has("setting"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProcessIcons(t *testing.T) {
|
func TestProcessIcons(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ func TestLoad(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, 9, len(Forms))
|
assert.Equal(t, 10, len(Forms))
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepare(t *testing.T, language ...string) {
|
func prepare(t *testing.T, language ...string) {
|
||||||
|
|
|
||||||
93
widgets/list/action.go
Normal file
93
widgets/list/action.go
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/yao/widgets/action"
|
||||||
|
"github.com/yaoapp/yao/widgets/hook"
|
||||||
|
"github.com/yaoapp/yao/widgets/table"
|
||||||
|
)
|
||||||
|
|
||||||
|
var processActionDefaults = map[string]*action.Process{
|
||||||
|
|
||||||
|
"Setting": {
|
||||||
|
Name: "yao.list.Setting",
|
||||||
|
Guard: "bearer-jwt",
|
||||||
|
Process: "yao.list.Xgen",
|
||||||
|
Default: []interface{}{nil},
|
||||||
|
},
|
||||||
|
"Component": {
|
||||||
|
Name: "yao.list.Component",
|
||||||
|
Guard: "bearer-jwt",
|
||||||
|
Default: []interface{}{nil, nil, nil},
|
||||||
|
},
|
||||||
|
"Upload": {
|
||||||
|
Name: "yao.list.Upload",
|
||||||
|
Guard: "bearer-jwt",
|
||||||
|
Default: []interface{}{nil, nil, nil},
|
||||||
|
},
|
||||||
|
"Download": {
|
||||||
|
Name: "yao.list.Download",
|
||||||
|
Guard: "-",
|
||||||
|
Process: "fs.system.Download",
|
||||||
|
Default: []interface{}{nil},
|
||||||
|
},
|
||||||
|
"Get": {
|
||||||
|
Name: "yao.list.Get",
|
||||||
|
Guard: "bearer-jwt",
|
||||||
|
Default: []interface{}{nil},
|
||||||
|
},
|
||||||
|
"Save": {
|
||||||
|
Name: "yao.list.Save",
|
||||||
|
Guard: "bearer-jwt",
|
||||||
|
Default: []interface{}{nil},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaultProcess set the default value of action
|
||||||
|
func (act *ActionDSL) SetDefaultProcess() {
|
||||||
|
|
||||||
|
act.Setting = action.ProcessOf(act.Setting).
|
||||||
|
Merge(processActionDefaults["Setting"]).
|
||||||
|
SetHandler(processHandler)
|
||||||
|
|
||||||
|
act.Component = action.ProcessOf(act.Component).
|
||||||
|
Merge(processActionDefaults["Component"]).
|
||||||
|
SetHandler(processHandler)
|
||||||
|
|
||||||
|
act.Upload = action.ProcessOf(act.Upload).
|
||||||
|
Merge(processActionDefaults["Upload"]).
|
||||||
|
SetHandler(processHandler)
|
||||||
|
|
||||||
|
act.Download = action.ProcessOf(act.Download).
|
||||||
|
Merge(processActionDefaults["Download"]).
|
||||||
|
SetHandler(processHandler)
|
||||||
|
|
||||||
|
act.Save = action.ProcessOf(act.Save).
|
||||||
|
WithBefore(act.BeforeSave).WithAfter(act.AfterSave).
|
||||||
|
Merge(processActionDefaults["Save"]).
|
||||||
|
SetHandler(processHandler)
|
||||||
|
|
||||||
|
act.Get = action.ProcessOf(act.Get).
|
||||||
|
WithBefore(act.BeforeSave).WithAfter(act.AfterSave).
|
||||||
|
Merge(processActionDefaults["Get"]).
|
||||||
|
SetHandler(processHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindModel bind model
|
||||||
|
func (act *ActionDSL) BindModel(m *gou.Model) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindTable bind table
|
||||||
|
func (act *ActionDSL) BindTable(tab *table.DSL) error {
|
||||||
|
|
||||||
|
// Copy Hooks
|
||||||
|
hook.CopyBefore(act.BeforeSave, tab.Action.BeforeSave)
|
||||||
|
|
||||||
|
hook.CopyAfter(act.AfterSave, tab.Action.AfterSave)
|
||||||
|
|
||||||
|
// Merge Actions
|
||||||
|
act.Save.Merge(tab.Action.Save)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
162
widgets/list/api.go
Normal file
162
widgets/list/api.go
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
|
"github.com/yaoapp/yao/widgets/action"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Guard list widget guard
|
||||||
|
func Guard(c *gin.Context) {
|
||||||
|
|
||||||
|
id := c.Param("id")
|
||||||
|
if id == "" {
|
||||||
|
abort(c, 400, "the list widget id does not found")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
list, has := Lists[id]
|
||||||
|
if !has {
|
||||||
|
abort(c, 404, fmt.Sprintf("the list widget %s does not exist", id))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
act, err := list.getAction(c.FullPath())
|
||||||
|
if err != nil {
|
||||||
|
abort(c, 404, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = act.UseGuard(c, id)
|
||||||
|
if err != nil {
|
||||||
|
abort(c, 400, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func abort(c *gin.Context, code int, message string) {
|
||||||
|
c.JSON(code, gin.H{"code": code, "message": message})
|
||||||
|
c.Abort()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (list *DSL) getAction(path string) (*action.Process, error) {
|
||||||
|
|
||||||
|
switch path {
|
||||||
|
case "/api/__yao/list/:id/setting":
|
||||||
|
return list.Action.Setting, nil
|
||||||
|
case "/api/__yao/list/:id/component/:xpath/:method":
|
||||||
|
return list.Action.Component, nil
|
||||||
|
case "/api/__yao/list/:id/upload/:xpath/:method":
|
||||||
|
return list.Action.Upload, nil
|
||||||
|
case "/api/__yao/list/:id/download/:field":
|
||||||
|
return list.Action.Download, nil
|
||||||
|
case "/api/__yao/list/:id/save":
|
||||||
|
return list.Action.Save, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("the list widget %s %s action does not exist", list.ID, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// export API
|
||||||
|
func exportAPI() error {
|
||||||
|
|
||||||
|
http := gou.HTTP{
|
||||||
|
Name: "Widget List API",
|
||||||
|
Description: "Widget List API",
|
||||||
|
Version: share.VERSION,
|
||||||
|
Guard: "widget-list",
|
||||||
|
Group: "__yao/list",
|
||||||
|
Paths: []gou.Path{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /api/__yao/list/:id/setting -> Default process: yao.list.Xgen
|
||||||
|
path := gou.Path{
|
||||||
|
Label: "Setting",
|
||||||
|
Description: "Setting",
|
||||||
|
Path: "/:id/setting",
|
||||||
|
Method: "GET",
|
||||||
|
Process: "yao.list.Setting",
|
||||||
|
In: []string{"$param.id"},
|
||||||
|
Out: gou.Out{Status: 200, Type: "application/json"},
|
||||||
|
}
|
||||||
|
http.Paths = append(http.Paths, path)
|
||||||
|
|
||||||
|
// GET /api/__yao/list/:id/find -> Default process: yao.list.Get $param.id :query
|
||||||
|
path = gou.Path{
|
||||||
|
Label: "Get",
|
||||||
|
Description: "Get",
|
||||||
|
Path: "/:id/get",
|
||||||
|
Method: "GET",
|
||||||
|
Process: "yao.list.Find",
|
||||||
|
In: []string{"$param.id", ":query-param"},
|
||||||
|
Out: gou.Out{Status: 200, Type: "application/json"},
|
||||||
|
}
|
||||||
|
http.Paths = append(http.Paths, path)
|
||||||
|
|
||||||
|
// GET /api/__yao/list/:id/component/:xpath/:method -> Default process: yao.list.Component $param.id $param.xpath $param.method :query
|
||||||
|
path = gou.Path{
|
||||||
|
Label: "Component",
|
||||||
|
Description: "Component",
|
||||||
|
Path: "/:id/component/:xpath/:method",
|
||||||
|
Method: "GET",
|
||||||
|
Process: "yao.list.Component",
|
||||||
|
In: []string{"$param.id", "$param.xpath", "$param.method", ":query"},
|
||||||
|
Out: gou.Out{Status: 200, Type: "application/json"},
|
||||||
|
}
|
||||||
|
http.Paths = append(http.Paths, path)
|
||||||
|
|
||||||
|
// POST /api/__yao/table/:id/upload/:xpath/:method -> Default process: yao.list.Upload $param.id $param.xpath $param.method $file.file
|
||||||
|
path = gou.Path{
|
||||||
|
Label: "Upload",
|
||||||
|
Description: "Upload",
|
||||||
|
Path: "/:id/upload/:xpath/:method",
|
||||||
|
Method: "POST",
|
||||||
|
Process: "yao.list.Upload",
|
||||||
|
In: []string{"$param.id", "$param.xpath", "$param.method", "$file.file"},
|
||||||
|
Out: gou.Out{Status: 200, Type: "application/json"},
|
||||||
|
}
|
||||||
|
http.Paths = append(http.Paths, path)
|
||||||
|
|
||||||
|
// GET /api/__yao/list/:id/download/:field -> Default process: yao.list.Download $param.id $param.xpath $param.field $query.name $query.token
|
||||||
|
path = gou.Path{
|
||||||
|
Label: "Download",
|
||||||
|
Description: "Download",
|
||||||
|
Path: "/:id/download/:field",
|
||||||
|
Method: "GET",
|
||||||
|
Process: "yao.list.Download",
|
||||||
|
In: []string{"$param.id", "$param.field", "$query.name", "$query.token"},
|
||||||
|
Out: gou.Out{
|
||||||
|
Status: 200,
|
||||||
|
Body: "{{content}}",
|
||||||
|
Headers: map[string]string{"Content-Type": "{{type}}"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
http.Paths = append(http.Paths, path)
|
||||||
|
|
||||||
|
// POST /api/__yao/list/:id/save -> Default process: yao.list.Save $param.id :payload
|
||||||
|
path = gou.Path{
|
||||||
|
Label: "Save",
|
||||||
|
Description: "Save",
|
||||||
|
Path: "/:id/save",
|
||||||
|
Method: "POST",
|
||||||
|
Process: "yao.list.Save",
|
||||||
|
In: []string{"$param.id", ":payload"},
|
||||||
|
Out: gou.Out{Status: 200, Type: "application/json"},
|
||||||
|
}
|
||||||
|
http.Paths = append(http.Paths, path)
|
||||||
|
|
||||||
|
// api source
|
||||||
|
source, err := jsoniter.Marshal(http)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// load apis
|
||||||
|
_, err = gou.LoadAPIReturn(string(source), "widgets.list")
|
||||||
|
return err
|
||||||
|
}
|
||||||
85
widgets/list/bind.go
Normal file
85
widgets/list/bind.go
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/yao/widgets/table"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Bind model / store / table / ...
|
||||||
|
func (dsl *DSL) Bind() error {
|
||||||
|
|
||||||
|
if dsl.Action.Bind == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsl.Action.Bind.Model != "" {
|
||||||
|
return dsl.bindModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsl.Action.Bind.Store != "" {
|
||||||
|
return dsl.bindStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsl.Action.Bind.Table != "" {
|
||||||
|
return dsl.bindTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) bindModel() error {
|
||||||
|
|
||||||
|
id := dsl.Action.Bind.Model
|
||||||
|
m, has := gou.Models[id]
|
||||||
|
if !has {
|
||||||
|
return fmt.Errorf("%s does not exist", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
dsl.Action.BindModel(m)
|
||||||
|
dsl.Fields.BindModel(m)
|
||||||
|
// dsl.Layout.BindModel(m, dsl.ID, dsl.Fields, dsl.Action.Bind.Option)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) bindTable() error {
|
||||||
|
id := dsl.Action.Bind.Table
|
||||||
|
|
||||||
|
// Load table
|
||||||
|
if _, has := table.Tables[id]; !has {
|
||||||
|
if err := table.LoadID(id, dsl.Root); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tab, err := table.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind Fields
|
||||||
|
err = dsl.Fields.BindTable(tab)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind Actions
|
||||||
|
err = dsl.Action.BindTable(tab)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind Layout
|
||||||
|
err = dsl.Layout.BindTable(tab, dsl.ID, dsl.Fields)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) bindStore() error {
|
||||||
|
id := dsl.Action.Bind.Store
|
||||||
|
return fmt.Errorf("bind.store %s does not support yet", id)
|
||||||
|
}
|
||||||
61
widgets/list/compute.go
Normal file
61
widgets/list/compute.go
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/yaoapp/yao/widgets/compute"
|
||||||
|
"github.com/yaoapp/yao/widgets/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (dsl *DSL) getField() func(string) (*field.ColumnDSL, string, string, error) {
|
||||||
|
return func(name string) (*field.ColumnDSL, string, string, error) {
|
||||||
|
field, has := dsl.Fields.List[name]
|
||||||
|
if !has {
|
||||||
|
return nil, "fields.list", dsl.ID, fmt.Errorf("fields.list.%s does not exist", name)
|
||||||
|
}
|
||||||
|
return &field, "fields.list", dsl.ID, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) computeMapping() error {
|
||||||
|
if dsl.Computes == nil {
|
||||||
|
dsl.Computes = &compute.Maps{
|
||||||
|
Filter: map[string][]compute.Unit{},
|
||||||
|
Edit: map[string][]compute.Unit{},
|
||||||
|
View: map[string][]compute.Unit{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsl.Fields == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsl.Fields.List != nil && dsl.Layout.List != nil {
|
||||||
|
|
||||||
|
for _, inst := range dsl.Layout.List.Columns {
|
||||||
|
|
||||||
|
if field, has := dsl.Fields.List[inst.Name]; has {
|
||||||
|
|
||||||
|
// View
|
||||||
|
if field.View != nil && field.View.Compute != nil {
|
||||||
|
bind := field.ViewBind()
|
||||||
|
if _, has := dsl.Computes.View[bind]; !has {
|
||||||
|
dsl.Computes.View[bind] = []compute.Unit{}
|
||||||
|
}
|
||||||
|
dsl.Computes.View[bind] = append(dsl.Computes.View[bind], compute.Unit{Name: inst.Name, Kind: compute.View})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit
|
||||||
|
if field.Edit != nil && field.Edit.Compute != nil {
|
||||||
|
bind := field.EditBind()
|
||||||
|
if _, has := dsl.Computes.Edit[bind]; !has {
|
||||||
|
dsl.Computes.Edit[bind] = []compute.Unit{}
|
||||||
|
}
|
||||||
|
dsl.Computes.Edit[bind] = append(dsl.Computes.Edit[bind], compute.Unit{Name: inst.Name, Kind: compute.Edit})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
7
widgets/list/export.go
Normal file
7
widgets/list/export.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
// Export process & api
|
||||||
|
func Export() error {
|
||||||
|
exportProcess()
|
||||||
|
return exportAPI()
|
||||||
|
}
|
||||||
121
widgets/list/fields.go
Normal file
121
widgets/list/fields.go
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/yao/widgets/table"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BindModel bind model
|
||||||
|
func (fields *FieldsDSL) BindModel(m *gou.Model) error {
|
||||||
|
|
||||||
|
// fields.listMap = map[string]field.ColumnDSL{}
|
||||||
|
|
||||||
|
// trans, err := field.ModelTranslist()
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for _, col := range m.Columns {
|
||||||
|
// data := col.Map()
|
||||||
|
// listField, err := trans.List(col.Type, data)
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // append columns
|
||||||
|
// if _, has := fields.List[listField.Key]; !has {
|
||||||
|
// fields.List[listField.Key] = *listField
|
||||||
|
|
||||||
|
// // PASSWORD Fields
|
||||||
|
// if col.Crypt == "PASSWORD" {
|
||||||
|
// if fields.List[listField.Key].View != nil {
|
||||||
|
// fields.List[listField.Key].View.Compute = &component.Compute{
|
||||||
|
// Process: "Hide",
|
||||||
|
// Args: []component.CArg{component.NewExp("value")},
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if fields.List[listField.Key].Edit != nil {
|
||||||
|
// fields.List[listField.Key].Edit.Props["type"] = "password"
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// fields.listMap[col.Name] = fields.List[listField.Key]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindTable bind table
|
||||||
|
func (fields *FieldsDSL) BindTable(tab *table.DSL) error {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Bind tab
|
||||||
|
// if fields.List == nil || len(fields.List) == 0 {
|
||||||
|
// fields.List = field.Columns{}
|
||||||
|
// fields.listMap = map[string]field.ColumnDSL{}
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if tab.Fields.Table != nil {
|
||||||
|
// for key, list := range tab.Fields.Table {
|
||||||
|
// if list.Edit == nil {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if _, has := fields.List[key]; !has {
|
||||||
|
// edit := *list.Edit
|
||||||
|
// fields.List[key] = field.ColumnDSL{Key: key, Bind: list.Bind, Edit: &edit}
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// mapping := tab.Fields.TableMap()
|
||||||
|
// for name, list := range mapping {
|
||||||
|
// if _, has := fields.listMap[name]; !has {
|
||||||
|
// if list.Edit == nil {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// fields.listMap[name] = fields.List[name]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// }
|
||||||
|
// return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Xgen trans to xgen setting
|
||||||
|
func (fields *FieldsDSL) Xgen(layout *LayoutDSL) (map[string]interface{}, error) {
|
||||||
|
res := map[string]interface{}{}
|
||||||
|
lists := map[string]interface{}{}
|
||||||
|
messages := []string{}
|
||||||
|
if layout.List != nil && layout.List.Columns != nil {
|
||||||
|
|
||||||
|
for i, f := range layout.List.Columns {
|
||||||
|
name := f.Name
|
||||||
|
field, has := fields.List[name]
|
||||||
|
if !has {
|
||||||
|
if strings.HasPrefix(f.Name, "::") {
|
||||||
|
name = fmt.Sprintf("$L(%s)", strings.TrimPrefix(f.Name, "::"))
|
||||||
|
if field, has = fields.List[name]; has {
|
||||||
|
lists[name] = field.Map()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("layout.columns[%d]", i)
|
||||||
|
messages = append(messages, fmt.Sprintf("fields.list.%s not found, checking %s", f.Name, path))
|
||||||
|
}
|
||||||
|
lists[name] = field.Map()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
return nil, fmt.Errorf(strings.Join(messages, ";\n"))
|
||||||
|
}
|
||||||
|
res["list"] = lists
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
121
widgets/list/handler.go
Normal file
121
widgets/list/handler.go
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/i18n"
|
||||||
|
"github.com/yaoapp/yao/widgets/action"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ********************************
|
||||||
|
// * Execute the process of list *
|
||||||
|
// ********************************
|
||||||
|
// Life-Circle: Before Hook → Compute Edit → Run Process → Compute View → After Hook
|
||||||
|
// Execute Compute Edit On: Save
|
||||||
|
// Execute Compute View On: Get
|
||||||
|
func processHandler(p *action.Process, process *gou.Process) (interface{}, error) {
|
||||||
|
|
||||||
|
list, err := Get(process)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
args := p.Args(process)
|
||||||
|
|
||||||
|
// Process
|
||||||
|
name := p.Process
|
||||||
|
if name == "" {
|
||||||
|
name = p.ProcessBind
|
||||||
|
}
|
||||||
|
|
||||||
|
if name == "" {
|
||||||
|
log.Error("[list] %s %s process is required", list.ID, p.Name)
|
||||||
|
return nil, fmt.Errorf("[list] %s %s process is required", list.ID, p.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before Hook
|
||||||
|
if p.Before != nil {
|
||||||
|
log.Trace("[list] %s %s before: exec(%v)", list.ID, p.Name, args)
|
||||||
|
newArgs, err := p.Before.Exec(args, process.Sid, process.Global)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[list] %s %s before: %s", list.ID, p.Name, err.Error())
|
||||||
|
} else {
|
||||||
|
log.Trace("[list] %s %s before: args:%v", list.ID, p.Name, args)
|
||||||
|
args = newArgs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute Edit
|
||||||
|
err = list.ComputeEdit(p.Name, process, args, list.getField())
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[list] %s %s Compute Edit Error: %s", list.ID, p.Name, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute Process
|
||||||
|
act, err := gou.ProcessOf(name, args...)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[list] %s %s -> %s %s", list.ID, p.Name, name, err.Error())
|
||||||
|
return nil, fmt.Errorf("[list] %s %s -> %s %s", list.ID, p.Name, name, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := act.WithGlobal(process.Global).WithSID(process.Sid).Exec()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[list] %s %s -> %s %s", list.ID, p.Name, name, err.Error())
|
||||||
|
return nil, fmt.Errorf("[list] %s %s -> %s %s", list.ID, p.Name, name, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute View
|
||||||
|
err = list.ComputeView(p.Name, process, res, list.getField())
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[list] %s %s Compute View Error: %s", list.ID, p.Name, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// After hook
|
||||||
|
if p.After != nil {
|
||||||
|
log.Trace("[list] %s %s after: exec(%v)", list.ID, p.Name, res)
|
||||||
|
newRes, err := p.After.Exec(res, process.Sid, process.Global)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[list] %s %s after: %s", list.ID, p.Name, err.Error())
|
||||||
|
} else {
|
||||||
|
log.Trace("[list] %s %s after: %v", list.ID, p.Name, newRes)
|
||||||
|
res = newRes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tranlate the result
|
||||||
|
newRes, err := list.translate(p.Name, process, res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("[list] %s %s Translate Error: %s", list.ID, p.Name, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// translateSetting
|
||||||
|
func (dsl *DSL) translate(name string, process *gou.Process, data interface{}) (interface{}, error) {
|
||||||
|
|
||||||
|
if strings.ToLower(name) != "yao.list.setting" {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
widgets := []string{}
|
||||||
|
if dsl.Action.Bind.Model != "" {
|
||||||
|
m := gou.Select(dsl.Action.Bind.Model)
|
||||||
|
widgets = append(widgets, fmt.Sprintf("model.%s", m.ID))
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsl.Action.Bind.Table != "" {
|
||||||
|
widgets = append(widgets, fmt.Sprintf("table.%s", dsl.Action.Bind.Table))
|
||||||
|
}
|
||||||
|
|
||||||
|
widgets = append(widgets, fmt.Sprintf("list.%s", dsl.ID))
|
||||||
|
res, err := i18n.Trans(process.Lang(config.Conf.Lang), widgets, data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
135
widgets/list/layout.go
Normal file
135
widgets/list/layout.go
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/yao/widgets/table"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BindModel bind model
|
||||||
|
func (layout *LayoutDSL) BindModel(m *gou.Model, listID string, fields *FieldsDSL, option map[string]interface{}) {
|
||||||
|
// if layout.Primary == "" {
|
||||||
|
// layout.Primary = m.PrimaryKey
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if layout.Operation == nil {
|
||||||
|
// layout.Operation = &OperationLayoutDSL{
|
||||||
|
// Preset: map[string]map[string]interface{}{"save": {}, "back": {}},
|
||||||
|
// Actions: []component.ActionDSL{
|
||||||
|
// {
|
||||||
|
// Title: "::Delete",
|
||||||
|
// Icon: "icon-trash-2",
|
||||||
|
// Style: "danger",
|
||||||
|
// Action: map[string]component.ParamsDSL{
|
||||||
|
// "List.delete": {"model": listID},
|
||||||
|
// },
|
||||||
|
// Confirm: &component.ConfirmActionDSL{
|
||||||
|
// Title: "::Confirm",
|
||||||
|
// Desc: "::Please confirm, the data cannot be recovered",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if layout.List == nil && len(fields.List) > 0 {
|
||||||
|
// layout.List = &ViewLayoutDSL{
|
||||||
|
// Props: component.PropsDSL{},
|
||||||
|
// Sections: []SectionDSL{{Columns: []Column{}}},
|
||||||
|
// }
|
||||||
|
|
||||||
|
// columns := []Column{}
|
||||||
|
// for _, namev := range m.ColumnNames {
|
||||||
|
// name, ok := namev.(string)
|
||||||
|
// if ok && name != "deleted_at" {
|
||||||
|
// if col, has := fields.listMap[name]; has {
|
||||||
|
// width := 12
|
||||||
|
// if col.Edit != nil && (col.Edit.Type == "TextArea" || col.Edit.Type == "Upload") {
|
||||||
|
// width = 24
|
||||||
|
// }
|
||||||
|
// // if c, has := m.Columns[name]; has {
|
||||||
|
// // typ := strings.ToLower(c.Type)
|
||||||
|
// // if typ == "id" || strings.Contains(typ, "integer") || strings.Contains(typ, "float") {
|
||||||
|
// // width = 6
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// columns = append(columns, Column{InstanceDSL: component.InstanceDSL{Name: col.Key, Width: width}})
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// layout.List.Sections = []SectionDSL{{Columns: columns}}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindTable bind table
|
||||||
|
func (layout *LayoutDSL) BindTable(tab *table.DSL, listID string, fields *FieldsDSL) error {
|
||||||
|
|
||||||
|
// if layout.Primary == "" {
|
||||||
|
// layout.Primary = tab.Layout.Primary
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if layout.Operation == nil {
|
||||||
|
// layout.Operation = &OperationLayoutDSL{
|
||||||
|
// Preset: map[string]map[string]interface{}{"save": {}, "back": {}},
|
||||||
|
// Actions: []component.ActionDSL{
|
||||||
|
// {
|
||||||
|
// Title: "::Delete",
|
||||||
|
// Icon: "icon-trash-2",
|
||||||
|
// Style: "danger",
|
||||||
|
// Action: map[string]component.ParamsDSL{
|
||||||
|
// "List.delete": {"model": listID},
|
||||||
|
// },
|
||||||
|
// Confirm: &component.ConfirmActionDSL{
|
||||||
|
// Title: "::Confirm",
|
||||||
|
// Desc: "::Please confirm, the data cannot be recovered",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if layout.List == nil &&
|
||||||
|
// tab.Layout != nil && tab.Layout.Table != nil && tab.Layout.Table.Columns != nil &&
|
||||||
|
// len(tab.Layout.Table.Columns) > 0 {
|
||||||
|
|
||||||
|
// layout.List = &ViewLayoutDSL{
|
||||||
|
// Props: component.PropsDSL{},
|
||||||
|
// Sections: []SectionDSL{{Columns: []Column{}}},
|
||||||
|
// }
|
||||||
|
|
||||||
|
// columns := []Column{}
|
||||||
|
// for _, column := range tab.Fields.Table {
|
||||||
|
// if column.Edit == nil {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
|
||||||
|
// name := column.Key
|
||||||
|
// if col, has := fields.List[name]; has && column.Bind != "deleted_at" {
|
||||||
|
// width := 12
|
||||||
|
// if col.Edit != nil && (col.Edit.Type == "TextArea" || col.Edit.Type == "Upload") {
|
||||||
|
// width = 24
|
||||||
|
// }
|
||||||
|
// columns = append(columns, Column{InstanceDSL: component.InstanceDSL{Name: col.Key, Width: width}})
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// layout.List.Sections = []SectionDSL{{Columns: columns}}
|
||||||
|
// }
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Xgen trans to Xgen setting
|
||||||
|
func (layout *LayoutDSL) Xgen() (map[string]interface{}, error) {
|
||||||
|
res := map[string]interface{}{}
|
||||||
|
data, err := jsoniter.Marshal(layout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jsoniter.Unmarshal(data, &res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
252
widgets/list/list.go
Normal file
252
widgets/list/list.go
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/kun/exception"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
|
"github.com/yaoapp/yao/widgets/component"
|
||||||
|
"github.com/yaoapp/yao/widgets/environment"
|
||||||
|
"github.com/yaoapp/yao/widgets/field"
|
||||||
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
// API:
|
||||||
|
// GET /api/__yao/list/:id/setting -> Default process: yao.list.Xgen
|
||||||
|
// GET /api/__yao/list/:id/get -> Default process: yao.list.Get $param.id :query
|
||||||
|
// GET /api/__yao/list/:id/component/:xpath/:method -> Default process: yao.list.Component $param.id $param.xpath $param.method :query
|
||||||
|
// POST /api/__yao/list/:id/save -> Default process: yao.list.Save $param.id :payload
|
||||||
|
// GET /api/__yao/list/:id/upload/:xpath/:method -> Default process: yao.list.Upload $param.id $param.xpath $param.method $file.file
|
||||||
|
// GET /api/__yao/list/:id/download/:field -> Default process: yao.list.Download $param.id $param.field $query.name $query.token
|
||||||
|
//
|
||||||
|
// Process:
|
||||||
|
// yao.list.Setting Return the App DSL
|
||||||
|
// yao.list.Xgen Return the Xgen setting
|
||||||
|
// yao.list.Component Return the result defined in props.xProps
|
||||||
|
// yao.list.Upload Upload file defined in props
|
||||||
|
// yao.list.Download Download file defined in props
|
||||||
|
// yao.list.Get Return the query record
|
||||||
|
// yao.list.Save Save a record
|
||||||
|
|
||||||
|
//
|
||||||
|
// Hook:
|
||||||
|
// before:get
|
||||||
|
// after:get
|
||||||
|
// before:save
|
||||||
|
// after:save
|
||||||
|
|
||||||
|
// Lists the loaded list widgets
|
||||||
|
var Lists map[string]*DSL = map[string]*DSL{}
|
||||||
|
|
||||||
|
// New create a new DSL
|
||||||
|
func New(id string) *DSL {
|
||||||
|
return &DSL{
|
||||||
|
ID: id,
|
||||||
|
Fields: &FieldsDSL{List: field.Columns{}},
|
||||||
|
Layout: &LayoutDSL{},
|
||||||
|
CProps: field.CloudProps{},
|
||||||
|
Config: map[string]interface{}{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadAndExport load list
|
||||||
|
func LoadAndExport(cfg config.Config) error {
|
||||||
|
err := Load(cfg)
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
}
|
||||||
|
return Export()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load load task
|
||||||
|
func Load(cfg config.Config) error {
|
||||||
|
var root = filepath.Join(cfg.Root, "lists")
|
||||||
|
return LoadFrom(root, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadFrom load from dir
|
||||||
|
func LoadFrom(dir string, prefix string) error {
|
||||||
|
|
||||||
|
if share.DirNotExists(dir) {
|
||||||
|
return fmt.Errorf("%s does not exists", dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
messages := []string{}
|
||||||
|
err := share.Walk(dir, ".json", func(root, filename string) {
|
||||||
|
id := prefix + share.ID(root, filename)
|
||||||
|
data, err := environment.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = LoadData(data, id, filepath.Dir(dir))
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if len(messages) > 0 {
|
||||||
|
return fmt.Errorf(strings.Join(messages, ";\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadID load via id
|
||||||
|
func LoadID(id string, root string) error {
|
||||||
|
dirs := strings.Split(id, ".")
|
||||||
|
name := fmt.Sprintf("%s.list.json", dirs[len(dirs)-1])
|
||||||
|
elems := []string{root}
|
||||||
|
elems = append(elems, dirs[0:len(dirs)-1]...)
|
||||||
|
elems = append(elems, "lists", name)
|
||||||
|
filename := filepath.Join(elems...)
|
||||||
|
data, err := environment.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[List] LoadID %s root=%s %s", id, root, err.Error())
|
||||||
|
}
|
||||||
|
return LoadData(data, id, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadData load via data
|
||||||
|
func LoadData(data []byte, id string, root string) error {
|
||||||
|
dsl := New(id)
|
||||||
|
dsl.Root = root
|
||||||
|
|
||||||
|
err := jsoniter.Unmarshal(data, dsl)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[List] LoadData %s %s", id, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsl.Action == nil {
|
||||||
|
dsl.Action = &ActionDSL{}
|
||||||
|
}
|
||||||
|
dsl.Action.SetDefaultProcess()
|
||||||
|
|
||||||
|
if dsl.Layout == nil {
|
||||||
|
dsl.Layout = &LayoutDSL{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsl.Fields == nil {
|
||||||
|
dsl.Fields = &FieldsDSL{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bind model / store / list / ...
|
||||||
|
err = dsl.Bind()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[List] LoadData Bind %s %s", id, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse
|
||||||
|
err = dsl.Parse()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[List] LoadData Parse %s %s", id, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
err = dsl.Validate()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[List] LoadData Validate %s %s", id, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
Lists[id] = dsl
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get list via process or id
|
||||||
|
func Get(list interface{}) (*DSL, error) {
|
||||||
|
id := ""
|
||||||
|
switch list.(type) {
|
||||||
|
case string:
|
||||||
|
id = list.(string)
|
||||||
|
case *gou.Process:
|
||||||
|
id = list.(*gou.Process).ArgsString(0)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%v type does not support", list)
|
||||||
|
}
|
||||||
|
|
||||||
|
t, has := Lists[id]
|
||||||
|
if !has {
|
||||||
|
return nil, fmt.Errorf("%s does not exist", id)
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustGet Get list via process or id thow error
|
||||||
|
func MustGet(list interface{}) *DSL {
|
||||||
|
t, err := Get(list)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 400).Throw()
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Layout
|
||||||
|
func (dsl *DSL) Parse() error {
|
||||||
|
|
||||||
|
// ComputeFields
|
||||||
|
err := dsl.computeMapping()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Columns
|
||||||
|
return dsl.Fields.List.CPropsMerge(dsl.CProps, func(name string, kind string, column field.ColumnDSL) (xpath string) {
|
||||||
|
return fmt.Sprintf("fields.list.%s.%s.props", name, kind)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Xgen trans to xgen setting
|
||||||
|
func (dsl *DSL) Xgen() (map[string]interface{}, error) {
|
||||||
|
|
||||||
|
if dsl.Layout == nil {
|
||||||
|
dsl.Layout = &LayoutDSL{List: &ViewLayoutDSL{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsl.Layout.List == nil {
|
||||||
|
dsl.Layout.List = &ViewLayoutDSL{}
|
||||||
|
}
|
||||||
|
|
||||||
|
setting, err := dsl.Layout.Xgen()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fields, err := dsl.Fields.Xgen(dsl.Layout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// full width default value
|
||||||
|
if _, has := dsl.Config["full"]; !has {
|
||||||
|
dsl.Config["full"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
setting["fields"] = fields
|
||||||
|
setting["config"] = dsl.Config
|
||||||
|
for _, cProp := range dsl.CProps {
|
||||||
|
err := cProp.Replace(setting, func(cProp component.CloudPropsDSL) interface{} {
|
||||||
|
|
||||||
|
if cProp.Type == "Upload" {
|
||||||
|
return fmt.Sprintf("/api/__yao/list/%s%s", dsl.ID, cProp.UploadPath())
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"api": fmt.Sprintf("/api/__yao/list/%s%s", dsl.ID, cProp.Path()),
|
||||||
|
"params": cProp.Query,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setting["name"] = dsl.Name
|
||||||
|
return setting, nil
|
||||||
|
}
|
||||||
81
widgets/list/list_test.go
Normal file
81
widgets/list/list_test.go
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
"github.com/yaoapp/yao/fs"
|
||||||
|
"github.com/yaoapp/yao/i18n"
|
||||||
|
"github.com/yaoapp/yao/model"
|
||||||
|
"github.com/yaoapp/yao/runtime"
|
||||||
|
"github.com/yaoapp/yao/script"
|
||||||
|
"github.com/yaoapp/yao/share"
|
||||||
|
"github.com/yaoapp/yao/table"
|
||||||
|
"github.com/yaoapp/yao/widgets/expression"
|
||||||
|
"github.com/yaoapp/yao/widgets/field"
|
||||||
|
"github.com/yaoapp/yao/widgets/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLoad(t *testing.T) {
|
||||||
|
prepare(t)
|
||||||
|
err := Load(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, 3, len(Lists))
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepare(t *testing.T, language ...string) {
|
||||||
|
|
||||||
|
runtime.Load(config.Conf)
|
||||||
|
err := test.LoadEngine(language...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
i18n.Load(config.Conf)
|
||||||
|
share.DBConnect(config.Conf.DB) // removed later
|
||||||
|
|
||||||
|
// load fs
|
||||||
|
err = fs.Load(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load scripts
|
||||||
|
err = script.Load(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load models
|
||||||
|
err = model.Load(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load field transform
|
||||||
|
err = field.LoadAndExport(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load expression
|
||||||
|
err = expression.Export()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load tables
|
||||||
|
err = table.Load(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// export
|
||||||
|
err = Export()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
158
widgets/list/process.go
Normal file
158
widgets/list/process.go
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/gou/fs"
|
||||||
|
"github.com/yaoapp/kun/exception"
|
||||||
|
"github.com/yaoapp/kun/log"
|
||||||
|
"github.com/yaoapp/yao/helper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Export process
|
||||||
|
func exportProcess() {
|
||||||
|
gou.RegisterProcessHandler("yao.list.setting", processSetting)
|
||||||
|
gou.RegisterProcessHandler("yao.list.xgen", processXgen)
|
||||||
|
gou.RegisterProcessHandler("yao.list.component", processComponent)
|
||||||
|
gou.RegisterProcessHandler("yao.list.upload", processUpload)
|
||||||
|
gou.RegisterProcessHandler("yao.list.download", processDownload)
|
||||||
|
gou.RegisterProcessHandler("yao.list.save", processSave)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processXgen(process *gou.Process) interface{} {
|
||||||
|
|
||||||
|
list := MustGet(process)
|
||||||
|
setting, err := list.Xgen()
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
return setting
|
||||||
|
}
|
||||||
|
|
||||||
|
func processComponent(process *gou.Process) interface{} {
|
||||||
|
|
||||||
|
process.ValidateArgNums(3)
|
||||||
|
list := MustGet(process)
|
||||||
|
xpath := process.ArgsString(1)
|
||||||
|
method := process.ArgsString(2)
|
||||||
|
key := fmt.Sprintf("%s.$%s", xpath, method)
|
||||||
|
|
||||||
|
// get cloud props
|
||||||
|
cProp, has := list.CProps[key]
|
||||||
|
if !has {
|
||||||
|
exception.New("%s does not exist", 400, key).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// :query
|
||||||
|
query := map[string]interface{}{}
|
||||||
|
if process.NumOfArgsIs(4) {
|
||||||
|
query = process.ArgsMap(3)
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute query
|
||||||
|
res, err := cProp.ExecQuery(process, query)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func processDownload(process *gou.Process) interface{} {
|
||||||
|
|
||||||
|
process.ValidateArgNums(4)
|
||||||
|
list := MustGet(process)
|
||||||
|
field := process.ArgsString(1)
|
||||||
|
file := process.ArgsString(2)
|
||||||
|
tokenString := process.ArgsString(3)
|
||||||
|
|
||||||
|
// checking
|
||||||
|
ext := fs.ExtName(file)
|
||||||
|
if _, has := fs.DownloadWhitelist[ext]; !has {
|
||||||
|
exception.New("%s.%s .%s file does not allow", 403, list.ID, field, ext).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
tokenString = strings.TrimSpace(strings.TrimPrefix(tokenString, "Bearer "))
|
||||||
|
if tokenString == "" {
|
||||||
|
exception.New("%s.%s No permission", 403, list.ID, field).Throw()
|
||||||
|
}
|
||||||
|
claims := helper.JwtValidate(tokenString)
|
||||||
|
|
||||||
|
// Get Process name
|
||||||
|
name := "fs.system.Download"
|
||||||
|
if list.Action.Download.Process != "" {
|
||||||
|
name = list.Action.Download.Process
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create process
|
||||||
|
p, err := gou.ProcessOf(name, file)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[downalod] %s.%s %s", list.ID, field, err.Error())
|
||||||
|
exception.New("[downalod] %s.%s %s", 400, list.ID, field, err.Error()).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Excute process
|
||||||
|
res, err := p.WithGlobal(process.Global).WithSID(claims.SID).Exec()
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[downalod] %s.%s %s", list.ID, field, err.Error())
|
||||||
|
exception.New("[downalod] %s.%s %s", 500, list.ID, field, err.Error()).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func processUpload(process *gou.Process) interface{} {
|
||||||
|
|
||||||
|
process.ValidateArgNums(4)
|
||||||
|
list := MustGet(process)
|
||||||
|
xpath := process.ArgsString(1)
|
||||||
|
method := process.ArgsString(2)
|
||||||
|
key := fmt.Sprintf("%s.$%s", xpath, method)
|
||||||
|
|
||||||
|
// get cloud props
|
||||||
|
cProp, has := list.CProps[key]
|
||||||
|
if !has {
|
||||||
|
exception.New("%s does not exist", 400, key).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// $file.file
|
||||||
|
tmpfile, ok := process.Args[3].(gou.UploadFile)
|
||||||
|
if !ok {
|
||||||
|
exception.New("parameters error: %v", 400, process.Args[3]).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute upload
|
||||||
|
res, err := cProp.ExecUpload(process, tmpfile)
|
||||||
|
if err != nil {
|
||||||
|
exception.New(err.Error(), 500).Throw()
|
||||||
|
}
|
||||||
|
|
||||||
|
if file, ok := res.(string); ok {
|
||||||
|
field := strings.TrimSuffix(xpath, ".edit.props")
|
||||||
|
file = fmt.Sprintf("/api/__yao/list/%s/download/%s?name=%s", list.ID, url.QueryEscape(field), file)
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func processSetting(process *gou.Process) interface{} {
|
||||||
|
list := MustGet(process)
|
||||||
|
process.Args = append(process.Args, process.Args[0]) // listle name
|
||||||
|
return list.Action.Setting.MustExec(process)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processGet(process *gou.Process) interface{} {
|
||||||
|
list := MustGet(process)
|
||||||
|
return list.Action.Get.MustExec(process)
|
||||||
|
}
|
||||||
|
|
||||||
|
func processSave(process *gou.Process) interface{} {
|
||||||
|
list := MustGet(process)
|
||||||
|
return list.Action.Save.MustExec(process)
|
||||||
|
}
|
||||||
91
widgets/list/process_test.go
Normal file
91
widgets/list/process_test.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/yaoapp/gou"
|
||||||
|
"github.com/yaoapp/kun/any"
|
||||||
|
"github.com/yaoapp/yao/config"
|
||||||
|
q "github.com/yaoapp/yao/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProcessSetting(t *testing.T) {
|
||||||
|
load(t)
|
||||||
|
clear(t)
|
||||||
|
testData(t)
|
||||||
|
args := []interface{}{"category"}
|
||||||
|
res, err := gou.NewProcess("yao.list.Setting", args...).Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
data := any.Of(res).MapStr().Dot()
|
||||||
|
assert.Equal(t, "/api/__yao/list/category/component/fields.list."+url.QueryEscape("父类")+".edit.props.xProps/remote", data.Get("fields.list.父类.edit.props.xProps.remote.api"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessXgen(t *testing.T) {
|
||||||
|
load(t)
|
||||||
|
clear(t)
|
||||||
|
testData(t)
|
||||||
|
args := []interface{}{"category"}
|
||||||
|
res, err := gou.NewProcess("yao.list.Xgen", args...).Exec()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
data := any.Of(res).MapStr().Dot()
|
||||||
|
assert.Equal(t, "/api/__yao/list/category/component/fields.list."+url.QueryEscape("父类")+".edit.props.xProps/remote", data.Get("fields.list.父类.edit.props.xProps.remote.api"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func load(t *testing.T) {
|
||||||
|
prepare(t)
|
||||||
|
err := Load(config.Conf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
q.Load(config.Conf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testData(t *testing.T) {
|
||||||
|
category := gou.Select("category")
|
||||||
|
err := category.Insert(
|
||||||
|
[]string{"name", "stock", "status", "rank"},
|
||||||
|
[][]interface{}{
|
||||||
|
{"机器人", 100, "启用", 1},
|
||||||
|
{"运输车", 80, "启用", 2},
|
||||||
|
{"货柜", 100, "停用", 3},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func tempFile(t *testing.T) string {
|
||||||
|
file, err := os.CreateTemp("", "unit-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = file.Write([]byte("HELLO"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Name()
|
||||||
|
}
|
||||||
|
|
||||||
|
func clear(t *testing.T) {
|
||||||
|
for _, m := range gou.Models {
|
||||||
|
err := m.DropTable()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = m.Migrate(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
69
widgets/list/types.go
Normal file
69
widgets/list/types.go
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/yaoapp/yao/widgets/action"
|
||||||
|
"github.com/yaoapp/yao/widgets/component"
|
||||||
|
"github.com/yaoapp/yao/widgets/compute"
|
||||||
|
"github.com/yaoapp/yao/widgets/field"
|
||||||
|
"github.com/yaoapp/yao/widgets/hook"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DSL the list DSL
|
||||||
|
type DSL struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Root string `json:"-"`
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Action *ActionDSL `json:"action"`
|
||||||
|
Layout *LayoutDSL `json:"layout"`
|
||||||
|
Fields *FieldsDSL `json:"fields"`
|
||||||
|
Config map[string]interface{} `json:"config,omitempty"`
|
||||||
|
CProps field.CloudProps `json:"-"`
|
||||||
|
compute.Computable
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionDSL the list action DSL
|
||||||
|
type ActionDSL struct {
|
||||||
|
Bind *BindActionDSL `json:"bind,omitempty"`
|
||||||
|
Setting *action.Process `json:"setting,omitempty"`
|
||||||
|
Component *action.Process `json:"component,omitempty"`
|
||||||
|
Upload *action.Process `json:"upload,omitempty"`
|
||||||
|
Download *action.Process `json:"download,omitempty"`
|
||||||
|
Get *action.Process `json:"get,omitempty"`
|
||||||
|
Save *action.Process `json:"save,omitempty"`
|
||||||
|
BeforeGet *hook.Before `json:"before:find,omitempty"`
|
||||||
|
AfterGet *hook.After `json:"after:find,omitempty"`
|
||||||
|
BeforeSave *hook.Before `json:"before:save,omitempty"`
|
||||||
|
AfterSave *hook.After `json:"after:save,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindActionDSL action.bind
|
||||||
|
type BindActionDSL struct {
|
||||||
|
Model string `json:"model,omitempty"` // bind model
|
||||||
|
Store string `json:"store,omitempty"` // bind store
|
||||||
|
Table string `json:"table,omitempty"` // bind table
|
||||||
|
Option map[string]interface{} `json:"option,omitempty"` // bind option
|
||||||
|
}
|
||||||
|
|
||||||
|
// LayoutDSL the list layout DSL
|
||||||
|
type LayoutDSL struct {
|
||||||
|
List *ViewLayoutDSL `json:"list,omitempty"`
|
||||||
|
Config map[string]interface{} `json:"config,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// OperationLayoutDSL layout.operation
|
||||||
|
type OperationLayoutDSL struct {
|
||||||
|
Preset map[string]map[string]interface{} `json:"preset,omitempty"`
|
||||||
|
Actions []component.ActionDSL `json:"actions,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FieldsDSL the list fields DSL
|
||||||
|
type FieldsDSL struct {
|
||||||
|
List field.Columns `json:"list,omitempty"`
|
||||||
|
listMap map[string]field.ColumnDSL
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewLayoutDSL layout.list
|
||||||
|
type ViewLayoutDSL struct {
|
||||||
|
Props component.PropsDSL `json:"props,omitempty"`
|
||||||
|
Columns []component.InstanceDSL `json:"columns,omitempty"`
|
||||||
|
}
|
||||||
6
widgets/list/vaildate.go
Normal file
6
widgets/list/vaildate.go
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
// Validate table
|
||||||
|
func (dsl *DSL) Validate() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -70,7 +70,7 @@ func TestAPISearch(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
data := any.Of(resp).MapStr().Dot()
|
data := any.Of(resp).MapStr().Dot()
|
||||||
assert.Equal(t, "20", fmt.Sprintf("%v", data.Get("pagesize")))
|
assert.Equal(t, "1", fmt.Sprintf("%v", data.Get("pagesize")))
|
||||||
assert.Equal(t, "3", fmt.Sprintf("%v", data.Get("total")))
|
assert.Equal(t, "3", fmt.Sprintf("%v", data.Get("total")))
|
||||||
assert.Equal(t, "checked", data.Get("data.0.status"))
|
assert.Equal(t, "checked", data.Get("data.0.status"))
|
||||||
assert.Equal(t, "enabled", data.Get("data.0.mode"))
|
assert.Equal(t, "enabled", data.Get("data.0.mode"))
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/yaoapp/yao/model"
|
"github.com/yaoapp/yao/model"
|
||||||
"github.com/yaoapp/yao/runtime"
|
"github.com/yaoapp/yao/runtime"
|
||||||
"github.com/yaoapp/yao/script"
|
"github.com/yaoapp/yao/script"
|
||||||
|
_ "github.com/yaoapp/yao/utils"
|
||||||
"github.com/yaoapp/yao/widgets/app"
|
"github.com/yaoapp/yao/widgets/app"
|
||||||
"github.com/yaoapp/yao/widgets/component"
|
"github.com/yaoapp/yao/widgets/component"
|
||||||
"github.com/yaoapp/yao/widgets/expression"
|
"github.com/yaoapp/yao/widgets/expression"
|
||||||
|
|
@ -24,7 +25,7 @@ func TestLoad(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
assert.Equal(t, 11, len(Tables))
|
assert.Equal(t, 12, len(Tables))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadID(t *testing.T) {
|
func TestLoadID(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/yaoapp/yao/widgets/expression"
|
"github.com/yaoapp/yao/widgets/expression"
|
||||||
"github.com/yaoapp/yao/widgets/field"
|
"github.com/yaoapp/yao/widgets/field"
|
||||||
"github.com/yaoapp/yao/widgets/form"
|
"github.com/yaoapp/yao/widgets/form"
|
||||||
|
"github.com/yaoapp/yao/widgets/list"
|
||||||
"github.com/yaoapp/yao/widgets/login"
|
"github.com/yaoapp/yao/widgets/login"
|
||||||
"github.com/yaoapp/yao/widgets/table"
|
"github.com/yaoapp/yao/widgets/table"
|
||||||
)
|
)
|
||||||
|
|
@ -56,6 +57,12 @@ func Load(cfg config.Config) error {
|
||||||
messages = append(messages, err.Error())
|
messages = append(messages, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// list widget
|
||||||
|
err = list.LoadAndExport(cfg)
|
||||||
|
if err != nil {
|
||||||
|
messages = append(messages, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
// form widget
|
// form widget
|
||||||
err = form.LoadAndExport(cfg)
|
err = form.LoadAndExport(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue