Skip to content

Commit

Permalink
request variables
Browse files Browse the repository at this point in the history
  • Loading branch information
mirzakhany committed Jan 31, 2025
1 parent 83da84c commit 1053248
Show file tree
Hide file tree
Showing 6 changed files with 420 additions and 12 deletions.
22 changes: 22 additions & 0 deletions internal/domain/requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,28 @@ type SShTunnel struct {
Flags []string `yaml:"flags"`
}

type VariableFrom string

func (v VariableFrom) String() string {
return string(v)
}

const (
VariableFromBody VariableFrom = "body"
VariableFromHeader VariableFrom = "header"
VariableFromCookies VariableFrom = "cookies"
)

type Variable struct {
ID string `yaml:"id"` // Unique identifier
TargetEnvVariable string `yaml:"TargetEnvVariable"` // The environment variable to set
From VariableFrom `yaml:"from"` // Source: "body", "header", "cookie"
SourceKey string `yaml:"sourceKey"` // For "header" or "cookie", specify the key name
OnStatusCode int `yaml:"onStatusCode"` // Trigger on a specific status code
JsonPath string `yaml:"jsonPath"` // JSONPath for extracting value (for "body")
Enable bool `yaml:"enable"` // Enable or disable the variable
}

func (r *Request) Clone() *Request {
clone := *r
clone.MetaData.ID = uuid.NewString()
Expand Down
3 changes: 2 additions & 1 deletion internal/domain/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ type HTTPRequest struct {

Body Body `yaml:"body"`

Auth Auth `yaml:"auth"`
Auth Auth `yaml:"auth"`
Variables []Variable `yaml:"variables"`

PreRequest PreRequest `yaml:"preRequest"`
PostRequest PostRequest `yaml:"postRequest"`
Expand Down
338 changes: 338 additions & 0 deletions ui/pages/requests/component/variables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,338 @@
package component

import (
"strconv"

"gioui.org/layout"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"github.com/google/uuid"

"github.com/chapar-rest/chapar/internal/domain"
"github.com/chapar-rest/chapar/ui/chapartheme"
"github.com/chapar-rest/chapar/ui/keys"
"github.com/chapar-rest/chapar/ui/widgets"
)

type Variables struct {
theme *chapartheme.Theme

Items []*Variable
addButton *widgets.IconButton
list *widget.List

onSelectFile func(id string)
onChanged func(values []domain.Variable)

preview string
}

type Variable struct {
Identifier string
TargetEnvVariable string // The environment variable to set
From domain.VariableFrom // Source: "body", "header", "cookie"
SourceKey string // For "header" or "cookie", specify the key name
OnStatusCode int // Trigger on a specific status code
JsonPath string // JSONPath for extracting value (for "body")
Enable bool // Enable or disable this variable

targetEnvEditor *widget.Editor
fromDropDown *widgets.DropDown
sourceKeyEditor *widget.Editor
onStatusCodeEditor *widgets.NumericEditor
jsonPathCodeEditor *widget.Editor

enableBool *widget.Bool
deleteButton widget.Clickable
}

func NewVariables(theme *chapartheme.Theme, items ...*Variable) *Variables {
f := &Variables{
theme: theme,
addButton: &widgets.IconButton{
Icon: widgets.PlusIcon,
Size: unit.Dp(20),
Clickable: &widget.Clickable{},
},
list: &widget.List{
List: layout.List{
Axis: layout.Vertical,
},
},
}

for _, item := range items {
f.addItem(item)
}

f.addButton.OnClick = func() {
f.addItem(NewVariable())
if f.onChanged != nil {
f.onChanged(f.GetValues())
}
}

return f
}

func (f *Variables) SetOnChanged(fn func(values []domain.Variable)) {
f.onChanged = fn
}

func (f *Variables) GetValues() []domain.Variable {
values := make([]domain.Variable, 0, len(f.Items))
for _, item := range f.Items {
values = append(values, domain.Variable{
ID: item.Identifier,
TargetEnvVariable: item.TargetEnvVariable,
From: item.From,
SourceKey: item.SourceKey,
OnStatusCode: item.OnStatusCode,
JsonPath: item.JsonPath,
Enable: item.Enable,
})
}
return values
}

func (f *Variables) SetValues(values []domain.Variable) {
f.Items = make([]*Variable, 0, len(values))
for _, item := range values {
f.addItem(&Variable{
Identifier: item.ID,
TargetEnvVariable: item.TargetEnvVariable,
From: item.From,
SourceKey: item.SourceKey,
OnStatusCode: item.OnStatusCode,
JsonPath: item.JsonPath,
Enable: item.Enable,
})
}
}

func (f *Variables) addItem(item *Variable) {
item.fromDropDown = widgets.NewDropDownWithoutBorder(
f.theme,
widgets.NewDropDownOption("Body").WithIdentifier("body").WithValue("body"),
widgets.NewDropDownOption("Header").WithIdentifier("header").WithValue("header"),
widgets.NewDropDownOption("Cookie").WithIdentifier("cookie").WithValue("cookie"),
)
item.fromDropDown.SetSelectedByValue(item.From.String())
item.fromDropDown.MinWidth = unit.Dp(60)
item.fromDropDown.MaxWidth = unit.Dp(80)

item.targetEnvEditor = &widget.Editor{SingleLine: true}
item.targetEnvEditor.SetText(item.TargetEnvVariable)

item.sourceKeyEditor = &widget.Editor{SingleLine: true}
item.sourceKeyEditor.SetText(item.SourceKey)

item.onStatusCodeEditor = &widgets.NumericEditor{Editor: widget.Editor{SingleLine: true}}
item.onStatusCodeEditor.Editor.SetText(strconv.Itoa(item.OnStatusCode))

item.enableBool = new(widget.Bool)
item.enableBool.Value = item.Enable

item.fromDropDown.SetOnChanged(func(selected string) {
item.From = domain.VariableFrom(selected)
f.triggerChanged()
})

item.jsonPathCodeEditor = &widget.Editor{SingleLine: true}
item.jsonPathCodeEditor.SetText(item.JsonPath)

f.Items = append(f.Items, item)
}

func NewVariable() *Variable {
return &Variable{
Identifier: uuid.NewString(),
TargetEnvVariable: "",
From: domain.VariableFromBody,
SourceKey: "",
OnStatusCode: 0,
JsonPath: "",
Enable: true,
}
}

func (f *Variables) itemLayout(gtx layout.Context, theme *chapartheme.Theme, item *Variable) layout.Dimensions {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle}.Layout(gtx, f.itemLayouts(gtx, theme, item)...)
}

func (f *Variables) triggerChanged() {
if f.onChanged != nil {
f.onChanged(f.GetValues())
}
}

func (f *Variables) itemLayouts(gtx layout.Context, theme *chapartheme.Theme, item *Variable) []layout.FlexChild {
keys.OnEditorChange(gtx, item.targetEnvEditor, func() {
item.TargetEnvVariable = item.targetEnvEditor.Text()
f.triggerChanged()
})

keys.OnEditorChange(gtx, item.sourceKeyEditor, func() {
item.SourceKey = item.sourceKeyEditor.Text()
f.triggerChanged()
})

keys.OnEditorChange(gtx, &item.onStatusCodeEditor.Editor, func() {
item.OnStatusCode = item.onStatusCodeEditor.Value()
f.triggerChanged()
})

keys.OnEditorChange(gtx, item.jsonPathCodeEditor, func() {
item.JsonPath = item.jsonPathCodeEditor.Text()
f.triggerChanged()
})

if item.enableBool.Update(gtx) {
item.Enable = item.enableBool.Value
f.triggerChanged()
}

items := []layout.FlexChild{
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
ch := widgets.CheckBox(theme.Material(), item.enableBool, "")
ch.IconColor = theme.CheckBoxColor
return ch.Layout(gtx)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Inset{Left: unit.Dp(1), Right: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(100))
editor := material.Editor(theme.Material(), item.sourceKeyEditor, "Target")
editor.SelectionColor = theme.TextSelectionColor
return editor.Layout(gtx)
})
}),
widgets.DrawLineFlex(theme.TableBorderColor, unit.Dp(35), unit.Dp(1)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return item.fromDropDown.Layout(gtx, theme)
}),
widgets.DrawLineFlex(theme.TableBorderColor, unit.Dp(35), unit.Dp(1)),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Inset{Left: unit.Dp(4), Right: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Min.X = gtx.Dp(unit.Dp(40))
return item.onStatusCodeEditor.Layout(gtx, theme)
})
}),
widgets.DrawLineFlex(theme.TableBorderColor, unit.Dp(35), unit.Dp(1)),
}

itemType := item.fromDropDown.GetSelected().Identifier
if itemType == string(domain.VariableFromBody) {
items = append(items, layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return layout.Inset{Left: unit.Dp(4), Right: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
editor := material.Editor(theme.Material(), item.jsonPathCodeEditor, "e.g. $.data[0].name")
editor.SelectionColor = theme.TextSelectionColor
return editor.Layout(gtx)
})
}))
} else {
items = append(items, layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return layout.Inset{Left: unit.Dp(4), Right: unit.Dp(4)}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
editor := material.Editor(theme.Material(), item.sourceKeyEditor, "Source Key")
editor.SelectionColor = theme.TextSelectionColor
return editor.Layout(gtx)
})
}))
}

items = append(items, layout.Rigid(func(gtx layout.Context) layout.Dimensions {
ib := widgets.IconButton{
Icon: widgets.DeleteIcon,
Size: unit.Dp(18),
Color: theme.TextColor,
Clickable: &item.deleteButton,
}
return ib.Layout(gtx, theme)
}))

return items
}

func (f *Variables) layout(gtx layout.Context, theme *chapartheme.Theme) layout.Dimensions {
border := widget.Border{
Color: theme.TableBorderColor,
CornerRadius: unit.Dp(4),
Width: unit.Dp(1),
}

return border.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
if len(f.Items) == 0 {
return layout.UniformInset(unit.Dp(10)).Layout(gtx, material.Label(theme.Material(), unit.Sp(14), "No items").Layout)
}

return material.List(theme.Material(), f.list).Layout(gtx, len(f.Items), func(gtx layout.Context, i int) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return f.itemLayout(gtx, theme, f.Items[i])
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
// only if it's not the last item
if i == len(f.Items)-1 {
return layout.Dimensions{}
}
return widgets.DrawLine(gtx, theme.TableBorderColor, unit.Dp(1), unit.Dp(gtx.Constraints.Max.X))
}),
)
})
})
}

func (f *Variables) Layout(gtx layout.Context, title, hint string, theme *chapartheme.Theme) layout.Dimensions {
for i, field := range f.Items {
if field.deleteButton.Clicked(gtx) {
f.Items = append(f.Items[:i], f.Items[i+1:]...)
f.triggerChanged()
}
}

inset := layout.Inset{Top: unit.Dp(15), Right: unit.Dp(10)}
prevInset := layout.Inset{Top: unit.Dp(8), Bottom: unit.Dp(4)}
return inset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Flex{Axis: layout.Horizontal, Alignment: layout.Middle, Spacing: layout.SpaceBetween}.Layout(gtx,
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return material.Label(theme.Material(), theme.TextSize, title).Layout(gtx)
}),
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
return layout.Inset{
Left: unit.Dp(10),
Right: unit.Dp(10),
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return material.Label(theme.Material(), unit.Sp(10), hint).Layout(gtx)
})
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Inset{
Top: 0,
Bottom: unit.Dp(10),
Left: 0,
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
f.addButton.BackgroundColor = theme.Palette.Bg
f.addButton.Color = theme.TextColor
return f.addButton.Layout(gtx, theme)
})
}),
)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return f.layout(gtx, theme)
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return prevInset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return material.Label(theme.Material(), theme.TextSize, "Preview:").Layout(gtx)
})
}),
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return prevInset.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return material.Label(theme.Material(), theme.TextSize, f.preview).Layout(gtx)
})
}),
)
})
}
Loading

0 comments on commit 1053248

Please sign in to comment.