devices: properly implemented and integrated windows wallpaper mode
This commit is contained in:
parent
0c8e6622bc
commit
e8da8717ea
|
@ -262,6 +262,9 @@ func (post *Post) GetName() string {
|
|||
}
|
||||
|
||||
func (post *Post) GetImageTargetPath(cfg *config.Config, device *models.Device) string {
|
||||
if device.WindowsWallpaperMode == 1 {
|
||||
return post.GetWindowsWallpaperImageTargetPath(cfg, device)
|
||||
}
|
||||
baseDownloadDir := cfg.String("download.directory")
|
||||
p := path.Join(baseDownloadDir, device.Slug, post.GetSubreddit(), post.GetImageFilename())
|
||||
abs, _ := filepath.Abs(p)
|
||||
|
@ -269,6 +272,9 @@ func (post *Post) GetImageTargetPath(cfg *config.Config, device *models.Device)
|
|||
}
|
||||
|
||||
func (post *Post) GetImageTargetDir(cfg *config.Config, device *models.Device) string {
|
||||
if device.WindowsWallpaperMode == 1 {
|
||||
return post.GetWindowsWallpaperImageTargetDir(cfg, device)
|
||||
}
|
||||
baseDownloadDir := cfg.String("download.directory")
|
||||
p := path.Join(baseDownloadDir, device.Slug, post.GetSubreddit())
|
||||
abs, _ := filepath.Abs(p)
|
||||
|
@ -316,6 +322,10 @@ func (post *Post) GetThumbnailRelativePath() string {
|
|||
}
|
||||
|
||||
func (post *Post) GetImageRelativePath(device *models.Device) string {
|
||||
if device.WindowsWallpaperMode == 1 {
|
||||
return post.GetWindowsWallpaperImageRelativePath(device)
|
||||
}
|
||||
|
||||
return path.Join(device.Slug, post.GetSubreddit(), post.GetImageFilename())
|
||||
}
|
||||
|
||||
|
|
|
@ -186,6 +186,10 @@ func (api *API) SubredditGetByNameWithImages(ctx context.Context, name string, i
|
|||
return result, errs.Wrapw(err, "failed to get images by subreddit", "subreddit", result.Subreddit.Name, "params", imageParams)
|
||||
}
|
||||
|
||||
if err := result.Images.LoadImageDevice(ctx, api.db); err != nil {
|
||||
return result, errs.Wrapw(err, "failed to get device by images")
|
||||
}
|
||||
|
||||
result.Total, err = models.Images.
|
||||
Query(ctx, api.db, append(imageParams.CountQuery(), models.SelectWhere.Images.Subreddit.EQ(result.Subreddit.Name))...).
|
||||
Count()
|
||||
|
|
|
@ -6,11 +6,13 @@ import (
|
|||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/tigorlazuardi/redmage/models"
|
||||
"github.com/tigorlazuardi/redmage/pkg/errs"
|
||||
"github.com/tigorlazuardi/redmage/pkg/log"
|
||||
"github.com/tigorlazuardi/redmage/pkg/telemetry"
|
||||
"github.com/tigorlazuardi/redmage/views/components"
|
||||
)
|
||||
|
||||
func (routes *Routes) APIDeviceCreate(rw http.ResponseWriter, r *http.Request) {
|
||||
|
@ -95,3 +97,67 @@ func validateCreateDevice(params *models.Device) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (routes *Routes) DevicesCreateHTMX(rw http.ResponseWriter, req *http.Request) {
|
||||
var err error
|
||||
ctx, span := tracer.Start(req.Context(), "*Routes.DevicesCreateHTMX")
|
||||
defer func() { telemetry.EndWithStatus(span, err) }()
|
||||
|
||||
device, err := createDeviceFromParams(req)
|
||||
if err != nil {
|
||||
rw.WriteHeader(400)
|
||||
if err := components.ErrorToast(err.Error()).Render(ctx, rw); err != nil {
|
||||
log.New(ctx).Err(err).Error("failed to render error notification")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
_, err = routes.API.DevicesCreate(ctx, device)
|
||||
if err != nil {
|
||||
log.New(ctx).Err(err).Error("failed to create device", "device", 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", "/devices")
|
||||
rw.WriteHeader(http.StatusCreated)
|
||||
|
||||
if err := components.SuccessToast("device created").Render(ctx, rw); err != nil {
|
||||
log.New(ctx).Err(err).Error("failed to render success notification")
|
||||
}
|
||||
}
|
||||
|
||||
func createDeviceFromParams(req *http.Request) (*models.Device, error) {
|
||||
device := new(models.Device)
|
||||
|
||||
device.Enable = 1
|
||||
device.Name = req.FormValue("name")
|
||||
device.Slug = req.FormValue("slug")
|
||||
device.ResolutionX, _ = strconv.ParseFloat(req.FormValue("resolution_x"), 32)
|
||||
device.ResolutionY, _ = strconv.ParseFloat(req.FormValue("resolution_y"), 32)
|
||||
device.AspectRatioTolerance, _ = strconv.ParseFloat(req.FormValue("aspect_ratio_tolerance"), 32)
|
||||
|
||||
maxX, _ := strconv.ParseInt(req.FormValue("max_x"), 10, 32)
|
||||
device.MaxX = int32(maxX)
|
||||
|
||||
maxY, _ := strconv.ParseInt(req.FormValue("max_y"), 10, 32)
|
||||
device.MaxY = int32(maxY)
|
||||
|
||||
minX, _ := strconv.ParseInt(req.FormValue("min_x"), 10, 32)
|
||||
device.MinX = int32(minX)
|
||||
|
||||
minY, _ := strconv.ParseInt(req.FormValue("min_y"), 10, 32)
|
||||
device.MinY = int32(minY)
|
||||
|
||||
nsfw, _ := strconv.ParseInt(req.FormValue("nsfw"), 10, 32)
|
||||
device.NSFW = int32(nsfw)
|
||||
|
||||
windowsWallpaperMode, _ := strconv.ParseInt(req.FormValue("windows_wallpaper_mode"), 10, 32)
|
||||
device.WindowsWallpaperMode = int32(windowsWallpaperMode)
|
||||
|
||||
return device, validateCreateDevice(device)
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ func (routes *Routes) PageSubredditsDetails(rw http.ResponseWriter, r *http.Requ
|
|||
data.Subreddit = result.Subreddit
|
||||
data.Images = result.Images
|
||||
data.TotalImages = result.Total
|
||||
data.Devices, err = routes.API.GetDevices(ctx, api.DevicesListParams{})
|
||||
data.Devices, err = routes.API.GetDevices(ctx, api.DevicesListParams{Status: -1})
|
||||
if err != nil {
|
||||
log.New(ctx).Err(err).Error("failed to get devices")
|
||||
code, message := errs.HTTPMessage(err)
|
||||
|
|
|
@ -63,6 +63,7 @@ func (routes *Routes) registerHTMXRoutes(router chi.Router) {
|
|||
router.Post("/subreddits/check", routes.SubredditCheckHTMX)
|
||||
router.Get("/subreddits/validate/schedule", routes.SubredditValidateScheduleHTMX)
|
||||
|
||||
router.Post("/devices/add", routes.DevicesCreateHTMX)
|
||||
router.Post("/devices/add/validate/slug", routes.DevicesValidateSlugHTMX)
|
||||
router.Post("/devices/add/validate/name", routes.DevicesValidateNameHTMX)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ const (
|
|||
HideTitle ImageCardOption = 1 << iota
|
||||
HideSubreddit
|
||||
HidePoster
|
||||
HideDevice
|
||||
)
|
||||
|
||||
templ ImageCard(data *models.Image, opts ImageCardOption) {
|
||||
|
@ -48,6 +49,11 @@ templ ImageCard(data *models.Image, opts ImageCardOption) {
|
|||
<p class="text-xs">{ fmt.Sprintf("%d \u00d7 %d", data.ImageWidth, data.ImageHeight) } px</p>
|
||||
<p class="text-xs text-end">{ formatByteSize(data.ImageSize) }</p>
|
||||
</div>
|
||||
if data.R.Device != nil && !opts.Has(HideDevice) {
|
||||
<a href={ templ.URL(fmt.Sprintf("/devices/details/%s", data.R.Device.Slug)) }>
|
||||
<div class="divider text-accent underline">{ data.R.Device.Name }</div>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ type NameInputData struct {
|
|||
}
|
||||
|
||||
templ NameInput(data NameInputData) {
|
||||
<label class="form-control">
|
||||
<label id="name-input-form" class="form-control">
|
||||
<div class="label">
|
||||
<span
|
||||
class={ utils.CXX("label-text", true, "text-error", data.Error != "") }
|
||||
|
@ -21,6 +21,8 @@ templ NameInput(data NameInputData) {
|
|||
hx-post="/htmx/devices/add/validate/name"
|
||||
hx-include="[name='slug']"
|
||||
hx-trigger="change"
|
||||
hx-target="#name-input-form"
|
||||
hx-swap="outerHTML"
|
||||
name="name"
|
||||
type="text"
|
||||
class={ utils.CXX("input input-bordered", true, "input-error", data.Error != "") }
|
||||
|
|
24
views/devicesview/adddevice/nsfw_checkbox.templ
Normal file
24
views/devicesview/adddevice/nsfw_checkbox.templ
Normal file
|
@ -0,0 +1,24 @@
|
|||
package adddevice
|
||||
|
||||
type NSFWCheckboxData struct {
|
||||
Checked bool
|
||||
}
|
||||
|
||||
templ NSFWCheckbox(data NSFWCheckboxData) {
|
||||
<div class="form-control">
|
||||
<div class="divider my-0 py-0"></div>
|
||||
<div class="divider my-0 py-0"></div>
|
||||
<label class="label cursor-pointer">
|
||||
<span class="label-text">NSFW</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked?={ data.Checked }
|
||||
class="checkbox"
|
||||
name="nsfw"
|
||||
value="1"
|
||||
/>
|
||||
</label>
|
||||
<div class="divider my-0 py-0"></div>
|
||||
<span class="label-text pl-1">Whether to allow NSFW images for current device.</span>
|
||||
</div>
|
||||
}
|
|
@ -22,6 +22,9 @@ templ Content(c *views.Context) {
|
|||
hx-post="/htmx/devices/add"
|
||||
action="/htmx/devices/add"
|
||||
class="grid sm:grid-cols-2 gap-4"
|
||||
hx-target={ components.NotificationContainerID }
|
||||
hx-target-error={ components.NotificationContainerID }
|
||||
hx-swap="afterbegin"
|
||||
>
|
||||
@NameInput(NameInputData{})
|
||||
@SlugInput(SlugInputData{})
|
||||
|
@ -29,6 +32,8 @@ templ Content(c *views.Context) {
|
|||
@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})
|
||||
@NSFWCheckbox(NSFWCheckboxData{Checked: true})
|
||||
@WindowsWallpaperCheckbox(WindowsWallpaperCheckboxData{})
|
||||
@MinImageResolutionXInput(ResolutionData{})
|
||||
@MinImageResolutionYInput(ResolutionData{})
|
||||
@MaxImageResolutionXInput(ResolutionData{})
|
||||
|
@ -37,4 +42,5 @@ templ Content(c *views.Context) {
|
|||
</form>
|
||||
}
|
||||
</main>
|
||||
@components.NotificationContainer()
|
||||
}
|
||||
|
|
27
views/devicesview/adddevice/windows_wallpaper_checkbox.templ
Normal file
27
views/devicesview/adddevice/windows_wallpaper_checkbox.templ
Normal file
|
@ -0,0 +1,27 @@
|
|||
package adddevice
|
||||
|
||||
type WindowsWallpaperCheckboxData struct {
|
||||
Checked bool
|
||||
}
|
||||
|
||||
templ WindowsWallpaperCheckbox(data WindowsWallpaperCheckboxData) {
|
||||
<div class="form-control">
|
||||
<div class="divider my-0 py-0"></div>
|
||||
<div class="divider my-0 py-0"></div>
|
||||
<label class="label cursor-pointer">
|
||||
<span class="label-text">Windows Wallpaper Mode</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked?={ data.Checked }
|
||||
class="checkbox"
|
||||
name="windows_wallpaper_mode"
|
||||
value="1"
|
||||
/>
|
||||
</label>
|
||||
<div class="divider my-0 py-0"></div>
|
||||
<span class="label-text pl-1">
|
||||
Windows Wallpaper Mode puts images under one folder instead of split by subreddits.
|
||||
This allows the user to target Windows Wallpaper to the whole image collections.
|
||||
</span>
|
||||
</div>
|
||||
}
|
|
@ -137,7 +137,7 @@ templ nsfwToggle(c *views.Context, data Data) {
|
|||
templ RecentlyAddedImageList(images models.ImageSlice, opts components.ImageCardOption) {
|
||||
<div class="overflow-x-auto flex gap-4 p-6 shadow-inner bg-base-300 rounded-2xl w-[85vw] md:w-full scrollbar-track-base-100 scrollbar-thumb-primary scrollbar-thin hover:scrollbar-thumb-base-300">
|
||||
for _, data := range images {
|
||||
@components.ImageCard(data, 0)
|
||||
@components.ImageCard(data, components.HideDevice)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -114,10 +114,8 @@ templ FilterBar(c *views.Context, data Data) {
|
|||
<select name="device" class="select select-bordered w-full">
|
||||
if len(data.Devices) == 0 {
|
||||
<option disabled selected>No Devices</option>
|
||||
} else if data.Params.Device == "" {
|
||||
<option value="" selected>*No Filter</option>
|
||||
} else {
|
||||
<option value="">*No Filter</option>
|
||||
<option value="" selected?={ data.Params.Device == "" }>*No Filter</option>
|
||||
}
|
||||
for _, device := range data.Devices {
|
||||
@deviceOption(data.Params, device)
|
||||
|
|
Loading…
Reference in a new issue