devices: enhanced more displays

This commit is contained in:
Tigor Hutasuhut 2024-05-08 19:32:14 +07:00
parent 1dc3617df3
commit 3c8a1b1fd6
15 changed files with 302 additions and 50 deletions

18
api/devices_exist.go Normal file
View 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
}

View file

@ -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
}

View 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")
}
}

View file

@ -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)
}) })
} }

View file

@ -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>

View file

@ -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) {

View file

@ -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>
}

View file

@ -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)

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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' (_).

View file

@ -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>

View file

@ -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>
} }

View file

@ -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: