From 2d100d5bd235e242bae4d7ce0342851664d4f922 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 10 Dec 2022 16:43:30 +0800 Subject: [PATCH] [add] component action parser --- widgets/chart/chart.go | 4 +- widgets/component/action.go | 137 +++++++++++++++++++++----- widgets/component/action_test.go | 163 +++++++++++++++++++++++++++++++ widgets/component/types.go | 17 +++- widgets/form/form.go | 2 +- widgets/form/layout.go | 4 +- widgets/table/layout.go | 8 +- widgets/table/table.go | 6 +- 8 files changed, 302 insertions(+), 39 deletions(-) create mode 100644 widgets/component/action_test.go diff --git a/widgets/chart/chart.go b/widgets/chart/chart.go index 30982cb7..35dd3f55 100644 --- a/widgets/chart/chart.go +++ b/widgets/chart/chart.go @@ -212,7 +212,7 @@ func (dsl *DSL) Actions() []component.ActionsExport { res = append(res, component.ActionsExport{ Type: "operation", Xpath: "layout.operation.actions", - Actions: dsl.Layout.Operation.Actions.Hash(), + Actions: dsl.Layout.Operation.Actions, }) } @@ -225,7 +225,7 @@ func (dsl *DSL) Actions() []component.ActionsExport { res = append(res, component.ActionsExport{ Type: "filter", Xpath: "layout.filter.actions", - Actions: dsl.Layout.Filter.Actions.Hash(), + Actions: dsl.Layout.Filter.Actions, }) } return res diff --git a/widgets/component/action.go b/widgets/component/action.go index e0a73dfa..952320f9 100644 --- a/widgets/component/action.go +++ b/widgets/component/action.go @@ -3,13 +3,127 @@ package component import ( "fmt" "io" - "sort" "strings" jsoniter "github.com/json-iterator/go" "golang.org/x/crypto/md4" ) +// UnmarshalJSON for json UnmarshalJSON +func (action *ActionDSL) UnmarshalJSON(data []byte) error { + var alias aliasActionDSL + err := jsoniter.Unmarshal(data, &alias) + if err != nil { + return err + } + + *action = ActionDSL(alias) + action.ID, err = action.Hash() + if err != nil { + return err + } + + if action.Action == nil { + action.Action = ActionNodes{} + } + + err = action.Action.Parse() + if err != nil { + return err + } + + return nil +} + +// UnmarshalJSON for json UnmarshalJSON +func (nodes *ActionNodes) UnmarshalJSON(data []byte) error { + + var alias interface{} + err := jsoniter.Unmarshal(data, &alias) + if err != nil { + return err + } + + switch values := alias.(type) { + + case string: // "Actions.test.back" + *nodes = ActionNodes{{ + "name": values, + "type": values, + "payload": map[string]interface{}{}, + }} + return nil + + case map[string]interface{}: // {"Form.delete": { "pathname": "/x/Table/env" }} + node := ActionNode{} + for name, payload := range values { + node["name"] = name + node["type"] = name + node["payload"] = payload + break + } + *nodes = ActionNodes{node} + return nil + + case []interface{}, []map[string]interface{}: // [{ "name": "Save", "type": "Form.save", "payload": { "id": ":id", "status": "cured" }}] + new := aliasActionNodes{} + err := jsoniter.Unmarshal(data, &new) + if err != nil { + return err + } + + *nodes = ActionNodes(new) + return nil + + // case []ActionNode: + // *nodes = ActionNodes(values) + // return nil + + // case ActionNodes: + // *nodes = values + // return nil + + // case *ActionNodes: + // nodes = values + // return nil + } + + return fmt.Errorf("the format does not support. %s", string(data)) +} + +// MarshalJSON for json MarshalJSON +// func (nodes ActionNodes) MarshalJSON() ([]byte, error) { +// return nil, nil +// } + +// Parse the custom nodes +func (nodes *ActionNodes) Parse() error { + for i := range *nodes { + // merge the developer-defined actions + if (*nodes)[i].Custom() { + // (*nodes)[i]["custom"] = true + } + } + return nil +} + +// Custom check if the action node is custom +func (node ActionNode) Custom() bool { + if name, ok := node["type"].(string); ok && strings.HasPrefix(strings.ToLower(name), "actions.") { + return true + } + return false +} + +// Hash hash value +func (action ActionDSL) Hash() (string, error) { + h := md4.New() + origin := fmt.Sprintf("%#v", action.Action) + // fmt.Println(origin) + io.WriteString(h, origin) + return fmt.Sprintf("%x", h.Sum(nil)), nil +} + // SetPath set actions xpath func (actions Actions) SetPath(root string) Actions { for i := range actions { @@ -17,24 +131,3 @@ func (actions Actions) SetPath(root string) Actions { } return actions } - -// Hash hash value -func (actions Actions) Hash() Actions { - h := md4.New() - for i := range actions { - keys := []string{} - for i, value := range actions[i].Action { - key := fmt.Sprintf("%d", i) - for k, v := range value { - data, _ := jsoniter.Marshal(v) - key = fmt.Sprintf("%s%s%s", key, k, data) - } - keys = append(keys, key) - } - sort.Strings(keys) - origin := strings.Join(keys, "|") - io.WriteString(h, origin) - actions[i].ID = fmt.Sprintf("%x", h.Sum(nil)) - } - return actions -} diff --git a/widgets/component/action_test.go b/widgets/component/action_test.go new file mode 100644 index 00000000..a3c32148 --- /dev/null +++ b/widgets/component/action_test.go @@ -0,0 +1,163 @@ +package component + +import ( + "testing" + + jsoniter "github.com/json-iterator/go" + "github.com/stretchr/testify/assert" +) + +func TestActionUnmarshalJSON(t *testing.T) { + + data := testActionData() + var action ActionDSL + err := jsoniter.Unmarshal(data["one"], &action) + if err != nil { + t.Fatal(err) + } + assert.Len(t, action.Action, 1) + assert.Equal(t, "Delete", action.Action[0]["name"]) + assert.Equal(t, "Form.delete", action.Action[0]["type"]) + assert.Equal(t, "6212f23054ddacf15e7a7c7354c6a5a6", action.ID) + + action = ActionDSL{} + err = jsoniter.Unmarshal(data["many"], &action) + if err != nil { + t.Fatal(err) + } + assert.Len(t, action.Action, 2) + assert.Equal(t, "Save", action.Action[0]["name"]) + assert.Equal(t, "Form.save", action.Action[0]["type"]) + assert.Equal(t, "historyPush", action.Action[1]["name"]) + assert.Equal(t, "Common.historyPush", action.Action[1]["type"]) + assert.Equal(t, "85f029739d324bcc3185c419000739e3", action.ID) + + action = ActionDSL{} + err = jsoniter.Unmarshal(data["flow"], &action) + if err != nil { + t.Fatal(err) + } + assert.Len(t, action.Action, 2) + assert.Equal(t, "Save", action.Action[0]["name"]) + assert.Equal(t, "Form.save", action.Action[0]["type"]) + assert.Equal(t, "Flow", action.Action[1]["name"]) + assert.Equal(t, "Actions.test.check", action.Action[1]["type"]) + assert.Equal(t, "0e1d99723154fbbeea64833be4151ada", action.ID) + + action = ActionDSL{} + err = jsoniter.Unmarshal(data["sugar-string"], &action) + if err != nil { + t.Fatal(err) + } + assert.Len(t, action.Action, 1) + assert.Equal(t, "Actions.test.back", action.Action[0]["name"]) + assert.Equal(t, "Actions.test.back", action.Action[0]["type"]) + assert.Equal(t, "9c8e6e7281ef5df76c2ef84c85308884", action.ID) + + action = ActionDSL{} + err = jsoniter.Unmarshal(data["sugar-map"], &action) + if err != nil { + t.Fatal(err) + } + assert.Len(t, action.Action, 1) + assert.Equal(t, "Form.delete", action.Action[0]["name"]) + assert.Equal(t, "Form.delete", action.Action[0]["type"]) + assert.Equal(t, "d5d48c687da8afd46145cab1f5825523", action.ID) + + action = ActionDSL{} + err = jsoniter.Unmarshal(data["sugar-map-custom"], &action) + if err != nil { + t.Fatal(err) + } + assert.Len(t, action.Action, 1) + assert.Equal(t, "Actions.test.back", action.Action[0]["name"]) + assert.Equal(t, "Actions.test.back", action.Action[0]["type"]) + assert.Equal(t, "99434a339accfe83ff38b74358e0b3af", action.ID) + +} + +// testActionData +func testActionData() map[string][]byte { + + return map[string][]byte{ + "one": []byte(`{ + "title": "Delete", + "icon": "icon-trash-2", + "style": "danger", + "action": [ + { + "name": "Delete", + "type": "Form.delete", + "payload": { "pathname": "/x/Table/env", "foo":"bar", "hello":"world" } + } + ], + "confirm": { "title": "Tips", "desc": "Delete Confirm" } + }`), + + "many": []byte(`{ + "title": "Cured", + "icon": "icon-check", + "style": "success", + "action": [ + { + "name": "Save", + "type": "Form.save", + "payload": { "id": ":id", "status": "cured" } + }, + { + "name": "historyPush", + "type": "Common.historyPush", + "payload": { "pathname": "/x/Form/pet/:id/edit" } + } + ], + "confirm": { "title": "Tips", "desc": "Cured Confirm" } + }`), + + "flow": []byte(`{ + "title": "Delete", + "icon": "icon-trash-2", + "style": "danger", + "action": [ + { + "name": "Save", + "type": "Form.save", + "payload": { "id": ":id", "status": "cured" } + }, + { + "name": "Flow", + "type": "Actions.test.check", + "payload": { "pathname": "/x/Form/pet/:id/edit" } + } + ], + "confirm": { "title": "Tips", "desc": "Delete Confirm" } + }`), + + "sugar-string": []byte(`{ + "title": "Delete", + "icon": "icon-trash-2", + "style": "danger", + "action": "Actions.test.back", + "confirm": { "title": "Tips", "desc": "Delete Confirm" } + }`), + + "sugar-map": []byte(`{ + "title": "Delete", + "icon": "icon-trash-2", + "style": "danger", + "action": { + "Form.delete": { "pathname": "/x/Table/env" } + }, + "confirm": { "title": "Tips", "desc": "Delete Confirm" } + }`), + + "sugar-map-custom": []byte(`{ + "title": "Delete", + "icon": "icon-trash-2", + "style": "danger", + "action": { + "Actions.test.back": { "pathname": "/x/Table/env" } + }, + "confirm": { "title": "Tips", "desc": "Delete Confirm" } + }`), + } +} diff --git a/widgets/component/types.go b/widgets/component/types.go index 64007af3..b5bcbdd7 100644 --- a/widgets/component/types.go +++ b/widgets/component/types.go @@ -28,19 +28,29 @@ type ActionsExport struct { Actions Actions `json:"actions,omitempty"` } +type aliasActionDSL ActionDSL + // ActionDSL the component action DSL type ActionDSL struct { + ID string `json:"id,omitempty"` Title string `json:"title,omitempty"` Width int `json:"width,omitempty"` Icon string `json:"icon,omitempty"` Style string `json:"style,omitempty"` - ID string `json:"id,omitempty"` Xpath string `json:"xpath,omitempty"` Props PropsDSL `json:"props,omitempty"` Confirm *ConfirmActionDSL `json:"confirm,omitempty"` - Action []ParamsDSL `json:"action,omitempty"` + Action ActionNodes `json:"action,omitempty"` } +type aliasActionNodes []ActionNode + +// ActionNodes the action nodes +type ActionNodes []ActionNode + +// ActionNode the action node +type ActionNode map[string]interface{} + // ConfirmActionDSL action.confirm type ConfirmActionDSL struct { Title string `json:"title,omitempty"` @@ -50,9 +60,6 @@ type ConfirmActionDSL struct { // PropsDSL component props type PropsDSL map[string]interface{} -// ParamsDSL action params -type ParamsDSL map[string]interface{} - // Compute process type Compute struct { Process string `json:"process"` diff --git a/widgets/form/form.go b/widgets/form/form.go index 8b1d6877..3fa59b33 100644 --- a/widgets/form/form.go +++ b/widgets/form/form.go @@ -284,7 +284,7 @@ func (dsl *DSL) Actions() []component.ActionsExport { res = append(res, component.ActionsExport{ Type: "operation", Xpath: "layout.operation.actions", - Actions: dsl.Layout.Actions.Hash(), + Actions: dsl.Layout.Actions, }) } return res diff --git a/widgets/form/layout.go b/widgets/form/layout.go index a152dca9..23b6b253 100644 --- a/widgets/form/layout.go +++ b/widgets/form/layout.go @@ -21,7 +21,7 @@ func (layout *LayoutDSL) BindModel(m *gou.Model, formID string, fields *FieldsDS Title: "::Delete", Icon: "icon-trash-2", Style: "danger", - Action: []component.ParamsDSL{ + Action: component.ActionNodes{ { "name": "Delete", "type": "Form.delete", @@ -97,7 +97,7 @@ func (layout *LayoutDSL) BindTable(tab *table.DSL, formID string, fields *Fields Title: "::Delete", Icon: "icon-trash-2", Style: "danger", - Action: []component.ParamsDSL{ + Action: component.ActionNodes{ { "name": "Delete", "type": "Form.delete", diff --git a/widgets/table/layout.go b/widgets/table/layout.go index 1bdc6438..cb35adbc 100644 --- a/widgets/table/layout.go +++ b/widgets/table/layout.go @@ -31,7 +31,7 @@ func (layout *LayoutDSL) BindModel(m *gou.Model, fields *FieldsDSL, option map[s Title: "::Create", Icon: "icon-plus", Width: 3, - Action: []component.ParamsDSL{ + Action: component.ActionNodes{ { "name": "HistoryPush", "type": "Common.historyPush", @@ -82,7 +82,7 @@ func (layout *LayoutDSL) BindModel(m *gou.Model, fields *FieldsDSL, option map[s component.ActionDSL{ Title: "::View", Icon: "icon-eye", - Action: []component.ParamsDSL{ + Action: component.ActionNodes{ { "name": "OpenModal", "type": "Common.openModal", @@ -99,7 +99,7 @@ func (layout *LayoutDSL) BindModel(m *gou.Model, fields *FieldsDSL, option map[s component.ActionDSL{ Title: "::Edit", Icon: "icon-edit-2", - Action: []component.ParamsDSL{ + Action: component.ActionNodes{ { "name": "OpenModal", "type": "Common.openModal", @@ -117,7 +117,7 @@ func (layout *LayoutDSL) BindModel(m *gou.Model, fields *FieldsDSL, option map[s Title: "::Delete", Icon: "icon-trash-2", Style: "danger", - Action: []component.ParamsDSL{ + Action: component.ActionNodes{ { "name": "Delete", "type": "Table.delete", diff --git a/widgets/table/table.go b/widgets/table/table.go index 12c79f95..08e9644d 100644 --- a/widgets/table/table.go +++ b/widgets/table/table.go @@ -311,7 +311,7 @@ func (dsl *DSL) Actions() []component.ActionsExport { res = append(res, component.ActionsExport{ Type: "import", Xpath: "layout.header.preset.import.actions", - Actions: dsl.Layout.Header.Preset.Import.Actions.Hash(), + Actions: dsl.Layout.Header.Preset.Import.Actions, }) } @@ -323,7 +323,7 @@ func (dsl *DSL) Actions() []component.ActionsExport { res = append(res, component.ActionsExport{ Type: "filter", Xpath: "layout.filter.actions", - Actions: dsl.Layout.Filter.Actions.Hash(), + Actions: dsl.Layout.Filter.Actions, }) } @@ -335,7 +335,7 @@ func (dsl *DSL) Actions() []component.ActionsExport { res = append(res, component.ActionsExport{ Type: "operation", Xpath: "layout.table.operation.actions", - Actions: dsl.Layout.Table.Operation.Actions.Hash(), + Actions: dsl.Layout.Table.Operation.Actions, }) }