subreddit-details-view: added filter by device
This commit is contained in:
parent
42646c601d
commit
e5b148a933
|
@ -46,6 +46,8 @@ func (dlp DevicesListParams) Query() (expr []bob.Mod[*dialect.SelectQuery]) {
|
||||||
} else {
|
} else {
|
||||||
expr = append(expr, order.Asc())
|
expr = append(expr, order.Asc())
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
expr = append(expr, sm.OrderBy(models.DeviceColumns.Name).Asc())
|
||||||
}
|
}
|
||||||
|
|
||||||
return expr
|
return expr
|
||||||
|
@ -77,7 +79,7 @@ func (api *API) DevicesList(ctx context.Context, params DevicesListParams) (resu
|
||||||
ctx, span := tracer.Start(ctx, "*API.DevicesList")
|
ctx, span := tracer.Start(ctx, "*API.DevicesList")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
result.Devices, err = models.Devices.Query(ctx, api.db, params.Query()...).All()
|
result.Devices, err = api.GetDevices(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, errs.Wrapw(err, "failed to query devices", "params", params)
|
return result, errs.Wrapw(err, "failed to query devices", "params", params)
|
||||||
}
|
}
|
||||||
|
@ -89,3 +91,15 @@ func (api *API) DevicesList(ctx context.Context, params DevicesListParams) (resu
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *API) GetDevices(ctx context.Context, params DevicesListParams) (result models.DeviceSlice, err error) {
|
||||||
|
ctx, span := tracer.Start(ctx, "*API.GetDevices")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
result, err = models.Devices.Query(ctx, api.db, params.Query()...).All()
|
||||||
|
if err != nil {
|
||||||
|
return result, errs.Wrapw(err, "failed to query devices", "params", params)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
|
@ -39,6 +39,7 @@ type SubredditGetByNameImageParams struct {
|
||||||
Sort string
|
Sort string
|
||||||
SFW int
|
SFW int
|
||||||
After time.Time
|
After time.Time
|
||||||
|
Device string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sgb SubredditGetByNameImageParams) IntoQuery() url.Values {
|
func (sgb SubredditGetByNameImageParams) IntoQuery() url.Values {
|
||||||
|
@ -62,6 +63,9 @@ func (sgb SubredditGetByNameImageParams) IntoQuery() url.Values {
|
||||||
if !sgb.After.IsZero() {
|
if !sgb.After.IsZero() {
|
||||||
queries.Set("after", strconv.FormatInt(sgb.After.Unix(), 10))
|
queries.Set("after", strconv.FormatInt(sgb.After.Unix(), 10))
|
||||||
}
|
}
|
||||||
|
if sgb.Device != "" {
|
||||||
|
queries.Set("device", sgb.Device)
|
||||||
|
}
|
||||||
|
|
||||||
return queries
|
return queries
|
||||||
}
|
}
|
||||||
|
@ -76,6 +80,7 @@ func (sgb SubredditGetByNameImageParams) IntoQueryWith(keyValue ...string) url.V
|
||||||
|
|
||||||
func (sgb *SubredditGetByNameImageParams) FillFromQuery(query Queryable) {
|
func (sgb *SubredditGetByNameImageParams) FillFromQuery(query Queryable) {
|
||||||
sgb.Q = query.Get("q")
|
sgb.Q = query.Get("q")
|
||||||
|
sgb.Device = query.Get("device")
|
||||||
sgb.Limit, _ = strconv.ParseInt(query.Get("limit"), 10, 64)
|
sgb.Limit, _ = strconv.ParseInt(query.Get("limit"), 10, 64)
|
||||||
if sgb.Limit < 1 {
|
if sgb.Limit < 1 {
|
||||||
sgb.Limit = 25
|
sgb.Limit = 25
|
||||||
|
@ -119,6 +124,10 @@ func (sgb *SubredditGetByNameImageParams) CountQuery() (expr []bob.Mod[*dialect.
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sgb.Device != "" {
|
||||||
|
expr = append(expr, models.SelectWhere.Images.Device.EQ(sgb.Device))
|
||||||
|
}
|
||||||
|
|
||||||
if !sgb.After.IsZero() {
|
if !sgb.After.IsZero() {
|
||||||
expr = append(expr, models.SelectWhere.Images.CreatedAt.GTE(sgb.After.Unix()))
|
expr = append(expr, models.SelectWhere.Images.CreatedAt.GTE(sgb.After.Unix()))
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,7 @@ CREATE TABLE images(
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_subreddit_images ON images(subreddit);
|
CREATE INDEX idx_subreddit_images ON images(subreddit);
|
||||||
|
CREATE INDEX idx_subreddit_device_images ON images(device, subreddit);
|
||||||
CREATE INDEX idx_nsfw_images ON images(nsfw);
|
CREATE INDEX idx_nsfw_images ON images(nsfw);
|
||||||
CREATE INDEX idx_images_created_at_nsfw ON images(created_at DESC, nsfw);
|
CREATE INDEX idx_images_created_at_nsfw ON images(created_at DESC, nsfw);
|
||||||
CREATE UNIQUE INDEX idx_unique_images_per_device ON images(device, post_name);
|
CREATE UNIQUE INDEX idx_unique_images_per_device ON images(device, post_name);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
@ -33,7 +32,6 @@ func (routes *Routes) PageSubredditsDetails(rw http.ResponseWriter, r *http.Requ
|
||||||
code, message := errs.HTTPMessage(err)
|
code, message := errs.HTTPMessage(err)
|
||||||
rw.WriteHeader(code)
|
rw.WriteHeader(code)
|
||||||
data.Error = message
|
data.Error = message
|
||||||
fmt.Println(data)
|
|
||||||
if err := detailsview.Detailsview(c, data).Render(ctx, rw); err != nil {
|
if err := detailsview.Detailsview(c, data).Render(ctx, rw); err != nil {
|
||||||
log.New(ctx).Err(err).Error("failed to render subreddit details page")
|
log.New(ctx).Err(err).Error("failed to render subreddit details page")
|
||||||
}
|
}
|
||||||
|
@ -42,6 +40,16 @@ 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{})
|
||||||
|
if err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to get devices")
|
||||||
|
code, message := errs.HTTPMessage(err)
|
||||||
|
rw.WriteHeader(code)
|
||||||
|
data.Error = message
|
||||||
|
if err := detailsview.Detailsview(c, data).Render(ctx, rw); err != nil {
|
||||||
|
log.New(ctx).Err(err).Error("failed to render subreddit details page")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := detailsview.Detailsview(c, data).Render(ctx, rw); err != nil {
|
if err := detailsview.Detailsview(c, data).Render(ctx, rw); err != nil {
|
||||||
log.New(ctx).Err(err).Error("failed to render subreddit details page")
|
log.New(ctx).Err(err).Error("failed to render subreddit details page")
|
||||||
|
|
|
@ -9,6 +9,7 @@ import "fmt"
|
||||||
|
|
||||||
type Data struct {
|
type Data struct {
|
||||||
Subreddit *models.Subreddit
|
Subreddit *models.Subreddit
|
||||||
|
Devices models.DeviceSlice
|
||||||
Images models.ImageSlice
|
Images models.ImageSlice
|
||||||
TotalImages int64
|
TotalImages int64
|
||||||
Error string
|
Error string
|
||||||
|
@ -54,11 +55,9 @@ templ DetailsContent(c *views.Context, data Data) {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="flex gap-4 flex-wrap justify-between content-center">
|
@FilterBar(c, data)
|
||||||
@FilterBar(c, data)
|
@paginationButtons(c, data)
|
||||||
@showingImageFromTo(data)
|
@showingImageFromTo(data)
|
||||||
@paginationButtons(c, data)
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap gap-4 my-8 justify-around">
|
<div class="flex flex-wrap gap-4 my-8 justify-around">
|
||||||
for _, image := range data.Images {
|
for _, image := range data.Images {
|
||||||
@components.ImageCard(image, 0)
|
@components.ImageCard(image, 0)
|
||||||
|
@ -81,9 +80,9 @@ templ FilterBar(c *views.Context, data Data) {
|
||||||
hx-target="main"
|
hx-target="main"
|
||||||
hx-select="main"
|
hx-select="main"
|
||||||
hx-push-url="true"
|
hx-push-url="true"
|
||||||
class="flex flex-wrap gap-4"
|
class="grid sm:grid-cols-2 md:grid-cols-3 gap-4"
|
||||||
>
|
>
|
||||||
<label class="input input-bordered flex items-center gap-2">
|
<label class="input input-bordered flex items-center gap-2 sm:col-span-2 md:col-auto">
|
||||||
<input
|
<input
|
||||||
id="search"
|
id="search"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -101,7 +100,7 @@ templ FilterBar(c *views.Context, 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>
|
||||||
<label class="input flex items-center gap-2">
|
<label class="flex items-center gap-2">
|
||||||
Limit
|
Limit
|
||||||
<select name="limit" class="select select-bordered w-full">
|
<select name="limit" class="select select-bordered w-full">
|
||||||
@limitOption(data.Params, 25)
|
@limitOption(data.Params, 25)
|
||||||
|
@ -110,19 +109,36 @@ templ FilterBar(c *views.Context, data Data) {
|
||||||
@limitOption(data.Params, 100)
|
@limitOption(data.Params, 100)
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</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 if data.Params.Device == "" {
|
||||||
|
<option value="" selected>*No Filter</option>
|
||||||
|
} else {
|
||||||
|
<option value="">*No Filter</option>
|
||||||
|
}
|
||||||
|
for _, device := range data.Devices {
|
||||||
|
@deviceOption(data.Params, device)
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
templ paginationButtons(_ *views.Context, data Data) {
|
templ paginationButtons(_ *views.Context, data Data) {
|
||||||
if data.TotalImages > data.Params.Limit {
|
if data.TotalImages > data.Params.Limit {
|
||||||
<div class="join">
|
<div class="flex justify-center my-4">
|
||||||
for i, count := 1, int64(0); count < data.TotalImages; i, count = i+1, count+data.Params.Limit {
|
<div class="join">
|
||||||
if data.Params.Offset <= count && data.Params.Offset > count-data.Params.Limit {
|
for i, count := 1, int64(0); count < data.TotalImages; i, count = i+1, count+data.Params.Limit {
|
||||||
<a href={ buildURL(data.Subreddit.Name, data.Params, "offset", strconv.FormatInt(count, 10)) } class="join-item btn btn-active no-underline">{ strconv.Itoa(i) }</a>
|
if data.Params.Offset <= count && data.Params.Offset > count-data.Params.Limit {
|
||||||
} else {
|
<a href={ buildURL(data.Subreddit.Name, data.Params, "offset", strconv.FormatInt(count, 10)) } class="join-item btn btn-active no-underline">{ strconv.Itoa(i) }</a>
|
||||||
<a href={ buildURL(data.Subreddit.Name, data.Params, "offset", strconv.FormatInt(count, 10)) } class="join-item btn no-underline">{ strconv.Itoa(i) }</a>
|
} else {
|
||||||
|
<a href={ buildURL(data.Subreddit.Name, data.Params, "offset", strconv.FormatInt(count, 10)) } class="join-item btn no-underline">{ strconv.Itoa(i) }</a>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,9 +157,17 @@ templ limitOption(params api.SubredditGetByNameImageParams, value int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
templ showingImageFromTo(data Data) {
|
||||||
if data.TotalImages > 1 {
|
if data.TotalImages > 1 {
|
||||||
<span class="my-auto">{ showingFromToImages(data) }</span>
|
<p class="text-center my-4">{ showingFromToImages(data) }</p>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue