Compare commits

..

11 commits

Author SHA1 Message Date
Nikhil Nawgiri e04f567586 Fix jellyfin api filter param 2024-11-02 19:13:54 +01:00
Nikhil Nawgiri e24dd3bc55 Implement item status fetch logic 2024-11-02 18:59:29 +01:00
Nikhil Nawgiri 493372633c Enable CORS for express 2024-11-02 18:59:10 +01:00
Nikhil Nawgiri 9be03784d2 Add internalApiHelper 2024-11-02 18:42:04 +01:00
Nikhil Nawgiri 843ef1395d WIP 2024-11-02 18:41:12 +01:00
Nikhil Nawgiri 41f12db8f0 Add basic rendering logic for ItemStatusInLibrary component 2024-11-02 18:31:37 +01:00
Nikhil Nawgiri 84ed033a8d Create separate component for item status 2024-11-02 18:22:12 +01:00
Nikhil Nawgiri cfad242ab8 Rearrange item children 2024-11-02 18:15:15 +01:00
Nikhil Nawgiri 616646c0d9 Truncate item title 2024-11-02 16:23:02 +01:00
Nikhil Nawgiri 9f6a7d38b5 Improve styles & Add emoji for item types movie/series 2024-11-02 16:18:58 +01:00
Nikhil Nawgiri d08b54d14a Improve styling - WIP 2024-11-02 16:05:49 +01:00
6 changed files with 130 additions and 22 deletions

View file

@ -3,9 +3,16 @@ import bodyParser from "body-parser";
const app: Express = express();
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
export { app };

View file

@ -2,7 +2,7 @@ import fetch from "node-fetch";
import { API_BASE, API_TOKEN } from "../config.ts";
const API_ENDPOINT_WITH_PARAMS = "/Items?isMovie=true&isSeries=true&recursive=true&fields=ProviderIds&filters=&mediaTypes=Video&enableTotalRecordCount=true&enableImages=false";
const API_ENDPOINT_WITH_PARAMS = "/Items?isMovie=true&isSeries=true&recursive=true&fields=ProviderIds&filters=&enableTotalRecordCount=true&enableImages=false";
const API_URL = API_BASE + API_ENDPOINT_WITH_PARAMS;
export const fetchFromJellyfinApi = async () => {

View file

@ -99,7 +99,7 @@ h1:hover {
flex-direction: column;
height: 100%;
justify-content: center;
max-width: calc(50% + 15px);
max-width: calc(50% + 20px);
input {
font-size: 17px;
@ -116,22 +116,42 @@ h1:hover {
section {
flex: 1;
padding: 8px 0 0 8px;
margin-left: 8px;
display: flex;
justify-content: space-evenly;text-align: left;
justify-content: space-evenly;
text-align: left;
}
}
.search-result-item-interaction {
min-width: 20px;
.search-result-item-icons {
font-size: 14px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.search-result-item-text {
flex: 1;
.search-result-item-year {
padding-left: 5px;
&:after {
margin-left: 4px;
content: "-";
display: inline-block;
}
}
.search-result-item-title {
flex: 1;
padding: 0 4px;
}
.search-result-item-action {
width: 48px;
height: 48px;
width: 50px;
height: 48px;
font-size: 16px;
text-align: center;
background: linear-gradient(310deg, var(--jf-gradient-color-primary-dark), var(--jf-gradient-color-secondary-dark));
padding: 0 6px;
color: white;
border: 1px solid white;
}

View file

@ -0,0 +1,59 @@
import { useEffect, useState } from "react";
import { getItemStatusInLibrary } from "../../helper/internalApiHelper";
const LIBRARY_STATUS_AVAILABLE = "ON_JF";
const LIBRARY_STATUS_REQUESTED= "ON_WISHLIST";
const LIBRARY_STATUS_MISSING = "NO_MATCH";
const statusIcons: { [key: string]: string } = {
loading: "⏳",
[LIBRARY_STATUS_AVAILABLE]: "✅",
[LIBRARY_STATUS_REQUESTED]: "📋",
[LIBRARY_STATUS_MISSING]: " ",
};
interface ItemStatusInLibraryProps {
imdbId: string,
};
const FETCH_STATUS_DEFAULT = "FETCH_STATUS_DEFAULT";
const FETCH_STATUS_STARTED_FETCHING = "FETCH_STATUS_STARTED_FETCHING";
const FETCH_STATUS_DONE_FETCHING = "FETCH_STATUS_DONE_FETCHING";
const ItemStatusInLibrary = ({
imdbId,
}: ItemStatusInLibraryProps) => {
const [fetchStatus , setFetchStatus] = useState(FETCH_STATUS_DEFAULT);
const [libraryState, setLibraryState] = useState<string | null>(null);
console.log(`libraryState: ${imdbId}: `, libraryState);
const callBackFunc = (status: string) => {
console.log("status", status)
setFetchStatus(FETCH_STATUS_DONE_FETCHING);
setLibraryState(status);
};
useEffect(() => {
// begin fetching
if (fetchStatus === FETCH_STATUS_DEFAULT) {
setFetchStatus(FETCH_STATUS_STARTED_FETCHING)
getItemStatusInLibrary(
imdbId,
callBackFunc,
);
}
});
return (
<div>
{fetchStatus === FETCH_STATUS_STARTED_FETCHING && statusIcons.loading}
{fetchStatus === FETCH_STATUS_DONE_FETCHING
&& !!libraryState
&& Object.keys(statusIcons).includes(libraryState)
&& statusIcons[libraryState]}
</div>
);
}
export default ItemStatusInLibrary

View file

@ -1,16 +1,17 @@
import ItemStatusInLibrary from "../item/ItemStatusInLibrary";
interface SearchResultItemProps {
title: string,
image: string,
imdbId: string,
year: string,
type: string,
type: "series" | "movie",
clickHandler?: Function,
}
const itemStatusInLibrary = {
available: "✅",
requested: "⏳",
missing: " ",
const itemTypeIcons = {
movie: "📽",
series: "📺",
}
// todo add link to movie if available
@ -32,15 +33,21 @@ const SearchResultItem = ({
>
<img src={image} alt={title} />
<section>
<div className="search-result-item-interaction">
{Object.values(itemStatusInLibrary)[Math.floor(Math.random() * Object.values(itemStatusInLibrary).length)]}
<div className="search-result-item-icons">
<div>
{itemTypeIcons[type]}
</div>
<ItemStatusInLibrary imdbId={imdbId} />
</div>
<span className="search-result-item-text">
{year.substring(0, 4)} - {title}
</span>
<div className="search-result-item-action">
🔗
<div className="search-result-item-year">
{year.substring(0, 4)}
</div>
<div className="search-result-item-title">
{title.length > 40 ? `${title.substring(0, 40)}...` : title}
</div>
<button className="search-result-item-action">
Wish
</button>
</section>
</div>
);

View file

@ -0,0 +1,15 @@
const buildApiUrl = (imdbId: string) => `http://localhost:1312/check/${imdbId}`;
export const getItemStatusInLibrary = async (
imdbId: string,
callback: Function,
) => {
if (imdbId.length > 0) {
fetch(buildApiUrl(imdbId))
.then(response => response.json())
.then(data => {
callback(data.status);
});
}
}