func TestBranchControlView_Render(t *testing.T) { cb := ctests.New(t).SetApp(true, false, false) defer cb.Finish() b := NewBranchControlView(cb.Ctx(), models.NewBranchModel(cb.Ctx(), &models.RootContents{Name: "a"})) expected := elem.Div( elem.Anchor( vecty.ClassMap{ "toggle": true, "empty": true, }, event.Click(nil), ), elem.Div( vecty.ClassMap{ "node-content": true, }, elem.Span( prop.Class("node-label"), event.Click(nil), vecty.Text("a"), ), elem.Span( prop.Class("badge"), vecty.Style("display", "none"), ), ), ) equal(t, expected, b.Render()) cb.AssertAppSuccess() }
func (v *IconEditorView) Render() *vecty.HTML { v.editor = editors.NewStringEditorView(v.Ctx, v.model.Node.Map["url"], editable.Inline) url := "" if v.icon.Url != nil { url = v.icon.Url.Value() } return elem.Div( prop.Class("container-fluid"), elem.Div( prop.Class("row"), elem.Div( prop.Class("col-sm-10"), vecty.Style("padding-left", "0"), vecty.Style("padding-right", "0"), v.editor, ), elem.Div( prop.Class("col-sm-2"), elem.Image( prop.Class("img-responsive"), style.MaxHeight("200px"), prop.Src(url), ), ), ), ) }
func (v *PanelView) Render() *vecty.HTML { var editor vecty.Component if v.node != nil { switch v.node.Type.NativeJsonType(v.Ctx) { case system.J_MAP: editor = NewMapView(v.Ctx, v.node) case system.J_ARRAY: editor = NewArrayView(v.Ctx, v.node) default: editor = NewStructView(v.Ctx, v.node) } } else if v.branch != nil { switch v.branch.Contents.(type) { case *models.DataContents: editor = NewPanelNavView(v.Ctx, v.branch).Contents( func() vecty.MarkupOrComponentOrHTML { return elem.UnorderedList( prop.Class("nav navbar-nav navbar-right"), elem.ListItem( elem.Anchor( vecty.Text("Add"), prop.Href("#"), event.Click(func(ev *vecty.Event) { addNewFile(v.Ctx, v.App, true) }).PreventDefault(), ), ), ) }, ) case *models.TypesContents: editor = NewPanelNavView(v.Ctx, v.branch).Contents( func() vecty.MarkupOrComponentOrHTML { return elem.UnorderedList( prop.Class("nav navbar-nav navbar-right"), elem.ListItem( elem.Anchor( vecty.Text("Add"), prop.Href("#"), event.Click(func(ev *vecty.Event) { addNewFile(v.Ctx, v.App, false) }).PreventDefault(), ), ), ) }, ) default: editor = NewPanelNavView(v.Ctx, v.branch) } } v.panel = elem.Div( prop.Class("content content-panel"), editor, ) return v.panel }
func (v *PanelNavView) Render() *vecty.HTML { return elem.Navigation( prop.Class("navbar navbar-default navbar-static-top nagative-margin"), elem.Div( prop.Class("container-fluid"), elem.Div( prop.Class("navbar-header"), NewBreadcrumbsView(v.Ctx, v.branch), ), v.contents(), ), ) }
func (v *AddPopupView) modal(markup ...vecty.MarkupOrComponentOrHTML) *vecty.HTML { return elem.Div( prop.ID("add-modal"), prop.Class("modal"), vecty.Data("backdrop", "static"), vecty.Data("keyboard", "false"), elem.Div( prop.Class("modal-dialog"), elem.Div( prop.Class("modal-content"), elem.Div( prop.Class("modal-header"), elem.Button( prop.Type(prop.TypeButton), prop.Class("close"), elem.Span( vecty.Text("×"), ), event.Click(func(ev *vecty.Event) { v.App.Dispatch(&actions.CloseAddPopup{}) }).PreventDefault(), ), elem.Header4( prop.Class("modal-title"), vecty.Text("Add item"), ), ), vecty.List(markup), elem.Div( prop.Class("modal-footer"), elem.Button( prop.Type(prop.TypeButton), prop.Class("btn btn-default"), elem.Span( vecty.Text("Close"), ), event.Click(func(ev *vecty.Event) { v.App.Dispatch(&actions.CloseAddPopup{}) }).PreventDefault(), ), elem.Button( prop.Type(prop.TypeButton), prop.Class("btn btn-primary"), elem.Span( vecty.Text("Save"), ), event.Click(func(ev *vecty.Event) { v.save() }).PreventDefault(), ), ), ), ), ) }
func TestBranchRender2(t *testing.T) { cb := ctests.New(t).SetApp(true, true, false) defer cb.Finish() b := NewBranchView(cb.Ctx(), models.NewBranchModel(cb.Ctx(), &models.RootContents{Name: "a"})) expected := elem.Div( prop.Class("node"), NewBranchControlView(cb.Ctx(), models.NewBranchModel(cb.Ctx(), nil)), elem.Div( prop.Class("children"), ), ) equal(t, expected, b.Render()) }
func (v *BoolEditorView) Render() *vecty.HTML { v.input = elem.Input( prop.Type(prop.TypeCheckbox), prop.Checked(v.model.Node.ValueBool), event.Change(func(e *vecty.Event) { v.App.Dispatch(&actions.Modify{ Undoer: &actions.Undoer{}, Editor: v.model, Before: v.model.Node.NativeValue(), After: e.Target.Get("checked").Bool(), Immediate: true, }) }), ) return elem.Div( prop.Class("checkbox"), elem.Label( v.input, vecty.Text(v.model.Node.Label(v.Ctx)), ), ) }
func (v *MapView) errorBlock() *vecty.HTML { if !v.node.Invalid { return nil } errors := vecty.List{} for _, e := range v.node.Errors { errors = append(errors, elem.ListItem(vecty.Text(e.Description))) } return elem.Div( prop.Class("has-error"), elem.Paragraph( prop.Class("help-block text-danger"), elem.UnorderedList(errors), ), ) }
func (v *BreadcrumbsView) Render() *vecty.HTML { if v.branch == nil { return elem.Div() } b := v.branch crumbs := vecty.List{} for b != nil { // copy value of b into new var because it will be used in the click // handler current := b if v.branch.Parent != nil && current.Parent == nil { break } var content vecty.List if current == v.branch { content = append(content, vecty.Text( current.Contents.Label(v.Ctx), ), ) } else { content = append(content, elem.Anchor( prop.Href("#"), vecty.Text( current.Contents.Label(v.Ctx), ), event.Click(func(ev *vecty.Event) { v.App.Dispatch(&actions.BranchSelecting{Branch: current, Op: models.BranchOpClickBreadcrumb}) }).PreventDefault(), ), ) } crumbs = append( vecty.List{ elem.ListItem( vecty.ClassMap{ "active": current == v.branch, }, content, ), }, crumbs..., ) b = b.Parent } return elem.OrderedList( prop.Class("breadcrumb"), crumbs, ) }
func (v *TreeView) Render() *vecty.HTML { if v.Root == nil { return elem.Div() } return elem.Div( prop.Class("content content-tree"), NewBranchView(v.Ctx, v.Root), ) }
func (v *PageView) Render() *vecty.HTML { return elem.Body( prop.ID("wrapper"), NewHeader(v.Ctx), elem.Div( prop.Class("wrapper"), elem.Div( prop.ID("tree"), prop.Class("split split-horizontal"), NewTreeView(v.Ctx), ), elem.Div( prop.ID("main"), prop.Class("split split-horizontal"), NewPanelView(v.Ctx), ), ), NewAddPopupView(v.Ctx), ) }
func (v *BranchControlView) Render() *vecty.HTML { if v.model == nil { v.holder = elem.Div() return v.holder } selected := v.App.Branches.Selected() == v.model icon := v.model.Icon() v.holder = elem.Div( elem.Anchor( vecty.ClassMap{ "toggle": true, "selected": selected, "plus": icon == "plus", "minus": icon == "minus", "unknown": icon == "unknown", "empty": icon == "empty", }, event.Click(v.toggleClick), ), elem.Div( vecty.ClassMap{ "node-content": true, "selected": selected, }, elem.Span( prop.Class("node-label"), event.Click(v.labelClick), vecty.Text(v.model.Contents.Label(v.Ctx)), ), elem.Span( prop.Class("badge"), vecty.Style("display", "none"), ), ), ) return v.holder }
func (v *EditorView) helpBlock() *vecty.HTML { if v.model.Node.Rule == nil || v.model.Node.Rule.Interface == nil { return nil } description := v.model.Node.Rule.Interface.(system.ObjectInterface).GetObject(v.Ctx).Description if description == "" { return nil } return elem.Paragraph( prop.Class("help-block"), vecty.Text(description), ) }
func nullEditor(ctx context.Context, n *node.Node, app *stores.App) *EditorView { add := func(e *vecty.Event) { types := n.Rule.PermittedTypes() if len(types) == 1 { // if only one type is compatible, don't show the // popup, just add it. app.Dispatch(&actions.Add{ Undoer: &actions.Undoer{}, Parent: n.Parent, Node: n, Key: n.Key, Type: types[0], }) return } app.Dispatch(&actions.OpenAddPopup{ Parent: n.Parent, Node: n, Types: types, }) } return NewEditorView(ctx, n).Icons( func() vecty.MarkupOrComponentOrHTML { return elem.Anchor( prop.Href("#"), event.Click(add).PreventDefault(), elem.Italic( prop.Class("editor-icon editor-icon-after glyphicon glyphicon-plus-sign"), ), ) }, ).Dropdown( func() vecty.MarkupOrComponentOrHTML { return elem.ListItem( elem.Anchor( prop.Href("#"), vecty.Text("Add"), event.Click(add).PreventDefault(), ), ) }, ) }
func (v *StringEditorView) Render() *vecty.HTML { contents := vecty.List{ prop.Value(v.model.Node.ValueString), prop.Class("form-control"), event.KeyUp(func(e *vecty.Event) { getVal := func() interface{} { return e.Target.Get("value").String() } val := getVal() changed := func() bool { return val != getVal() } go func() { <-time.After(common.EditorKeyboardDebounceShort) if changed() { return } v.App.Dispatch(&actions.Modify{ Undoer: &actions.Undoer{}, Editor: v.model, Before: v.model.Node.NativeValue(), After: val, Changed: changed, }) }() }), } if sr, ok := v.model.Node.Rule.Interface.(*system.StringRule); ok && sr.Long { v.input = elem.TextArea( contents, ) } else { v.input = elem.Input( prop.Type(prop.TypeText), contents, ) } return elem.Div(v.input) }
func (v *NumberEditorView) Render() *vecty.HTML { v.input = elem.Input( prop.Type(prop.TypeNumber), prop.Value(fmt.Sprintf("%v", v.model.Node.ValueNumber)), prop.Class("form-control"), event.KeyUp(func(e *vecty.Event) { getVal := func() interface{} { val, err := strconv.ParseFloat(e.Target.Get("value").String(), 64) if err != nil { // if there's an error converting to a float, ignore it return nil } return val } val := getVal() changed := func() bool { return val != getVal() } go func() { <-time.After(common.EditorKeyboardDebounceShort) if changed() { return } v.App.Dispatch(&actions.Modify{ Undoer: &actions.Undoer{}, Editor: v.model, Before: v.model.Node.NativeValue(), After: val, Changed: changed, }) }() }), ) return elem.Div(v.input) }
func (v *ViewMenuView) Render() *vecty.HTML { return elem.ListItem( prop.Class("dropdown"), elem.Anchor( prop.Href("#"), prop.Class("dropdown-toggle"), vecty.Data("toggle", "dropdown"), vecty.Text("View"), elem.Span( prop.Class("caret"), ), ), elem.UnorderedList( prop.Class("dropdown-menu"), elem.ListItem( vecty.ClassMap{ "disabled": v.App.Branches.View() == models.Data, }, elem.Anchor( event.Click(func(ev *vecty.Event) { v.App.Dispatch(&actions.ChangeView{View: models.Data}) }).PreventDefault(), prop.Href("#"), vecty.Text("Data"), ), ), elem.ListItem( vecty.ClassMap{ "disabled": v.App.Branches.View() == models.Types, }, elem.Anchor( event.Click(func(ev *vecty.Event) { v.App.Dispatch(&actions.ChangeView{View: models.Types}) }).PreventDefault(), prop.Href("#"), vecty.Text("Types"), ), ), elem.ListItem( vecty.ClassMap{ "disabled": v.App.Branches.View() == models.Package, }, elem.Anchor( event.Click(func(ev *vecty.Event) { v.App.Dispatch(&actions.ChangeView{View: models.Package}) }).PreventDefault(), prop.Href("#"), vecty.Text("Package"), ), ), elem.ListItem( prop.Class("divider"), vecty.Property("role", "separator"), ), elem.ListItem( elem.Anchor( prop.Href("#"), event.Click(func(ev *vecty.Event) { v.App.Dispatch(&actions.ToggleInfoState{}) }).PreventDefault().StopPropagation(), elem.Italic( vecty.ClassMap{ "dropdown-icon": true, "glyphicon": true, "glyphicon-check": v.App.Misc.Info(), "glyphicon-unchecked": !v.App.Misc.Info(), }, ), vecty.Text("Show info"), ), ), ), ) }
func (v *EditorListView) Render() *vecty.HTML { if v.model == nil || v.model.Node.Missing || v.model.Node.Null { return elem.Div(vecty.Text("editor (nil)")) } children := vecty.List{} add := func(n *node.Node) { for _, v := range v.exclude { if n.Key == v { return } } if n.Null || n.Missing { children = append(children, nullEditor(v.Ctx, n, v.App)) return } f := editable.Branch if e := models.GetEditable(v.Ctx, n); e != nil { f = e.Format(n.Rule) if f == editable.Block || f == editable.Inline { children = append(children, NewEditorView(v.Ctx, n).Controls( func() vecty.MarkupOrComponentOrHTML { return e.EditorView(v.Ctx, n, editable.Block) }, ), ) return } } if f == editable.Branch { b := v.App.Branches.Get(n) children = append(children, NewEditorView(v.Ctx, n).Icons( func() vecty.MarkupOrComponentOrHTML { return elem.Anchor( prop.Href("#"), event.Click(func(e *vecty.Event) { v.App.Dispatch(&actions.BranchSelecting{Branch: b, Op: models.BranchOpClickEditorLink}) }).PreventDefault(), elem.Italic( prop.Class("editor-icon editor-icon-after glyphicon glyphicon-share-alt"), ), ) }, )) } } for _, n := range v.model.Node.Map { // TODO: hide optional fields //if n.Missing || n.Null { // continue //} if v.filter != nil && *n.Origin != *v.filter { continue } add(n) } for _, n := range v.model.Node.Array { add(n) } v.items = len(children) if len(children) == 0 { v.container = elem.Div() } else { v.container = elem.Form( event.Submit(func(*vecty.Event) {}).PreventDefault(), children, ) } return v.container }
func (v *MapView) Render() *vecty.HTML { if v.model == nil { return elem.Div(vecty.Text("Map (nil)")) } var info vecty.List if v.App.Misc.Info() { ir, err := v.node.Node.Rule.ItemsRule() if err != nil { v.App.Fail <- kerr.Wrap("KYTRRFBKGP", err) return nil } dt, err := ir.DisplayType() if err != nil { v.App.Fail <- kerr.Wrap("RRJAVDKLSI", err) return nil } info = append(info, elem.Paragraph( prop.Class("lead"), vecty.Text("type: map of "+dt), ), ) } return elem.Div( NewPanelNavView(v.Ctx, v.branch).Contents( func() vecty.MarkupOrComponentOrHTML { return elem.UnorderedList( prop.Class("nav navbar-nav navbar-right"), elem.ListItem( elem.Anchor( vecty.Text("Add"), prop.Href("#"), event.Click(func(ev *vecty.Event) { addCollectionItem(v.Ctx, v.App, v.model.Node) }).PreventDefault(), ), ), elem.ListItem( prop.Class("dropdown"), elem.Anchor( prop.Href("#"), prop.Class("dropdown-toggle"), vecty.Data("toggle", "dropdown"), vecty.Property("role", "button"), vecty.Property("aria-haspopup", "true"), vecty.Property("aria-expanded", "false"), vecty.Text("Options"), elem.Span( prop.Class("caret"), ), ), elem.UnorderedList( prop.Class("dropdown-menu"), elem.ListItem( elem.Anchor( prop.Href("#"), vecty.Text("Delete"), event.Click(func(e *vecty.Event) { v.App.Dispatch(&actions.Delete{ Undoer: &actions.Undoer{}, Node: v.model.Node, Parent: v.model.Node.Parent, }) }).PreventDefault(), ), ), ), ), ) }, ), info, NewEditorListView(v.Ctx, v.model, nil, nil), v.errorBlock(), ) }
func (v *EditorView) Render() *vecty.HTML { var dropdownItems []vecty.MarkupOrComponentOrHTML dropdownItems = append(dropdownItems, v.dropdown()) if !v.model.Node.Missing && !v.model.Node.Null { dropdownItems = append(dropdownItems, elem.ListItem( elem.Anchor( prop.Href("#"), vecty.Text("Delete"), event.Click(func(e *vecty.Event) { v.App.Dispatch(&actions.Delete{ Undoer: &actions.Undoer{}, Node: v.model.Node, Parent: v.model.Node.Parent, }) }).PreventDefault(), ), )) } var dropdown *vecty.HTML if dropdownItems != nil { dropdown = elem.Span( prop.Class("dropdown"), elem.Anchor( prop.Href("#"), prop.Class("dropdown-toggle"), vecty.Data("toggle", "dropdown"), vecty.Property("aria-haspopup", "true"), vecty.Property("aria-expanded", "true"), elem.Italic( prop.Class("editor-icon editor-icon-before glyphicon glyphicon-collapse-down"), ), ), elem.UnorderedList( append(dropdownItems, prop.Class("dropdown-menu"))..., ), ) } var handle vecty.List if v.model.Node.Index != -1 { handle = append(handle, elem.Italic( prop.Class("handle"), elem.Span( prop.Class("glyphicon glyphicon-option-vertical"), ), )) } label := elem.Label( prop.Class("control-label"), vecty.Text( v.model.Node.Label(v.Ctx), ), ) group := elem.Div( vecty.ClassMap{ "form-group": true, "has-error": v.node.Invalid, }, handle, dropdown, label, v.icons(), v.controls(), v.helpBlock(), v.errorBlock(), ) return group }
func (v *ObjectEditorView) Render() *vecty.HTML { sections := vecty.List{} sections = append(sections, views.NewPanelNavView(v.Ctx, v.branch).Contents( func() vecty.MarkupOrComponentOrHTML { return elem.UnorderedList( prop.Class("nav navbar-nav navbar-right"), elem.ListItem( prop.Class("dropdown"), elem.Anchor( prop.Href("#"), prop.Class("dropdown-toggle"), vecty.Data("toggle", "dropdown"), vecty.Property("role", "button"), vecty.Property("aria-haspopup", "true"), vecty.Property("aria-expanded", "false"), vecty.Text("Options"), elem.Span( prop.Class("caret"), ), ), elem.UnorderedList( prop.Class("dropdown-menu"), elem.ListItem( elem.Anchor( prop.Href("#"), event.Click(func(ev *vecty.Event) { v.App.Dispatch(&actions.ToggleSystemControls{ Node: v.model.Node, }) }).PreventDefault(), elem.Italic( vecty.ClassMap{ "dropdown-icon": true, "glyphicon": true, "glyphicon-check": v.node.ShowSystemControls, "glyphicon-unchecked": !v.node.ShowSystemControls, }, ), vecty.Text("System controls"), ), ), elem.ListItem( prop.Class("divider"), vecty.Property("role", "separator"), ), elem.ListItem( elem.Anchor( prop.Href("#"), vecty.Text("Delete"), event.Click(func(e *vecty.Event) { v.App.Dispatch(&actions.Delete{ Undoer: &actions.Undoer{}, Node: v.model.Node, Parent: v.model.Node.Parent, }) }).PreventDefault(), ), ), ), ), ) }, ), ) d := v.model.Node.Map["description"] if !d.Null && !d.Missing && d.ValueString != "" { sections = append(sections, elem.Paragraph( prop.Class("lead"), vecty.Text(d.ValueString), )) } if v.App.Misc.Info() { dt, err := v.node.Node.DisplayType(v.Ctx) if err != nil { v.App.Fail <- kerr.Wrap("KFKGCGFULR", err) return nil } sections = append(sections, elem.Paragraph( prop.Class("lead"), vecty.Text("type: "+dt), ), ) } if v.node.ShowSystemControls { sections = append(sections, elem.Div( prop.Class("well object-editor"), views.NewEditorListView(v.Ctx, v.model, system.NewReference("kego.io/system", "object"), []string{"id", "type"}), ), ) } return elem.Div( sections, ) }
func (v *Header) Render() *vecty.HTML { return elem.Navigation( prop.Class("navbar navbar-inverse navbar-fixed-top"), elem.Div( prop.Class("container-fluid"), elem.Div( prop.Class("navbar-header"), elem.Button( prop.Type("button"), prop.Class("navbar-toggle collapsed"), vecty.Data("toggle", "collapse"), vecty.Data("target", "#navbar"), elem.Span( prop.Class("sr-only"), vecty.Text("Toggle navigation"), ), elem.Span( prop.Class("icon-bar"), ), elem.Span( prop.Class("icon-bar"), ), elem.Span( prop.Class("icon-bar"), ), ), /* elem.Anchor( prop.Class("navbar-brand"), prop.Href("#"), vecty.Text("Ke"), ), */ ), elem.Div( prop.ID("navbar"), prop.Class("navbar-collapse collapse"), elem.UnorderedList( prop.Class("nav navbar-nav"), NewViewMenuView(v.Ctx), NewEditMenuView(v.Ctx), NewSaveView(v.Ctx), ), elem.Form( prop.Class("navbar-form navbar-right"), elem.Input( prop.Type("text"), prop.Class("form-control"), prop.Placeholder("Search..."), ), ), ), ), ) }
func (v *AddPopupView) Render() *vecty.HTML { if v.model == nil || !v.model.Visible { return v.modal() } v.nameInput = nil v.typeSelect = nil var nameControl, typeControl *vecty.HTML if v.model.HasName() { v.nameInput = elem.Input( prop.Class("form-control"), prop.ID("add-modal-name"), ) nameControl = elem.Div( prop.Class("form-group"), elem.Label( prop.For("add-modal-name"), vecty.Text("name"), ), v.nameInput, elem.Paragraph( prop.Class("help-block"), vecty.Text("Enter a name for the new item"), ), ) } if len(v.model.Types) > 1 { var options vecty.List for _, t := range v.model.Types { displayName, err := t.Id.ValueContext(v.Ctx) if err != nil { // we shouldn't be able to get here v.App.Fail <- kerr.Wrap("NTSWPIEAHC", err) } options = append(options, elem.Option( prop.ID(t.Id.String()), vecty.Text(displayName), )) } v.typeSelect = elem.Select( prop.Class("form-control"), prop.ID("add-modal-type"), elem.Option( prop.ID(""), vecty.Text(""), ), options, ) typeControl = elem.Div( prop.Class("form-group"), elem.Label( prop.For("add-modal-type"), vecty.Text("type"), ), v.typeSelect, elem.Paragraph( prop.Class("help-block"), vecty.Text("Select a type for the new object"), ), ) } return v.modal( elem.Div( prop.Class("modal-body"), elem.Form( event.Submit(func(ev *vecty.Event) { v.save() }).PreventDefault(), nameControl, typeControl, ), ), ) }
func (v *EditMenuView) Render() *vecty.HTML { var undoLabel, redoLabel string var undoEnabled, redoEnabled bool if v.App.Actions.Index() <= 0 { undoLabel = "Undo" } else { undoEnabled = true undoLabel = fmt.Sprintf("Undo %s", v.App.Actions.UndoPeek().Description()) } if v.App.Actions.Index() >= v.App.Actions.Count() { redoLabel = "Redo" } else { redoEnabled = true redoLabel = fmt.Sprintf("Redo %s", v.App.Actions.RedoPeek().Description()) } return elem.ListItem( prop.Class("dropdown"), elem.Anchor( prop.Href("#"), prop.Class("dropdown-toggle"), vecty.Data("toggle", "dropdown"), vecty.Text("Edit"), elem.Span( prop.Class("caret"), ), ), elem.UnorderedList( prop.Class("dropdown-menu"), elem.ListItem( vecty.ClassMap{ "disabled": !undoEnabled, }, elem.Anchor( event.Click(func(e *vecty.Event) { action := v.App.Actions.Undo() if action == nil { return } v.App.Dispatch(action) }).PreventDefault().StopPropagation(), prop.Href("#"), vecty.Text(undoLabel), ), ), elem.ListItem( vecty.ClassMap{ "disabled": !redoEnabled, }, elem.Anchor( event.Click(func(e *vecty.Event) { action := v.App.Actions.Redo() if action == nil { return } v.App.Dispatch(action) }).PreventDefault().StopPropagation(), prop.Href("#"), vecty.Text(redoLabel), ), ), ), ) }