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 {
|
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")
|
baseDownloadDir := cfg.String("download.directory")
|
||||||
p := path.Join(baseDownloadDir, device.Slug, post.GetSubreddit(), post.GetImageFilename())
|
p := path.Join(baseDownloadDir, device.Slug, post.GetSubreddit(), post.GetImageFilename())
|
||||||
abs, _ := filepath.Abs(p)
|
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 {
|
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")
|
baseDownloadDir := cfg.String("download.directory")
|
||||||
p := path.Join(baseDownloadDir, device.Slug, post.GetSubreddit())
|
p := path.Join(baseDownloadDir, device.Slug, post.GetSubreddit())
|
||||||
abs, _ := filepath.Abs(p)
|
abs, _ := filepath.Abs(p)
|
||||||
|
@ -316,6 +322,10 @@ func (post *Post) GetThumbnailRelativePath() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (post *Post) GetImageRelativePath(device *models.Device) 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())
|
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)
|
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.
|
result.Total, err = models.Images.
|
||||||
Query(ctx, api.db, append(imageParams.CountQuery(), models.SelectWhere.Images.Subreddit.EQ(result.Subreddit.Name))...).
|
Query(ctx, api.db, append(imageParams.CountQuery(), models.SelectWhere.Images.Subreddit.EQ(result.Subreddit.Name))...).
|
||||||
Count()
|
Count()
|
||||||
|
|
|
@ -6,11 +6,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"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/pkg/telemetry"
|
"github.com/tigorlazuardi/redmage/pkg/telemetry"
|
||||||
|
"github.com/tigorlazuardi/redmage/views/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (routes *Routes) APIDeviceCreate(rw http.ResponseWriter, r *http.Request) {
|
func (routes *Routes) APIDeviceCreate(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -95,3 +97,67 @@ func validateCreateDevice(params *models.Device) error {
|
||||||
}
|
}
|
||||||
return nil
|
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.Subreddit = result.Subreddit
|
||||||
data.Images = result.Images
|
data.Images = result.Images
|
||||||
data.TotalImages = result.Total
|
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 {
|
if err != nil {
|
||||||
log.New(ctx).Err(err).Error("failed to get devices")
|
log.New(ctx).Err(err).Error("failed to get devices")
|
||||||
code, message := errs.HTTPMessage(err)
|
code, message := errs.HTTPMessage(err)
|
||||||
|
|
|
@ -63,6 +63,7 @@ func (routes *Routes) registerHTMXRoutes(router chi.Router) {
|
||||||
router.Post("/subreddits/check", routes.SubredditCheckHTMX)
|
router.Post("/subreddits/check", routes.SubredditCheckHTMX)
|
||||||
router.Get("/subreddits/validate/schedule", routes.SubredditValidateScheduleHTMX)
|
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/slug", routes.DevicesValidateSlugHTMX)
|
||||||
router.Post("/devices/add/validate/name", routes.DevicesValidateNameHTMX)
|
router.Post("/devices/add/validate/name", routes.DevicesValidateNameHTMX)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ const (
|
||||||
HideTitle ImageCardOption = 1 << iota
|
HideTitle ImageCardOption = 1 << iota
|
||||||
HideSubreddit
|
HideSubreddit
|
||||||
HidePoster
|
HidePoster
|
||||||
|
HideDevice
|
||||||
)
|
)
|
||||||
|
|
||||||
templ ImageCard(data *models.Image, opts ImageCardOption) {
|
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">{ fmt.Sprintf("%d \u00d7 %d", data.ImageWidth, data.ImageHeight) } px</p>
|
||||||
<p class="text-xs text-end">{ formatByteSize(data.ImageSize) }</p>
|
<p class="text-xs text-end">{ formatByteSize(data.ImageSize) }</p>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ type NameInputData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
templ NameInput(data NameInputData) {
|
templ NameInput(data NameInputData) {
|
||||||
<label class="form-control">
|
<label id="name-input-form" class="form-control">
|
||||||
<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 != "") }
|
||||||
|
@ -21,6 +21,8 @@ templ NameInput(data NameInputData) {
|
||||||
hx-post="/htmx/devices/add/validate/name"
|
hx-post="/htmx/devices/add/validate/name"
|
||||||
hx-include="[name='slug']"
|
hx-include="[name='slug']"
|
||||||
hx-trigger="change"
|
hx-trigger="change"
|
||||||
|
hx-target="#name-input-form"
|
||||||
|
hx-swap="outerHTML"
|
||||||
name="name"
|
name="name"
|
||||||
type="text"
|
type="text"
|
||||||
class={ utils.CXX("input input-bordered", true, "input-error", data.Error != "") }
|
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"
|
hx-post="/htmx/devices/add"
|
||||||
action="/htmx/devices/add"
|
action="/htmx/devices/add"
|
||||||
class="grid sm:grid-cols-2 gap-4"
|
class="grid sm:grid-cols-2 gap-4"
|
||||||
|
hx-target={ components.NotificationContainerID }
|
||||||
|
hx-target-error={ components.NotificationContainerID }
|
||||||
|
hx-swap="afterbegin"
|
||||||
>
|
>
|
||||||
@NameInput(NameInputData{})
|
@NameInput(NameInputData{})
|
||||||
@SlugInput(SlugInputData{})
|
@SlugInput(SlugInputData{})
|
||||||
|
@ -29,6 +32,8 @@ templ Content(c *views.Context) {
|
||||||
@ResolutionYInput(ResolutionData{})
|
@ResolutionYInput(ResolutionData{})
|
||||||
<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"><h3 class="m-0 p-0">Filter</h3></div>
|
||||||
@AspectRatioToleranceInput(AspectRatioToleranceData{Value: 0.2})
|
@AspectRatioToleranceInput(AspectRatioToleranceData{Value: 0.2})
|
||||||
|
@NSFWCheckbox(NSFWCheckboxData{Checked: true})
|
||||||
|
@WindowsWallpaperCheckbox(WindowsWallpaperCheckboxData{})
|
||||||
@MinImageResolutionXInput(ResolutionData{})
|
@MinImageResolutionXInput(ResolutionData{})
|
||||||
@MinImageResolutionYInput(ResolutionData{})
|
@MinImageResolutionYInput(ResolutionData{})
|
||||||
@MaxImageResolutionXInput(ResolutionData{})
|
@MaxImageResolutionXInput(ResolutionData{})
|
||||||
|
@ -37,4 +42,5 @@ templ Content(c *views.Context) {
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
</main>
|
</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) {
|
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">
|
<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 {
|
for _, data := range images {
|
||||||
@components.ImageCard(data, 0)
|
@components.ImageCard(data, components.HideDevice)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,10 +114,8 @@ templ FilterBar(c *views.Context, data Data) {
|
||||||
<select name="device" class="select select-bordered w-full">
|
<select name="device" class="select select-bordered w-full">
|
||||||
if len(data.Devices) == 0 {
|
if len(data.Devices) == 0 {
|
||||||
<option disabled selected>No Devices</option>
|
<option disabled selected>No Devices</option>
|
||||||
} else if data.Params.Device == "" {
|
|
||||||
<option value="" selected>*No Filter</option>
|
|
||||||
} else {
|
} else {
|
||||||
<option value="">*No Filter</option>
|
<option value="" selected?={ data.Params.Device == "" }>*No Filter</option>
|
||||||
}
|
}
|
||||||
for _, device := range data.Devices {
|
for _, device := range data.Devices {
|
||||||
@deviceOption(data.Params, device)
|
@deviceOption(data.Params, device)
|
||||||
|
|
Loading…
Reference in a new issue