2021-09-07 17:36:50 +03:00
|
|
|
package rui
|
|
|
|
|
|
|
|
import (
|
2024-11-18 17:20:25 +03:00
|
|
|
"fmt"
|
2022-05-03 12:12:57 +03:00
|
|
|
"math"
|
2021-09-07 17:36:50 +03:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2024-09-12 14:05:11 +03:00
|
|
|
// Constants related to [NumberPicker] specific properties and events
|
2021-09-07 17:36:50 +03:00
|
|
|
const (
|
2024-09-18 13:50:06 +03:00
|
|
|
// NumberChangedEvent is the constant for "number-changed" property tag.
|
|
|
|
//
|
|
|
|
// Used by `NumberPicker`.
|
|
|
|
// Set listener(s) that track the change in the entered value.
|
|
|
|
//
|
|
|
|
// General listener format:
|
|
|
|
// `func(picker rui.NumberPicker, newValue, oldValue float64)`.
|
|
|
|
//
|
|
|
|
// where:
|
|
|
|
// picker - Interface of a number picker which generated this event,
|
|
|
|
// newValue - New value,
|
|
|
|
// oldValue - Old Value.
|
|
|
|
//
|
|
|
|
// Allowed listener formats:
|
|
|
|
// `func(picker rui.NumberPicker, newValue float64)`,
|
|
|
|
// `func(newValue, oldValue float64)`,
|
|
|
|
// `func(newValue float64)`,
|
|
|
|
// `func()`.
|
2024-11-13 12:56:39 +03:00
|
|
|
NumberChangedEvent PropertyName = "number-changed"
|
2024-04-23 19:34:36 +03:00
|
|
|
|
2024-09-18 13:50:06 +03:00
|
|
|
// NumberPickerType is the constant for "number-picker-type" property tag.
|
|
|
|
//
|
|
|
|
// Used by `NumberPicker`.
|
|
|
|
// Sets the visual representation.
|
|
|
|
//
|
|
|
|
// Supported types: `int`, `string`.
|
|
|
|
//
|
|
|
|
// Values:
|
|
|
|
// `0`(`NumberEditor`) or "editor" - Displayed as an editor.
|
|
|
|
// `1`(`NumberSlider`) or "slider" - Displayed as a slider.
|
2024-11-13 12:56:39 +03:00
|
|
|
NumberPickerType PropertyName = "number-picker-type"
|
2024-04-23 19:34:36 +03:00
|
|
|
|
2024-09-18 13:50:06 +03:00
|
|
|
// NumberPickerMin is the constant for "number-picker-min" property tag.
|
|
|
|
//
|
|
|
|
// Used by `NumberPicker`.
|
|
|
|
// Set the minimum value. The default value is 0.
|
|
|
|
//
|
|
|
|
// Supported types: `float`, `int`, `string`.
|
|
|
|
//
|
|
|
|
// Internal type is `float`, other types converted to it during assignment.
|
2024-11-13 12:56:39 +03:00
|
|
|
NumberPickerMin PropertyName = "number-picker-min"
|
2024-04-23 19:34:36 +03:00
|
|
|
|
2024-09-18 13:50:06 +03:00
|
|
|
// NumberPickerMax is the constant for "number-picker-max" property tag.
|
|
|
|
//
|
|
|
|
// Used by `NumberPicker`.
|
|
|
|
// Set the maximum value. The default value is 1.
|
|
|
|
//
|
|
|
|
// Supported types: `float`, `int`, `string`.
|
|
|
|
//
|
|
|
|
// Internal type is `float`, other types converted to it during assignment.
|
2024-11-13 12:56:39 +03:00
|
|
|
NumberPickerMax PropertyName = "number-picker-max"
|
2024-04-23 19:34:36 +03:00
|
|
|
|
2024-09-18 13:50:06 +03:00
|
|
|
// NumberPickerStep is the constant for "number-picker-step" property tag.
|
|
|
|
//
|
|
|
|
// Used by `NumberPicker`.
|
|
|
|
// Set the value change step.
|
|
|
|
//
|
|
|
|
// Supported types: `float`, `int`, `string`.
|
|
|
|
//
|
|
|
|
// Internal type is `float`, other types converted to it during assignment.
|
2024-11-13 12:56:39 +03:00
|
|
|
NumberPickerStep PropertyName = "number-picker-step"
|
2024-04-23 19:34:36 +03:00
|
|
|
|
2024-09-18 13:50:06 +03:00
|
|
|
// NumberPickerValue is the constant for "number-picker-value" property tag.
|
|
|
|
//
|
|
|
|
// Used by `NumberPicker`.
|
|
|
|
// Current value. The default value is 0.
|
|
|
|
//
|
|
|
|
// Supported types: `float`, `int`, `string`.
|
|
|
|
//
|
|
|
|
// Internal type is `float`, other types converted to it during assignment.
|
2024-11-13 12:56:39 +03:00
|
|
|
NumberPickerValue PropertyName = "number-picker-value"
|
2024-11-18 17:20:25 +03:00
|
|
|
|
|
|
|
// NumberPickerValue is the constant for "number-picker-value" property tag.
|
|
|
|
//
|
|
|
|
// Used by `NumberPicker`.
|
|
|
|
// Precision of displaying fractional part in editor. The default value is 0 (not used).
|
|
|
|
//
|
|
|
|
// Supported types: `int`, `int8`...`int64`, `uint`, `uint8`...`uint64`, `string`.
|
|
|
|
//
|
|
|
|
// Internal type is `float`, other types converted to it during assignment.
|
|
|
|
NumberPickerPrecision PropertyName = "number-picker-precision"
|
2021-09-07 17:36:50 +03:00
|
|
|
)
|
|
|
|
|
2024-09-12 14:05:11 +03:00
|
|
|
// Constants which describe values of the "number-picker-type" property of a [NumberPicker]
|
2021-09-07 17:36:50 +03:00
|
|
|
const (
|
|
|
|
// NumberEditor - type of NumberPicker. NumberPicker is presented by editor
|
|
|
|
NumberEditor = 0
|
2024-04-23 19:34:36 +03:00
|
|
|
|
2021-09-07 17:36:50 +03:00
|
|
|
// NumberSlider - type of NumberPicker. NumberPicker is presented by slider
|
|
|
|
NumberSlider = 1
|
|
|
|
)
|
|
|
|
|
2024-09-12 14:05:11 +03:00
|
|
|
// NumberPicker represents a NumberPicker view
|
2021-09-07 17:36:50 +03:00
|
|
|
type NumberPicker interface {
|
|
|
|
View
|
|
|
|
}
|
|
|
|
|
|
|
|
type numberPickerData struct {
|
|
|
|
viewData
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewNumberPicker create new NumberPicker object and return it
|
|
|
|
func NewNumberPicker(session Session, params Params) NumberPicker {
|
|
|
|
view := new(numberPickerData)
|
2022-09-01 11:04:50 +03:00
|
|
|
view.init(session)
|
2021-09-07 17:36:50 +03:00
|
|
|
setInitParams(view, params)
|
|
|
|
return view
|
|
|
|
}
|
|
|
|
|
|
|
|
func newNumberPicker(session Session) View {
|
2024-11-13 12:56:39 +03:00
|
|
|
return new(numberPickerData)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2022-09-01 11:04:50 +03:00
|
|
|
func (picker *numberPickerData) init(session Session) {
|
|
|
|
picker.viewData.init(session)
|
2021-09-07 17:36:50 +03:00
|
|
|
picker.tag = "NumberPicker"
|
2024-04-23 18:24:51 +03:00
|
|
|
picker.hasHtmlDisabled = true
|
2024-11-13 12:56:39 +03:00
|
|
|
picker.normalize = normalizeNumberPickerTag
|
2024-11-18 17:20:25 +03:00
|
|
|
picker.set = picker.setFunc
|
|
|
|
picker.changed = picker.propertyChanged
|
2022-05-22 12:54:02 +03:00
|
|
|
}
|
|
|
|
|
2022-01-15 01:20:04 +03:00
|
|
|
func (picker *numberPickerData) Focusable() bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-11-13 12:56:39 +03:00
|
|
|
func normalizeNumberPickerTag(tag PropertyName) PropertyName {
|
|
|
|
tag = defaultNormalize(tag)
|
2021-09-07 17:36:50 +03:00
|
|
|
switch tag {
|
2024-11-18 17:20:25 +03:00
|
|
|
case Type, Min, Max, Step, Value, "precision":
|
2021-09-07 17:36:50 +03:00
|
|
|
return "number-picker-" + tag
|
|
|
|
}
|
|
|
|
|
2024-11-13 12:56:39 +03:00
|
|
|
return normalizeDataListTag(tag)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2024-11-18 17:20:25 +03:00
|
|
|
func (picker *numberPickerData) setFunc(tag PropertyName, value any) []PropertyName {
|
2021-09-07 17:36:50 +03:00
|
|
|
switch tag {
|
|
|
|
case NumberChangedEvent:
|
2024-11-18 17:20:25 +03:00
|
|
|
return setTwoArgEventListener[NumberPicker, float64](picker, tag, value)
|
2023-04-23 18:39:25 +03:00
|
|
|
|
|
|
|
case NumberPickerValue:
|
2024-11-18 17:20:25 +03:00
|
|
|
picker.setRaw("old-number", GetNumberPickerValue(picker))
|
|
|
|
min, max := GetNumberPickerMinMax(picker)
|
2021-09-07 17:36:50 +03:00
|
|
|
|
2024-11-18 17:20:25 +03:00
|
|
|
return setFloatProperty(picker, NumberPickerValue, value, min, max)
|
2024-05-18 18:57:41 +03:00
|
|
|
|
2024-11-13 12:56:39 +03:00
|
|
|
case DataList:
|
2024-11-18 17:20:25 +03:00
|
|
|
return setDataList(picker, value, "")
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2024-11-18 17:20:25 +03:00
|
|
|
return picker.viewData.setFunc(tag, value)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2024-11-18 17:20:25 +03:00
|
|
|
func (picker *numberPickerData) numberFormat() string {
|
|
|
|
if precission := GetNumberPickerPrecision(picker); precission > 0 {
|
|
|
|
return fmt.Sprintf("%%.%df", precission)
|
|
|
|
}
|
|
|
|
return "%g"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (picker *numberPickerData) propertyChanged(tag PropertyName) {
|
2021-09-07 17:36:50 +03:00
|
|
|
switch tag {
|
2024-11-13 12:56:39 +03:00
|
|
|
case NumberPickerType:
|
2024-11-18 17:20:25 +03:00
|
|
|
if GetNumberPickerType(picker) == NumberSlider {
|
|
|
|
picker.Session().updateProperty(picker.htmlID(), "type", "range")
|
2024-11-13 12:56:39 +03:00
|
|
|
} else {
|
2024-11-18 17:20:25 +03:00
|
|
|
picker.Session().updateProperty(picker.htmlID(), "type", "number")
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2024-11-13 12:56:39 +03:00
|
|
|
case NumberPickerMin:
|
2024-11-18 17:20:25 +03:00
|
|
|
min, _ := GetNumberPickerMinMax(picker)
|
|
|
|
picker.Session().updateProperty(picker.htmlID(), "min", fmt.Sprintf(picker.numberFormat(), min))
|
2021-09-07 17:36:50 +03:00
|
|
|
|
2024-11-13 12:56:39 +03:00
|
|
|
case NumberPickerMax:
|
2024-11-18 17:20:25 +03:00
|
|
|
_, max := GetNumberPickerMinMax(picker)
|
|
|
|
picker.Session().updateProperty(picker.htmlID(), "max", fmt.Sprintf(picker.numberFormat(), max))
|
2024-05-18 18:57:41 +03:00
|
|
|
|
2024-11-13 12:56:39 +03:00
|
|
|
case NumberPickerStep:
|
2024-11-18 17:20:25 +03:00
|
|
|
if step := GetNumberPickerStep(picker); step > 0 {
|
|
|
|
picker.Session().updateProperty(picker.htmlID(), "step", fmt.Sprintf(picker.numberFormat(), step))
|
2024-11-13 12:56:39 +03:00
|
|
|
} else {
|
2024-11-18 17:20:25 +03:00
|
|
|
picker.Session().updateProperty(picker.htmlID(), "step", "any")
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2024-11-18 17:20:25 +03:00
|
|
|
case NumberPickerValue:
|
|
|
|
value := GetNumberPickerValue(picker)
|
|
|
|
format := picker.numberFormat()
|
|
|
|
picker.Session().callFunc("setInputValue", picker.htmlID(), fmt.Sprintf(format, value))
|
2021-09-07 17:36:50 +03:00
|
|
|
|
2024-11-18 17:20:25 +03:00
|
|
|
if listeners := GetNumberChangedListeners(picker); len(listeners) > 0 {
|
2024-11-13 12:56:39 +03:00
|
|
|
old := 0.0
|
2024-11-18 17:20:25 +03:00
|
|
|
if val := picker.getRaw("old-number"); val != nil {
|
2024-11-13 12:56:39 +03:00
|
|
|
if n, ok := val.(float64); ok {
|
|
|
|
old = n
|
|
|
|
}
|
|
|
|
}
|
2024-11-18 17:20:25 +03:00
|
|
|
if old != value {
|
|
|
|
for _, listener := range listeners {
|
|
|
|
listener(picker, value, old)
|
|
|
|
}
|
2021-11-20 11:15:28 +03:00
|
|
|
}
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
2024-05-18 18:57:41 +03:00
|
|
|
|
2021-09-07 17:36:50 +03:00
|
|
|
default:
|
2024-11-18 17:20:25 +03:00
|
|
|
picker.viewData.propertyChanged(tag)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (picker *numberPickerData) htmlTag() string {
|
|
|
|
return "input"
|
|
|
|
}
|
|
|
|
|
2024-05-18 18:57:41 +03:00
|
|
|
func (picker *numberPickerData) htmlSubviews(self View, buffer *strings.Builder) {
|
2024-11-13 12:56:39 +03:00
|
|
|
dataListHtmlSubviews(self, buffer, func(text string, session Session) string {
|
|
|
|
text, _ = session.resolveConstants(text)
|
|
|
|
return text
|
|
|
|
})
|
2024-05-18 18:57:41 +03:00
|
|
|
}
|
|
|
|
|
2021-09-07 17:36:50 +03:00
|
|
|
func (picker *numberPickerData) htmlProperties(self View, buffer *strings.Builder) {
|
|
|
|
picker.viewData.htmlProperties(self, buffer)
|
|
|
|
|
2022-08-31 22:17:46 +03:00
|
|
|
if GetNumberPickerType(picker) == NumberSlider {
|
2021-09-07 17:36:50 +03:00
|
|
|
buffer.WriteString(` type="range"`)
|
|
|
|
} else {
|
|
|
|
buffer.WriteString(` type="number"`)
|
|
|
|
}
|
|
|
|
|
2024-11-18 17:20:25 +03:00
|
|
|
format := picker.numberFormat()
|
2022-08-31 22:17:46 +03:00
|
|
|
min, max := GetNumberPickerMinMax(picker)
|
2022-05-03 12:12:57 +03:00
|
|
|
if min != math.Inf(-1) {
|
|
|
|
buffer.WriteString(` min="`)
|
2024-11-18 17:20:25 +03:00
|
|
|
buffer.WriteString(fmt.Sprintf(format, min))
|
2022-05-03 12:12:57 +03:00
|
|
|
buffer.WriteByte('"')
|
|
|
|
}
|
2021-09-07 17:36:50 +03:00
|
|
|
|
2022-05-03 12:12:57 +03:00
|
|
|
if max != math.Inf(1) {
|
|
|
|
buffer.WriteString(` max="`)
|
2024-11-18 17:20:25 +03:00
|
|
|
buffer.WriteString(fmt.Sprintf(format, max))
|
2022-05-03 12:12:57 +03:00
|
|
|
buffer.WriteByte('"')
|
|
|
|
}
|
2021-09-07 17:36:50 +03:00
|
|
|
|
2022-08-31 22:17:46 +03:00
|
|
|
step := GetNumberPickerStep(picker)
|
2021-09-07 17:36:50 +03:00
|
|
|
if step != 0 {
|
|
|
|
buffer.WriteString(` step="`)
|
2024-11-18 17:20:25 +03:00
|
|
|
buffer.WriteString(fmt.Sprintf(format, step))
|
2021-09-07 17:36:50 +03:00
|
|
|
buffer.WriteByte('"')
|
|
|
|
} else {
|
|
|
|
buffer.WriteString(` step="any"`)
|
|
|
|
}
|
|
|
|
|
|
|
|
buffer.WriteString(` value="`)
|
2024-11-18 17:20:25 +03:00
|
|
|
buffer.WriteString(fmt.Sprintf(format, GetNumberPickerValue(picker)))
|
2021-09-07 17:36:50 +03:00
|
|
|
buffer.WriteByte('"')
|
|
|
|
|
|
|
|
buffer.WriteString(` oninput="editViewInputEvent(this)"`)
|
2024-05-18 18:57:41 +03:00
|
|
|
|
2024-11-13 12:56:39 +03:00
|
|
|
dataListHtmlProperties(picker, buffer)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2024-11-13 12:56:39 +03:00
|
|
|
func (picker *numberPickerData) handleCommand(self View, command PropertyName, data DataObject) bool {
|
2021-09-07 17:36:50 +03:00
|
|
|
switch command {
|
|
|
|
case "textChanged":
|
|
|
|
if text, ok := data.PropertyValue("text"); ok {
|
|
|
|
if value, err := strconv.ParseFloat(text, 32); err == nil {
|
2022-08-31 22:17:46 +03:00
|
|
|
oldValue := GetNumberPickerValue(picker)
|
2022-08-18 18:18:36 +03:00
|
|
|
picker.properties[NumberPickerValue] = text
|
2021-09-07 17:36:50 +03:00
|
|
|
if value != oldValue {
|
2024-11-13 12:56:39 +03:00
|
|
|
for _, listener := range GetNumberChangedListeners(picker) {
|
2023-04-23 18:39:25 +03:00
|
|
|
listener(picker, value, oldValue)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
2024-11-13 12:56:39 +03:00
|
|
|
if listener, ok := picker.changeListener[NumberPickerValue]; ok {
|
|
|
|
listener(picker, NumberPickerValue)
|
|
|
|
}
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return picker.viewData.handleCommand(self, command, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetNumberPickerType returns the type of NumberPicker subview. Valid values:
|
2021-11-04 21:13:34 +03:00
|
|
|
// NumberEditor (0) - NumberPicker is presented by editor (default type);
|
|
|
|
// NumberSlider (1) - NumberPicker is presented by slider.
|
2022-08-31 22:17:46 +03:00
|
|
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
|
|
|
func GetNumberPickerType(view View, subviewID ...string) int {
|
2022-07-28 12:41:50 +03:00
|
|
|
return enumStyledProperty(view, subviewID, NumberPickerType, NumberEditor, false)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetNumberPickerMinMax returns the min and max value of NumberPicker subview.
|
2022-08-31 22:17:46 +03:00
|
|
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
|
|
|
func GetNumberPickerMinMax(view View, subviewID ...string) (float64, float64) {
|
2024-11-24 16:43:31 +03:00
|
|
|
view = getSubview(view, subviewID)
|
|
|
|
pickerType := GetNumberPickerType(view)
|
2022-08-31 22:17:46 +03:00
|
|
|
|
2022-07-28 12:11:27 +03:00
|
|
|
var defMin, defMax float64
|
2022-08-31 22:17:46 +03:00
|
|
|
if pickerType == NumberSlider {
|
2022-07-28 12:11:27 +03:00
|
|
|
defMin = 0
|
|
|
|
defMax = 1
|
|
|
|
} else {
|
|
|
|
defMin = math.Inf(-1)
|
|
|
|
defMax = math.Inf(1)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
2024-11-24 16:43:31 +03:00
|
|
|
min := floatStyledProperty(view, nil, NumberPickerMin, defMin)
|
|
|
|
max := floatStyledProperty(view, nil, NumberPickerMax, defMax)
|
2021-09-07 17:36:50 +03:00
|
|
|
|
2022-07-28 12:11:27 +03:00
|
|
|
if min > max {
|
|
|
|
return max, min
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
2022-07-28 12:11:27 +03:00
|
|
|
return min, max
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetNumberPickerStep returns the value changing step of NumberPicker subview.
|
2022-08-31 22:17:46 +03:00
|
|
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
|
|
|
func GetNumberPickerStep(view View, subviewID ...string) float64 {
|
2024-11-24 16:43:31 +03:00
|
|
|
view = getSubview(view, subviewID)
|
|
|
|
_, max := GetNumberPickerMinMax(view)
|
2022-08-31 22:17:46 +03:00
|
|
|
|
2024-11-24 16:43:31 +03:00
|
|
|
result := floatStyledProperty(view, nil, NumberPickerStep, 0)
|
2021-09-07 17:36:50 +03:00
|
|
|
if result > max {
|
|
|
|
return max
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetNumberPickerValue returns the value of NumberPicker subview.
|
2022-08-31 22:17:46 +03:00
|
|
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
|
|
|
func GetNumberPickerValue(view View, subviewID ...string) float64 {
|
2024-11-24 16:43:31 +03:00
|
|
|
view = getSubview(view, subviewID)
|
|
|
|
min, _ := GetNumberPickerMinMax(view)
|
|
|
|
return floatStyledProperty(view, nil, NumberPickerValue, min)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetNumberChangedListeners returns the NumberChangedListener list of an NumberPicker subview.
|
|
|
|
// If there are no listeners then the empty list is returned
|
2022-08-31 22:17:46 +03:00
|
|
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
2023-04-23 18:39:25 +03:00
|
|
|
func GetNumberChangedListeners(view View, subviewID ...string) []func(NumberPicker, float64, float64) {
|
2024-11-18 17:20:25 +03:00
|
|
|
return getTwoArgEventListeners[NumberPicker, float64](view, subviewID, NumberChangedEvent)
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetNumberPickerPrecision returns the precision of displaying fractional part in editor of NumberPicker subview.
|
|
|
|
// If the second argument (subviewID) is not specified or it is "" then a value from the first argument (view) is returned.
|
|
|
|
func GetNumberPickerPrecision(view View, subviewID ...string) int {
|
|
|
|
return intStyledProperty(view, subviewID, NumberPickerPrecision, 0)
|
2021-09-07 17:36:50 +03:00
|
|
|
}
|