devices: filled the add device page
This commit is contained in:
parent
19ffbdf98c
commit
494a4bc956
|
@ -23,9 +23,14 @@ type DevicesListParams struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dlp *DevicesListParams) FillFromQuery(query Queryable) {
|
func (dlp *DevicesListParams) FillFromQuery(query Queryable) {
|
||||||
dlp.Status, _ = strconv.Atoi(query.Get("status"))
|
statusStr := query.Get("status")
|
||||||
if dlp.Status > 2 {
|
switch statusStr {
|
||||||
dlp.Status = 2
|
case "0":
|
||||||
|
dlp.Status = 0
|
||||||
|
case "1":
|
||||||
|
dlp.Status = 1
|
||||||
|
default:
|
||||||
|
dlp.Status = -1
|
||||||
}
|
}
|
||||||
dlp.Q = query.Get("q")
|
dlp.Q = query.Get("q")
|
||||||
|
|
||||||
|
@ -68,8 +73,8 @@ func (dlp DevicesListParams) Query() (expr []bob.Mod[*dialect.SelectQuery]) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dlp DevicesListParams) CountQuery() (expr []bob.Mod[*dialect.SelectQuery]) {
|
func (dlp DevicesListParams) CountQuery() (expr []bob.Mod[*dialect.SelectQuery]) {
|
||||||
if dlp.Status > 0 {
|
if dlp.Status >= 0 {
|
||||||
expr = append(expr, models.SelectWhere.Devices.Enable.EQ(int32(dlp.Status-1)))
|
expr = append(expr, models.SelectWhere.Devices.Enable.EQ(int32(dlp.Status)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if dlp.Q != "" {
|
if dlp.Q != "" {
|
||||||
|
|
|
@ -58,6 +58,7 @@ var DefaultConfig = map[string]any{
|
||||||
|
|
||||||
"web.dependencies.htmx.version": "1.9.12",
|
"web.dependencies.htmx.version": "1.9.12",
|
||||||
"web.dependencies.dayjs.version": "1.11.10",
|
"web.dependencies.dayjs.version": "1.11.10",
|
||||||
|
"web.dependencies.alpinejs.version": "3.13.10",
|
||||||
|
|
||||||
"runtime.version": "0.0.1",
|
"runtime.version": "0.0.1",
|
||||||
"runtime.environment": "development",
|
"runtime.environment": "development",
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -40,6 +40,8 @@ require (
|
||||||
github.com/boreq/errors v0.1.0 // indirect
|
github.com/boreq/errors v0.1.0 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gosimple/slug v1.14.0 // indirect
|
||||||
|
github.com/gosimple/unidecode v1.0.1 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||||
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
|
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
|
||||||
github.com/oklog/ulid v1.3.1 // indirect
|
github.com/oklog/ulid v1.3.1 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -184,6 +184,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
github.com/gosimple/slug v1.14.0 h1:RtTL/71mJNDfpUbCOmnf/XFkzKRtD6wL6Uy+3akm4Es=
|
||||||
|
github.com/gosimple/slug v1.14.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
|
||||||
|
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
|
||||||
|
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
|
|
59
server/routes/device_validate_name.go
Normal file
59
server/routes/device_validate_name.go
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gosimple/slug"
|
||||||
|
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||||
|
"github.com/tigorlazuardi/redmage/pkg/log"
|
||||||
|
"github.com/tigorlazuardi/redmage/views/devicesview/adddevice"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (routes *Routes) DevicesValidateNameHTMX(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
ctx, span := tracer.Start(req.Context(), "*Routes.ValidateName")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
var nameData adddevice.NameInputData
|
||||||
|
nameData.Value = req.FormValue("name")
|
||||||
|
nameComponent := adddevice.NameInput(nameData)
|
||||||
|
s := req.FormValue("slug")
|
||||||
|
if s != "" || nameData.Value == "" {
|
||||||
|
if err := nameComponent.Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render name input")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s = slug.Make(nameData.Value)
|
||||||
|
|
||||||
|
slugData := adddevice.SlugInputData{
|
||||||
|
Value: s,
|
||||||
|
HXSwapOOB: true,
|
||||||
|
}
|
||||||
|
exist, err := routes.API.DevicesValidateSlug(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to validate slug")
|
||||||
|
_, message := errs.HTTPMessage(err)
|
||||||
|
slugData.Error = message
|
||||||
|
_ = nameComponent.Render(ctx, rw)
|
||||||
|
if err := adddevice.SlugInput(slugData).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render name input")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if exist {
|
||||||
|
slugData.Error = "Device with this identifier already exist. Please change the value manually."
|
||||||
|
// rw.WriteHeader(http.StatusConflict)
|
||||||
|
_ = nameComponent.Render(ctx, rw)
|
||||||
|
if err := adddevice.SlugInput(slugData).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render name input")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slugData.Valid = "Identifier is available"
|
||||||
|
|
||||||
|
_ = nameComponent.Render(ctx, rw)
|
||||||
|
if err := adddevice.SlugInput(slugData).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render name input")
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package routes
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gosimple/slug"
|
||||||
"github.com/tigorlazuardi/redmage/pkg/errs"
|
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||||
"github.com/tigorlazuardi/redmage/pkg/log"
|
"github.com/tigorlazuardi/redmage/pkg/log"
|
||||||
"github.com/tigorlazuardi/redmage/views/devicesview/adddevice"
|
"github.com/tigorlazuardi/redmage/views/devicesview/adddevice"
|
||||||
|
@ -13,7 +14,7 @@ func (routes *Routes) DevicesValidateSlugHTMX(rw http.ResponseWriter, req *http.
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
var data adddevice.SlugInputData
|
var data adddevice.SlugInputData
|
||||||
data.Value = req.FormValue("slug")
|
data.Value = slug.Make(req.FormValue("slug"))
|
||||||
if data.Value == "" {
|
if data.Value == "" {
|
||||||
if err := adddevice.SlugInput(data).Render(ctx, rw); err != nil {
|
if err := adddevice.SlugInput(data).Render(ctx, rw); err != nil {
|
||||||
log.New(ctx).Err(err).Error("failed to render slug input")
|
log.New(ctx).Err(err).Error("failed to render slug input")
|
||||||
|
|
|
@ -64,6 +64,7 @@ func (routes *Routes) registerHTMXRoutes(router chi.Router) {
|
||||||
router.Get("/subreddits/validate/schedule", routes.SubredditValidateScheduleHTMX)
|
router.Get("/subreddits/validate/schedule", routes.SubredditValidateScheduleHTMX)
|
||||||
|
|
||||||
router.Post("/devices/add/validate/slug", routes.DevicesValidateSlugHTMX)
|
router.Post("/devices/add/validate/slug", routes.DevicesValidateSlugHTMX)
|
||||||
|
router.Post("/devices/add/validate/name", routes.DevicesValidateNameHTMX)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (routes *Routes) registerWWWRoutes(router chi.Router) {
|
func (routes *Routes) registerWWWRoutes(router chi.Router) {
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
package adddevice
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type AspectRatioToleranceData struct {
|
||||||
|
Value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
templ AspectRatioToleranceInput(data AspectRatioToleranceData) {
|
||||||
|
<label
|
||||||
|
id="aspect-ratio-tolerance-form"
|
||||||
|
class="form-control sm:col-span-2"
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Aspect Ratio Tolerance</span>
|
||||||
|
<span class="label-text-alt hidden sm:inline">NOTE: This is NOT a filter for image sizes, but just the shape of it.</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="aspect-ratio-tolerance-field"
|
||||||
|
name="aspect_ratio_tolerance"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
@change="$el.setCustomValidity(''); this.error = false"
|
||||||
|
class="input input-bordered"
|
||||||
|
:class="{'text-error': error, 'input-error': error}"
|
||||||
|
value={ fmt.Sprintf("%.2f", data.Value) }
|
||||||
|
placeholder="0.20"
|
||||||
|
step="0.01"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">
|
||||||
|
Aspect Ratio Tolerance is a filter on how close the shape of the download images to the device shape is.
|
||||||
|
The bigger the value, the more images will be accepted and vice versa. '0.20' is the default value because
|
||||||
|
it accept quite a bit of images to download while still looking good even when the image is stretched for the device's wallpaper.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
}
|
99
views/devicesview/adddevice/max_image_resolution_input.templ
Normal file
99
views/devicesview/adddevice/max_image_resolution_input.templ
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
package adddevice
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
templ MaxImageResolutionXInput(data ResolutionData) {
|
||||||
|
<label
|
||||||
|
x-data={ fmt.Sprintf(`{error: %t}`, data.Error != "") }
|
||||||
|
id="max-image-width-form"
|
||||||
|
class="form-control"
|
||||||
|
if data.HXSwapOOB {
|
||||||
|
hx-swap-oob="true"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>Maximum Image Width</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="max-image-width-field"
|
||||||
|
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
||||||
|
name="max_x"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
@change="$el.setCustomValidity(''); this.error = false"
|
||||||
|
class={ utils.CXX(
|
||||||
|
"input input-bordered", true,
|
||||||
|
"text-error", data.Error != "",
|
||||||
|
"input-error", data.Error != "",
|
||||||
|
) }
|
||||||
|
:class="{'text-error': error, 'input-error': error}"
|
||||||
|
value={ strconv.Itoa(data.Value) }
|
||||||
|
placeholder="0"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>
|
||||||
|
if data.Error != "" {
|
||||||
|
{ data.Error }
|
||||||
|
} else {
|
||||||
|
Maximum image width resolution to download for this device. Set to '0' to disable this filter. Set this value to avoid image sizes that are too big.
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MaxImageResolutionYInput(data ResolutionData) {
|
||||||
|
<label
|
||||||
|
x-data={ fmt.Sprintf(`{error: %t}`, data.Error != "") }
|
||||||
|
id="max-image-height-form"
|
||||||
|
class="form-control"
|
||||||
|
if data.HXSwapOOB {
|
||||||
|
hx-swap-oob="true"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>Maximum Image Height</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="max-image-height-field"
|
||||||
|
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
||||||
|
name="min_y"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
@change="$el.setCustomValidity(''); this.error = false"
|
||||||
|
class={ utils.CXX(
|
||||||
|
"input input-bordered", true,
|
||||||
|
"text-error", data.Error != "",
|
||||||
|
"input-error", data.Error != "",
|
||||||
|
) }
|
||||||
|
:class="{'text-error': error, 'input-error': error}"
|
||||||
|
value={ strconv.Itoa(data.Value) }
|
||||||
|
placeholder="0"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>
|
||||||
|
if data.Error != "" {
|
||||||
|
{ data.Error }
|
||||||
|
} else {
|
||||||
|
Maximum image height resolution to download for this device. Set to '0' to disable this filter. Set this value to avoid image sizes that are too big.
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
}
|
101
views/devicesview/adddevice/min_image_resolution_input.templ
Normal file
101
views/devicesview/adddevice/min_image_resolution_input.templ
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package adddevice
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
templ MinImageResolutionXInput(data ResolutionData) {
|
||||||
|
<label
|
||||||
|
x-data={ fmt.Sprintf(`{error: %t}`, data.Error != "") }
|
||||||
|
id="min-image-width-form"
|
||||||
|
class="form-control"
|
||||||
|
if data.HXSwapOOB {
|
||||||
|
hx-swap-oob="true"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>Minimum Image Width</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="min-image-width-field"
|
||||||
|
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
||||||
|
name="min_x"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
@change="$el.setCustomValidity(''); this.error = false"
|
||||||
|
class={ utils.CXX(
|
||||||
|
"input input-bordered", true,
|
||||||
|
"text-error", data.Error != "",
|
||||||
|
"input-error", data.Error != "",
|
||||||
|
) }
|
||||||
|
:class="{'text-error': error, 'input-error': error}"
|
||||||
|
value={ strconv.Itoa(data.Value) }
|
||||||
|
placeholder="0"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>
|
||||||
|
if data.Error != "" {
|
||||||
|
{ data.Error }
|
||||||
|
} else {
|
||||||
|
Minimum image width resolution to download for this device. Set to '0' to disable this filter. Recommended to set this value same as your device's width resolution
|
||||||
|
so you will get non-blurry images when used as wallpaper.
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ MinImageResolutionYInput(data ResolutionData) {
|
||||||
|
<label
|
||||||
|
x-data={ fmt.Sprintf(`{error: %t}`, data.Error != "") }
|
||||||
|
id="min-image-height-form"
|
||||||
|
class="form-control"
|
||||||
|
if data.HXSwapOOB {
|
||||||
|
hx-swap-oob="true"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>Minimum Image Height</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="min-image-height-field"
|
||||||
|
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
||||||
|
name="min_y"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
@change="$el.setCustomValidity(''); this.error = false"
|
||||||
|
class={ utils.CXX(
|
||||||
|
"input input-bordered", true,
|
||||||
|
"text-error", data.Error != "",
|
||||||
|
"input-error", data.Error != "",
|
||||||
|
) }
|
||||||
|
:class="{'text-error': error, 'input-error': error}"
|
||||||
|
value={ strconv.Itoa(data.Value) }
|
||||||
|
placeholder="0"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>
|
||||||
|
if data.Error != "" {
|
||||||
|
{ data.Error }
|
||||||
|
} else {
|
||||||
|
Minimum image height resolution to download for this device. Set to '0' to disable this filter. Recommended to set this value same as your device's height resolution
|
||||||
|
so you will get non-blurry images when used as wallpaper.
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
}
|
109
views/devicesview/adddevice/resolution_input.templ
Normal file
109
views/devicesview/adddevice/resolution_input.templ
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
package adddevice
|
||||||
|
|
||||||
|
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
|
import "fmt"
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
type ResolutionData struct {
|
||||||
|
Error string
|
||||||
|
Value int
|
||||||
|
HXSwapOOB bool
|
||||||
|
}
|
||||||
|
|
||||||
|
templ ResolutionXInput(data ResolutionData) {
|
||||||
|
<label
|
||||||
|
x-data={ fmt.Sprintf(`{error: %t}`, data.Error != "") }
|
||||||
|
id="resolution-x-form"
|
||||||
|
class="form-control"
|
||||||
|
if data.HXSwapOOB {
|
||||||
|
hx-swap-oob="true"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>Width</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="resolution-x-field"
|
||||||
|
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
||||||
|
name="resolution_x"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
@change="$el.setCustomValidity(''); this.error = false"
|
||||||
|
class={ utils.CXX(
|
||||||
|
"input input-bordered", true,
|
||||||
|
"text-error", data.Error != "",
|
||||||
|
"input-error", data.Error != "",
|
||||||
|
) }
|
||||||
|
:class="{'text-error': error, 'input-error': error}"
|
||||||
|
if data.Value > 0 {
|
||||||
|
value={ strconv.Itoa(data.Value) }
|
||||||
|
}
|
||||||
|
placeholder="1920"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>
|
||||||
|
if data.Error != "" {
|
||||||
|
{ data.Error }
|
||||||
|
} else {
|
||||||
|
Your intended device width resolution.
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
|
||||||
|
templ ResolutionYInput(data ResolutionData) {
|
||||||
|
<label
|
||||||
|
x-data={ fmt.Sprintf(`{error: %t}`, data.Error != "") }
|
||||||
|
id="resolution-y-form"
|
||||||
|
class="form-control"
|
||||||
|
if data.HXSwapOOB {
|
||||||
|
hx-swap-oob="true"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>Height</span>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="resolution-y-field"
|
||||||
|
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
||||||
|
name="resolution_y"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
@change="$el.setCustomValidity(''); this.error = false"
|
||||||
|
class={ utils.CXX(
|
||||||
|
"input input-bordered", true,
|
||||||
|
"text-error", data.Error != "",
|
||||||
|
"input-error", data.Error != "",
|
||||||
|
) }
|
||||||
|
:class="{'text-error': error, 'input-error': error}"
|
||||||
|
if data.Value > 0 {
|
||||||
|
value={ strconv.Itoa(data.Value) }
|
||||||
|
}
|
||||||
|
placeholder="1080"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<div class="label">
|
||||||
|
<span
|
||||||
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
:class="{'text-error': error}"
|
||||||
|
>
|
||||||
|
if data.Error != "" {
|
||||||
|
{ data.Error }
|
||||||
|
} else {
|
||||||
|
Your intended device height resolution.
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
}
|
|
@ -11,7 +11,13 @@ type SlugInputData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
templ SlugInput(data SlugInputData) {
|
templ SlugInput(data SlugInputData) {
|
||||||
<label id="slug-input-form" class="form-control">
|
<label
|
||||||
|
id="slug-input-form"
|
||||||
|
class="form-control"
|
||||||
|
if data.HXSwapOOB {
|
||||||
|
hx-swap-oob="true"
|
||||||
|
}
|
||||||
|
>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span
|
<span
|
||||||
class={ utils.CXX(
|
class={ utils.CXX(
|
||||||
|
@ -35,12 +41,15 @@ templ SlugInput(data SlugInputData) {
|
||||||
"input-success", data.Valid != "",
|
"input-success", data.Valid != "",
|
||||||
) }
|
) }
|
||||||
hx-post="/htmx/devices/add/validate/slug"
|
hx-post="/htmx/devices/add/validate/slug"
|
||||||
hx-trigger="change, input delay:200ms"
|
hx-trigger="change, input delay:2s"
|
||||||
hx-target="#slug-input-form"
|
hx-target="#slug-input-form"
|
||||||
hx-target-409="#slug-input-form"
|
hx-target-409="#slug-input-form"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
value={ data.Value }
|
value={ data.Value }
|
||||||
placeholder="my-awesome-device"
|
placeholder="my-awesome-device"
|
||||||
|
title="Url Friendly Characters Only"
|
||||||
|
pattern="^[a-z0-9-_]+$"
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span class={ utils.CXX("label-text", true, "text-error", data.Error != "", "text-success", data.Valid != "") }>
|
<span class={ utils.CXX("label-text", true, "text-error", data.Error != "", "text-success", data.Valid != "") }>
|
||||||
|
@ -50,7 +59,7 @@ templ SlugInput(data SlugInputData) {
|
||||||
{ data.Error }
|
{ data.Error }
|
||||||
} else {
|
} else {
|
||||||
Unique identifier for the device.
|
Unique identifier for the device.
|
||||||
Value must be lowercase english alphabet and supported separator is only 'dash' (-).
|
Value must be lowercase english alphabet and supported separator is only 'dash' (-) and 'underscores' (_).
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -17,9 +17,23 @@ templ Content(c *views.Context) {
|
||||||
@components.Container() {
|
@components.Container() {
|
||||||
<h1>Add Device</h1>
|
<h1>Add Device</h1>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<form class="grid sm:grid-cols-2 gap-4">
|
<form
|
||||||
|
method="post"
|
||||||
|
hx-post="/htmx/devices/add"
|
||||||
|
action="/htmx/devices/add"
|
||||||
|
class="grid sm:grid-cols-2 gap-4"
|
||||||
|
>
|
||||||
@NameInput(NameInputData{})
|
@NameInput(NameInputData{})
|
||||||
@SlugInput(SlugInputData{})
|
@SlugInput(SlugInputData{})
|
||||||
|
@ResolutionXInput(ResolutionData{})
|
||||||
|
@ResolutionYInput(ResolutionData{})
|
||||||
|
<div class="divider my-auto sm:col-span-2"><h3 class="m-0 p-0">Filter</h3></div>
|
||||||
|
@AspectRatioToleranceInput(AspectRatioToleranceData{Value: 0.2})
|
||||||
|
@MinImageResolutionXInput(ResolutionData{})
|
||||||
|
@MinImageResolutionYInput(ResolutionData{})
|
||||||
|
@MaxImageResolutionXInput(ResolutionData{})
|
||||||
|
@MaxImageResolutionYInput(ResolutionData{})
|
||||||
|
<button type="submit" class="btn btn-primary sm:col-span-2">Add</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -36,16 +36,8 @@ templ filter(data Data) {
|
||||||
<label for="status">Status</label>
|
<label for="status">Status</label>
|
||||||
<select id="status" name="status" class="select select-bordered w-full">
|
<select id="status" name="status" class="select select-bordered w-full">
|
||||||
<option value="">*No Filter</option>
|
<option value="">*No Filter</option>
|
||||||
if data.Params.Status == 2 {
|
<option value="2" selected?={ data.Params.Status == 2 }>Enabled</option>
|
||||||
<option selected value="2">Enabled</option>
|
<option value="1" selected?={ data.Params.Status == 1 }>Disabled</option>
|
||||||
} else {
|
|
||||||
<option value="2">Enabled</option>
|
|
||||||
}
|
|
||||||
if data.Params.Status == 1 {
|
|
||||||
<option selected value="1">Disabled</option>
|
|
||||||
} else {
|
|
||||||
<option value="1">Disabled</option>
|
|
||||||
}
|
|
||||||
</select>
|
</select>
|
||||||
<label for="limit">Limit</label>
|
<label for="limit">Limit</label>
|
||||||
<select id="limit" name="limit" class="select select-bordered w-full">
|
<select id="limit" name="limit" class="select select-bordered w-full">
|
||||||
|
|
Loading…
Reference in a new issue