From 3c8a1b1fd615742ac6f07c3f184993d3b88590f2 Mon Sep 17 00:00:00 2001 From: Tigor Hutasuhut Date: Wed, 8 May 2024 19:32:14 +0700 Subject: [PATCH] devices: enhanced more displays --- api/devices_exist.go | 18 +++ server/routes/device_update.go | 107 +++++++++++++++++- server/routes/page_devices_edit.go | 81 +++++++++++++ server/routes/routes.go | 2 + views/components/image_card.templ | 19 +++- .../{404.templ => page_error.templ} | 2 +- views/devices/details/filter.templ | 34 +++++- views/devices/details/view.templ | 10 +- .../put/max_image_resolution_input.templ | 2 +- views/devices/put/name_input.templ | 10 +- views/devices/put/nsfw_checkbox.templ | 6 +- views/devices/put/slug_input.templ | 41 +++---- views/devices/put/view.templ | 15 ++- .../put/windows_wallpaper_checkbox.templ | 1 + .../detailsview/detailsview.templ | 4 +- 15 files changed, 302 insertions(+), 50 deletions(-) create mode 100644 api/devices_exist.go create mode 100644 server/routes/page_devices_edit.go rename views/components/{404.templ => page_error.templ} (75%) diff --git a/api/devices_exist.go b/api/devices_exist.go new file mode 100644 index 0000000..bee8574 --- /dev/null +++ b/api/devices_exist.go @@ -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 +} diff --git a/server/routes/device_update.go b/server/routes/device_update.go index b222a61..64d7765 100644 --- a/server/routes/device_update.go +++ b/server/routes/device_update.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "time" "github.com/aarondl/opt/omit" @@ -11,6 +12,7 @@ import ( "github.com/tigorlazuardi/redmage/models" "github.com/tigorlazuardi/redmage/pkg/errs" "github.com/tigorlazuardi/redmage/pkg/log" + "github.com/tigorlazuardi/redmage/views/components" ) 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{ 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), ResolutionY: omit.FromCond(body.ResolutionY, body.ResolutionY != 0), AspectRatioTolerance: omit.FromPtr(body.AspectRatioTolerance), @@ -61,7 +64,6 @@ func (routes *Routes) APIDeviceUpdate(rw http.ResponseWriter, r *http.Request) { MaxX: omit.FromPtr(body.MaxX), MaxY: omit.FromPtr(body.MaxY), NSFW: omit.FromPtr(body.NSFW), - WindowsWallpaperMode: omit.FromPtr(body.WindowsWallpaperMode), UpdatedAt: omit.From(time.Now().Unix()), }) if err != nil { @@ -74,3 +76,106 @@ func (routes *Routes) APIDeviceUpdate(rw http.ResponseWriter, r *http.Request) { _ = 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 +} diff --git a/server/routes/page_devices_edit.go b/server/routes/page_devices_edit.go new file mode 100644 index 0000000..3aa1b8f --- /dev/null +++ b/server/routes/page_devices_edit.go @@ -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") + } +} diff --git a/server/routes/routes.go b/server/routes/routes.go index 1eca88e..8ef2d2a 100644 --- a/server/routes/routes.go +++ b/server/routes/routes.go @@ -88,6 +88,8 @@ func (routes *Routes) registerWWWRoutes(router chi.Router) { r.Get("/devices/add", routes.PageDevicesAdd) r.Post("/devices/add", routes.DevicesCreateHTMX) 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) }) } diff --git a/views/components/image_card.templ b/views/components/image_card.templ index 74ba6f2..78dd5fb 100644 --- a/views/components/image_card.templ +++ b/views/components/image_card.templ @@ -2,7 +2,7 @@ package components import "github.com/tigorlazuardi/redmage/models" import "fmt" -import "github.com/tigorlazuardi/redmage/views/utils" +import "time" type ImageCardOption uint @@ -26,7 +26,18 @@ const ( ) templ ImageCard(data *models.Image, opts ImageCardOption) { -
+
{ data.PostAuthor }
- @utils.RelativeTimeNode(fmt.Sprintf("relative-time-%s", data.PostName), data.CreatedAt, "text-sm") +
+ { time.Unix(data.CreatedAt, 0).Format("Mon, _2 Jan 2006 15:04:05 MST") } +

{ fmt.Sprintf("%d \u00d7 %d", data.ImageWidth, data.ImageHeight) } px

diff --git a/views/components/404.templ b/views/components/page_error.templ similarity index 75% rename from views/components/404.templ rename to views/components/page_error.templ index 998f5a3..97c41a5 100644 --- a/views/components/404.templ +++ b/views/components/page_error.templ @@ -2,7 +2,7 @@ package components import "github.com/tigorlazuardi/redmage/views" -templ Page404(c *views.Context, text string) { +templ PageError(c *views.Context, text string) { @Doctype() { @Head(c, HeadTitle(text)) @Body(c) { diff --git a/views/devices/details/filter.templ b/views/devices/details/filter.templ index bc47b80..40c3123 100644 --- a/views/devices/details/filter.templ +++ b/views/devices/details/filter.templ @@ -1,8 +1,9 @@ package details import "fmt" +import "github.com/tigorlazuardi/redmage/views" -templ filter(data Data) { +templ filter(c *views.Context, data Data) {
-
} + +templ rangeOption(c *views.Context, value, display string) { + +} diff --git a/views/devices/details/view.templ b/views/devices/details/view.templ index f21a3e5..82a559c 100644 --- a/views/devices/details/view.templ +++ b/views/devices/details/view.templ @@ -24,9 +24,15 @@ templ Content(c *views.Context, data Data) { if data.Error != "" { @components.ErrorToast(data.Error) } else { -

{ data.Device.Name }

+
+

{ data.Device.Name }

+ Edit +
- @filter(data) + @filter(c, data) for _, group := range data.splitImages() {

{ group.Subreddit }

@imageList(group.Images) diff --git a/views/devices/put/max_image_resolution_input.templ b/views/devices/put/max_image_resolution_input.templ index b90ca6f..8bbfd75 100644 --- a/views/devices/put/max_image_resolution_input.templ +++ b/views/devices/put/max_image_resolution_input.templ @@ -69,7 +69,7 @@ templ MaxImageResolutionYInput(data ResolutionData) { +