view(subreddits): added nsfw filter to subreddit details view
This commit is contained in:
parent
13eb3ddbb9
commit
55845b4b45
|
@ -37,7 +37,7 @@ type SubredditGetByNameImageParams struct {
|
|||
Offset int64
|
||||
OrderBy string
|
||||
Sort string
|
||||
SFW int
|
||||
NSFW int
|
||||
After time.Time
|
||||
Device string
|
||||
}
|
||||
|
@ -103,11 +103,10 @@ func (sgb *SubredditGetByNameImageParams) FillFromQuery(query Queryable) {
|
|||
sgb.After = time.Now().Add(time.Duration(afterint) * time.Second)
|
||||
}
|
||||
|
||||
sgb.SFW, _ = strconv.Atoi(query.Get("sfw"))
|
||||
if sgb.SFW < 0 {
|
||||
sgb.SFW = 0
|
||||
} else if sgb.SFW > 1 {
|
||||
sgb.SFW = 1
|
||||
if nsfw, err := strconv.Atoi(query.Get("nsfw")); err == nil {
|
||||
sgb.NSFW = nsfw
|
||||
} else {
|
||||
sgb.NSFW = -1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,8 +131,8 @@ func (sgb *SubredditGetByNameImageParams) CountQuery() (expr []bob.Mod[*dialect.
|
|||
expr = append(expr, models.SelectWhere.Images.CreatedAt.GTE(sgb.After.Unix()))
|
||||
}
|
||||
|
||||
if sgb.SFW == 1 {
|
||||
expr = append(expr, models.SelectWhere.Images.NSFW.EQ(0))
|
||||
if sgb.NSFW >= 0 {
|
||||
expr = append(expr, models.SelectWhere.Images.NSFW.EQ(int32(sgb.NSFW)))
|
||||
}
|
||||
|
||||
return expr
|
||||
|
|
|
@ -1,208 +0,0 @@
|
|||
package details
|
||||
|
||||
import "github.com/tigorlazuardi/redmage/views"
|
||||
import "github.com/tigorlazuardi/redmage/models"
|
||||
import "github.com/tigorlazuardi/redmage/views/components"
|
||||
import "strconv"
|
||||
import "github.com/tigorlazuardi/redmage/api"
|
||||
import "fmt"
|
||||
import "github.com/tigorlazuardi/redmage/views/icons"
|
||||
|
||||
type Data struct {
|
||||
Subreddit *models.Subreddit
|
||||
Devices models.DeviceSlice
|
||||
Images models.ImageSlice
|
||||
TotalImages int64
|
||||
Error string
|
||||
Params api.SubredditGetByNameImageParams
|
||||
FlashMessageSuccess string
|
||||
}
|
||||
|
||||
templ View(c *views.Context, data Data) {
|
||||
@components.Doctype() {
|
||||
if data.Subreddit != nil {
|
||||
@components.Head(c, components.HeadTitle(fmt.Sprintf("Subreddit - %s", data.Subreddit.Name)))
|
||||
} else {
|
||||
@components.Head(c, components.HeadTitle("Subreddit - 404 NOT FOUND"))
|
||||
}
|
||||
@components.Body(c) {
|
||||
@Content(c, data)
|
||||
@components.NotificationContainer() {
|
||||
if data.FlashMessageSuccess != "" {
|
||||
@components.SuccessNotification(data.FlashMessageSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templ Content(c *views.Context, data Data) {
|
||||
<main class="prose min-w-full">
|
||||
@components.Container() {
|
||||
if data.Error != "" {
|
||||
<h1>Error: { data.Error }</h1>
|
||||
} else {
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="my-auto">Subreddit { data.Subreddit.Name }</h1>
|
||||
<div class="max-xs:toast max-xs:z-40">
|
||||
<div class="dropdown dropdown-hover dropdown-top xs:dropdown-bottom dropdown-end">
|
||||
<div
|
||||
tabindex="0"
|
||||
role="button"
|
||||
class="btn btn-primary max-xs:btn-circle max-lg:btn-square xs:btn-outline m-1 max-xs:border-none"
|
||||
>
|
||||
@icons.Kebab("h-8 w-8")
|
||||
</div>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52 m-0 border-primary border-2"
|
||||
onclick="document.activeElement.blur()"
|
||||
>
|
||||
<li class="m-0 p-0 hover:bg-primary rounded-btn">
|
||||
<button
|
||||
hx-get="/htmx/subreddits/start"
|
||||
hx-target={ components.NotificationContainerID }
|
||||
hx-target-error={ components.NotificationContainerID }
|
||||
hx-swap="afterbegin"
|
||||
hx-vals={ fmt.Sprintf(`{"subreddit":%q}`, data.Subreddit.Name) }
|
||||
class="btn btn-ghost btn-sm m-0"
|
||||
>
|
||||
Start Download
|
||||
</button>
|
||||
</li>
|
||||
<div class="divider m-0 p-0"></div>
|
||||
<li class="m-0 p-0 hover:bg-primary rounded-btn">
|
||||
<a
|
||||
href={ templ.SafeURL(fmt.Sprintf("/subreddits/edit/%s", data.Subreddit.Name)) }
|
||||
class="btn btn-ghost btn-sm no-underline m-0"
|
||||
>Edit</a>
|
||||
</li>
|
||||
<div class="xs:hidden divider m-0 p-0"></div>
|
||||
<li class="xs:hidden m-0 p-0 hover:bg-primary rounded-btn">
|
||||
<button
|
||||
class="btn btn-ghost btn-sm m-0"
|
||||
onclick="window.scrollTo({ top: 0, behavior: 'smooth' })"
|
||||
>Scroll to Top</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="my-8">
|
||||
Total Images:
|
||||
{ strconv.FormatInt(data.TotalImages, 10) }
|
||||
</h2>
|
||||
<div class="divider"></div>
|
||||
@FilterBar(c, data)
|
||||
<div class="grid justify-center my-4">
|
||||
@components.Pagination(c, components.PaginationData{
|
||||
Offset: data.Params.Offset,
|
||||
Limit: data.Params.Limit,
|
||||
BaseURL: fmt.Sprintf("/subreddits/details/%s", data.Subreddit.Name),
|
||||
Total: data.TotalImages,
|
||||
})
|
||||
</div>
|
||||
@showingImageFromTo(data)
|
||||
<div class="flex flex-wrap gap-4 my-8 justify-around">
|
||||
for _, image := range data.Images {
|
||||
@components.ImageCard(image, components.HideNothing.SetCond(components.HideDevice, data.Params.Device != ""))
|
||||
}
|
||||
</div>
|
||||
<div class="flex w-full justify-center">
|
||||
@components.Pagination(c, components.PaginationData{
|
||||
Offset: data.Params.Offset,
|
||||
Limit: data.Params.Limit,
|
||||
BaseURL: fmt.Sprintf("/subreddits/details/%s", data.Subreddit.Name),
|
||||
Total: data.TotalImages,
|
||||
})
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</main>
|
||||
}
|
||||
|
||||
templ FilterBar(c *views.Context, data Data) {
|
||||
<div
|
||||
id="filter-bar"
|
||||
hx-get={ fmt.Sprintf("/subreddits/details/%s", data.Subreddit.Name) }
|
||||
hx-include="this"
|
||||
hx-trigger="change, on-rapid delay:500ms, on-demand"
|
||||
hx-target="main"
|
||||
hx-select="main"
|
||||
hx-push-url="true"
|
||||
class="grid sm:grid-cols-2 md:grid-cols-3 gap-4"
|
||||
>
|
||||
<label class="input input-bordered flex items-center gap-2 sm:col-span-2 md:col-auto">
|
||||
<input
|
||||
id="search"
|
||||
type="text"
|
||||
class="grow"
|
||||
placeholder="Search"
|
||||
name="q"
|
||||
value={ data.Params.Q }
|
||||
oninput="htmx.trigger('#filter-bar', 'on-rapid')"
|
||||
/>
|
||||
<svg
|
||||
onclick="htmx.trigger('#filter-bar', 'on-demand')"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="currentColor"
|
||||
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>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
Limit
|
||||
<select name="limit" class="select select-bordered w-full">
|
||||
@limitOption(data.Params, 25)
|
||||
@limitOption(data.Params, 50)
|
||||
@limitOption(data.Params, 75)
|
||||
@limitOption(data.Params, 100)
|
||||
</select>
|
||||
</label>
|
||||
<label class="flex items-center gap-2">
|
||||
Device
|
||||
<select name="device" class="select select-bordered w-full">
|
||||
if len(data.Devices) == 0 {
|
||||
<option disabled selected>No Devices</option>
|
||||
} else {
|
||||
<option value="" selected?={ data.Params.Device == "" }>*No Filter</option>
|
||||
}
|
||||
for _, device := range data.Devices {
|
||||
@deviceOption(data.Params, device)
|
||||
}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ limitOption(params api.SubredditGetByNameImageParams, value int) {
|
||||
if int(params.Limit) == value {
|
||||
<option selected>{ strconv.Itoa(value) }</option>
|
||||
} else {
|
||||
<option>{ strconv.Itoa(value) }</option>
|
||||
}
|
||||
}
|
||||
|
||||
templ deviceOption(params api.SubredditGetByNameImageParams, device *models.Device) {
|
||||
if params.Device == device.Slug {
|
||||
<option value={ device.Slug } selected>{ device.Name }</option>
|
||||
} else {
|
||||
<option value={ device.Slug }>{ device.Name }</option>
|
||||
}
|
||||
}
|
||||
|
||||
templ showingImageFromTo(data Data) {
|
||||
if data.TotalImages > 1 {
|
||||
<p class="text-center my-4">{ showingFromToImages(data) }</p>
|
||||
}
|
||||
}
|
||||
|
||||
func showingFromToImages(data Data) string {
|
||||
params := data.Params
|
||||
start := params.Offset + 1
|
||||
end := params.Limit + params.Offset
|
||||
if end > data.TotalImages {
|
||||
end = data.TotalImages
|
||||
}
|
||||
return fmt.Sprintf("Showing from %d to %d", start, end)
|
||||
}
|
92
views/subreddits/details/filter.templ
Normal file
92
views/subreddits/details/filter.templ
Normal file
|
@ -0,0 +1,92 @@
|
|||
package details
|
||||
|
||||
import "github.com/tigorlazuardi/redmage/views"
|
||||
import "fmt"
|
||||
import "github.com/tigorlazuardi/redmage/api"
|
||||
import "strconv"
|
||||
import "github.com/tigorlazuardi/redmage/models"
|
||||
|
||||
templ FilterBar(c *views.Context, data Data) {
|
||||
<div
|
||||
id="filter-bar"
|
||||
hx-get={ fmt.Sprintf("/subreddits/details/%s", data.Subreddit.Name) }
|
||||
hx-include="this"
|
||||
hx-trigger="change, input delay:200ms"
|
||||
hx-target="#image-content"
|
||||
hx-select="#image-content"
|
||||
hx-push-url="true"
|
||||
class="grid grid-cols-[1fr,3fr] sm:grid-cols-[1fr,4fr,1fr,4fr] gap-4 items-center"
|
||||
>
|
||||
@searchInput(data.Params)
|
||||
@nsfwInput(data.Params)
|
||||
@limitInput(data.Params)
|
||||
@deviceInput(data)
|
||||
</div>
|
||||
}
|
||||
|
||||
templ searchInput(params api.SubredditGetByNameImageParams) {
|
||||
<label class="label" for="search">
|
||||
Search
|
||||
</label>
|
||||
<input
|
||||
id="search"
|
||||
type="text"
|
||||
class="grow input input-bordered"
|
||||
placeholder="Search"
|
||||
name="q"
|
||||
value={ params.Q }
|
||||
/>
|
||||
}
|
||||
|
||||
templ nsfwInput(params api.SubredditGetByNameImageParams) {
|
||||
<label for="nsfw" class="label">
|
||||
NSFW
|
||||
</label>
|
||||
<select id="nsfw" name="nsfw" class="select select-bordered">
|
||||
<option value="-1" selected?={ params.NSFW == -1 }>*No Filter</option>
|
||||
<option value="0" selected?={ params.NSFW == 0 }>Hide</option>
|
||||
<option value="1" selected?={ params.NSFW == 1 }>Show Only</option>
|
||||
</select>
|
||||
}
|
||||
|
||||
templ limitInput(params api.SubredditGetByNameImageParams) {
|
||||
<label for="limit" class="label">
|
||||
Limit
|
||||
</label>
|
||||
<select id="limit" name="limit" class="select select-bordered w-full">
|
||||
@limitOption(params, 25)
|
||||
@limitOption(params, 50)
|
||||
@limitOption(params, 75)
|
||||
@limitOption(params, 100)
|
||||
</select>
|
||||
}
|
||||
|
||||
templ limitOption(params api.SubredditGetByNameImageParams, value int) {
|
||||
if int(params.Limit) == value {
|
||||
<option selected>{ strconv.Itoa(value) }</option>
|
||||
} else {
|
||||
<option>{ strconv.Itoa(value) }</option>
|
||||
}
|
||||
}
|
||||
|
||||
templ deviceInput(data Data) {
|
||||
<label for="devices" class="label">Device</label>
|
||||
<select id="devices" name="device" class="select select-bordered">
|
||||
if len(data.Devices) == 0 {
|
||||
<option disabled selected>No Devices</option>
|
||||
} else {
|
||||
<option value="" selected?={ data.Params.Device == "" }>*No Filter</option>
|
||||
}
|
||||
for _, device := range data.Devices {
|
||||
@deviceOption(data.Params, device)
|
||||
}
|
||||
</select>
|
||||
}
|
||||
|
||||
templ deviceOption(params api.SubredditGetByNameImageParams, device *models.Device) {
|
||||
if params.Device == device.Slug {
|
||||
<option value={ device.Slug } selected>{ device.Name }</option>
|
||||
} else {
|
||||
<option value={ device.Slug }>{ device.Name }</option>
|
||||
}
|
||||
}
|
144
views/subreddits/details/view.templ
Normal file
144
views/subreddits/details/view.templ
Normal file
|
@ -0,0 +1,144 @@
|
|||
package details
|
||||
|
||||
import "github.com/tigorlazuardi/redmage/views"
|
||||
import "github.com/tigorlazuardi/redmage/models"
|
||||
import "github.com/tigorlazuardi/redmage/views/components"
|
||||
import "strconv"
|
||||
import "github.com/tigorlazuardi/redmage/api"
|
||||
import "fmt"
|
||||
import "github.com/tigorlazuardi/redmage/views/icons"
|
||||
|
||||
type Data struct {
|
||||
Subreddit *models.Subreddit
|
||||
Devices models.DeviceSlice
|
||||
Images models.ImageSlice
|
||||
TotalImages int64
|
||||
Error string
|
||||
Params api.SubredditGetByNameImageParams
|
||||
FlashMessageSuccess string
|
||||
}
|
||||
|
||||
templ View(c *views.Context, data Data) {
|
||||
@components.Doctype() {
|
||||
if data.Subreddit != nil {
|
||||
@components.Head(c, components.HeadTitle(fmt.Sprintf("Subreddit - %s", data.Subreddit.Name)))
|
||||
} else {
|
||||
@components.Head(c, components.HeadTitle("Subreddit - 404 NOT FOUND"))
|
||||
}
|
||||
@components.Body(c) {
|
||||
@Content(c, data)
|
||||
@components.NotificationContainer() {
|
||||
if data.FlashMessageSuccess != "" {
|
||||
@components.SuccessNotification(data.FlashMessageSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
templ Content(c *views.Context, data Data) {
|
||||
<main class="prose min-w-full">
|
||||
@components.Container() {
|
||||
if data.Error != "" {
|
||||
<h1>Error: { data.Error }</h1>
|
||||
} else {
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="my-auto">Subreddit { data.Subreddit.Name }</h1>
|
||||
@actionButton(data)
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
@FilterBar(c, data)
|
||||
<div id="image-content">
|
||||
<h2 class="mt-8">
|
||||
Total Images:
|
||||
{ strconv.FormatInt(data.TotalImages, 10) }
|
||||
</h2>
|
||||
<div class="grid justify-center my-4">
|
||||
@components.Pagination(c, components.PaginationData{
|
||||
Offset: data.Params.Offset,
|
||||
Limit: data.Params.Limit,
|
||||
BaseURL: fmt.Sprintf("/subreddits/details/%s", data.Subreddit.Name),
|
||||
Total: data.TotalImages,
|
||||
})
|
||||
</div>
|
||||
@showingImageFromTo(data)
|
||||
<div class="flex flex-wrap gap-4 my-8 justify-around">
|
||||
for _, image := range data.Images {
|
||||
@components.ImageCard(image, components.HideNothing.SetCond(components.HideDevice, data.Params.Device != ""))
|
||||
}
|
||||
</div>
|
||||
<div class="flex w-full justify-center">
|
||||
@components.Pagination(c, components.PaginationData{
|
||||
Offset: data.Params.Offset,
|
||||
Limit: data.Params.Limit,
|
||||
BaseURL: fmt.Sprintf("/subreddits/details/%s", data.Subreddit.Name),
|
||||
Total: data.TotalImages,
|
||||
})
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</main>
|
||||
}
|
||||
|
||||
templ actionButton(data Data) {
|
||||
<div class="max-xs:toast max-xs:z-40">
|
||||
<div class="dropdown dropdown-hover dropdown-top xs:dropdown-bottom dropdown-end">
|
||||
<div
|
||||
tabindex="0"
|
||||
role="button"
|
||||
class="btn btn-primary max-xs:btn-circle max-lg:btn-square xs:btn-outline m-1 max-xs:border-none"
|
||||
>
|
||||
@icons.Kebab("h-8 w-8")
|
||||
</div>
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52 m-0 border-primary border-2"
|
||||
onclick="document.activeElement.blur()"
|
||||
>
|
||||
<li class="m-0 p-0 hover:bg-primary rounded-btn">
|
||||
<button
|
||||
hx-get="/htmx/subreddits/start"
|
||||
hx-target={ components.NotificationContainerID }
|
||||
hx-target-error={ components.NotificationContainerID }
|
||||
hx-swap="afterbegin"
|
||||
hx-vals={ fmt.Sprintf(`{"subreddit":%q}`, data.Subreddit.Name) }
|
||||
class="btn btn-ghost btn-sm m-0"
|
||||
>
|
||||
Start Download
|
||||
</button>
|
||||
</li>
|
||||
<div class="divider m-0 p-0"></div>
|
||||
<li class="m-0 p-0 hover:bg-primary rounded-btn">
|
||||
<a
|
||||
href={ templ.SafeURL(fmt.Sprintf("/subreddits/edit/%s", data.Subreddit.Name)) }
|
||||
class="btn btn-ghost btn-sm no-underline m-0"
|
||||
>Edit</a>
|
||||
</li>
|
||||
<div class="xs:hidden divider m-0 p-0"></div>
|
||||
<li class="xs:hidden m-0 p-0 hover:bg-primary rounded-btn">
|
||||
<button
|
||||
class="btn btn-ghost btn-sm m-0"
|
||||
onclick="window.scrollTo({ top: 0, behavior: 'smooth' })"
|
||||
>Scroll to Top</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
templ showingImageFromTo(data Data) {
|
||||
if data.TotalImages > 1 {
|
||||
<p class="text-center my-4">{ showingFromToImages(data) }</p>
|
||||
}
|
||||
}
|
||||
|
||||
func showingFromToImages(data Data) string {
|
||||
params := data.Params
|
||||
start := params.Offset + 1
|
||||
end := params.Limit + params.Offset
|
||||
if end > data.TotalImages {
|
||||
end = data.TotalImages
|
||||
}
|
||||
return fmt.Sprintf("Showing from %d to %d", start, end)
|
||||
}
|
Loading…
Reference in a new issue