Skip to content

Commit

Permalink
Support pinning watched list items to top (#498)
Browse files Browse the repository at this point in the history
* Support for pinning watched list items

* GamePoster: Support `pinned` prop
  • Loading branch information
IRHM authored Apr 22, 2024
1 parent b1a2b3d commit 10923fd
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 93 deletions.
11 changes: 8 additions & 3 deletions server/watched.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Watched struct {
Status WatchedStatus `json:"status"`
Rating int8 `json:"rating"`
Thoughts string `json:"thoughts"`
Pinned bool `json:"pinned" gorm:"default:false;not null"`
UserID uint `json:"-" gorm:"uniqueIndex:usernctnidx;uniqueIndex:userngamidx"`
ContentID *int `json:"-" gorm:"uniqueIndex:usernctnidx"`
Content *Content `json:"content,omitempty"`
Expand All @@ -52,10 +53,11 @@ type WatchedAddRequest struct {
}

type WatchedUpdateRequest struct {
Status WatchedStatus `json:"status" binding:"required_without_all=Rating Thoughts RemoveThoughts"`
Rating int8 `json:"rating" binding:"max=10,required_without_all=Status Thoughts RemoveThoughts"`
Thoughts string `json:"thoughts" binding:"required_without_all=Status Rating RemoveThoughts"`
Status WatchedStatus `json:"status" binding:"required_without_all=Rating Thoughts RemoveThoughts Pinned"`
Rating int8 `json:"rating" binding:"max=10,required_without_all=Status Thoughts RemoveThoughts Pinned"`
Thoughts string `json:"thoughts" binding:"required_without_all=Status Rating RemoveThoughts Pinned"`
RemoveThoughts bool `json:"removeThoughts"`
Pinned *bool `json:"pinned" binding:"required_without_all=Status Rating Thoughts RemoveThoughts"`
}

type WatchedUpdateResponse struct {
Expand Down Expand Up @@ -194,6 +196,9 @@ func updateWatched(db *gorm.DB, userId uint, id uint, ar WatchedUpdateRequest) (
if ar.RemoveThoughts {
upwat.Thoughts = ""
}
if ar.Pinned != nil {
upwat.Pinned = *ar.Pinned
}
res = db.Save(upwat)
if res.RowsAffected <= 0 {
return WatchedUpdateResponse{}, errors.New("no watched entry found")
Expand Down
43 changes: 21 additions & 22 deletions src/lib/Icon.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,16 @@
<path d="M256 70H148l108 186-108 186h108l108-186z" fill="#e5a00d" />
</svg>
{:else if i === "trash"}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width={wh} height={wh}>
<path
d="M112 112l20 320c.95 18.49 14.4 32 32 32h184c17.67 0 30.87-13.51 32-32l20-320"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="32"
/>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-miterlimit="10"
stroke-width="32"
d="M80 112h352"
/>
<svg
xmlns="http://www.w3.org/2000/svg"
width={wh}
height={wh}
viewBox="0 0 512 512"
fill="currentColor"
>
<path d="M296 64h-80a7.91 7.91 0 00-8 8v24h96V72a7.91 7.91 0 00-8-8z" fill="none" />
<path
d="M192 112V72h0a23.93 23.93 0 0124-24h80a23.93 23.93 0 0124 24h0v40M256 176v224M184 176l8 224M328 176l-8 224"
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="32"
d="M432 96h-96V72a40 40 0 00-40-40h-80a40 40 0 00-40 40v24H80a16 16 0 000 32h17l19 304.92c1.42 26.85 22 47.08 48 47.08h184c26.13 0 46.3-19.78 48-47l19-305h17a16 16 0 000-32zM192.57 416H192a16 16 0 01-16-15.43l-8-224a16 16 0 1132-1.14l8 224A16 16 0 01192.57 416zM272 400a16 16 0 01-32 0V176a16 16 0 0132 0zm32-304h-96V72a7.91 7.91 0 018-8h80a7.91 7.91 0 018 8zm32 304.57A16 16 0 01320 416h-.58A16 16 0 01304 399.43l8-224a16 16 0 1132 1.14z"
/>
</svg>
{:else if i === "close"}
Expand Down Expand Up @@ -327,6 +314,18 @@
d="M128 416h256"
/>
</svg>
{:else if i === "pin"}
<svg xmlns="http://www.w3.org/2000/svg" width={wh} height={wh} viewBox="0 -960 960 960">
<path
d="m640-480 80 80v80H520v240l-40 40-40-40v-240H240v-80l80-80v-280h-40v-80h400v80h-40v280Z"
/>
</svg>
{:else if i === "unpin"}
<svg xmlns="http://www.w3.org/2000/svg" width={wh} height={wh} viewBox="0 -960 960 960">
<path
d="M680-840v80h-40v327L313-760l-33-33v-47h400ZM480-40l-40-40v-240H240v-80l80-80v-46L56-792l56-56 736 736-58 56-264-264h-6v240l-40 40Z"
/>
</svg>
{/if}

<style lang="scss">
Expand Down
106 changes: 57 additions & 49 deletions src/lib/WatchedList.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -60,56 +60,62 @@
function filt() {
try {
// Set watched to list and sort it.
watched = list.sort((a, b) => {
if (sort[0] === "DATEADDED" && sort[1] === "UP") {
return Date.parse(a.createdAt) - Date.parse(b.createdAt);
} else if (sort[0] === "ALPHA") {
const atitle = a.content ? a.content.title : a.game ? a.game.name : "";
const btitle = b.content ? b.content.title : b.game ? b.game.name : "";
if (sort[1] === "UP") {
return atitle.localeCompare(btitle);
} else if (sort[1] === "DOWN") {
return btitle.localeCompare(atitle);
watched = list
.sort((a, b) => {
if (sort[0] === "DATEADDED" && sort[1] === "UP") {
return Date.parse(a.createdAt) - Date.parse(b.createdAt);
} else if (sort[0] === "ALPHA") {
const atitle = a.content ? a.content.title : a.game ? a.game.name : "";
const btitle = b.content ? b.content.title : b.game ? b.game.name : "";
if (sort[1] === "UP") {
return atitle.localeCompare(btitle);
} else if (sort[1] === "DOWN") {
return btitle.localeCompare(atitle);
}
} else if (sort[0] === "LASTCHANGED") {
if (sort[1] === "UP") return Date.parse(a.updatedAt) - Date.parse(b.updatedAt);
else if (sort[1] === "DOWN") return Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
} else if (sort[0] === "LASTFIN") {
const aLastFinishActivity = a.activity
?.sort(
(aa, bb) =>
Date.parse(bb.customDate ?? bb.updatedAt) -
Date.parse(aa.customDate ?? aa.updatedAt)
)
?.find(
(aa) =>
(aa.type === "STATUS_CHANGED" && aa.data === "FINISHED") ||
(aa.type === "ADDED_WATCHED" && aa.data?.includes("FINISHED"))
);
const bLastFinishActivity = b.activity
?.sort(
(aa, bb) =>
Date.parse(bb.customDate ?? bb.updatedAt) -
Date.parse(aa.customDate ?? aa.updatedAt)
)
?.find(
(aa) =>
(aa.type === "STATUS_CHANGED" && aa.data === "FINISHED") ||
(aa.type === "ADDED_WATCHED" && aa.data?.includes("FINISHED"))
);
if (!aLastFinishActivity) return 1;
if (!bLastFinishActivity) return -1;
const alfaDate = aLastFinishActivity.customDate ?? aLastFinishActivity.updatedAt;
const blfaDate = bLastFinishActivity.customDate ?? bLastFinishActivity.updatedAt;
if (sort[1] === "UP") return Date.parse(alfaDate) - Date.parse(blfaDate);
else if (sort[1] === "DOWN") return Date.parse(blfaDate) - Date.parse(alfaDate);
} else if (sort[0] === "RATING") {
if (sort[1] === "UP") return (a.rating ?? 0) - (b.rating ?? 0);
else if (sort[1] === "DOWN") return (b.rating ?? 0) - (a.rating ?? 0);
}
} else if (sort[0] === "LASTCHANGED") {
if (sort[1] === "UP") return Date.parse(a.updatedAt) - Date.parse(b.updatedAt);
else if (sort[1] === "DOWN") return Date.parse(b.updatedAt) - Date.parse(a.updatedAt);
} else if (sort[0] === "LASTFIN") {
const aLastFinishActivity = a.activity
?.sort(
(aa, bb) =>
Date.parse(bb.customDate ?? bb.updatedAt) -
Date.parse(aa.customDate ?? aa.updatedAt)
)
?.find(
(aa) =>
(aa.type === "STATUS_CHANGED" && aa.data === "FINISHED") ||
(aa.type === "ADDED_WATCHED" && aa.data?.includes("FINISHED"))
);
const bLastFinishActivity = b.activity
?.sort(
(aa, bb) =>
Date.parse(bb.customDate ?? bb.updatedAt) -
Date.parse(aa.customDate ?? aa.updatedAt)
)
?.find(
(aa) =>
(aa.type === "STATUS_CHANGED" && aa.data === "FINISHED") ||
(aa.type === "ADDED_WATCHED" && aa.data?.includes("FINISHED"))
);
if (!aLastFinishActivity) return 1;
if (!bLastFinishActivity) return -1;
const alfaDate = aLastFinishActivity.customDate ?? aLastFinishActivity.updatedAt;
const blfaDate = bLastFinishActivity.customDate ?? bLastFinishActivity.updatedAt;
if (sort[1] === "UP") return Date.parse(alfaDate) - Date.parse(blfaDate);
else if (sort[1] === "DOWN") return Date.parse(blfaDate) - Date.parse(alfaDate);
} else if (sort[0] === "RATING") {
if (sort[1] === "UP") return (a.rating ?? 0) - (b.rating ?? 0);
else if (sort[1] === "DOWN") return (b.rating ?? 0) - (a.rating ?? 0);
}
// default DATEADDED DOWN
return Date.parse(b.createdAt) - Date.parse(a.createdAt);
});
// default DATEADDED DOWN
return Date.parse(b.createdAt) - Date.parse(a.createdAt);
})
.sort((a, b) => {
if (a.pinned && !b.pinned) return -1;
if (!a.pinned && b.pinned) return 1;
return 0;
});
// If games type filter enabled, but games disabled on server, make sure we remove it from active filters.
if (!features.games) {
const af = get(activeFilters);
Expand Down Expand Up @@ -176,6 +182,7 @@
dateModified: w.updatedAt
}}
fluidSize={true}
pinned={w.pinned}
/>
{:else if w.content}
<Poster
Expand All @@ -198,6 +205,7 @@
lastWatched: getLatestWatchedInTv(w.watchedSeasons, w.watchedEpisodes)
}}
fluidSize={true}
pinned={w.pinned}
/>
{/if}
{/each}
Expand Down
7 changes: 6 additions & 1 deletion src/lib/poster/GamePoster.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
export let hideButtons = false;
export let extraDetails: ExtraDetailsGame | undefined = undefined;
export let fluidSize = false;
export let pinned = false;
// When provided, default click handlers will instead run this callback.
export let onClick: (() => void) | undefined = undefined;
Expand Down Expand Up @@ -134,7 +135,7 @@
on:mouseleave={() => (posterActive = false)}
on:click={() => (posterActive = true)}
on:keypress={() => console.log("on kpress")}
class={`${posterActive ? "active " : ""}`}
class={`${posterActive ? "active " : ""}${pinned ? "pinned " : ""}`}
>
<div
class={`container${!poster || (!media.coverId && !media.poster?.path) ? " details-shown" : ""}`}
Expand Down Expand Up @@ -214,6 +215,10 @@
cursor: pointer;
}
li.pinned:not(.active) .container {
outline: 3px solid gold;
}
.container {
display: flex;
flex-flow: column;
Expand Down
7 changes: 6 additions & 1 deletion src/lib/poster/Poster.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
export let hideButtons = false;
export let extraDetails: ExtraDetails | undefined = undefined;
export let fluidSize = false;
export let pinned = false;
// When provided, default click handlers will instead run this callback.
export let onClick: (() => void) | undefined = undefined;
Expand Down Expand Up @@ -126,7 +127,7 @@
on:mouseleave={() => (posterActive = false)}
on:click={() => (posterActive = true)}
on:keypress={() => console.log("on kpress")}
class={`${posterActive ? "active " : ""}`}
class={`${posterActive ? "active " : ""}${pinned ? "pinned " : ""}`}
>
<div
class={`container${!poster || !media.poster_path ? " details-shown" : ""}`}
Expand Down Expand Up @@ -202,6 +203,10 @@
cursor: pointer;
}
li.pinned:not(.active) .container {
outline: 3px solid gold;
}
.container {
display: flex;
flex-flow: column;
Expand Down
22 changes: 15 additions & 7 deletions src/lib/util/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,28 @@ async function _updateWatched(
wEntry: Watched,
status?: WatchedStatus,
rating?: number,
thoughts?: string
thoughts?: string,
pinned?: boolean
): Promise<boolean> {
if (!status && !rating && typeof thoughts === "undefined" && typeof pinned === "undefined") {
console.warn("_updateWatched: Nothing was provided, so nothing can be updated!!!!");
return false;
}
const nid = notify({ text: `Saving`, type: "loading" });
if (!status && !rating && typeof thoughts === "undefined") return false;
const obj = {} as WatchedUpdateRequest;
if (status) obj.status = status;
if (rating) obj.rating = rating;
if (typeof thoughts !== "undefined") obj.thoughts = thoughts;
if (thoughts === "") obj.removeThoughts = true;
if (typeof pinned !== "undefined") obj.pinned = pinned;
return await axios
.put<WatchedUpdateResponse>(`/watched/${wEntry.id}`, obj)
.then((resp) => {
if (status) wEntry.status = status;
if (rating) wEntry.rating = rating;
if (typeof thoughts !== "undefined") wEntry.thoughts = thoughts;
if (resp?.data?.newActivity) {
if (typeof pinned !== "undefined") wEntry.pinned = pinned;
if (resp?.data?.newActivity && resp?.data?.newActivity?.id) {
if (wEntry.activity?.length > 0) {
wEntry.activity.push(resp.data.newActivity);
} else {
Expand Down Expand Up @@ -78,15 +84,16 @@ export async function updateWatched(
contentType: MediaType,
status?: WatchedStatus,
rating?: number,
thoughts?: string
thoughts?: string,
pinned?: boolean
): Promise<boolean> {
// If item is already in watched store, run update request instead
const wList = get(watchedList);
const wEntry = wList.find(
(w) => w.content?.tmdbId === contentId && w.content?.type === contentType
);
if (wEntry?.id) {
return await _updateWatched(wEntry, status, rating, thoughts);
return await _updateWatched(wEntry, status, rating, thoughts, pinned);
}
// Add new watched item
const nid = notify({ text: `Adding`, type: "loading" });
Expand Down Expand Up @@ -142,13 +149,14 @@ export async function updatePlayed(
igdbId: number,
status?: WatchedStatus,
rating?: number,
thoughts?: string
thoughts?: string,
pinned?: boolean
): Promise<boolean> {
// If item is already in watched store, run update request instead
const wList = get(watchedList);
const wEntry = wList.find((w) => w.game?.igdbId === igdbId);
if (wEntry?.id) {
return await _updateWatched(wEntry, status, rating, thoughts);
return await _updateWatched(wEntry, status, rating, thoughts, pinned);
}
// Add new played item
const nid = notify({ text: `Adding`, type: "loading" });
Expand Down
25 changes: 22 additions & 3 deletions src/routes/(app)/game/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@
async function contentChanged(
newStatus?: WatchedStatus,
newRating?: number,
newThoughts?: string
newThoughts?: string,
pinned?: boolean
): Promise<boolean> {
if (!gameId) {
console.error("contentChanged: no gameId");
return false;
}
return await updatePlayed(gameId, newStatus, newRating, newThoughts);
return await updatePlayed(gameId, newStatus, newRating, newThoughts, pinned);
}
</script>

Expand Down Expand Up @@ -159,6 +160,22 @@
{/if}
{/if}
{#if wListItem}
<button
class="pin-btn"
on:click={() => {
if (wListItem?.pinned) {
contentChanged(undefined, undefined, undefined, false);
} else {
contentChanged(undefined, undefined, undefined, true);
}
}}
use:tooltip={{
text: `${wListItem?.pinned ? "Unpin from" : "Pin to"} top of list`,
pos: "bot"
}}
>
<Icon i={wListItem?.pinned ? "unpin" : "pin"} wh={19} />
</button>
<button
class="delete-btn"
on:click={() =>
Expand Down Expand Up @@ -310,9 +327,11 @@
}
}
.delete-btn {
.pin-btn {
margin-left: auto;
}
.delete-btn {
&:hover {
color: $error;
}
Expand Down
Loading

0 comments on commit 10923fd

Please sign in to comment.