devices: enhanced more displays
This commit is contained in:
parent
1dc3617df3
commit
3c8a1b1fd6
18
api/devices_exist.go
Normal file
18
api/devices_exist.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tigorlazuardi/redmage/models"
|
||||||
|
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *API) DevicesExist(ctx context.Context, slug string) (exist bool, err error) {
|
||||||
|
ctx, span := tracer.Start(ctx, "API.DevicesExist")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
exist, err = models.Devices.Query(ctx, api.db, models.SelectWhere.Devices.Slug.EQ(slug)).Exists()
|
||||||
|
if err != nil {
|
||||||
|
return false, errs.Wrapw(err, "failed to check device existence", "slug", slug)
|
||||||
|
}
|
||||||
|
return exist, nil
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/aarondl/opt/omit"
|
"github.com/aarondl/opt/omit"
|
||||||
|
@ -11,6 +12,7 @@ import (
|
||||||
"github.com/tigorlazuardi/redmage/models"
|
"github.com/tigorlazuardi/redmage/models"
|
||||||
"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/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
type deviceUpdate struct {
|
type deviceUpdate struct {
|
||||||
|
@ -53,6 +55,7 @@ func (routes *Routes) APIDeviceUpdate(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
device, err := routes.API.DevicesUpdate(ctx, slug, &models.DeviceSetter{
|
device, err := routes.API.DevicesUpdate(ctx, slug, &models.DeviceSetter{
|
||||||
Name: omit.FromCond(body.Name, body.Name != ""),
|
Name: omit.FromCond(body.Name, body.Name != ""),
|
||||||
|
Enable: omit.FromCond(body.Enable, body.Enable == 1 || body.Enable == 0),
|
||||||
ResolutionX: omit.FromCond(body.ResolutionX, body.ResolutionX != 0),
|
ResolutionX: omit.FromCond(body.ResolutionX, body.ResolutionX != 0),
|
||||||
ResolutionY: omit.FromCond(body.ResolutionY, body.ResolutionY != 0),
|
ResolutionY: omit.FromCond(body.ResolutionY, body.ResolutionY != 0),
|
||||||
AspectRatioTolerance: omit.FromPtr(body.AspectRatioTolerance),
|
AspectRatioTolerance: omit.FromPtr(body.AspectRatioTolerance),
|
||||||
|
@ -61,7 +64,6 @@ func (routes *Routes) APIDeviceUpdate(rw http.ResponseWriter, r *http.Request) {
|
||||||
MaxX: omit.FromPtr(body.MaxX),
|
MaxX: omit.FromPtr(body.MaxX),
|
||||||
MaxY: omit.FromPtr(body.MaxY),
|
MaxY: omit.FromPtr(body.MaxY),
|
||||||
NSFW: omit.FromPtr(body.NSFW),
|
NSFW: omit.FromPtr(body.NSFW),
|
||||||
WindowsWallpaperMode: omit.FromPtr(body.WindowsWallpaperMode),
|
|
||||||
UpdatedAt: omit.From(time.Now().Unix()),
|
UpdatedAt: omit.From(time.Now().Unix()),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -74,3 +76,106 @@ func (routes *Routes) APIDeviceUpdate(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
_ = enc.Encode(device)
|
_ = enc.Encode(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (routes *Routes) DevicesUpdateHTMX(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
ctx, span := tracer.Start(req.Context(), "*Routes.DevicesUpdateHTMX")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
slug := chi.URLParam(req, "slug")
|
||||||
|
exist, err := routes.API.DevicesExist(ctx, slug)
|
||||||
|
if err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to check device slug existence")
|
||||||
|
code, message := errs.HTTPMessage(err)
|
||||||
|
rw.WriteHeader(code)
|
||||||
|
if err := components.ErrorToast(message).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render error notification")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exist {
|
||||||
|
rw.WriteHeader(http.StatusNotFound)
|
||||||
|
if err := components.ErrorToast("Device with slug identifier '%s' does not exist", slug).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render error notification")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
device, err := routes.API.DevicesUpdate(ctx, slug, deviceSetterFromRequest(req))
|
||||||
|
if err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to update device")
|
||||||
|
code, message := errs.HTTPMessage(err)
|
||||||
|
rw.WriteHeader(code)
|
||||||
|
if err := components.ErrorToast(message).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render error notification")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Header().Set("HX-Redirect", fmt.Sprintf("/devices/details/%s", slug))
|
||||||
|
if err := components.SuccessToast("Device %q has been updated", device.Name).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render success notification")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deviceSetterFromRequest(req *http.Request) *models.DeviceSetter {
|
||||||
|
setter := &models.DeviceSetter{
|
||||||
|
UpdatedAt: omit.From(time.Now().Unix()),
|
||||||
|
}
|
||||||
|
|
||||||
|
name := req.FormValue("name")
|
||||||
|
setter.Name = omit.FromCond(name, name != "")
|
||||||
|
|
||||||
|
enable, err := strconv.Atoi(req.FormValue("enable"))
|
||||||
|
if err == nil {
|
||||||
|
if enable > 1 {
|
||||||
|
enable = 1
|
||||||
|
} else if enable < 0 {
|
||||||
|
enable = 0
|
||||||
|
}
|
||||||
|
setter.Enable = omit.From(int32(enable))
|
||||||
|
}
|
||||||
|
|
||||||
|
resx, _ := strconv.Atoi(req.FormValue("resolution_x"))
|
||||||
|
setter.ResolutionX = omit.FromCond(float64(resx), resx != 0)
|
||||||
|
|
||||||
|
resy, _ := strconv.Atoi(req.FormValue("resolution_y"))
|
||||||
|
setter.ResolutionY = omit.FromCond(float64(resy), resy != 0)
|
||||||
|
|
||||||
|
art, err := strconv.ParseFloat(req.FormValue("aspect_ratio_tolerance"), 64)
|
||||||
|
if err == nil {
|
||||||
|
setter.AspectRatioTolerance = omit.FromCond(art, art >= 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
minX, err := strconv.Atoi(req.FormValue("min_x"))
|
||||||
|
if err == nil {
|
||||||
|
setter.MinX = omit.FromCond(int32(minX), minX >= 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
minY, err := strconv.Atoi(req.FormValue("min_y"))
|
||||||
|
if err == nil {
|
||||||
|
setter.MinY = omit.FromCond(int32(minY), minY >= 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxX, err := strconv.Atoi(req.FormValue("max_x"))
|
||||||
|
if err == nil {
|
||||||
|
setter.MaxX = omit.FromCond(int32(maxX), maxX >= 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxY, err := strconv.Atoi(req.FormValue("max_y"))
|
||||||
|
if err == nil {
|
||||||
|
setter.MaxY = omit.FromCond(int32(maxY), maxY >= 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
nsfw, err := strconv.Atoi(req.FormValue("nsfw"))
|
||||||
|
if err == nil {
|
||||||
|
if nsfw > 1 {
|
||||||
|
nsfw = 1
|
||||||
|
} else if nsfw < 0 {
|
||||||
|
nsfw = 0
|
||||||
|
}
|
||||||
|
setter.NSFW = omit.From(int32(nsfw))
|
||||||
|
}
|
||||||
|
|
||||||
|
return setter
|
||||||
|
}
|
||||||
|
|
81
server/routes/page_devices_edit.go
Normal file
81
server/routes/page_devices_edit.go
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
package routes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||||
|
"github.com/tigorlazuardi/redmage/pkg/log"
|
||||||
|
"github.com/tigorlazuardi/redmage/views"
|
||||||
|
"github.com/tigorlazuardi/redmage/views/components"
|
||||||
|
"github.com/tigorlazuardi/redmage/views/devices/put"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (routes *Routes) PageDevicesEdit(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
ctx, span := tracer.Start(req.Context(), "*Routes.PageDevicesEdit")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
c := views.NewContext(routes.Config, req)
|
||||||
|
|
||||||
|
slug := chi.URLParam(req, "slug")
|
||||||
|
|
||||||
|
device, err := routes.API.DeviceBySlug(ctx, slug)
|
||||||
|
if err != nil {
|
||||||
|
code, message := errs.HTTPMessage(err)
|
||||||
|
if code >= 500 {
|
||||||
|
log.New(ctx).Err(err).Error("failed to get device by slug")
|
||||||
|
}
|
||||||
|
rw.WriteHeader(code)
|
||||||
|
msg := fmt.Sprintf("%d: %s", code, message)
|
||||||
|
if err := components.PageError(c, msg).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render device edit page")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := put.Data{
|
||||||
|
PageTitle: fmt.Sprintf("Edit Device %q", device.Name),
|
||||||
|
PostAction: fmt.Sprintf("/devices/edit/%s", device.Slug),
|
||||||
|
EditMode: true,
|
||||||
|
NameInput: put.NameInputData{
|
||||||
|
Value: device.Name,
|
||||||
|
EditMode: true,
|
||||||
|
},
|
||||||
|
SlugInput: put.SlugInputData{
|
||||||
|
Value: device.Slug,
|
||||||
|
},
|
||||||
|
ResolutionX: put.ResolutionData{
|
||||||
|
Value: int(device.ResolutionX),
|
||||||
|
},
|
||||||
|
ResolutionY: put.ResolutionData{
|
||||||
|
Value: int(device.ResolutionY),
|
||||||
|
},
|
||||||
|
AspectRatioTolerance: put.AspectRatioToleranceData{
|
||||||
|
Value: device.AspectRatioTolerance,
|
||||||
|
},
|
||||||
|
NSFWCheckbox: put.NSFWCheckboxData{
|
||||||
|
Checked: device.NSFW == 1,
|
||||||
|
EditMode: true,
|
||||||
|
},
|
||||||
|
WindowsWallpaperCheckbox: put.WindowsWallpaperCheckboxData{
|
||||||
|
Checked: device.WindowsWallpaperMode == 1,
|
||||||
|
},
|
||||||
|
MinImageResolutionXInput: put.ResolutionData{
|
||||||
|
Value: int(device.MinX),
|
||||||
|
},
|
||||||
|
MinImageResolutionYInput: put.ResolutionData{
|
||||||
|
Value: int(device.MinY),
|
||||||
|
},
|
||||||
|
MaxImageResolutionXInput: put.ResolutionData{
|
||||||
|
Value: int(device.MaxX),
|
||||||
|
},
|
||||||
|
MaxImageResolutionYInput: put.ResolutionData{
|
||||||
|
Value: int(device.MaxY),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := put.View(c, data).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render device edit page")
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,6 +88,8 @@ func (routes *Routes) registerWWWRoutes(router chi.Router) {
|
||||||
r.Get("/devices/add", routes.PageDevicesAdd)
|
r.Get("/devices/add", routes.PageDevicesAdd)
|
||||||
r.Post("/devices/add", routes.DevicesCreateHTMX)
|
r.Post("/devices/add", routes.DevicesCreateHTMX)
|
||||||
r.Get("/devices/details/{slug}", routes.PageDeviceDetails)
|
r.Get("/devices/details/{slug}", routes.PageDeviceDetails)
|
||||||
|
r.Get("/devices/edit/{slug}", routes.PageDevicesEdit)
|
||||||
|
r.Post("/devices/edit/{slug}", routes.DevicesUpdateHTMX)
|
||||||
r.Get("/schedules", routes.PageScheduleHistory)
|
r.Get("/schedules", routes.PageScheduleHistory)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package components
|
||||||
|
|
||||||
import "github.com/tigorlazuardi/redmage/models"
|
import "github.com/tigorlazuardi/redmage/models"
|
||||||
import "fmt"
|
import "fmt"
|
||||||
import "github.com/tigorlazuardi/redmage/views/utils"
|
import "time"
|
||||||
|
|
||||||
type ImageCardOption uint
|
type ImageCardOption uint
|
||||||
|
|
||||||
|
@ -26,7 +26,18 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
templ ImageCard(data *models.Image, opts ImageCardOption) {
|
templ ImageCard(data *models.Image, opts ImageCardOption) {
|
||||||
<div class="not-prose card card-bordered bg-base-100 hover:bg-base-200 shadow-xl min-w-[16rem] max-w-[16rem] rounded-xl top-0 hover:-top-1 hover:drop-shadow-2xl transition-all">
|
<div
|
||||||
|
x-data={ fmt.Sprintf(`{
|
||||||
|
time: %d,
|
||||||
|
get timeTooltip() {
|
||||||
|
return dayjs.unix(this.time).tz(dayjs.tz.guess()).format('ddd, D MMM YYYY HH:mm:ss Z')
|
||||||
|
},
|
||||||
|
get relativeTime() {
|
||||||
|
return dayjs.unix(this.time).tz(dayjs.tz.guess()).fromNow()
|
||||||
|
},
|
||||||
|
}`, data.CreatedAt) }
|
||||||
|
class="not-prose card card-bordered bg-base-100 hover:bg-base-200 shadow-xl min-w-[16rem] max-w-[16rem] rounded-xl top-0 hover:-top-1 hover:drop-shadow-2xl transition-all"
|
||||||
|
>
|
||||||
<figure>
|
<figure>
|
||||||
<a
|
<a
|
||||||
href={ templ.URL(fmt.Sprintf("/img/%s", data.ImageRelativePath)) }
|
href={ templ.URL(fmt.Sprintf("/img/%s", data.ImageRelativePath)) }
|
||||||
|
@ -51,7 +62,9 @@ templ ImageCard(data *models.Image, opts ImageCardOption) {
|
||||||
<a class="text-primary text-sm underline" href={ templ.URL(data.PostAuthorURL) }>{ data.PostAuthor }</a>
|
<a class="text-primary text-sm underline" href={ templ.URL(data.PostAuthorURL) }>{ data.PostAuthor }</a>
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
@utils.RelativeTimeNode(fmt.Sprintf("relative-time-%s", data.PostName), data.CreatedAt, "text-sm")
|
<div class="tooltip" :data-tip="timeTooltip">
|
||||||
|
<span class="text-xs" :class="{ 'text-xs': false }" x-text="relativeTime">{ time.Unix(data.CreatedAt, 0).Format("Mon, _2 Jan 2006 15:04:05 MST") } </span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-2">
|
<div class="grid grid-cols-2">
|
||||||
<p class="text-xs">{ fmt.Sprintf("%d \u00d7 %d", data.ImageWidth, data.ImageHeight) } px</p>
|
<p class="text-xs">{ fmt.Sprintf("%d \u00d7 %d", data.ImageWidth, data.ImageHeight) } px</p>
|
||||||
|
|
|
@ -2,7 +2,7 @@ package components
|
||||||
|
|
||||||
import "github.com/tigorlazuardi/redmage/views"
|
import "github.com/tigorlazuardi/redmage/views"
|
||||||
|
|
||||||
templ Page404(c *views.Context, text string) {
|
templ PageError(c *views.Context, text string) {
|
||||||
@Doctype() {
|
@Doctype() {
|
||||||
@Head(c, HeadTitle(text))
|
@Head(c, HeadTitle(text))
|
||||||
@Body(c) {
|
@Body(c) {
|
|
@ -1,8 +1,9 @@
|
||||||
package details
|
package details
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
import "github.com/tigorlazuardi/redmage/views"
|
||||||
|
|
||||||
templ filter(data Data) {
|
templ filter(c *views.Context, data Data) {
|
||||||
<div
|
<div
|
||||||
id="filter-bar"
|
id="filter-bar"
|
||||||
hx-get={ fmt.Sprintf("/devices/details/%s", data.Device.Slug) }
|
hx-get={ fmt.Sprintf("/devices/details/%s", data.Device.Slug) }
|
||||||
|
@ -12,9 +13,9 @@ templ filter(data Data) {
|
||||||
hx-select="main"
|
hx-select="main"
|
||||||
hx-swap="outerHTML"
|
hx-swap="outerHTML"
|
||||||
hx-push-url="true"
|
hx-push-url="true"
|
||||||
class="grid sm:grid-cols-2 md:grid-cols-4 gap-4"
|
class="grid md:grid-cols-2 gap-4 items-center"
|
||||||
>
|
>
|
||||||
<label class="input input-bordered flex items-center gap-2 sm:col-span-2 md:col-auto">
|
<label class="input input-bordered flex items-center gap-2">
|
||||||
<input
|
<input
|
||||||
id="search"
|
id="search"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -31,5 +32,32 @@ templ filter(data Data) {
|
||||||
class="w-4 h-4 opacity-70"
|
class="w-4 h-4 opacity-70"
|
||||||
><path fill-rule="evenodd" d="M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z" clip-rule="evenodd"></path></svg>
|
><path fill-rule="evenodd" d="M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06l-2.755-2.754ZM10.5 7a3.5 3.5 0 1 1-7 0 3.5 3.5 0 0 1 7 0Z" clip-rule="evenodd"></path></svg>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="grid grid-cols-[1fr,3fr] sm:grid-cols-[1fr,3fr,1fr,3fr] gap-4 items-center">
|
||||||
|
<label for="limit">Limit</label>
|
||||||
|
<select id="limit" name="limit" class="select select-bordered w-full">
|
||||||
|
<option value="25" selected?={ data.Params.Limit == 25 }>25</option>
|
||||||
|
<option value="50" selected?={ data.Params.Limit == 50 }>50</option>
|
||||||
|
<option value="75" selected?={ data.Params.Limit == 75 }>75</option>
|
||||||
|
<option value="100" selected?={ data.Params.Limit == 100 }>100</option>
|
||||||
|
</select>
|
||||||
|
<label for="range">Range</label>
|
||||||
|
<select id="range" name="created_at" class="select select-bordered w-full">
|
||||||
|
@rangeOption(c, "-10800", "3 Hours")
|
||||||
|
@rangeOption(c, "-21600", "6 Hours")
|
||||||
|
@rangeOption(c, "-43200", "12 Hours")
|
||||||
|
<option
|
||||||
|
value="-86400"
|
||||||
|
selected?={ c.Request.URL.Query().Get("created_at") == "" || c.Request.URL.Query().Get("created_at") == "-86400" }
|
||||||
|
>1 Day</option>
|
||||||
|
@rangeOption(c, "-172800", "2 Days")
|
||||||
|
@rangeOption(c, "-259200", "3 Days")
|
||||||
|
@rangeOption(c, "-604800", "7 Days")
|
||||||
|
@rangeOption(c, "-2592000", "30 Days")
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
templ rangeOption(c *views.Context, value, display string) {
|
||||||
|
<option value={ value } selected?={ c.Request.URL.Query().Get("created_at") == value }>{ display }</option>
|
||||||
|
}
|
||||||
|
|
|
@ -24,9 +24,15 @@ templ Content(c *views.Context, data Data) {
|
||||||
if data.Error != "" {
|
if data.Error != "" {
|
||||||
@components.ErrorToast(data.Error)
|
@components.ErrorToast(data.Error)
|
||||||
} else {
|
} else {
|
||||||
<h1>{ data.Device.Name }</h1>
|
<div class="flex justify-between items-center">
|
||||||
|
<h1 class="my-auto">{ data.Device.Name }</h1>
|
||||||
|
<a
|
||||||
|
href={ templ.SafeURL(fmt.Sprintf("/devices/edit/%s", data.Device.Slug)) }
|
||||||
|
class="btn btn-primary no-underline sm:w-24"
|
||||||
|
>Edit</a>
|
||||||
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
@filter(data)
|
@filter(c, data)
|
||||||
for _, group := range data.splitImages() {
|
for _, group := range data.splitImages() {
|
||||||
<h2>{ group.Subreddit }</h2>
|
<h2>{ group.Subreddit }</h2>
|
||||||
@imageList(group.Images)
|
@imageList(group.Images)
|
||||||
|
|
|
@ -69,7 +69,7 @@ templ MaxImageResolutionYInput(data ResolutionData) {
|
||||||
<input
|
<input
|
||||||
id="max-image-height-field"
|
id="max-image-height-field"
|
||||||
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
||||||
name="min_y"
|
name="max_y"
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
@change="$el.setCustomValidity(''); this.error = false"
|
@change="$el.setCustomValidity(''); this.error = false"
|
||||||
|
|
|
@ -4,13 +4,13 @@ import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
type NameInputData struct {
|
type NameInputData struct {
|
||||||
Error string
|
Error string
|
||||||
Value string
|
Value string
|
||||||
DisableValidation bool
|
EditMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
templ NameInput(data NameInputData) {
|
templ NameInput(data NameInputData) {
|
||||||
<label id="name-input-form" class="form-control">
|
<label id="name-input-form" class={ utils.CXX("form-control", true, "col-span-2", data.EditMode) }>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<span
|
<span
|
||||||
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||||
|
@ -18,7 +18,7 @@ templ NameInput(data NameInputData) {
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
id="name-input-field"
|
id="name-input-field"
|
||||||
if !data.DisableValidation {
|
if !data.EditMode {
|
||||||
required
|
required
|
||||||
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
||||||
hx-get="/htmx/devices/add/validate/name"
|
hx-get="/htmx/devices/add/validate/name"
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
package put
|
package put
|
||||||
|
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
|
|
||||||
type NSFWCheckboxData struct {
|
type NSFWCheckboxData struct {
|
||||||
Checked bool
|
Checked bool
|
||||||
|
EditMode bool
|
||||||
}
|
}
|
||||||
|
|
||||||
templ NSFWCheckbox(data NSFWCheckboxData) {
|
templ NSFWCheckbox(data NSFWCheckboxData) {
|
||||||
<div
|
<div
|
||||||
x-data={ fmt.Sprintf(`{checked: %t}`, data.Checked) }
|
x-data={ fmt.Sprintf(`{checked: %t}`, data.Checked) }
|
||||||
class="form-control"
|
class={ utils.CXX("form-control", true, "col-span-2", data.EditMode) }
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
class="label cursor-pointer border input input-bordered"
|
class="label cursor-pointer border input input-bordered"
|
||||||
|
|
|
@ -4,12 +4,10 @@ import "github.com/tigorlazuardi/redmage/views/utils"
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
type SlugInputData struct {
|
type SlugInputData struct {
|
||||||
Error string
|
Error string
|
||||||
Value string
|
Value string
|
||||||
Valid string
|
Valid string
|
||||||
HXSwapOOB bool
|
HXSwapOOB bool
|
||||||
Disabled bool
|
|
||||||
DisabledText string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
templ SlugInput(data SlugInputData) {
|
templ SlugInput(data SlugInputData) {
|
||||||
|
@ -28,17 +26,14 @@ templ SlugInput(data SlugInputData) {
|
||||||
"text-success", data.Valid != "",
|
"text-success", data.Valid != "",
|
||||||
) }
|
) }
|
||||||
>Slug Identifier</span>
|
>Slug Identifier</span>
|
||||||
|
<span class="label-text-alt italic font-bold text-primary">NOTE: Slug Identifier cannot be changed after creation</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
id="slug-input-field"
|
id="slug-input-field"
|
||||||
if !data.Disabled {
|
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
||||||
x-data={ fmt.Sprintf(`{ init() { $el.setCustomValidity(%q) } }`, data.Error) }
|
|
||||||
}
|
|
||||||
name="slug"
|
name="slug"
|
||||||
type="text"
|
type="text"
|
||||||
if !data.Disabled {
|
@change="$el.setCustomValidity('')"
|
||||||
@change="$el.setCustomValidity('')"
|
|
||||||
}
|
|
||||||
class={ utils.CXX(
|
class={ utils.CXX(
|
||||||
"input input-bordered", true,
|
"input input-bordered", true,
|
||||||
"text-error", data.Error != "",
|
"text-error", data.Error != "",
|
||||||
|
@ -46,18 +41,14 @@ templ SlugInput(data SlugInputData) {
|
||||||
"input-error", data.Error != "",
|
"input-error", data.Error != "",
|
||||||
"input-success", data.Valid != "",
|
"input-success", data.Valid != "",
|
||||||
) }
|
) }
|
||||||
if data.Disabled {
|
hx-get="/htmx/devices/add/validate/slug"
|
||||||
disabled
|
hx-trigger="change, input delay:2s"
|
||||||
} else {
|
hx-target="#slug-input-form"
|
||||||
hx-get="/htmx/devices/add/validate/slug"
|
hx-target-error="#slug-input-form"
|
||||||
hx-trigger="change, input delay:2s"
|
hx-swap="outerHTML"
|
||||||
hx-target="#slug-input-form"
|
placeholder="my-awesome-device"
|
||||||
hx-target-error="#slug-input-form"
|
title="Url Friendly Characters Only"
|
||||||
hx-swap="outerHTML"
|
required
|
||||||
placeholder="my-awesome-device"
|
|
||||||
title="Url Friendly Characters Only"
|
|
||||||
required
|
|
||||||
}
|
|
||||||
value={ data.Value }
|
value={ data.Value }
|
||||||
/>
|
/>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
|
@ -66,8 +57,6 @@ templ SlugInput(data SlugInputData) {
|
||||||
{ data.Valid }
|
{ data.Valid }
|
||||||
} else if data.Error != "" {
|
} else if data.Error != "" {
|
||||||
{ data.Error }
|
{ data.Error }
|
||||||
} else if data.DisabledText != "" {
|
|
||||||
{ data.DisabledText }
|
|
||||||
} else {
|
} else {
|
||||||
URL friendly Unique identifier for the device.
|
URL friendly Unique identifier for the device.
|
||||||
Value must be lowercase english alphabet and supported separator is only 'dash' (-) and 'underscores' (_).
|
Value must be lowercase english alphabet and supported separator is only 'dash' (-) and 'underscores' (_).
|
||||||
|
|
|
@ -6,6 +6,7 @@ import "github.com/tigorlazuardi/redmage/views/components"
|
||||||
type Data struct {
|
type Data struct {
|
||||||
PageTitle string
|
PageTitle string
|
||||||
PostAction string
|
PostAction string
|
||||||
|
EditMode bool
|
||||||
|
|
||||||
NameInput NameInputData
|
NameInput NameInputData
|
||||||
SlugInput SlugInputData
|
SlugInput SlugInputData
|
||||||
|
@ -38,24 +39,28 @@ templ Content(c *views.Context, data Data) {
|
||||||
method="post"
|
method="post"
|
||||||
hx-post={ data.PostAction }
|
hx-post={ data.PostAction }
|
||||||
action={ templ.SafeURL(data.PostAction) }
|
action={ templ.SafeURL(data.PostAction) }
|
||||||
class="grid sm:grid-cols-2 gap-4"
|
class="grid sm:grid-cols-2 gap-4 sm:gap-y-8"
|
||||||
hx-target={ components.NotificationContainerID }
|
hx-target={ components.NotificationContainerID }
|
||||||
hx-target-error={ components.NotificationContainerID }
|
hx-target-error={ components.NotificationContainerID }
|
||||||
hx-swap="afterbegin"
|
hx-swap="afterbegin"
|
||||||
>
|
>
|
||||||
@NameInput(data.NameInput)
|
@NameInput(data.NameInput)
|
||||||
@SlugInput(data.SlugInput)
|
if !data.EditMode {
|
||||||
|
@SlugInput(data.SlugInput)
|
||||||
|
}
|
||||||
@ResolutionXInput(data.ResolutionX)
|
@ResolutionXInput(data.ResolutionX)
|
||||||
@ResolutionYInput(data.ResolutionY)
|
@ResolutionYInput(data.ResolutionY)
|
||||||
<div class="divider my-auto sm:col-span-2"><h3 class="m-0 p-0">Filter</h3></div>
|
<div class="divider my-auto sm:col-span-2 sm:my-8"><h3 class="m-0 p-0">Filter</h3></div>
|
||||||
@AspectRatioToleranceInput(data.AspectRatioTolerance)
|
@AspectRatioToleranceInput(data.AspectRatioTolerance)
|
||||||
@NSFWCheckbox(data.NSFWCheckbox)
|
@NSFWCheckbox(data.NSFWCheckbox)
|
||||||
@WindowsWallpaperCheckbox(data.WindowsWallpaperCheckbox)
|
if !data.EditMode {
|
||||||
|
@WindowsWallpaperCheckbox(data.WindowsWallpaperCheckbox)
|
||||||
|
}
|
||||||
@MinImageResolutionXInput(data.MinImageResolutionXInput)
|
@MinImageResolutionXInput(data.MinImageResolutionXInput)
|
||||||
@MinImageResolutionYInput(data.MinImageResolutionYInput)
|
@MinImageResolutionYInput(data.MinImageResolutionYInput)
|
||||||
@MaxImageResolutionXInput(data.MaxImageResolutionXInput)
|
@MaxImageResolutionXInput(data.MaxImageResolutionXInput)
|
||||||
@MaxImageResolutionYInput(data.MaxImageResolutionYInput)
|
@MaxImageResolutionYInput(data.MaxImageResolutionYInput)
|
||||||
<button type="submit" class="btn btn-primary sm:col-span-2">Add</button>
|
<button type="submit" class="btn btn-primary sm:col-span-2">Save</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -35,6 +35,7 @@ templ WindowsWallpaperCheckbox(data WindowsWallpaperCheckboxData) {
|
||||||
>
|
>
|
||||||
Windows Wallpaper Mode puts images for this device under one folder instead of split by subreddits.
|
Windows Wallpaper Mode puts images for this device under one folder instead of split by subreddits.
|
||||||
This allows the user to target Windows Wallpaper to the whole image collections.
|
This allows the user to target Windows Wallpaper to the whole image collections.
|
||||||
|
<span class="text-primary italic font-bold">Windows wallpaper mode cannot be changed after creation.</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,9 @@ templ DetailsContent(c *views.Context, data Data) {
|
||||||
if data.Error != "" {
|
if data.Error != "" {
|
||||||
<h1>Error: { data.Error }</h1>
|
<h1>Error: { data.Error }</h1>
|
||||||
} else {
|
} else {
|
||||||
<h1>Subreddit { data.Subreddit.Name }</h1>
|
<div class="flex items-center justify-between">
|
||||||
|
<h1>Subreddit { data.Subreddit.Name }</h1>
|
||||||
|
</div>
|
||||||
<div class="flex flex-wrap justify-between content-center">
|
<div class="flex flex-wrap justify-between content-center">
|
||||||
<h2 class="my-auto">
|
<h2 class="my-auto">
|
||||||
Total Images:
|
Total Images:
|
||||||
|
|
Loading…
Reference in a new issue