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
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: yaoapp/demo-app
|
||||
repository: yaoapp/yao-dev-app
|
||||
path: app
|
||||
|
||||
- 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:
|
||||
matrix:
|
||||
go: [1.19.2]
|
||||
runs-on: "ubuntu-latest"
|
||||
runs-on: "ubuntu-20.04"
|
||||
steps:
|
||||
- name: Arm Build
|
||||
run: |
|
||||
|
|
|
|||
2
.github/workflows/unit-test.yml
vendored
2
.github/workflows/unit-test.yml
vendored
|
|
@ -97,7 +97,7 @@ jobs:
|
|||
- name: Checkout Demo App
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: yaoapp/demo-app
|
||||
repository: yaoapp/yao-dev-app
|
||||
path: app
|
||||
|
||||
- name: Move Kun, Xun, Gou, V8Go
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/yaoapp/yao/widgets/chart"
|
||||
"github.com/yaoapp/yao/widgets/form"
|
||||
"github.com/yaoapp/yao/widgets/list"
|
||||
"github.com/yaoapp/yao/widgets/table"
|
||||
)
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ var Guards = map[string]gin.HandlerFunc{
|
|||
"cross-origin": guardCrossOrigin, // Cross-Origin Resource Sharing
|
||||
"table-guard": table_v0.Guard, // Table Guard ( v0.9 table)
|
||||
"widget-table": table.Guard, // Widget Table Guard
|
||||
"widget-list": list.Guard, // Widget List Guard
|
||||
"widget-form": form.Guard, // Widget Form Guard
|
||||
"widget-chart": chart.Guard, // Widget Chart Guard
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/yaoapp/yao/i18n"
|
||||
"github.com/yaoapp/yao/runtime"
|
||||
"github.com/yaoapp/yao/script"
|
||||
_ "github.com/yaoapp/yao/utils"
|
||||
"github.com/yaoapp/yao/widgets/login"
|
||||
)
|
||||
|
||||
|
|
@ -224,7 +225,10 @@ func TestProcessMenu(t *testing.T) {
|
|||
if err != nil {
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ func TestLoad(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, 9, len(Forms))
|
||||
assert.Equal(t, 10, len(Forms))
|
||||
}
|
||||
|
||||
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()
|
||||
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, "checked", data.Get("data.0.status"))
|
||||
assert.Equal(t, "enabled", data.Get("data.0.mode"))
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/yaoapp/yao/model"
|
||||
"github.com/yaoapp/yao/runtime"
|
||||
"github.com/yaoapp/yao/script"
|
||||
_ "github.com/yaoapp/yao/utils"
|
||||
"github.com/yaoapp/yao/widgets/app"
|
||||
"github.com/yaoapp/yao/widgets/component"
|
||||
"github.com/yaoapp/yao/widgets/expression"
|
||||
|
|
@ -24,7 +25,7 @@ func TestLoad(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, 11, len(Tables))
|
||||
assert.Equal(t, 12, len(Tables))
|
||||
}
|
||||
|
||||
func TestLoadID(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/yaoapp/yao/widgets/expression"
|
||||
"github.com/yaoapp/yao/widgets/field"
|
||||
"github.com/yaoapp/yao/widgets/form"
|
||||
"github.com/yaoapp/yao/widgets/list"
|
||||
"github.com/yaoapp/yao/widgets/login"
|
||||
"github.com/yaoapp/yao/widgets/table"
|
||||
)
|
||||
|
|
@ -56,6 +57,12 @@ func Load(cfg config.Config) error {
|
|||
messages = append(messages, err.Error())
|
||||
}
|
||||
|
||||
// list widget
|
||||
err = list.LoadAndExport(cfg)
|
||||
if err != nil {
|
||||
messages = append(messages, err.Error())
|
||||
}
|
||||
|
||||
// form widget
|
||||
err = form.LoadAndExport(cfg)
|
||||
if err != nil {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue