mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-20 14:37:43 +01:00
Bookmarking/continue watching + sorting, color options in caption settings
Co-authored-by: mrjvs <mistrjvs@gmail.com>
This commit is contained in:
parent
18ec79af07
commit
09c52d9f37
8 changed files with 119 additions and 53 deletions
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
@ -4,5 +4,8 @@
|
|||
"eslint.format.enable": true,
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "ms-vsliveshare.vsliveshare"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
import classNames from "classnames";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { Toggle } from "@/components/buttons/Toggle";
|
||||
|
@ -93,6 +94,31 @@ function QualityView({ id }: { id: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
function ColorOption(props: {
|
||||
color: string;
|
||||
active?: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"p-1.5 bg-video-context-buttonFocus rounded transition-colors duration-100",
|
||||
props.active ? "bg-opacity-100" : "bg-opacity-0 cursor-pointer"
|
||||
)}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<div
|
||||
className="w-6 h-6 rounded-full flex justify-center items-center"
|
||||
style={{ backgroundColor: props.color }}
|
||||
>
|
||||
{props.active ? (
|
||||
<Icon className="text-sm text-black" icon={Icons.CHECKMARK} />
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CaptionSettingsView({ id }: { id: string }) {
|
||||
const router = useOverlayRouter(id);
|
||||
|
||||
|
@ -102,12 +128,32 @@ function CaptionSettingsView({ id }: { id: string }) {
|
|||
Custom captions
|
||||
</Context.BackLink>
|
||||
<Context.Section>
|
||||
<Context.SmallText>Hello!</Context.SmallText>
|
||||
<div className="flex justify-between items-center">
|
||||
<Context.FieldTitle>Color</Context.FieldTitle>
|
||||
<div className="flex justify-center items-center">
|
||||
<ColorOption onClick={() => {}} color="#FFFFFF" active />
|
||||
<ColorOption onClick={() => {}} color="#80B1FA" />
|
||||
<ColorOption onClick={() => {}} color="#E2E535" />
|
||||
</div>
|
||||
</div>
|
||||
</Context.Section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function CaptionsView({ id }: { id: string }) {
|
||||
const router = useOverlayRouter(id);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Context.BackLink onClick={() => router.navigate("/captions")}>
|
||||
Captions
|
||||
</Context.BackLink>
|
||||
<Context.Section>Yee!</Context.Section>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SettingsOverlay({ id }: { id: string }) {
|
||||
const router = useOverlayRouter(id);
|
||||
const currentQuality = usePlayerStore((s) => s.currentQuality);
|
||||
|
@ -165,15 +211,7 @@ function SettingsOverlay({ id }: { id: string }) {
|
|||
</OverlayPage>
|
||||
<OverlayPage id={id} path="/captions" width={343} height={431}>
|
||||
<Context.Card>
|
||||
<Context.BackLink onClick={() => router.navigate("/")}>
|
||||
Captions
|
||||
</Context.BackLink>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => router.navigate("/captions/settings")}
|
||||
>
|
||||
Go to caption settings
|
||||
</button>
|
||||
<CaptionsView id={id} />
|
||||
</Context.Card>
|
||||
</OverlayPage>
|
||||
<OverlayPage id={id} path="/captions/settings" width={343} height={431}>
|
||||
|
|
|
@ -154,18 +154,23 @@ function Anchor(props: { children: React.ReactNode; onClick: () => void }) {
|
|||
);
|
||||
}
|
||||
|
||||
function FieldTitle(props: { children: React.ReactNode }) {
|
||||
return <p className="font-medium">{props.children}</p>;
|
||||
}
|
||||
|
||||
export const Context = {
|
||||
Card,
|
||||
CardWithScrollable,
|
||||
Title,
|
||||
SectionTitle,
|
||||
BackLink,
|
||||
Section,
|
||||
Link,
|
||||
LinkTitle,
|
||||
LinkChevron,
|
||||
IconButton,
|
||||
Divider,
|
||||
FieldTitle,
|
||||
SmallText,
|
||||
BackLink,
|
||||
LinkTitle,
|
||||
Section,
|
||||
Divider,
|
||||
Anchor,
|
||||
Title,
|
||||
Link,
|
||||
Card,
|
||||
};
|
||||
|
|
|
@ -8,26 +8,38 @@ import { SectionHeading } from "@/components/layout/SectionHeading";
|
|||
import { MediaGrid } from "@/components/media/MediaGrid";
|
||||
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard";
|
||||
import { useBookmarkStore } from "@/stores/bookmarks";
|
||||
import { useProgressStore } from "@/stores/progress";
|
||||
import { MediaItem } from "@/utils/mediaTypes";
|
||||
|
||||
export function BookmarksPart() {
|
||||
const { t } = useTranslation();
|
||||
const progressItems = useProgressStore((s) => s.items);
|
||||
const bookmarks = useBookmarkStore((s) => s.bookmarks);
|
||||
const removeBookmark = useBookmarkStore((s) => s.removeBookmark);
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [gridRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
// TODO sort on last watched
|
||||
const items = useMemo(() => {
|
||||
const output: MediaItem[] = [];
|
||||
let output: MediaItem[] = [];
|
||||
Object.entries(bookmarks).forEach((entry) => {
|
||||
output.push({
|
||||
id: entry[0],
|
||||
...entry[1],
|
||||
});
|
||||
});
|
||||
output = output.sort((a, b) => {
|
||||
const bookmarkA = bookmarks[a.id];
|
||||
const bookmarkB = bookmarks[b.id];
|
||||
const progressA = progressItems[a.id];
|
||||
const progressB = progressItems[b.id];
|
||||
|
||||
const dateA = Math.max(bookmarkA.updatedAt, progressA?.updatedAt ?? 0);
|
||||
const dateB = Math.max(bookmarkB.updatedAt, progressB?.updatedAt ?? 0);
|
||||
|
||||
return dateB - dateA;
|
||||
});
|
||||
return output;
|
||||
}, [bookmarks]);
|
||||
}, [bookmarks, progressItems]);
|
||||
|
||||
if (items.length === 0) return null;
|
||||
|
||||
|
|
|
@ -21,18 +21,19 @@ export function WatchingPart() {
|
|||
|
||||
const sortedProgressItems = useMemo(() => {
|
||||
let output: MediaItem[] = [];
|
||||
Object.entries(progressItems).forEach((entry) => {
|
||||
output.push({
|
||||
id: entry[0],
|
||||
...entry[1],
|
||||
Object.entries(progressItems)
|
||||
.sort((a, b) => b[1].updatedAt - a[1].updatedAt)
|
||||
.forEach((entry) => {
|
||||
output.push({
|
||||
id: entry[0],
|
||||
...entry[1],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
output = output.filter((v) => {
|
||||
const isBookMarked = !!bookmarks[v.id];
|
||||
return !isBookMarked;
|
||||
});
|
||||
// TODO sort on last modified date
|
||||
return output;
|
||||
}, [progressItems, bookmarks]);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ export interface BookmarkMediaItem {
|
|||
year: number;
|
||||
poster?: string;
|
||||
type: "show" | "movie";
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
export interface ProgressStore {
|
||||
|
@ -34,6 +35,7 @@ export const useBookmarkStore = create(
|
|||
title: meta.title,
|
||||
year: meta.releaseYear,
|
||||
poster: meta.poster,
|
||||
updatedAt: Date.now(),
|
||||
};
|
||||
});
|
||||
},
|
||||
|
|
|
@ -29,6 +29,7 @@ export interface ProgressMediaItem {
|
|||
poster?: string;
|
||||
type: "show" | "movie";
|
||||
progress?: ProgressItem;
|
||||
updatedAt: number;
|
||||
seasons: Record<string, ProgressSeasonItem>;
|
||||
episodes: Record<string, ProgressEpisodeItem>;
|
||||
}
|
||||
|
@ -61,11 +62,14 @@ export const useProgressStore = create(
|
|||
type: meta.type,
|
||||
episodes: {},
|
||||
seasons: {},
|
||||
updatedAt: 0,
|
||||
title: meta.title,
|
||||
year: meta.releaseYear,
|
||||
poster: meta.poster,
|
||||
};
|
||||
const item = s.items[meta.tmdbId];
|
||||
item.updatedAt = Date.now();
|
||||
|
||||
if (meta.type === "movie") {
|
||||
if (!item.progress)
|
||||
item.progress = {
|
||||
|
|
|
@ -26,23 +26,23 @@ module.exports = {
|
|||
"ash-400": "#3D394D",
|
||||
"ash-300": "#2C293A",
|
||||
"ash-200": "#2B2836",
|
||||
"ash-100": "#1E1C26"
|
||||
"ash-100": "#1E1C26",
|
||||
},
|
||||
|
||||
/* fonts */
|
||||
fontFamily: {
|
||||
"open-sans": "'Open Sans'"
|
||||
"open-sans": "'Open Sans'",
|
||||
},
|
||||
|
||||
/* animations */
|
||||
keyframes: {
|
||||
"loading-pin": {
|
||||
"0%, 40%, 100%": { height: "0.5em", "background-color": "#282336" },
|
||||
"20%": { height: "1em", "background-color": "white" }
|
||||
}
|
||||
"20%": { height: "1em", "background-color": "white" },
|
||||
},
|
||||
},
|
||||
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" }
|
||||
}
|
||||
animation: { "loading-pin": "loading-pin 1.8s ease-in-out infinite" },
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
require("tailwind-scrollbar"),
|
||||
|
@ -52,31 +52,31 @@ module.exports = {
|
|||
colors: {
|
||||
// Branding
|
||||
pill: {
|
||||
background: "#1C1C36"
|
||||
background: "#1C1C36",
|
||||
},
|
||||
|
||||
// meta data for the theme itself
|
||||
global: {
|
||||
accentA: "#505DBD",
|
||||
accentB: "#3440A1"
|
||||
accentB: "#3440A1",
|
||||
},
|
||||
|
||||
// light bar
|
||||
lightBar: {
|
||||
light: "#2A2A71"
|
||||
light: "#2A2A71",
|
||||
},
|
||||
|
||||
// Buttons
|
||||
buttons: {
|
||||
toggle: "#8D44D6",
|
||||
toggleDisabled: "#202836"
|
||||
toggleDisabled: "#202836",
|
||||
},
|
||||
|
||||
// only used for body colors/textures
|
||||
background: {
|
||||
main: "#0A0A10",
|
||||
accentA: "#6E3B80",
|
||||
accentB: "#1F1F50"
|
||||
accentB: "#1F1F50",
|
||||
},
|
||||
|
||||
// typography
|
||||
|
@ -85,7 +85,7 @@ module.exports = {
|
|||
text: "#73739D",
|
||||
dimmed: "#926CAD",
|
||||
divider: "#262632",
|
||||
secondary: "#64647B"
|
||||
secondary: "#64647B",
|
||||
},
|
||||
|
||||
// search bar
|
||||
|
@ -94,7 +94,7 @@ module.exports = {
|
|||
focused: "#24243C",
|
||||
placeholder: "#4A4A71",
|
||||
icon: "#545476",
|
||||
text: "#FFFFFF"
|
||||
text: "#FFFFFF",
|
||||
},
|
||||
|
||||
// media cards
|
||||
|
@ -106,7 +106,7 @@ module.exports = {
|
|||
barColor: "#4B4B63",
|
||||
barFillColor: "#BA7FD6",
|
||||
badge: "#151522",
|
||||
badgeText: "#5F5F7A"
|
||||
badgeText: "#5F5F7A",
|
||||
},
|
||||
|
||||
// video player
|
||||
|
@ -118,34 +118,35 @@ module.exports = {
|
|||
error: "#E44F4F",
|
||||
success: "#40B44B",
|
||||
loading: "#B759D8",
|
||||
noresult: "#64647B"
|
||||
noresult: "#64647B",
|
||||
},
|
||||
|
||||
progress: {
|
||||
background: "#8787A8",
|
||||
preloaded: "#8787A8",
|
||||
watched: "#A75FC9"
|
||||
watched: "#A75FC9",
|
||||
},
|
||||
|
||||
audio: {
|
||||
set: "#A75FC9"
|
||||
set: "#A75FC9",
|
||||
},
|
||||
|
||||
context: {
|
||||
background: "#0C1216",
|
||||
light: "#4D79A8",
|
||||
border: "#4F5C66",
|
||||
buttonFocus: "#202836",
|
||||
|
||||
type: {
|
||||
main: "#617A8A",
|
||||
secondary: "#374A56",
|
||||
accent: "#A570FA"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
]
|
||||
accent: "#A570FA",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue