Compare commits
4 Commits
276e0e9591
...
69115c7318
Author | SHA1 | Date | |
---|---|---|---|
69115c7318 | |||
a1922b2c75 | |||
051c973d88 | |||
05abcbf6fe |
30
.eslintrc
30
.eslintrc
@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"parser": "babel-eslint",
|
|
||||||
"extends": [
|
|
||||||
"eslint:recommended",
|
|
||||||
"plugin:react/recommended"
|
|
||||||
],
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"ecmascript": 6,
|
|
||||||
"jsx": true
|
|
||||||
},
|
|
||||||
"plugins": [
|
|
||||||
"react",
|
|
||||||
"react-hooks"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"strict": 1,
|
|
||||||
"quotes": 1,
|
|
||||||
"no-unused-vars": 1,
|
|
||||||
"camelcase": 1,
|
|
||||||
"no-underscore-dangle": 1,
|
|
||||||
"react/jsx-uses-react": 1,
|
|
||||||
"react/jsx-uses-vars": 1,
|
|
||||||
"react-hooks/rules-of-hooks": "error",
|
|
||||||
"react-hooks/exhaustive-deps": "warn"
|
|
||||||
}
|
|
||||||
}
|
|
46
frontend/.eslintrc
Normal file
46
frontend/.eslintrc
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"parser": "babel-eslint",
|
||||||
|
"extends": [
|
||||||
|
"eslint:recommended",
|
||||||
|
"plugin:import/recommended",
|
||||||
|
"plugin:react/recommended",
|
||||||
|
"plugin:prettier/recommended"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 6,
|
||||||
|
"sourceType": "module",
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": true,
|
||||||
|
"experimentalObjectRestSpread": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"mocha": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"strict": 1,
|
||||||
|
"quotes": 1,
|
||||||
|
"no-unused-vars": 1,
|
||||||
|
"camelcase": 1,
|
||||||
|
"no-underscore-dangle": 1,
|
||||||
|
"react/jsx-uses-react": 1,
|
||||||
|
"react/jsx-uses-vars": 1,
|
||||||
|
"react-hooks/rules-of-hooks": "error",
|
||||||
|
"react-hooks/exhaustive-deps": "warn",
|
||||||
|
"react/jsx-no-undef": 2,
|
||||||
|
"react/jsx-wrap-multilines": 2,
|
||||||
|
"react/no-string-refs": 0
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"import",
|
||||||
|
"react",
|
||||||
|
"react-hooks"
|
||||||
|
]
|
||||||
|
}
|
@ -1,42 +1,29 @@
|
|||||||
import { configureAxios, request } from "../requests"
|
import { configureAxios, request } from "../requests";
|
||||||
|
|
||||||
export function getUsers() {
|
export function getUsers() {
|
||||||
return request(
|
return request("ADMIN_LIST_USERS", configureAxios().get("/admins/users"));
|
||||||
"ADMIN_LIST_USERS",
|
|
||||||
configureAxios().get("/admins/users")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStats() {
|
export function getStats() {
|
||||||
return request(
|
return request("ADMIN_GET_STATS", configureAxios().get("/admins/stats"));
|
||||||
"ADMIN_GET_STATS",
|
|
||||||
configureAxios().get("/admins/stats")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAdminModules() {
|
export function getAdminModules() {
|
||||||
return request(
|
return request("ADMIN_GET_MODULES", configureAxios().get("/admins/modules"));
|
||||||
"ADMIN_GET_MODULES",
|
|
||||||
configureAxios().get("/admins/modules")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateUser(data) {
|
export function updateUser(data) {
|
||||||
return request(
|
return request(
|
||||||
"ADMIN_UPDATE_USER",
|
"ADMIN_UPDATE_USER",
|
||||||
configureAxios().post("/admins/users", data),
|
configureAxios().post("/admins/users", data),
|
||||||
[
|
[() => getUsers()]
|
||||||
() => getUsers(),
|
);
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteUser(username) {
|
export function deleteUser(username) {
|
||||||
return request(
|
return request(
|
||||||
"ADMIN_DELETE_USER",
|
"ADMIN_DELETE_USER",
|
||||||
configureAxios().delete("/admins/users/"+ username),
|
configureAxios().delete("/admins/users/" + username),
|
||||||
[
|
[() => getUsers()]
|
||||||
() => getUsers(),
|
);
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -2,22 +2,22 @@ export function addAlertError(message) {
|
|||||||
return {
|
return {
|
||||||
type: "ADD_ALERT_ERROR",
|
type: "ADD_ALERT_ERROR",
|
||||||
payload: {
|
payload: {
|
||||||
message,
|
message
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addAlertOk(message) {
|
export function addAlertOk(message) {
|
||||||
return {
|
return {
|
||||||
type: "ADD_ALERT_OK",
|
type: "ADD_ALERT_OK",
|
||||||
payload: {
|
payload: {
|
||||||
message,
|
message
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function dismissAlert() {
|
export function dismissAlert() {
|
||||||
return {
|
return {
|
||||||
type: "DISMISS_ALERT",
|
type: "DISMISS_ALERT"
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,40 @@
|
|||||||
import { configureAxios, request } from "../requests"
|
import { configureAxios, request } from "../requests";
|
||||||
|
|
||||||
import { addAlertOk } from "./alerts"
|
import { addAlertOk } from "./alerts";
|
||||||
import { sendNotification } from "./notifications"
|
import { sendNotification } from "./notifications";
|
||||||
|
|
||||||
export function updateLastMovieFetchUrl(url) {
|
export function updateLastMovieFetchUrl(url) {
|
||||||
return {
|
return {
|
||||||
type: "UPDATE_LAST_MOVIE_FETCH_URL",
|
type: "UPDATE_LAST_MOVIE_FETCH_URL",
|
||||||
payload: {
|
payload: {
|
||||||
url: url,
|
url: url
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectMovie(imdbId) {
|
export function selectMovie(imdbId) {
|
||||||
return {
|
return {
|
||||||
type: "SELECT_MOVIE",
|
type: "SELECT_MOVIE",
|
||||||
payload: {
|
payload: {
|
||||||
imdbId,
|
imdbId
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateFilter(filter) {
|
export function updateFilter(filter) {
|
||||||
return {
|
return {
|
||||||
type: "MOVIE_UPDATE_FILTER",
|
type: "MOVIE_UPDATE_FILTER",
|
||||||
payload: {
|
payload: {
|
||||||
filter,
|
filter
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMovieExploreOptions() {
|
export function getMovieExploreOptions() {
|
||||||
return request(
|
return request(
|
||||||
"MOVIE_GET_EXPLORE_OPTIONS",
|
"MOVIE_GET_EXPLORE_OPTIONS",
|
||||||
configureAxios().get("/movies/explore/options")
|
configureAxios().get("/movies/explore/options")
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMovieDetails(imdbId) {
|
export function getMovieDetails(imdbId) {
|
||||||
@ -43,27 +43,23 @@ export function getMovieDetails(imdbId) {
|
|||||||
configureAxios().post(`/movies/${imdbId}/refresh`),
|
configureAxios().post(`/movies/${imdbId}/refresh`),
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
imdbId,
|
imdbId
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteMovie(imdbId, lastFetchUrl) {
|
export function deleteMovie(imdbId, lastFetchUrl) {
|
||||||
return request(
|
return request("MOVIE_DELETE", configureAxios().delete(`/movies/${imdbId}`), [
|
||||||
"MOVIE_DELETE",
|
|
||||||
configureAxios().delete(`/movies/${imdbId}`),
|
|
||||||
[
|
|
||||||
fetchMovies(lastFetchUrl),
|
fetchMovies(lastFetchUrl),
|
||||||
addAlertOk("Movie deleted"),
|
addAlertOk("Movie deleted")
|
||||||
],
|
]);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function movieWishlistToggle(imdbId, currentState) {
|
export function movieWishlistToggle(imdbId, currentState) {
|
||||||
if (currentState == true) {
|
if (currentState == true) {
|
||||||
return deleteMovieFromWishlist(imdbId)
|
return deleteMovieFromWishlist(imdbId);
|
||||||
} else {
|
} else {
|
||||||
return addMovieToWishlist(imdbId)
|
return addMovieToWishlist(imdbId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,20 +67,16 @@ export function addMovieToWishlist(imdbId) {
|
|||||||
return request(
|
return request(
|
||||||
"MOVIE_ADD_TO_WISHLIST",
|
"MOVIE_ADD_TO_WISHLIST",
|
||||||
configureAxios().post(`/wishlist/movies/${imdbId}`),
|
configureAxios().post(`/wishlist/movies/${imdbId}`),
|
||||||
[
|
[updateMovieWishlistStore(imdbId, true)]
|
||||||
updateMovieWishlistStore(imdbId, true),
|
);
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteMovieFromWishlist(imdbId) {
|
export function deleteMovieFromWishlist(imdbId) {
|
||||||
return request(
|
return request(
|
||||||
"MOVIE_DELETE_FROM_WISHLIST",
|
"MOVIE_DELETE_FROM_WISHLIST",
|
||||||
configureAxios().delete(`/wishlist/movies/${imdbId}`),
|
configureAxios().delete(`/wishlist/movies/${imdbId}`),
|
||||||
[
|
[updateMovieWishlistStore(imdbId, false)]
|
||||||
updateMovieWishlistStore(imdbId, false),
|
);
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateMovieWishlistStore(imdbId, wishlisted) {
|
export function updateMovieWishlistStore(imdbId, wishlisted) {
|
||||||
@ -92,29 +84,27 @@ export function updateMovieWishlistStore(imdbId, wishlisted) {
|
|||||||
type: "MOVIE_UPDATE_STORE_WISHLIST",
|
type: "MOVIE_UPDATE_STORE_WISHLIST",
|
||||||
payload: {
|
payload: {
|
||||||
imdbId,
|
imdbId,
|
||||||
wishlisted,
|
wishlisted
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchMovies(url) {
|
export function fetchMovies(url) {
|
||||||
return request(
|
return request("MOVIE_LIST_FETCH", configureAxios().get(url), [
|
||||||
"MOVIE_LIST_FETCH",
|
updateLastMovieFetchUrl(url)
|
||||||
configureAxios().get(url),
|
]);
|
||||||
[
|
|
||||||
updateLastMovieFetchUrl(url),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const newMovieEvent = (data) => {
|
export const newMovieEvent = data => {
|
||||||
return (dispatch) => {
|
return dispatch => {
|
||||||
dispatch(sendNotification({
|
dispatch(
|
||||||
|
sendNotification({
|
||||||
icon: "film",
|
icon: "film",
|
||||||
autohide: true,
|
autohide: true,
|
||||||
delay: 10000,
|
delay: 10000,
|
||||||
title: `${data.title} added to the library`,
|
title: `${data.title} added to the library`,
|
||||||
imageUrl: data.thumb,
|
imageUrl: data.thumb
|
||||||
}));
|
})
|
||||||
}
|
);
|
||||||
}
|
};
|
||||||
|
};
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
export const sendNotification = (data) => ({
|
export const sendNotification = data => ({
|
||||||
type: "ADD_NOTIFICATION",
|
type: "ADD_NOTIFICATION",
|
||||||
payload: data,
|
payload: data
|
||||||
})
|
});
|
||||||
|
|
||||||
export const removeNotification = (id) => ({
|
export const removeNotification = id => ({
|
||||||
type: "REMOVE_NOTIFICATION",
|
type: "REMOVE_NOTIFICATION",
|
||||||
payload: { id },
|
payload: { id }
|
||||||
})
|
});
|
||||||
|
@ -1,50 +1,38 @@
|
|||||||
import { configureAxios, request } from "../requests"
|
import { configureAxios, request } from "../requests";
|
||||||
|
|
||||||
import { getUserInfos } from "./users"
|
import { getUserInfos } from "./users";
|
||||||
|
|
||||||
export const getPolochons = () => request(
|
export const getPolochons = () =>
|
||||||
"PUBLIC_POLOCHON_LIST_FETCH",
|
request("PUBLIC_POLOCHON_LIST_FETCH", configureAxios().get("/polochons"));
|
||||||
configureAxios().get("/polochons"),
|
|
||||||
)
|
|
||||||
|
|
||||||
export const getManagedPolochons = () => request(
|
export const getManagedPolochons = () =>
|
||||||
|
request(
|
||||||
"MANAGED_POLOCHON_LIST_FETCH",
|
"MANAGED_POLOCHON_LIST_FETCH",
|
||||||
configureAxios().get("/users/polochons"),
|
configureAxios().get("/users/polochons")
|
||||||
)
|
);
|
||||||
|
|
||||||
export const addPolochon = (params) => request(
|
export const addPolochon = params =>
|
||||||
"ADD_POLOCHON",
|
request("ADD_POLOCHON", configureAxios().post("/polochons", params), [
|
||||||
configureAxios().post("/polochons", params),
|
|
||||||
[
|
|
||||||
() => getPolochons(),
|
() => getPolochons(),
|
||||||
() => getManagedPolochons(),
|
() => getManagedPolochons()
|
||||||
],
|
]);
|
||||||
)
|
|
||||||
|
|
||||||
export const updatePolochon = ({ id, ...params }) => request(
|
export const updatePolochon = ({ id, ...params }) =>
|
||||||
|
request(
|
||||||
"UPDATE_POLOCHON",
|
"UPDATE_POLOCHON",
|
||||||
configureAxios().post(`/polochons/${id}`, params),
|
configureAxios().post(`/polochons/${id}`, params),
|
||||||
[
|
[() => getPolochons(), () => getManagedPolochons()]
|
||||||
() => getPolochons(),
|
);
|
||||||
() => getManagedPolochons(),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
export const deletePolochon = (id) => request(
|
export const deletePolochon = id =>
|
||||||
"DELETE_POLOCHON",
|
request("DELETE_POLOCHON", configureAxios().delete(`/polochons/${id}`), [
|
||||||
configureAxios().delete(`/polochons/${id}`),
|
|
||||||
[
|
|
||||||
() => getPolochons(),
|
() => getPolochons(),
|
||||||
() => getManagedPolochons(),
|
() => getManagedPolochons()
|
||||||
],
|
]);
|
||||||
)
|
|
||||||
|
|
||||||
export const editPolochonUser = ({ polochonId, id, ...params }) => request(
|
export const editPolochonUser = ({ polochonId, id, ...params }) =>
|
||||||
|
request(
|
||||||
"EDIT_POLOCHON_USER",
|
"EDIT_POLOCHON_USER",
|
||||||
configureAxios().post(`/polochons/${polochonId}/users/${id}`, params),
|
configureAxios().post(`/polochons/${polochonId}/users/${id}`, params),
|
||||||
[
|
[() => getPolochons(), () => getManagedPolochons(), () => getUserInfos()]
|
||||||
() => getPolochons(),
|
);
|
||||||
() => getManagedPolochons(),
|
|
||||||
() => getUserInfos(),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
import { configureAxios, request } from "../requests"
|
import { configureAxios, request } from "../requests";
|
||||||
|
|
||||||
import { prettyEpisodeName } from "../utils"
|
import { prettyEpisodeName } from "../utils";
|
||||||
import { sendNotification } from "./notifications"
|
import { sendNotification } from "./notifications";
|
||||||
|
|
||||||
export function fetchShows(url) {
|
export function fetchShows(url) {
|
||||||
return request(
|
return request("SHOW_LIST_FETCH", configureAxios().get(url), [
|
||||||
"SHOW_LIST_FETCH",
|
updateLastShowsFetchUrl(url)
|
||||||
configureAxios().get(url),
|
]);
|
||||||
[
|
|
||||||
updateLastShowsFetchUrl(url),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShowDetails(imdbId) {
|
export function getShowDetails(imdbId) {
|
||||||
@ -19,21 +15,22 @@ export function getShowDetails(imdbId) {
|
|||||||
configureAxios().post(`/shows/${imdbId}/refresh`),
|
configureAxios().post(`/shows/${imdbId}/refresh`),
|
||||||
null,
|
null,
|
||||||
{ imdbId }
|
{ imdbId }
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getEpisodeDetails(imdbId, season, episode) {
|
export function getEpisodeDetails(imdbId, season, episode) {
|
||||||
return request(
|
return request(
|
||||||
"EPISODE_GET_DETAILS",
|
"EPISODE_GET_DETAILS",
|
||||||
configureAxios().post(`/shows/${imdbId}/seasons/${season}/episodes/${episode}`),
|
configureAxios().post(
|
||||||
|
`/shows/${imdbId}/seasons/${season}/episodes/${episode}`
|
||||||
|
),
|
||||||
null,
|
null,
|
||||||
{
|
{
|
||||||
imdbId,
|
imdbId,
|
||||||
season,
|
season,
|
||||||
episode,
|
episode
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchShowDetails(imdbId) {
|
export function fetchShowDetails(imdbId) {
|
||||||
@ -42,7 +39,7 @@ export function fetchShowDetails(imdbId) {
|
|||||||
configureAxios().get(`/shows/${imdbId}`),
|
configureAxios().get(`/shows/${imdbId}`),
|
||||||
null,
|
null,
|
||||||
{ imdbId }
|
{ imdbId }
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addShowToWishlist(imdbId, season = null, episode = null) {
|
export function addShowToWishlist(imdbId, season = null, episode = null) {
|
||||||
@ -50,25 +47,26 @@ export function addShowToWishlist(imdbId, season = null, episode = null) {
|
|||||||
"SHOW_ADD_TO_WISHLIST",
|
"SHOW_ADD_TO_WISHLIST",
|
||||||
configureAxios().post(`/wishlist/shows/${imdbId}`, {
|
configureAxios().post(`/wishlist/shows/${imdbId}`, {
|
||||||
season: season,
|
season: season,
|
||||||
episode: episode,
|
episode: episode
|
||||||
}),
|
}),
|
||||||
[
|
[updateShowWishlistStore(imdbId, true, season, episode)]
|
||||||
updateShowWishlistStore(imdbId, true, season, episode),
|
);
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteShowFromWishlist(imdbId) {
|
export function deleteShowFromWishlist(imdbId) {
|
||||||
return request(
|
return request(
|
||||||
"SHOW_DELETE_FROM_WISHLIST",
|
"SHOW_DELETE_FROM_WISHLIST",
|
||||||
configureAxios().delete(`/wishlist/shows/${imdbId}`),
|
configureAxios().delete(`/wishlist/shows/${imdbId}`),
|
||||||
[
|
[updateShowWishlistStore(imdbId, false)]
|
||||||
updateShowWishlistStore(imdbId, false),
|
);
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showWishlistToggle(currentState, imdbId, season = 0, episode = 0) {
|
export function showWishlistToggle(
|
||||||
|
currentState,
|
||||||
|
imdbId,
|
||||||
|
season = 0,
|
||||||
|
episode = 0
|
||||||
|
) {
|
||||||
if (currentState === true) {
|
if (currentState === true) {
|
||||||
return deleteShowFromWishlist(imdbId);
|
return deleteShowFromWishlist(imdbId);
|
||||||
} else {
|
} else {
|
||||||
@ -76,60 +74,70 @@ export function showWishlistToggle(currentState, imdbId, season = 0, episode = 0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateShowWishlistStore(imdbId, wishlisted, season = null, episode = null) {
|
export function updateShowWishlistStore(
|
||||||
|
imdbId,
|
||||||
|
wishlisted,
|
||||||
|
season = null,
|
||||||
|
episode = null
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
type: "SHOW_UPDATE_STORE_WISHLIST",
|
type: "SHOW_UPDATE_STORE_WISHLIST",
|
||||||
payload: {
|
payload: {
|
||||||
imdbId,
|
imdbId,
|
||||||
season,
|
season,
|
||||||
episode,
|
episode
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShowExploreOptions() {
|
export function getShowExploreOptions() {
|
||||||
return request(
|
return request(
|
||||||
"SHOW_GET_EXPLORE_OPTIONS",
|
"SHOW_GET_EXPLORE_OPTIONS",
|
||||||
configureAxios().get("/shows/explore/options")
|
configureAxios().get("/shows/explore/options")
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectShow(imdbId) {
|
export function selectShow(imdbId) {
|
||||||
return {
|
return {
|
||||||
type: "SELECT_SHOW",
|
type: "SELECT_SHOW",
|
||||||
payload: {
|
payload: {
|
||||||
imdbId,
|
imdbId
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateFilter(filter) {
|
export function updateFilter(filter) {
|
||||||
return {
|
return {
|
||||||
type: "SHOWS_UPDATE_FILTER",
|
type: "SHOWS_UPDATE_FILTER",
|
||||||
payload: {
|
payload: {
|
||||||
filter,
|
filter
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function updateLastShowsFetchUrl(url) {
|
export function updateLastShowsFetchUrl(url) {
|
||||||
return {
|
return {
|
||||||
type: "UPDATE_LAST_SHOWS_FETCH_URL",
|
type: "UPDATE_LAST_SHOWS_FETCH_URL",
|
||||||
payload: {
|
payload: {
|
||||||
url: url,
|
url: url
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const newEpisodeEvent = (data) => {
|
export const newEpisodeEvent = data => {
|
||||||
return (dispatch) => {
|
return dispatch => {
|
||||||
dispatch(sendNotification({
|
dispatch(
|
||||||
|
sendNotification({
|
||||||
icon: "video-camera",
|
icon: "video-camera",
|
||||||
autohide: true,
|
autohide: true,
|
||||||
delay: 10000,
|
delay: 10000,
|
||||||
title: `${prettyEpisodeName(data.show_title, data.season, data.episode)} added to the library`,
|
title: `${prettyEpisodeName(
|
||||||
imageUrl: `img/shows/${data.show_imdb_id}-poster.jpg`,
|
data.show_title,
|
||||||
}));
|
data.season,
|
||||||
}
|
data.episode
|
||||||
}
|
)} added to the library`,
|
||||||
|
imageUrl: `img/shows/${data.show_imdb_id}-poster.jpg`
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { configureAxios, request } from "../requests"
|
import { configureAxios, request } from "../requests";
|
||||||
|
|
||||||
export const searchMovieSubtitles = (imdbId) => {
|
export const searchMovieSubtitles = imdbId => {
|
||||||
return request(
|
return request(
|
||||||
"MOVIE_SUBTITLES_UPDATE",
|
"MOVIE_SUBTITLES_UPDATE",
|
||||||
configureAxios().post(`/movies/${imdbId}/subtitles/refresh`),
|
configureAxios().post(`/movies/${imdbId}/subtitles/refresh`),
|
||||||
null,
|
null,
|
||||||
{ imdbId: imdbId },
|
{ imdbId: imdbId }
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export const searchEpisodeSubtitles = (imdbId, season, episode) => {
|
export const searchEpisodeSubtitles = (imdbId, season, episode) => {
|
||||||
const url = `/shows/${imdbId}/seasons/${season}/episodes/${episode}`;
|
const url = `/shows/${imdbId}/seasons/${season}/episodes/${episode}`;
|
||||||
@ -18,7 +18,7 @@ export const searchEpisodeSubtitles = (imdbId, season, episode) => {
|
|||||||
{
|
{
|
||||||
imdbId: imdbId,
|
imdbId: imdbId,
|
||||||
season: season,
|
season: season,
|
||||||
episode: episode,
|
episode: episode
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
@ -1,41 +1,29 @@
|
|||||||
import { configureAxios, request } from "../requests"
|
import { configureAxios, request } from "../requests";
|
||||||
|
|
||||||
import { addAlertOk } from "./alerts"
|
import { addAlertOk } from "./alerts";
|
||||||
|
|
||||||
export function addTorrent(url) {
|
export function addTorrent(url) {
|
||||||
return request(
|
return request(
|
||||||
"ADD_TORRENT",
|
"ADD_TORRENT",
|
||||||
configureAxios().post("/torrents", {
|
configureAxios().post("/torrents", {
|
||||||
url: url,
|
url: url
|
||||||
}),
|
}),
|
||||||
[
|
[addAlertOk("Torrent added")]
|
||||||
addAlertOk("Torrent added"),
|
);
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeTorrent(id) {
|
export function removeTorrent(id) {
|
||||||
return request(
|
return request("REMOVE_TORRENT", configureAxios().delete(`/torrents/${id}`), [
|
||||||
"REMOVE_TORRENT",
|
() => fetchTorrents()
|
||||||
configureAxios().delete(`/torrents/${id}`),
|
]);
|
||||||
[
|
|
||||||
() => fetchTorrents(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchTorrents() {
|
export function fetchTorrents() {
|
||||||
return request(
|
return request("TORRENTS_FETCH", configureAxios().get("/torrents"));
|
||||||
"TORRENTS_FETCH",
|
|
||||||
configureAxios().get("/torrents")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function searchTorrents(url) {
|
export function searchTorrents(url) {
|
||||||
return request(
|
return request("TORRENTS_SEARCH", configureAxios().get(url));
|
||||||
"TORRENTS_SEARCH",
|
|
||||||
configureAxios().get(url)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setFetchedTorrents(torrents) {
|
export function setFetchedTorrents(torrents) {
|
||||||
@ -43,8 +31,8 @@ export function setFetchedTorrents(torrents) {
|
|||||||
type: "TORRENTS_FETCH_FULFILLED",
|
type: "TORRENTS_FETCH_FULFILLED",
|
||||||
payload: {
|
payload: {
|
||||||
response: {
|
response: {
|
||||||
data : torrents,
|
data: torrents
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,40 +1,31 @@
|
|||||||
import { configureAxios, request } from "../requests"
|
import { configureAxios, request } from "../requests";
|
||||||
|
|
||||||
import { addAlertOk, dismissAlert } from "./alerts"
|
import { addAlertOk, dismissAlert } from "./alerts";
|
||||||
|
|
||||||
import { getManagedPolochons } from "./polochon"
|
import { getManagedPolochons } from "./polochon";
|
||||||
|
|
||||||
export function userLogout() {
|
export function userLogout() {
|
||||||
return {
|
return {
|
||||||
type: "USER_LOGOUT",
|
type: "USER_LOGOUT"
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loginUser(username, password) {
|
export function loginUser(username, password) {
|
||||||
return request(
|
return request(
|
||||||
"USER_LOGIN",
|
"USER_LOGIN",
|
||||||
configureAxios().post(
|
configureAxios().post("/users/login", {
|
||||||
"/users/login",
|
|
||||||
{
|
|
||||||
username: username.trim(),
|
username: username.trim(),
|
||||||
password: password,
|
password: password
|
||||||
},
|
}),
|
||||||
),
|
[() => dismissAlert()]
|
||||||
[
|
);
|
||||||
() => dismissAlert(),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateUser(config) {
|
export function updateUser(config) {
|
||||||
return request(
|
return request("USER_UPDATE", configureAxios().post("/users/edit", config), [
|
||||||
"USER_UPDATE",
|
|
||||||
configureAxios().post("/users/edit", config),
|
|
||||||
[
|
|
||||||
addAlertOk("User updated"),
|
addAlertOk("User updated"),
|
||||||
() => getManagedPolochons(),
|
() => getManagedPolochons()
|
||||||
],
|
]);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function userSignUp(config) {
|
export function userSignUp(config) {
|
||||||
@ -44,48 +35,39 @@ export function userSignUp(config) {
|
|||||||
|
|
||||||
return request(
|
return request(
|
||||||
"USER_SIGNUP",
|
"USER_SIGNUP",
|
||||||
configureAxios().post("/users/signup", config), [
|
configureAxios().post("/users/signup", config),
|
||||||
() => loginUser(config.username, config.password),
|
[() => loginUser(config.username, config.password)]
|
||||||
],
|
);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserInfos() {
|
export function getUserInfos() {
|
||||||
return request(
|
return request("GET_USER", configureAxios().get("/users/details"));
|
||||||
"GET_USER",
|
|
||||||
configureAxios().get("/users/details")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setUserToken(token) {
|
export function setUserToken(token) {
|
||||||
return {
|
return {
|
||||||
type: "USER_SET_TOKEN",
|
type: "USER_SET_TOKEN",
|
||||||
payload: {
|
payload: {
|
||||||
token: token,
|
token: token
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserTokens() {
|
export function getUserTokens() {
|
||||||
return request(
|
return request("GET_USER_TOKENS", configureAxios().get("/users/tokens"));
|
||||||
"GET_USER_TOKENS",
|
|
||||||
configureAxios().get("/users/tokens")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteUserToken(token) {
|
export function deleteUserToken(token) {
|
||||||
return request(
|
return request(
|
||||||
"DELETE_USER_TOKEN",
|
"DELETE_USER_TOKEN",
|
||||||
configureAxios().delete(`/users/tokens/${token}`),
|
configureAxios().delete(`/users/tokens/${token}`),
|
||||||
[
|
[() => getUserTokens()]
|
||||||
() => getUserTokens(),
|
);
|
||||||
]
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getUserModules() {
|
export function getUserModules() {
|
||||||
return request(
|
return request(
|
||||||
"GET_USER_MODULES",
|
"GET_USER_MODULES",
|
||||||
configureAxios().get("/users/modules/status")
|
configureAxios().get("/users/modules/status")
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,47 +1,47 @@
|
|||||||
// Import default image
|
// Import default image
|
||||||
import "../img/noimage.png"
|
import "../img/noimage.png";
|
||||||
|
|
||||||
// Import favicon settings
|
// Import favicon settings
|
||||||
import "../img/favicon-16x16.png"
|
import "../img/favicon-16x16.png";
|
||||||
import "../img/favicon-32x32.png"
|
import "../img/favicon-32x32.png";
|
||||||
import "../img/favicon.ico"
|
import "../img/favicon.ico";
|
||||||
import "../img/safari-pinned-tab.svg"
|
import "../img/safari-pinned-tab.svg";
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
import "../scss/app.scss"
|
import "../scss/app.scss";
|
||||||
|
|
||||||
// React
|
// React
|
||||||
import React from "react"
|
import React from "react";
|
||||||
import ReactDOM from "react-dom"
|
import ReactDOM from "react-dom";
|
||||||
import { Provider } from "react-redux"
|
import { Provider } from "react-redux";
|
||||||
import { Router, Route, Switch, Redirect } from "react-router-dom"
|
import { Router, Route, Switch, Redirect } from "react-router-dom";
|
||||||
import Container from "react-bootstrap/Container"
|
import Container from "react-bootstrap/Container";
|
||||||
|
|
||||||
// Auth
|
// Auth
|
||||||
import { ProtectedRoute, AdminRoute } from "./auth"
|
import { ProtectedRoute, AdminRoute } from "./auth";
|
||||||
|
|
||||||
// Store
|
// Store
|
||||||
import store, { history } from "./store"
|
import store, { history } from "./store";
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { AdminPanel } from "./components/admins/panel"
|
import { AdminPanel } from "./components/admins/panel";
|
||||||
import { Notifications } from "./components/notifications/notifications"
|
import { Notifications } from "./components/notifications/notifications";
|
||||||
import Alert from "./components/alerts/alert"
|
import Alert from "./components/alerts/alert";
|
||||||
import MovieList from "./components/movies/list"
|
import MovieList from "./components/movies/list";
|
||||||
import MoviesRoute from "./components/movies/route"
|
import MoviesRoute from "./components/movies/route";
|
||||||
import NavBar from "./components/navbar"
|
import NavBar from "./components/navbar";
|
||||||
import WsHandler from "./components/websocket"
|
import WsHandler from "./components/websocket";
|
||||||
import { ShowDetails } from "./components/shows/details"
|
import { ShowDetails } from "./components/shows/details";
|
||||||
import ShowList from "./components/shows/list"
|
import ShowList from "./components/shows/list";
|
||||||
import ShowsRoute from "./components/shows/route"
|
import ShowsRoute from "./components/shows/route";
|
||||||
import TorrentList from "./components/torrents/list"
|
import TorrentList from "./components/torrents/list";
|
||||||
import TorrentSearch from "./components/torrents/search"
|
import TorrentSearch from "./components/torrents/search";
|
||||||
import UserActivation from "./components/users/activation"
|
import UserActivation from "./components/users/activation";
|
||||||
import UserLoginForm from "./components/users/login"
|
import UserLoginForm from "./components/users/login";
|
||||||
import UserLogout from "./components/users/logout"
|
import UserLogout from "./components/users/logout";
|
||||||
import UserProfile from "./components/users/profile"
|
import UserProfile from "./components/users/profile";
|
||||||
import UserSignUp from "./components/users/signup"
|
import UserSignUp from "./components/users/signup";
|
||||||
import UserTokens from "./components/users/tokens"
|
import UserTokens from "./components/users/tokens";
|
||||||
|
|
||||||
const App = () => (
|
const App = () => (
|
||||||
<div>
|
<div>
|
||||||
@ -56,25 +56,43 @@ const App = () => (
|
|||||||
<Route path="/users/tokens" exact component={UserTokens} />
|
<Route path="/users/tokens" exact component={UserTokens} />
|
||||||
<Route path="/torrents/list" exact component={TorrentList} />
|
<Route path="/torrents/list" exact component={TorrentList} />
|
||||||
<Route path="/torrents/search" exact component={TorrentSearch} />
|
<Route path="/torrents/search" exact component={TorrentSearch} />
|
||||||
<Route path="/torrents/search/:type/:search" exact component={TorrentSearch} />
|
<Route
|
||||||
|
path="/torrents/search/:type/:search"
|
||||||
|
exact
|
||||||
|
component={TorrentSearch}
|
||||||
|
/>
|
||||||
<MoviesRoute path="/movies/polochon" exact component={MovieList} />
|
<MoviesRoute path="/movies/polochon" exact component={MovieList} />
|
||||||
<MoviesRoute path="/movies/wishlist" exact component={MovieList} />
|
<MoviesRoute path="/movies/wishlist" exact component={MovieList} />
|
||||||
<MoviesRoute path="/movies/search/:search" exact component={MovieList} />
|
<MoviesRoute
|
||||||
<MoviesRoute path="/movies/explore/:source/:category" exact component={MovieList} />
|
path="/movies/search/:search"
|
||||||
|
exact
|
||||||
|
component={MovieList}
|
||||||
|
/>
|
||||||
|
<MoviesRoute
|
||||||
|
path="/movies/explore/:source/:category"
|
||||||
|
exact
|
||||||
|
component={MovieList}
|
||||||
|
/>
|
||||||
<ShowsRoute path="/shows/polochon" exact component={ShowList} />
|
<ShowsRoute path="/shows/polochon" exact component={ShowList} />
|
||||||
<ShowsRoute path="/shows/wishlist" exact component={ShowList} />
|
<ShowsRoute path="/shows/wishlist" exact component={ShowList} />
|
||||||
<ShowsRoute path="/shows/search/:search" exact component={ShowList} />
|
<ShowsRoute path="/shows/search/:search" exact component={ShowList} />
|
||||||
<ShowsRoute path="/shows/explore/:source/:category" exact component={ShowList} />
|
<ShowsRoute
|
||||||
<ShowsRoute path="/shows/details/:imdbId" exact component={ShowDetails} />
|
path="/shows/explore/:source/:category"
|
||||||
<Route render={() =>
|
exact
|
||||||
<Redirect to="/movies/explore/yts/seeds" />
|
component={ShowList}
|
||||||
}/>
|
/>
|
||||||
|
<ShowsRoute
|
||||||
|
path="/shows/details/:imdbId"
|
||||||
|
exact
|
||||||
|
component={ShowDetails}
|
||||||
|
/>
|
||||||
|
<Route render={() => <Redirect to="/movies/explore/yts/seeds" />} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
ReactDOM.render((
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<Router history={history}>
|
<Router history={history}>
|
||||||
<Switch>
|
<Switch>
|
||||||
@ -85,5 +103,6 @@ ReactDOM.render((
|
|||||||
<ProtectedRoute path="*" component={App} />
|
<ProtectedRoute path="*" component={App} />
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
</Provider>
|
</Provider>,
|
||||||
),document.getElementById("app"));
|
document.getElementById("app")
|
||||||
|
);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { Route, Redirect } from "react-router-dom"
|
import { Route, Redirect } from "react-router-dom";
|
||||||
import { setUserToken } from "./actions/users"
|
import { setUserToken } from "./actions/users";
|
||||||
|
|
||||||
const protectedRoute = ({
|
const protectedRoute = ({
|
||||||
component: Component,
|
component: Component,
|
||||||
@ -22,59 +22,64 @@ const protectedRoute = ({
|
|||||||
if (!isTokenSet) {
|
if (!isTokenSet) {
|
||||||
setUserToken(token);
|
setUserToken(token);
|
||||||
}
|
}
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Route {...otherProps} render={(props) => {
|
<Route
|
||||||
|
{...otherProps}
|
||||||
|
render={props => {
|
||||||
if (isAuthenticated()) {
|
if (isAuthenticated()) {
|
||||||
if (isActivated) {
|
if (isActivated) {
|
||||||
return <Component {...props} />
|
return <Component {...props} />;
|
||||||
} else {
|
} else {
|
||||||
return <Redirect to="/users/activation" />
|
return <Redirect to="/users/activation" />;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return <Redirect to="/users/login" />
|
return <Redirect to="/users/login" />;
|
||||||
}
|
}
|
||||||
}} />
|
}}
|
||||||
)
|
/>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
protectedRoute.propTypes = {
|
protectedRoute.propTypes = {
|
||||||
component: PropTypes.func,
|
component: PropTypes.func,
|
||||||
isLogged: PropTypes.bool.isRequired,
|
isLogged: PropTypes.bool.isRequired,
|
||||||
isActivated: PropTypes.bool.isRequired,
|
isActivated: PropTypes.bool.isRequired,
|
||||||
isTokenSet: PropTypes.bool.isRequired,
|
isTokenSet: PropTypes.bool.isRequired,
|
||||||
setUserToken: PropTypes.func.isRequired,
|
setUserToken: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
export const ProtectedRoute = connect((state) => ({
|
export const ProtectedRoute = connect(
|
||||||
|
state => ({
|
||||||
isLogged: state.userStore.get("isLogged"),
|
isLogged: state.userStore.get("isLogged"),
|
||||||
isAdmin: state.userStore.get("isLogged"),
|
isAdmin: state.userStore.get("isLogged"),
|
||||||
isActivated: state.userStore.get("isActivated"),
|
isActivated: state.userStore.get("isActivated"),
|
||||||
isTokenSet: state.userStore.get("isTokenSet"),
|
isTokenSet: state.userStore.get("isTokenSet")
|
||||||
}), { setUserToken })(protectedRoute);
|
}),
|
||||||
|
{ setUserToken }
|
||||||
|
)(protectedRoute);
|
||||||
|
|
||||||
const adminRoute = ({
|
const adminRoute = ({ component: Component, isAdmin, ...otherProps }) => {
|
||||||
component: Component,
|
|
||||||
isAdmin,
|
|
||||||
...otherProps
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<Route {...otherProps} render={(props) => {
|
<Route
|
||||||
|
{...otherProps}
|
||||||
|
render={props => {
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
return <Component {...props} />
|
return <Component {...props} />;
|
||||||
} else {
|
} else {
|
||||||
return <Redirect to="/" />
|
return <Redirect to="/" />;
|
||||||
}
|
}
|
||||||
}} />
|
}}
|
||||||
)
|
/>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
adminRoute.propTypes = {
|
adminRoute.propTypes = {
|
||||||
component: PropTypes.func,
|
component: PropTypes.func,
|
||||||
isAdmin: PropTypes.bool.isRequired,
|
isAdmin: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
export const AdminRoute = connect((state) => ({
|
export const AdminRoute = connect(state => ({
|
||||||
isAdmin: state.userStore.get("isLogged"),
|
isAdmin: state.userStore.get("isLogged")
|
||||||
}))(adminRoute);
|
}))(adminRoute);
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { getAdminModules} from "../../actions/admins"
|
import { getAdminModules } from "../../actions/admins";
|
||||||
|
|
||||||
import Modules from "../modules/modules"
|
import Modules from "../modules/modules";
|
||||||
|
|
||||||
const AdminModulesConnected = ({ modules, loading, getAdminModules }) => {
|
const AdminModulesConnected = ({ modules, loading, getAdminModules }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getAdminModules();
|
getAdminModules();
|
||||||
}, [getAdminModules])
|
}, [getAdminModules]);
|
||||||
|
|
||||||
return (
|
return <Modules modules={modules} isLoading={loading} />;
|
||||||
<Modules modules={modules} isLoading={loading} />
|
};
|
||||||
)
|
|
||||||
}
|
|
||||||
AdminModulesConnected.propTypes = {
|
AdminModulesConnected.propTypes = {
|
||||||
modules: PropTypes.object,
|
modules: PropTypes.object,
|
||||||
loading: PropTypes.bool,
|
loading: PropTypes.bool,
|
||||||
getAdminModules: PropTypes.func.isRequired,
|
getAdminModules: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
loading: state.adminStore.get("fetchingModules"),
|
loading: state.adminStore.get("fetchingModules"),
|
||||||
modules: state.adminStore.get("modules"),
|
modules: state.adminStore.get("modules")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const AdminModules = connect(mapStateToProps, {getAdminModules})(AdminModulesConnected);
|
export const AdminModules = connect(mapStateToProps, { getAdminModules })(
|
||||||
|
AdminModulesConnected
|
||||||
|
);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
|
|
||||||
import { UserList } from "./userList"
|
import { UserList } from "./userList";
|
||||||
import { Stats } from "./stats"
|
import { Stats } from "./stats";
|
||||||
import { AdminModules } from "./modules"
|
import { AdminModules } from "./modules";
|
||||||
|
|
||||||
export const AdminPanel = () => (
|
export const AdminPanel = () => (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@ -10,4 +10,4 @@ export const AdminPanel = () => (
|
|||||||
<UserList />
|
<UserList />
|
||||||
<AdminModules />
|
<AdminModules />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import { TorrentsStat } from "./torrentsStat"
|
import { TorrentsStat } from "./torrentsStat";
|
||||||
|
|
||||||
export const Stat = ({ name, count, torrentCount, torrentCountById }) => (
|
export const Stat = ({ name, count, torrentCount, torrentCountById }) => (
|
||||||
<div className="col-12 col-md-4 my-2">
|
<div className="col-12 col-md-4 my-2">
|
||||||
@ -9,7 +9,9 @@ export const Stat = ({ name, count, torrentCount, torrentCountById }) => (
|
|||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
<h3>
|
<h3>
|
||||||
{name}
|
{name}
|
||||||
<span className="badge badge-pill badge-info pull-right">{count}</span>
|
<span className="badge badge-pill badge-info pull-right">
|
||||||
|
{count}
|
||||||
|
</span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
@ -21,10 +23,10 @@ export const Stat = ({ name, count, torrentCount, torrentCountById }) => (
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
Stat.propTypes = {
|
Stat.propTypes = {
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
count: PropTypes.number,
|
count: PropTypes.number,
|
||||||
torrentCount: PropTypes.number,
|
torrentCount: PropTypes.number,
|
||||||
torrentCountById: PropTypes.number,
|
torrentCountById: PropTypes.number
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { Stat } from "./stat"
|
import { Stat } from "./stat";
|
||||||
|
|
||||||
import { getStats } from "../../actions/admins"
|
import { getStats } from "../../actions/admins";
|
||||||
|
|
||||||
const StatsConnected = ({ stats, getStats }) => {
|
const StatsConnected = ({ stats, getStats }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getStats();
|
getStats();
|
||||||
}, [getStats])
|
}, [getStats]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row d-flex flex-wrap">
|
<div className="row d-flex flex-wrap">
|
||||||
@ -32,15 +32,15 @@ const StatsConnected = ({ stats, getStats }) => {
|
|||||||
torrentCountById={stats.get("episodes_torrents_count_by_id")}
|
torrentCountById={stats.get("episodes_torrents_count_by_id")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
StatsConnected.propTypes = {
|
StatsConnected.propTypes = {
|
||||||
stats: PropTypes.object,
|
stats: PropTypes.object,
|
||||||
getStats: PropTypes.func,
|
getStats: PropTypes.func
|
||||||
}
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
stats: state.adminStore.get("stats"),
|
stats: state.adminStore.get("stats")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Stats = connect(mapStateToProps, {getStats})(StatsConnected);
|
export const Stats = connect(mapStateToProps, { getStats })(StatsConnected);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const TorrentsStat = ({ count, torrentCount, torrentCountById }) => {
|
export const TorrentsStat = ({ count, torrentCount, torrentCountById }) => {
|
||||||
if (torrentCount === undefined) {
|
if (torrentCount === undefined) {
|
||||||
return (<span>No torrents</span>);
|
return <span>No torrents</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const percentage = Math.floor((torrentCountById * 100) / count);
|
const percentage = Math.floor((torrentCountById * 100) / count);
|
||||||
@ -13,9 +13,9 @@ export const TorrentsStat = ({ count, torrentCount, torrentCountById }) => {
|
|||||||
<small> - {torrentCount} total</small>
|
<small> - {torrentCount} total</small>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
TorrentsStat.propTypes = {
|
TorrentsStat.propTypes = {
|
||||||
count: PropTypes.number,
|
count: PropTypes.number,
|
||||||
torrentCount: PropTypes.number,
|
torrentCount: PropTypes.number,
|
||||||
torrentCountById: PropTypes.number,
|
torrentCountById: PropTypes.number
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import { UserEdit } from "./userEdit"
|
import { UserEdit } from "./userEdit";
|
||||||
|
|
||||||
export const User = ({
|
export const User = ({
|
||||||
id,
|
id,
|
||||||
@ -12,27 +12,31 @@ export const User = ({
|
|||||||
polochonUrl,
|
polochonUrl,
|
||||||
polochonName,
|
polochonName,
|
||||||
polochonId,
|
polochonId,
|
||||||
token,
|
token
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td>{id}</td>
|
<td>{id}</td>
|
||||||
<td>{name}</td>
|
<td>{name}</td>
|
||||||
<td>
|
<td>
|
||||||
<span className={activated ? "fa fa-check" : "fa fa-times text-danger"}></span>
|
<span
|
||||||
|
className={activated ? "fa fa-check" : "fa fa-times text-danger"}
|
||||||
|
></span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span className={admin ? "fa fa-check" : "fa fa-times"}></span>
|
<span className={admin ? "fa fa-check" : "fa fa-times"}></span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{polochonName !== "" ? polochonName : "-"}
|
{polochonName !== "" ? polochonName : "-"}
|
||||||
{polochonUrl !== "" &&
|
{polochonUrl !== "" && <small className="ml-1">({polochonUrl})</small>}
|
||||||
<small className="ml-1">({polochonUrl})</small>
|
|
||||||
}
|
|
||||||
</td>
|
</td>
|
||||||
<td>{token}</td>
|
<td>{token}</td>
|
||||||
<td>
|
<td>
|
||||||
<span className={polochonActivated ? "fa fa-check" : "fa fa-times text-danger"}></span>
|
<span
|
||||||
|
className={
|
||||||
|
polochonActivated ? "fa fa-check" : "fa fa-times text-danger"
|
||||||
|
}
|
||||||
|
></span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<UserEdit
|
<UserEdit
|
||||||
@ -47,7 +51,7 @@ export const User = ({
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
User.propTypes = {
|
User.propTypes = {
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
@ -57,5 +61,5 @@ User.propTypes = {
|
|||||||
token: PropTypes.string,
|
token: PropTypes.string,
|
||||||
admin: PropTypes.bool,
|
admin: PropTypes.bool,
|
||||||
activated: PropTypes.bool,
|
activated: PropTypes.bool,
|
||||||
polochonActivated: PropTypes.bool,
|
polochonActivated: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
|
|
||||||
import { updateUser, deleteUser } from "../../actions/admins"
|
import { updateUser, deleteUser } from "../../actions/admins";
|
||||||
|
|
||||||
import Toggle from "react-bootstrap-toggle";
|
import Toggle from "react-bootstrap-toggle";
|
||||||
|
|
||||||
import { PolochonSelect } from "../polochons/select"
|
import { PolochonSelect } from "../polochons/select";
|
||||||
import { FormModal } from "../forms/modal"
|
import { FormModal } from "../forms/modal";
|
||||||
import { FormInput } from "../forms/input"
|
import { FormInput } from "../forms/input";
|
||||||
|
|
||||||
const UserEditConnect = ({
|
const UserEditConnect = ({
|
||||||
id,
|
id,
|
||||||
@ -21,19 +21,23 @@ const UserEditConnect = ({
|
|||||||
polochonActivated: initPolochonActivated,
|
polochonActivated: initPolochonActivated,
|
||||||
updateUser,
|
updateUser,
|
||||||
deleteUser,
|
deleteUser,
|
||||||
publicPolochons,
|
publicPolochons
|
||||||
}) => {
|
}) => {
|
||||||
const [modal, setModal] = useState(false);
|
const [modal, setModal] = useState(false);
|
||||||
const [admin, setAdmin] = useState(initAdmin);
|
const [admin, setAdmin] = useState(initAdmin);
|
||||||
const [activated, setActivated] = useState(initActivated);
|
const [activated, setActivated] = useState(initActivated);
|
||||||
const [token, setToken] = useState(polochonToken);
|
const [token, setToken] = useState(polochonToken);
|
||||||
const [polochonId, setPolochonId] = useState(initPolochonId);
|
const [polochonId, setPolochonId] = useState(initPolochonId);
|
||||||
const [polochonActivated, setPolochonActivated] = useState(initPolochonActivated);
|
const [polochonActivated, setPolochonActivated] = useState(
|
||||||
|
initPolochonActivated
|
||||||
|
);
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [confirmDelete, setConfirmDelete] = useState(false);
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = e => {
|
||||||
if (e) { e.preventDefault(); }
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
updateUser({
|
updateUser({
|
||||||
userId: id,
|
userId: id,
|
||||||
polochonToken: token,
|
polochonToken: token,
|
||||||
@ -41,26 +45,26 @@ const UserEditConnect = ({
|
|||||||
activated,
|
activated,
|
||||||
polochonId,
|
polochonId,
|
||||||
polochonActivated,
|
polochonActivated,
|
||||||
password,
|
password
|
||||||
});
|
});
|
||||||
setModal(false);
|
setModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteUser = (e) => {
|
const handleDeleteUser = e => {
|
||||||
if (e) { e.preventDefault(); }
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
if (confirmDelete) {
|
if (confirmDelete) {
|
||||||
deleteUser(name)
|
deleteUser(name);
|
||||||
setModal(false)
|
setModal(false);
|
||||||
} else {
|
} else {
|
||||||
setConfirmDelete(true)
|
setConfirmDelete(true);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<i
|
<i className="fa fa-pencil clickable" onClick={() => setModal(true)} />
|
||||||
className="fa fa-pencil clickable"
|
|
||||||
onClick={() => setModal(true)} />
|
|
||||||
|
|
||||||
<FormModal
|
<FormModal
|
||||||
show={modal}
|
show={modal}
|
||||||
@ -69,28 +73,58 @@ const UserEditConnect = ({
|
|||||||
icon="edit"
|
icon="edit"
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Account status</label>
|
<label>Account status</label>
|
||||||
<Toggle className="pull-right" on="Activated" off="Deactivated" active={activated}
|
<Toggle
|
||||||
offstyle="danger" handlestyle="secondary" onClick={() => setActivated(!activated)}
|
className="pull-right"
|
||||||
|
on="Activated"
|
||||||
|
off="Deactivated"
|
||||||
|
active={activated}
|
||||||
|
offstyle="danger"
|
||||||
|
handlestyle="secondary"
|
||||||
|
onClick={() => setActivated(!activated)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Admin status</label>
|
<label>Admin status</label>
|
||||||
<Toggle className="pull-right" on="Admin" off="User" active={admin}
|
<Toggle
|
||||||
offstyle="info" handlestyle="secondary" onClick={() => setAdmin(!admin)} />
|
className="pull-right"
|
||||||
|
on="Admin"
|
||||||
|
off="User"
|
||||||
|
active={admin}
|
||||||
|
offstyle="info"
|
||||||
|
handlestyle="secondary"
|
||||||
|
onClick={() => setAdmin(!admin)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FormInput label="Password" value={password} updateValue={setPassword} />
|
<FormInput
|
||||||
|
label="Password"
|
||||||
|
value={password}
|
||||||
|
updateValue={setPassword}
|
||||||
|
/>
|
||||||
|
|
||||||
<PolochonSelect value={polochonId} changeValue={setPolochonId} polochonList={publicPolochons} />
|
<PolochonSelect
|
||||||
<FormInput label="Polochon Token" value={token} updateValue={setToken} />
|
value={polochonId}
|
||||||
|
changeValue={setPolochonId}
|
||||||
|
polochonList={publicPolochons}
|
||||||
|
/>
|
||||||
|
<FormInput
|
||||||
|
label="Polochon Token"
|
||||||
|
value={token}
|
||||||
|
updateValue={setToken}
|
||||||
|
/>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Polochon activated</label>
|
<label>Polochon activated</label>
|
||||||
<Toggle className="pull-right" on="Activated" off="Deactivated" active={polochonActivated}
|
<Toggle
|
||||||
offstyle="danger" handlestyle="secondary" onClick={() => setPolochonActivated(!polochonActivated)}
|
className="pull-right"
|
||||||
|
on="Activated"
|
||||||
|
off="Deactivated"
|
||||||
|
active={polochonActivated}
|
||||||
|
offstyle="danger"
|
||||||
|
handlestyle="secondary"
|
||||||
|
onClick={() => setPolochonActivated(!polochonActivated)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -99,11 +133,10 @@ const UserEditConnect = ({
|
|||||||
{!confirmDelete ? "Delete user forever" : "Are you sure ?"}
|
{!confirmDelete ? "Delete user forever" : "Are you sure ?"}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</FormModal>
|
</FormModal>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
UserEditConnect.propTypes = {
|
UserEditConnect.propTypes = {
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
@ -114,11 +147,13 @@ UserEditConnect.propTypes = {
|
|||||||
polochonToken: PropTypes.string,
|
polochonToken: PropTypes.string,
|
||||||
polochonId: PropTypes.string,
|
polochonId: PropTypes.string,
|
||||||
polochonActivated: PropTypes.bool,
|
polochonActivated: PropTypes.bool,
|
||||||
publicPolochons: PropTypes.instanceOf(List),
|
publicPolochons: PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
publicPolochons: state.polochon.get("public"),
|
publicPolochons: state.polochon.get("public")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const UserEdit = connect(mapStateToProps, {updateUser, deleteUser})(UserEditConnect);
|
export const UserEdit = connect(mapStateToProps, { updateUser, deleteUser })(
|
||||||
|
UserEditConnect
|
||||||
|
);
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { User } from "./user"
|
import { User } from "./user";
|
||||||
|
|
||||||
import { getUsers } from "../../actions/admins"
|
import { getUsers } from "../../actions/admins";
|
||||||
import { getPolochons } from "../../actions/polochon"
|
import { getPolochons } from "../../actions/polochon";
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
users: state.adminStore.get("users"),
|
users: state.adminStore.get("users")
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = { getUsers, getPolochons };
|
const mapDispatchToProps = { getUsers, getPolochons };
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ const UserListConnect = ({ users, getUsers, getPolochons }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getUsers();
|
getUsers();
|
||||||
getPolochons();
|
getPolochons();
|
||||||
}, [getUsers, getPolochons])
|
}, [getUsers, getPolochons]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="table-responsive my-2">
|
<div className="table-responsive my-2">
|
||||||
@ -35,7 +35,7 @@ const UserListConnect = ({ users, getUsers, getPolochons }) => {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{users.map((el, index) =>
|
{users.map((el, index) => (
|
||||||
<User
|
<User
|
||||||
key={index}
|
key={index}
|
||||||
id={el.get("id")}
|
id={el.get("id")}
|
||||||
@ -48,7 +48,7 @@ const UserListConnect = ({ users, getUsers, getPolochons }) => {
|
|||||||
polochonUrl={el.getIn(["polochon", "url"], "")}
|
polochonUrl={el.getIn(["polochon", "url"], "")}
|
||||||
polochonName={el.getIn(["polochon", "name"], "")}
|
polochonName={el.getIn(["polochon", "name"], "")}
|
||||||
/>
|
/>
|
||||||
)}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@ -57,7 +57,10 @@ const UserListConnect = ({ users, getUsers, getPolochons }) => {
|
|||||||
UserListConnect.propTypes = {
|
UserListConnect.propTypes = {
|
||||||
getUsers: PropTypes.func,
|
getUsers: PropTypes.func,
|
||||||
getPolochons: PropTypes.func,
|
getPolochons: PropTypes.func,
|
||||||
users: PropTypes.PropTypes.instanceOf(List),
|
users: PropTypes.PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserList = connect(mapStateToProps, mapDispatchToProps)(UserListConnect);
|
export const UserList = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(UserListConnect);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
|
|
||||||
import { Button, Modal } from "react-bootstrap"
|
import { Button, Modal } from "react-bootstrap";
|
||||||
import Toggle from "react-bootstrap-toggle";
|
import Toggle from "react-bootstrap-toggle";
|
||||||
|
|
||||||
export const UserList = props => (
|
export const UserList = props => (
|
||||||
@ -20,15 +20,16 @@ export const UserList = props => (
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{props.users.map((el, index) =>
|
{props.users.map((el, index) => (
|
||||||
<User key={index} data={el} updateUser={props.updateUser}/>)}
|
<User key={index} data={el} updateUser={props.updateUser} />
|
||||||
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
UserList.propTypes = {
|
UserList.propTypes = {
|
||||||
users: PropTypes.PropTypes.instanceOf(List),
|
users: PropTypes.PropTypes.instanceOf(List),
|
||||||
updateUser: PropTypes.func,
|
updateUser: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
const User = function(props) {
|
const User = function(props) {
|
||||||
@ -39,8 +40,12 @@ const User = function(props) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{props.data.get("id")}</td>
|
<td>{props.data.get("id")}</td>
|
||||||
<td>{props.data.get("Name")}</td>
|
<td>{props.data.get("Name")}</td>
|
||||||
<td><UserActivationStatus activated={props.data.get("Activated")}/></td>
|
<td>
|
||||||
<td><UserAdminStatus admin={props.data.get("Admin")}/></td>
|
<UserActivationStatus activated={props.data.get("Activated")} />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<UserAdminStatus admin={props.data.get("Admin")} />
|
||||||
|
</td>
|
||||||
<td>{polochonUrl}</td>
|
<td>{polochonUrl}</td>
|
||||||
<td>{polochonToken}</td>
|
<td>{polochonToken}</td>
|
||||||
<td>
|
<td>
|
||||||
@ -53,10 +58,10 @@ const User = function(props) {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
User.propTypes = {
|
User.propTypes = {
|
||||||
data: PropTypes.object,
|
data: PropTypes.object,
|
||||||
updateUser: PropTypes.func,
|
updateUser: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserAdminStatus = props => (
|
const UserAdminStatus = props => (
|
||||||
@ -65,7 +70,9 @@ const UserAdminStatus = props => (
|
|||||||
UserAdminStatus.propTypes = { admin: PropTypes.bool.isRequired };
|
UserAdminStatus.propTypes = { admin: PropTypes.bool.isRequired };
|
||||||
|
|
||||||
const UserActivationStatus = props => (
|
const UserActivationStatus = props => (
|
||||||
<span className={props.activated ? "fa fa-check" : "fa fa-times text-danger"}></span>
|
<span
|
||||||
|
className={props.activated ? "fa fa-check" : "fa fa-times text-danger"}
|
||||||
|
></span>
|
||||||
);
|
);
|
||||||
UserActivationStatus.propTypes = { activated: PropTypes.bool.isRequired };
|
UserActivationStatus.propTypes = { activated: PropTypes.bool.isRequired };
|
||||||
|
|
||||||
@ -77,20 +84,25 @@ function UserEdit(props) {
|
|||||||
const [token, setToken] = useState(props.polochonToken);
|
const [token, setToken] = useState(props.polochonToken);
|
||||||
|
|
||||||
const handleSubmit = function(e) {
|
const handleSubmit = function(e) {
|
||||||
if (e) { e.preventDefault(); }
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
props.updateUser({
|
props.updateUser({
|
||||||
userId: props.data.get("id"),
|
userId: props.data.get("id"),
|
||||||
admin: admin,
|
admin: admin,
|
||||||
activated: activated,
|
activated: activated,
|
||||||
polochonUrl: url,
|
polochonUrl: url,
|
||||||
polochonToken: token,
|
polochonToken: token
|
||||||
});
|
});
|
||||||
setModal(false);
|
setModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<span className="fa fa-pencil clickable" onClick={() => setModal(true)}></span>
|
<span
|
||||||
|
className="fa fa-pencil clickable"
|
||||||
|
onClick={() => setModal(true)}
|
||||||
|
></span>
|
||||||
<Modal show={modal} onHide={() => setModal(false)}>
|
<Modal show={modal} onHide={() => setModal(false)}>
|
||||||
<Modal.Header closeButton>
|
<Modal.Header closeButton>
|
||||||
<Modal.Title>
|
<Modal.Title>
|
||||||
@ -99,30 +111,53 @@ function UserEdit(props) {
|
|||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body bsPrefix="modal-body admin-edit-user-modal">
|
<Modal.Body bsPrefix="modal-body admin-edit-user-modal">
|
||||||
<form className="form-horizontal" onSubmit={(ev) => handleSubmit(ev)}>
|
<form className="form-horizontal" onSubmit={ev => handleSubmit(ev)}>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Account status</label>
|
<label>Account status</label>
|
||||||
<Toggle className="pull-right" on="Activated" off="Deactivated" active={activated}
|
<Toggle
|
||||||
offstyle="danger" handlestyle="secondary" onClick={() => setActivated(!activated)}
|
className="pull-right"
|
||||||
|
on="Activated"
|
||||||
|
off="Deactivated"
|
||||||
|
active={activated}
|
||||||
|
offstyle="danger"
|
||||||
|
handlestyle="secondary"
|
||||||
|
onClick={() => setActivated(!activated)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label>Admin status</label>
|
<label>Admin status</label>
|
||||||
<Toggle className="pull-right" on="Admin" off="User" active={admin}
|
<Toggle
|
||||||
offstyle="info" handlestyle="secondary" onClick={() => setAdmin(!admin)} />
|
className="pull-right"
|
||||||
|
on="Admin"
|
||||||
|
off="User"
|
||||||
|
active={admin}
|
||||||
|
offstyle="info"
|
||||||
|
handlestyle="secondary"
|
||||||
|
onClick={() => setAdmin(!admin)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="control-label">Polochon URL</label>
|
<label className="control-label">Polochon URL</label>
|
||||||
<input className="form-control" value={url} onChange={(e) => setUrl(e.target.value)}/>
|
<input
|
||||||
|
className="form-control"
|
||||||
|
value={url}
|
||||||
|
onChange={e => setUrl(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="control-label">Polochon token</label>
|
<label className="control-label">Polochon token</label>
|
||||||
<input className="form-control" value={token} onChange={(e) => setToken(e.target.value)} />
|
<input
|
||||||
|
className="form-control"
|
||||||
|
value={token}
|
||||||
|
onChange={e => setToken(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<Button variant="success" onClick={handleSubmit}>Apply</Button>
|
<Button variant="success" onClick={handleSubmit}>
|
||||||
|
Apply
|
||||||
|
</Button>
|
||||||
<Button onClick={() => setModal(false)}>Close</Button>
|
<Button onClick={() => setModal(false)}>Close</Button>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Modal>
|
</Modal>
|
||||||
@ -133,5 +168,5 @@ UserEdit.propTypes = {
|
|||||||
data: PropTypes.object,
|
data: PropTypes.object,
|
||||||
updateUser: PropTypes.func,
|
updateUser: PropTypes.func,
|
||||||
polochonUrl: PropTypes.string,
|
polochonUrl: PropTypes.string,
|
||||||
polochonToken: PropTypes.string,
|
polochonToken: PropTypes.string
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import SweetAlert from "react-bootstrap-sweetalert";
|
import SweetAlert from "react-bootstrap-sweetalert";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { dismissAlert } from "../../actions/alerts"
|
import { dismissAlert } from "../../actions/alerts";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
show: state.alerts.get("show"),
|
show: state.alerts.get("show"),
|
||||||
title: state.alerts.get("message"),
|
title: state.alerts.get("message"),
|
||||||
type: state.alerts.get("type"),
|
type: state.alerts.get("type")
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = { dismissAlert };
|
const mapDispatchToProps = { dismissAlert };
|
||||||
|
|
||||||
const Alert = (props) => {
|
const Alert = props => {
|
||||||
if (!props.show) {
|
if (!props.show) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -23,13 +23,13 @@ const Alert = (props) => {
|
|||||||
title={props.title}
|
title={props.title}
|
||||||
onConfirm={props.dismissAlert}
|
onConfirm={props.dismissAlert}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
Alert.propTypes = {
|
Alert.propTypes = {
|
||||||
show: PropTypes.bool.isRequired,
|
show: PropTypes.bool.isRequired,
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
dismissAlert: PropTypes.func.isRequired,
|
dismissAlert: PropTypes.func.isRequired,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Alert);
|
export default connect(mapStateToProps, mapDispatchToProps)(Alert);
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
|
|
||||||
import Modal from "react-bootstrap/Modal"
|
import Modal from "react-bootstrap/Modal";
|
||||||
|
|
||||||
export const DownloadAndStream = ({ url, name, subtitles }) => {
|
export const DownloadAndStream = ({ url, name, subtitles }) => {
|
||||||
if (!url || url === "") { return null; }
|
if (!url || url === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@ -13,11 +15,11 @@ export const DownloadAndStream = ({ url, name, subtitles }) => {
|
|||||||
<StreamButton url={url} name={name} subtitles={subtitles} />
|
<StreamButton url={url} name={name} subtitles={subtitles} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
DownloadAndStream.propTypes = {
|
DownloadAndStream.propTypes = {
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
subtitles: PropTypes.instanceOf(List),
|
subtitles: PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
|
|
||||||
const DownloadButton = ({ url }) => (
|
const DownloadButton = ({ url }) => (
|
||||||
@ -39,13 +41,22 @@ const StreamButton = ({ name, url, subtitles }) => {
|
|||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
className="btn btn-sm btn-primary m-1 clickable"
|
className="btn btn-sm btn-primary m-1 clickable"
|
||||||
onClick={(e) => { e.preventDefault(); setShowModal(true) }}>
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
|
setShowModal(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<i className="fa fa-play-circle mr-1" />
|
<i className="fa fa-play-circle mr-1" />
|
||||||
Play
|
Play
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
|
|
||||||
<Modal show={showModal} onHide={() => setShowModal(false)} size="lg" centered>
|
<Modal
|
||||||
|
show={showModal}
|
||||||
|
onHide={() => setShowModal(false)}
|
||||||
|
size="lg"
|
||||||
|
centered
|
||||||
|
>
|
||||||
<Modal.Header closeButton>{name}</Modal.Header>
|
<Modal.Header closeButton>{name}</Modal.Header>
|
||||||
<Modal.Body>
|
<Modal.Body>
|
||||||
<Player url={url} subtitles={subtitles} />
|
<Player url={url} subtitles={subtitles} />
|
||||||
@ -53,11 +64,11 @@ const StreamButton = ({ name, url, subtitles }) => {
|
|||||||
</Modal>
|
</Modal>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
StreamButton.propTypes = {
|
StreamButton.propTypes = {
|
||||||
name: PropTypes.string.isRequired,
|
name: PropTypes.string.isRequired,
|
||||||
url: PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired,
|
||||||
subtitles: PropTypes.instanceOf(List),
|
subtitles: PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
|
|
||||||
const Player = ({ url, subtitles }) => {
|
const Player = ({ url, subtitles }) => {
|
||||||
@ -65,8 +76,11 @@ const Player = ({ url, subtitles }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="embed-responsive embed-responsive-16by9">
|
<div className="embed-responsive embed-responsive-16by9">
|
||||||
<video className="embed-responsive-item" controls>
|
<video className="embed-responsive-item" controls>
|
||||||
<source src={url} type="video/mp4"/>
|
<source src={url} type="video/mp4" />
|
||||||
{hasSubtitles && subtitles.toIndexedSeq().map((el, index) => (
|
{hasSubtitles &&
|
||||||
|
subtitles
|
||||||
|
.toIndexedSeq()
|
||||||
|
.map((el, index) => (
|
||||||
<track
|
<track
|
||||||
key={index}
|
key={index}
|
||||||
kind="subtitles"
|
kind="subtitles"
|
||||||
@ -78,11 +92,11 @@ const Player = ({ url, subtitles }) => {
|
|||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Player.propTypes = {
|
Player.propTypes = {
|
||||||
subtitles: PropTypes.instanceOf(List),
|
subtitles: PropTypes.instanceOf(List),
|
||||||
url: PropTypes.string.isRequired,
|
url: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
Player.defaultProps = {
|
Player.defaultProps = {
|
||||||
subtitles: List(),
|
subtitles: List()
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const ImdbBadge = ({ imdbId }) => {
|
export const ImdbBadge = ({ imdbId }) => {
|
||||||
if (imdbId === undefined) { return null }
|
if (imdbId === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<h5 className="d-inline">
|
<h5 className="d-inline">
|
||||||
<a
|
<a
|
||||||
className="btn btn-sm btn-warning m-1"
|
className="btn btn-sm btn-warning m-1"
|
||||||
href={`https://www.imdb.com/title/${imdbId}`}>
|
href={`https://www.imdb.com/title/${imdbId}`}
|
||||||
|
>
|
||||||
IMDb
|
IMDb
|
||||||
<i className="ml-1 fa fa-external-link"></i>
|
<i className="ml-1 fa fa-external-link"></i>
|
||||||
</a>
|
</a>
|
||||||
</h5>
|
</h5>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
ImdbBadge.propTypes = { imdbId: PropTypes.string };
|
ImdbBadge.propTypes = { imdbId: PropTypes.string };
|
||||||
ImdbBadge.defaultProps = { imdbId: undefined };
|
ImdbBadge.defaultProps = { imdbId: undefined };
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useEffect } from "react"
|
import React, { useState, useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const ShowMore = ({ children, id, inLibrary }) => {
|
export const ShowMore = ({ children, id, inLibrary }) => {
|
||||||
const [ display, setDisplay ] = useState(!inLibrary)
|
const [display, setDisplay] = useState(!inLibrary);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDisplay(!inLibrary);
|
setDisplay(!inLibrary);
|
||||||
@ -18,21 +18,18 @@ export const ShowMore = ({ children, id, inLibrary }) => {
|
|||||||
More options ...
|
More options ...
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<React.Fragment>{children}</React.Fragment>)
|
return <React.Fragment>{children}</React.Fragment>;
|
||||||
}
|
};
|
||||||
|
|
||||||
ShowMore.propTypes = {
|
ShowMore.propTypes = {
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
inLibrary: PropTypes.bool.isRequired,
|
inLibrary: PropTypes.bool.isRequired,
|
||||||
children: PropTypes.oneOf(
|
children: PropTypes.oneOf(PropTypes.object, PropTypes.array)
|
||||||
PropTypes.object,
|
};
|
||||||
PropTypes.array,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
ShowMore.defaultProps = {
|
ShowMore.defaultProps = {
|
||||||
id: "",
|
id: "",
|
||||||
inLibrary: false,
|
inLibrary: false
|
||||||
}
|
};
|
||||||
|
@ -1,61 +1,60 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
|
|
||||||
import Dropdown from "react-bootstrap/Dropdown"
|
import Dropdown from "react-bootstrap/Dropdown";
|
||||||
|
|
||||||
export const SubtitlesButton = ({
|
export const SubtitlesButton = ({
|
||||||
subtitles,
|
subtitles,
|
||||||
inLibrary,
|
inLibrary,
|
||||||
searching,
|
searching,
|
||||||
search,
|
search
|
||||||
}) => {
|
}) => {
|
||||||
if (inLibrary === false) { return null }
|
if (inLibrary === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
||||||
const onSelect = (eventKey) => {
|
const onSelect = eventKey => {
|
||||||
if (eventKey === null || eventKey != 1) {
|
if (eventKey === null || eventKey != 1) {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const onToggle = (isOpen, event, metadata) => {
|
const onToggle = (isOpen, event, metadata) => {
|
||||||
// Don't close on select
|
// Don't close on select
|
||||||
if (metadata && metadata.source !== "select") {
|
if (metadata && metadata.source !== "select") {
|
||||||
setShow(isOpen);
|
setShow(isOpen);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const count = (subtitles && subtitles.size !== 0) ? subtitles.size : 0;
|
const count = subtitles && subtitles.size !== 0 ? subtitles.size : 0;
|
||||||
return (
|
return (
|
||||||
<span className="mr-1 mb-1">
|
<span className="mr-1 mb-1">
|
||||||
<Dropdown drop="up" show={show} onToggle={onToggle} onSelect={onSelect}>
|
<Dropdown drop="up" show={show} onToggle={onToggle} onSelect={onSelect}>
|
||||||
<Dropdown.Toggle variant="secondary" bsPrefix="btn-sm w-md-100">
|
<Dropdown.Toggle variant="secondary" bsPrefix="btn-sm w-md-100">
|
||||||
<i className="fa fa-commenting mr-1" />
|
<i className="fa fa-commenting mr-1" />
|
||||||
Subtitles
|
Subtitles
|
||||||
<span className="ml-1 badge badge-pill badge-info">
|
<span className="ml-1 badge badge-pill badge-info">{count}</span>
|
||||||
{count}
|
|
||||||
</span>
|
|
||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
|
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
<Dropdown.Item eventKey={1} onClick={search} >
|
<Dropdown.Item eventKey={1} onClick={search}>
|
||||||
<i className={`fa ${ searching ? "fa-spin" : "" } fa-refresh mr-1`} />
|
<i className={`fa ${searching ? "fa-spin" : ""} fa-refresh mr-1`} />
|
||||||
Automatic search
|
Automatic search
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
{count > 0 &&
|
{count > 0 && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
<Dropdown.Header>
|
<Dropdown.Header>
|
||||||
<span className="text-warning">
|
<span className="text-warning">Available subtitles</span>
|
||||||
Available subtitles
|
|
||||||
</span>
|
|
||||||
</Dropdown.Header>
|
</Dropdown.Header>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
)}
|
||||||
{count > 0 && subtitles.toIndexedSeq().map((subtitle, index) => (
|
{count > 0 &&
|
||||||
|
subtitles.toIndexedSeq().map((subtitle, index) => (
|
||||||
<Dropdown.Item href={subtitle.get("url")} key={index}>
|
<Dropdown.Item href={subtitle.get("url")} key={index}>
|
||||||
{subtitle.get("language").split("_")[1]}
|
{subtitle.get("language").split("_")[1]}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
@ -64,10 +63,10 @@ export const SubtitlesButton = ({
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
SubtitlesButton.propTypes = {
|
SubtitlesButton.propTypes = {
|
||||||
subtitles: PropTypes.instanceOf(List),
|
subtitles: PropTypes.instanceOf(List),
|
||||||
inLibrary: PropTypes.bool.isRequired,
|
inLibrary: PropTypes.bool.isRequired,
|
||||||
searching: PropTypes.bool.isRequired,
|
searching: PropTypes.bool.isRequired,
|
||||||
search: PropTypes.func.isRequired,
|
search: PropTypes.func.isRequired
|
||||||
}
|
};
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { prettySize } from "../../utils"
|
import { prettySize } from "../../utils";
|
||||||
import { addTorrent } from "../../actions/torrents"
|
import { addTorrent } from "../../actions/torrents";
|
||||||
|
|
||||||
import Dropdown from "react-bootstrap/Dropdown"
|
import Dropdown from "react-bootstrap/Dropdown";
|
||||||
|
|
||||||
function buildMenuItems(torrents) {
|
function buildMenuItems(torrents) {
|
||||||
if (!torrents || torrents.size === 0) {
|
if (!torrents || torrents.size === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const t = torrents.groupBy((el) => el.get("source"));
|
const t = torrents.groupBy(el => el.get("source"));
|
||||||
|
|
||||||
// Build the array of entries
|
// Build the array of entries
|
||||||
let entries = [];
|
let entries = [];
|
||||||
@ -22,7 +22,7 @@ function buildMenuItems(torrents) {
|
|||||||
// Push the title
|
// Push the title
|
||||||
entries.push({
|
entries.push({
|
||||||
type: "header",
|
type: "header",
|
||||||
value: source,
|
value: source
|
||||||
});
|
});
|
||||||
|
|
||||||
// Push the torrents
|
// Push the torrents
|
||||||
@ -31,7 +31,7 @@ function buildMenuItems(torrents) {
|
|||||||
type: "entry",
|
type: "entry",
|
||||||
quality: torrent.get("quality"),
|
quality: torrent.get("quality"),
|
||||||
url: torrent.get("url"),
|
url: torrent.get("url"),
|
||||||
size: torrent.get("size"),
|
size: torrent.get("size")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,32 +45,26 @@ function buildMenuItems(torrents) {
|
|||||||
return entries;
|
return entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
const torrentsButton = ({
|
const torrentsButton = ({ torrents, search, searching, addTorrent, url }) => {
|
||||||
torrents,
|
|
||||||
search,
|
|
||||||
searching,
|
|
||||||
addTorrent,
|
|
||||||
url,
|
|
||||||
}) => {
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
const entries = buildMenuItems(torrents);
|
const entries = buildMenuItems(torrents);
|
||||||
const count = (torrents && torrents.size !== 0) ? torrents.size : 0;
|
const count = torrents && torrents.size !== 0 ? torrents.size : 0;
|
||||||
|
|
||||||
const onSelect = (eventKey) => {
|
const onSelect = eventKey => {
|
||||||
// Close the dropdown if the eventkey is not specified
|
// Close the dropdown if the eventkey is not specified
|
||||||
if (eventKey === null) {
|
if (eventKey === null) {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const onToggle = (isOpen, event, metadata) => {
|
const onToggle = (isOpen, event, metadata) => {
|
||||||
// Don't close on select
|
// Don't close on select
|
||||||
if (metadata && metadata.source !== "select") {
|
if (metadata && metadata.source !== "select") {
|
||||||
setShow(isOpen);
|
setShow(isOpen);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="mr-1 mb-1">
|
<span className="mr-1 mb-1">
|
||||||
@ -78,23 +72,19 @@ const torrentsButton = ({
|
|||||||
<Dropdown.Toggle variant="secondary" bsPrefix="btn-sm w-md-100">
|
<Dropdown.Toggle variant="secondary" bsPrefix="btn-sm w-md-100">
|
||||||
<i className="fa fa-magnet mr-1" />
|
<i className="fa fa-magnet mr-1" />
|
||||||
Torrents
|
Torrents
|
||||||
<span className="ml-1 badge badge-pill badge-info">
|
<span className="ml-1 badge badge-pill badge-info">{count}</span>
|
||||||
{count}
|
|
||||||
</span>
|
|
||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
|
|
||||||
<Dropdown.Menu>
|
<Dropdown.Menu>
|
||||||
<Dropdown.Item eventKey={1} onClick={search} >
|
<Dropdown.Item eventKey={1} onClick={search}>
|
||||||
<i className={`fa ${ searching ? "fa-spin" : "" } fa-refresh mr-1`} />
|
<i className={`fa ${searching ? "fa-spin" : ""} fa-refresh mr-1`} />
|
||||||
Automatic search
|
Automatic search
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
<Dropdown.Item href={url} >
|
<Dropdown.Item href={url}>
|
||||||
<i className="fa fa-search mr-1" />
|
<i className="fa fa-search mr-1" />
|
||||||
Manual search
|
Manual search
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
{entries.length > 0 &&
|
{entries.length > 0 && <Dropdown.Divider />}
|
||||||
<Dropdown.Divider />
|
|
||||||
}
|
|
||||||
{entries.map((e, index) => {
|
{entries.map((e, index) => {
|
||||||
switch (e.type) {
|
switch (e.type) {
|
||||||
case "header":
|
case "header":
|
||||||
@ -104,16 +94,14 @@ const torrentsButton = ({
|
|||||||
</Dropdown.Header>
|
</Dropdown.Header>
|
||||||
);
|
);
|
||||||
case "divider":
|
case "divider":
|
||||||
return (
|
return <Dropdown.Divider key={index} />;
|
||||||
<Dropdown.Divider key={index}/>
|
|
||||||
);
|
|
||||||
case "entry":
|
case "entry":
|
||||||
return (
|
return (
|
||||||
<Dropdown.Item key={index} onClick={() => addTorrent(e.url)}>
|
<Dropdown.Item key={index} onClick={() => addTorrent(e.url)}>
|
||||||
{e.quality}
|
{e.quality}
|
||||||
{e.size !== 0 &&
|
{e.size !== 0 && (
|
||||||
<small className="ml-1">({prettySize(e.size)})</small>
|
<small className="ml-1">({prettySize(e.size)})</small>
|
||||||
}
|
)}
|
||||||
</Dropdown.Item>
|
</Dropdown.Item>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -122,16 +110,16 @@ const torrentsButton = ({
|
|||||||
</Dropdown>
|
</Dropdown>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
torrentsButton.propTypes = {
|
torrentsButton.propTypes = {
|
||||||
torrents: PropTypes.instanceOf(List),
|
torrents: PropTypes.instanceOf(List),
|
||||||
searching: PropTypes.bool,
|
searching: PropTypes.bool,
|
||||||
search: PropTypes.func.isRequired,
|
search: PropTypes.func.isRequired,
|
||||||
addTorrent: PropTypes.func.isRequired,
|
addTorrent: PropTypes.func.isRequired,
|
||||||
url: PropTypes.string,
|
url: PropTypes.string
|
||||||
}
|
};
|
||||||
torrentsButton.defaultProps = {
|
torrentsButton.defaultProps = {
|
||||||
torrents: List(),
|
torrents: List()
|
||||||
}
|
};
|
||||||
|
|
||||||
export const TorrentsButton = connect(null, {addTorrent})(torrentsButton);
|
export const TorrentsButton = connect(null, { addTorrent })(torrentsButton);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const WishlistButton = ({ wishlisted, wishlist }) => {
|
export const WishlistButton = ({ wishlisted, wishlist }) => {
|
||||||
return (
|
return (
|
||||||
@ -10,8 +10,8 @@ export const WishlistButton = ({ wishlisted, wishlist }) => {
|
|||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
WishlistButton.propTypes = {
|
WishlistButton.propTypes = {
|
||||||
wishlisted: PropTypes.bool.isRequired,
|
wishlisted: PropTypes.bool.isRequired,
|
||||||
wishlist: PropTypes.func.isRequired,
|
wishlist: PropTypes.func.isRequired
|
||||||
}
|
};
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
|
|
||||||
export const Genres = ({ genres }) => {
|
export const Genres = ({ genres }) => {
|
||||||
if (genres.size === 0) { return null }
|
if (genres.size === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Uppercase first genres
|
// Uppercase first genres
|
||||||
const prettyGenres = genres.toJS().map(
|
const prettyGenres = genres
|
||||||
(word) => word[0].toUpperCase() + word.substr(1)
|
.toJS()
|
||||||
).join(", ");
|
.map(word => word[0].toUpperCase() + word.substr(1))
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@ -16,6 +19,6 @@ export const Genres = ({ genres }) => {
|
|||||||
{prettyGenres}
|
{prettyGenres}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Genres.propTypes = { genres: PropTypes.instanceOf(List) };
|
Genres.propTypes = { genres: PropTypes.instanceOf(List) };
|
||||||
Genres.defaultProps = { genres: List() };
|
Genres.defaultProps = { genres: List() };
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const Plot = ({ plot }) => {
|
export const Plot = ({ plot }) => {
|
||||||
if (plot === "") { return null }
|
if (plot === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return <span className="text text-break plot">{plot}</span>;
|
||||||
<span className="text text-break plot">{plot}</span>
|
};
|
||||||
);
|
|
||||||
}
|
|
||||||
Plot.propTypes = { plot: PropTypes.string };
|
Plot.propTypes = { plot: PropTypes.string };
|
||||||
Plot.defaultProps = { plot: "" };
|
Plot.defaultProps = { plot: "" };
|
||||||
|
@ -1,26 +1,20 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const PolochonMetadata = ({
|
export const PolochonMetadata = ({
|
||||||
quality,
|
quality,
|
||||||
container,
|
container,
|
||||||
videoCodec,
|
videoCodec,
|
||||||
audioCodec,
|
audioCodec,
|
||||||
releaseGroup,
|
releaseGroup
|
||||||
}) => {
|
}) => {
|
||||||
if (!quality || quality === "") {
|
if (!quality || quality === "") {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const metadata = [
|
const metadata = [quality, container, videoCodec, audioCodec, releaseGroup]
|
||||||
quality,
|
.filter(m => m && m !== "")
|
||||||
container,
|
.join(", ");
|
||||||
videoCodec,
|
|
||||||
audioCodec,
|
|
||||||
releaseGroup,
|
|
||||||
].
|
|
||||||
filter(m => (m && m !== "")).
|
|
||||||
join(", ")
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@ -28,11 +22,11 @@ export const PolochonMetadata = ({
|
|||||||
{metadata}
|
{metadata}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
PolochonMetadata.propTypes = {
|
PolochonMetadata.propTypes = {
|
||||||
quality: PropTypes.string,
|
quality: PropTypes.string,
|
||||||
container: PropTypes.string,
|
container: PropTypes.string,
|
||||||
videoCodec: PropTypes.string,
|
videoCodec: PropTypes.string,
|
||||||
audioCodec: PropTypes.string,
|
audioCodec: PropTypes.string,
|
||||||
releaseGroup: PropTypes.string,
|
releaseGroup: PropTypes.string
|
||||||
};
|
};
|
||||||
|
@ -1,24 +1,24 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const Rating = ({ rating, votes }) => {
|
export const Rating = ({ rating, votes }) => {
|
||||||
if (rating === 0) { return null; }
|
if (rating === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<i className="fa fa-star mr-1"></i>
|
<i className="fa fa-star mr-1"></i>
|
||||||
{Number(rating).toFixed(1)}
|
{Number(rating).toFixed(1)}
|
||||||
{votes !== 0 &&
|
{votes !== 0 && <small className="ml-1">({votes} votes)</small>}
|
||||||
<small className="ml-1">({votes} votes)</small>
|
|
||||||
}
|
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Rating.propTypes = {
|
Rating.propTypes = {
|
||||||
rating: PropTypes.number,
|
rating: PropTypes.number,
|
||||||
votes: PropTypes.number,
|
votes: PropTypes.number
|
||||||
};
|
};
|
||||||
Rating.defaultProps = {
|
Rating.defaultProps = {
|
||||||
rating: 0,
|
rating: 0,
|
||||||
votes: 0,
|
votes: 0
|
||||||
};
|
};
|
||||||
|
@ -1,35 +1,42 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import moment from "moment"
|
import moment from "moment";
|
||||||
|
|
||||||
const prettyDate = (input) => {
|
const prettyDate = input => {
|
||||||
switch (typeof input) {
|
switch (typeof input) {
|
||||||
case "string":
|
case "string":
|
||||||
if (input === "") { return "" }
|
if (input === "") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case "number":
|
case "number":
|
||||||
if (input === 0) { return "" }
|
if (input === 0) {
|
||||||
return input
|
return "";
|
||||||
|
}
|
||||||
|
return input;
|
||||||
default:
|
default:
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
const date = moment(input);
|
const date = moment(input);
|
||||||
if (!date.isValid()) { return "" }
|
if (!date.isValid()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
let output = date.format("DD/MM/YYYY");
|
let output = date.format("DD/MM/YYYY");
|
||||||
|
|
||||||
if ((date > moment().subtract(1, "month"))
|
if (date > moment().subtract(1, "month") && date < moment().add(1, "month")) {
|
||||||
&& (date < moment().add(1, "month"))) {
|
output += " (" + date.fromNow() + ")";
|
||||||
output += " (" + date.fromNow() + ")"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return output
|
return output;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const ReleaseDate = ({ date }) => {
|
export const ReleaseDate = ({ date }) => {
|
||||||
const formattedDate = prettyDate(date);
|
const formattedDate = prettyDate(date);
|
||||||
if (formattedDate === "") { return null }
|
if (formattedDate === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@ -37,11 +44,8 @@ export const ReleaseDate = ({ date }) => {
|
|||||||
{formattedDate}
|
{formattedDate}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
ReleaseDate.propTypes = {
|
ReleaseDate.propTypes = {
|
||||||
date: PropTypes.oneOfType([
|
date: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
||||||
PropTypes.string,
|
|
||||||
PropTypes.number,
|
|
||||||
]),
|
|
||||||
};
|
};
|
||||||
ReleaseDate.defaultProps = { date: "" };
|
ReleaseDate.defaultProps = { date: "" };
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import { prettyDurationFromMinutes } from "../../utils"
|
import { prettyDurationFromMinutes } from "../../utils";
|
||||||
|
|
||||||
export const Runtime = ({ runtime }) => {
|
export const Runtime = ({ runtime }) => {
|
||||||
if (runtime === 0) { return null; }
|
if (runtime === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
@ -12,6 +14,6 @@ export const Runtime = ({ runtime }) => {
|
|||||||
{prettyDurationFromMinutes(runtime)}
|
{prettyDurationFromMinutes(runtime)}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Runtime.propTypes = { runtime: PropTypes.number };
|
Runtime.propTypes = { runtime: PropTypes.number };
|
||||||
Runtime.defaultProps = { runtime: 0 };
|
Runtime.defaultProps = { runtime: 0 };
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import { WishlistButton } from "../buttons/wishlist"
|
import { WishlistButton } from "../buttons/wishlist";
|
||||||
|
|
||||||
export const Title = ({ title, wishlist, wishlisted }) => {
|
export const Title = ({ title, wishlist, wishlisted }) => {
|
||||||
if (title === "") { return null; }
|
if (title === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="title">
|
<span className="title">
|
||||||
@ -12,12 +14,12 @@ export const Title = ({ title, wishlist, wishlisted }) => {
|
|||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Title.propTypes = {
|
Title.propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
wishlist: PropTypes.func,
|
wishlist: PropTypes.func,
|
||||||
wishlisted: PropTypes.bool,
|
wishlisted: PropTypes.bool
|
||||||
};
|
};
|
||||||
Title.defaultProps = {
|
Title.defaultProps = {
|
||||||
title: "",
|
title: ""
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const TrackingLabel = ({ wishlisted, inLibrary, trackedSeason, trackedEpisode }) => {
|
export const TrackingLabel = ({
|
||||||
|
wishlisted,
|
||||||
|
inLibrary,
|
||||||
|
trackedSeason,
|
||||||
|
trackedEpisode
|
||||||
|
}) => {
|
||||||
if (wishlisted === false) {
|
if (wishlisted === false) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -14,14 +19,14 @@ export const TrackingLabel = ({ wishlisted, inLibrary, trackedSeason, trackedEpi
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let str = ""
|
let str = "";
|
||||||
if (trackedSeason === 0 && trackedEpisode === 0) {
|
if (trackedSeason === 0 && trackedEpisode === 0) {
|
||||||
str = "All the episodes will be downloaded automatically";
|
str = "All the episodes will be downloaded automatically";
|
||||||
} else if (trackedSeason > 0 && trackedEpisode > 0) {
|
} else if (trackedSeason > 0 && trackedEpisode > 0) {
|
||||||
str = `All the episodes will be downloaded automatically starting from
|
str = `All the episodes will be downloaded automatically starting from
|
||||||
season ${trackedSeason} episode ${trackedEpisode}`;
|
season ${trackedSeason} episode ${trackedEpisode}`;
|
||||||
} else {
|
} else {
|
||||||
str = "This movie will be downloaded automatically"
|
str = "This movie will be downloaded automatically";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -31,11 +36,11 @@ export const TrackingLabel = ({ wishlisted, inLibrary, trackedSeason, trackedEpi
|
|||||||
{str}
|
{str}
|
||||||
</small>
|
</small>
|
||||||
</span>
|
</span>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
TrackingLabel.propTypes = {
|
TrackingLabel.propTypes = {
|
||||||
wishlisted: PropTypes.bool,
|
wishlisted: PropTypes.bool,
|
||||||
inLibrary: PropTypes.bool,
|
inLibrary: PropTypes.bool,
|
||||||
trackedSeason: PropTypes.number,
|
trackedSeason: PropTypes.number,
|
||||||
trackedEpisode: PropTypes.number,
|
trackedEpisode: PropTypes.number
|
||||||
};
|
};
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const FormInput = ({ label, value, updateValue }) => {
|
export const FormInput = ({ label, value, updateValue }) => {
|
||||||
return (
|
return (
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="control-label">
|
<label className="control-label">{label}</label>
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="form-control"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => updateValue(e.target.value)}
|
onChange={e => updateValue(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
FormInput.propTypes = {
|
FormInput.propTypes = {
|
||||||
label: PropTypes.string,
|
label: PropTypes.string,
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
updateValue: PropTypes.func,
|
updateValue: PropTypes.func
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import { Modal } from "react-bootstrap"
|
import { Modal } from "react-bootstrap";
|
||||||
|
|
||||||
export const FormModal = ({
|
export const FormModal = ({
|
||||||
show,
|
show,
|
||||||
@ -9,10 +9,12 @@ export const FormModal = ({
|
|||||||
title,
|
title,
|
||||||
icon,
|
icon,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
children,
|
children
|
||||||
}) => {
|
}) => {
|
||||||
const submit = function(e) {
|
const submit = function(e) {
|
||||||
if (e) { e.preventDefault(); }
|
if (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
handleSubmit();
|
handleSubmit();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -25,25 +27,26 @@ export const FormModal = ({
|
|||||||
</Modal.Title>
|
</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
<Modal.Body bsPrefix="modal-body">
|
<Modal.Body bsPrefix="modal-body">
|
||||||
<form className="form-horizontal" onSubmit={(ev) => submit(ev)}>
|
<form className="form-horizontal" onSubmit={ev => submit(ev)}>
|
||||||
{children}
|
{children}
|
||||||
</form>
|
</form>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<Modal.Footer>
|
||||||
<div className="btn btn-success" onClick={submit}>Apply</div>
|
<div className="btn btn-success" onClick={submit}>
|
||||||
<div className="btn btn-danger" onClick={() => setShow(false)}>Close</div>
|
Apply
|
||||||
|
</div>
|
||||||
|
<div className="btn btn-danger" onClick={() => setShow(false)}>
|
||||||
|
Close
|
||||||
|
</div>
|
||||||
</Modal.Footer>
|
</Modal.Footer>
|
||||||
</Modal>
|
</Modal>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
FormModal.propTypes = {
|
FormModal.propTypes = {
|
||||||
show: PropTypes.bool,
|
show: PropTypes.bool,
|
||||||
setShow: PropTypes.func,
|
setShow: PropTypes.func,
|
||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
handleSubmit: PropTypes.func,
|
handleSubmit: PropTypes.func,
|
||||||
children: PropTypes.oneOf(
|
children: PropTypes.oneOf(PropTypes.object, PropTypes.array)
|
||||||
PropTypes.object,
|
|
||||||
PropTypes.array,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
@ -1,24 +1,28 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Map } from "immutable"
|
import { Map } from "immutable";
|
||||||
|
|
||||||
import { inLibrary, isWishlisted } from "../../utils"
|
import { inLibrary, isWishlisted } from "../../utils";
|
||||||
|
|
||||||
import { DownloadAndStream } from "../buttons/download"
|
import { DownloadAndStream } from "../buttons/download";
|
||||||
import { ImdbBadge } from "../buttons/imdb"
|
import { ImdbBadge } from "../buttons/imdb";
|
||||||
|
|
||||||
import { TrackingLabel } from "../details/tracking"
|
import { TrackingLabel } from "../details/tracking";
|
||||||
import { Genres } from "../details/genres"
|
import { Genres } from "../details/genres";
|
||||||
import { Plot } from "../details/plot"
|
import { Plot } from "../details/plot";
|
||||||
import { PolochonMetadata } from "../details/polochon"
|
import { PolochonMetadata } from "../details/polochon";
|
||||||
import { Rating } from "../details/rating"
|
import { Rating } from "../details/rating";
|
||||||
import { ReleaseDate } from "../details/releaseDate"
|
import { ReleaseDate } from "../details/releaseDate";
|
||||||
import { Runtime } from "../details/runtime"
|
import { Runtime } from "../details/runtime";
|
||||||
import { Title } from "../details/title"
|
import { Title } from "../details/title";
|
||||||
|
|
||||||
const ListDetails = (props) => {
|
const ListDetails = props => {
|
||||||
if (props.data === undefined) { return null }
|
if (props.data === undefined) {
|
||||||
if (props.loading) { return null }
|
return null;
|
||||||
|
}
|
||||||
|
if (props.loading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-8 col-md-4 list-details pl-1 d-flex align-items-start flex-column">
|
<div className="col-8 col-md-4 list-details pl-1 d-flex align-items-start flex-column">
|
||||||
@ -61,11 +65,11 @@ const ListDetails = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
ListDetails.propTypes = {
|
ListDetails.propTypes = {
|
||||||
data: PropTypes.instanceOf(Map),
|
data: PropTypes.instanceOf(Map),
|
||||||
wishlist: PropTypes.func,
|
wishlist: PropTypes.func,
|
||||||
loading: PropTypes.bool,
|
loading: PropTypes.bool,
|
||||||
children: PropTypes.object,
|
children: PropTypes.object
|
||||||
};
|
};
|
||||||
export default ListDetails;
|
export default ListDetails;
|
||||||
|
@ -1,58 +1,52 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import { withRouter } from "react-router-dom"
|
import PropTypes from "prop-types";
|
||||||
import { Form, FormGroup, FormControl, FormLabel } from "react-bootstrap"
|
import { withRouter } from "react-router-dom";
|
||||||
|
import { Form, FormGroup, FormControl, FormLabel } from "react-bootstrap";
|
||||||
|
|
||||||
class ExplorerOptions extends React.PureComponent {
|
const ExplorerOptions = ({ display, params, options, type, history }) => {
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.handleSourceChange = this.handleSourceChange.bind(this);
|
|
||||||
this.handleCategoryChange = this.handleCategoryChange.bind(this);
|
|
||||||
}
|
|
||||||
handleSourceChange(event) {
|
|
||||||
let source = event.target.value;
|
|
||||||
let category = this.props.options.get(event.target.value).first();
|
|
||||||
this.props.history.push(`/${this.props.type}/explore/${source}/${category}`);
|
|
||||||
}
|
|
||||||
handleCategoryChange(event) {
|
|
||||||
let source = this.props.params.source;
|
|
||||||
let category = event.target.value;
|
|
||||||
this.props.history.push(`/${this.props.type}/explore/${source}/${category}`);
|
|
||||||
}
|
|
||||||
propsValid(props) {
|
|
||||||
if (!props.params
|
|
||||||
|| !props.params.source
|
|
||||||
|| !props.params.category
|
|
||||||
|| (props.params.source === "")
|
|
||||||
|| (props.params.category === "")) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
prettyName(name) {
|
|
||||||
return name.replace("_", " ")
|
|
||||||
.split(" ")
|
|
||||||
.map((w) => w[0].toUpperCase() + w.substr(1))
|
|
||||||
.join(" ");
|
|
||||||
}
|
|
||||||
render() {
|
|
||||||
// Should this componennt be displayed
|
// Should this componennt be displayed
|
||||||
if (!this.props.display) {
|
if (!display) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!params ||
|
||||||
|
!params.source ||
|
||||||
|
!params.category ||
|
||||||
|
params.source === "" ||
|
||||||
|
params.category === ""
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSourceChange = event => {
|
||||||
|
let source = event.target.value;
|
||||||
|
let category = options.get(event.target.value).first();
|
||||||
|
history.push(`/${type}/explore/${source}/${category}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCategoryChange = event => {
|
||||||
|
let source = params.source;
|
||||||
|
let category = event.target.value;
|
||||||
|
history.push(`/${type}/explore/${source}/${category}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const prettyName = name => {
|
||||||
|
return name
|
||||||
|
.replace("_", " ")
|
||||||
|
.split(" ")
|
||||||
|
.map(w => w[0].toUpperCase() + w.substr(1))
|
||||||
|
.join(" ");
|
||||||
|
};
|
||||||
|
|
||||||
// Options are not yet fetched
|
// Options are not yet fetched
|
||||||
if (this.props.options.size === 0) {
|
if (options.size === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid props
|
let source = params.source;
|
||||||
if (!this.propsValid(this.props)) {
|
let category = params.category;
|
||||||
return
|
let categories = options.get(params.source);
|
||||||
}
|
|
||||||
|
|
||||||
let source = this.props.params.source;
|
|
||||||
let category = this.props.params.category;
|
|
||||||
let categories = this.props.options.get(this.props.params.source);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@ -65,13 +59,16 @@ class ExplorerOptions extends React.PureComponent {
|
|||||||
<FormControl
|
<FormControl
|
||||||
bsPrefix="form-control input-sm"
|
bsPrefix="form-control input-sm"
|
||||||
as="select"
|
as="select"
|
||||||
onChange={this.handleSourceChange}
|
onChange={handleSourceChange}
|
||||||
value={source}
|
value={source}
|
||||||
>
|
>
|
||||||
{this.props.options.keySeq().map(function(source) {
|
{options.keySeq().map(function(source) {
|
||||||
return (
|
return (
|
||||||
<option key={source} value={source}>{this.prettyName(source)}</option>)
|
<option key={source} value={source}>
|
||||||
}, this)}
|
{prettyName(source)}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
@ -81,12 +78,16 @@ class ExplorerOptions extends React.PureComponent {
|
|||||||
<FormControl
|
<FormControl
|
||||||
bsPrefix="form-control input-sm"
|
bsPrefix="form-control input-sm"
|
||||||
as="select"
|
as="select"
|
||||||
onChange={this.handleCategoryChange}
|
onChange={handleCategoryChange}
|
||||||
value={category}
|
value={category}
|
||||||
>
|
>
|
||||||
{categories.map(function(category) {
|
{categories.map(function(category) {
|
||||||
return (<option key={category} value={category}>{this.prettyName(category)}</option>)
|
return (
|
||||||
}, this)}
|
<option key={category} value={category}>
|
||||||
|
{prettyName(category)}
|
||||||
|
</option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</div>
|
</div>
|
||||||
@ -95,7 +96,13 @@ class ExplorerOptions extends React.PureComponent {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
ExplorerOptions.propTypes = {
|
||||||
|
params: PropTypes.object,
|
||||||
|
history: PropTypes.object,
|
||||||
|
type: PropTypes.string,
|
||||||
|
options: PropTypes.object,
|
||||||
|
display: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
export default withRouter(ExplorerOptions);
|
export default withRouter(ExplorerOptions);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react"
|
import React, { useState, useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const ListFilter = ({ placeHolder, updateFilter }) => {
|
const ListFilter = ({ placeHolder, updateFilter }) => {
|
||||||
const [filter, setFilter] = useState("");
|
const [filter, setFilter] = useState("");
|
||||||
@ -13,18 +13,21 @@ const ListFilter = ({ placeHolder, updateFilter }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="input-group input-group-sm">
|
<div className="input-group input-group-sm">
|
||||||
<input type="text" className="form-control"
|
<input
|
||||||
|
type="text"
|
||||||
|
className="form-control"
|
||||||
placeholder={placeHolder}
|
placeholder={placeHolder}
|
||||||
onChange={(e) => setFilter(e.target.value)}
|
onChange={e => setFilter(e.target.value)}
|
||||||
value={filter} />
|
value={filter}
|
||||||
|
/>
|
||||||
<div className="input-group-append d-none d-md-block">
|
<div className="input-group-append d-none d-md-block">
|
||||||
<span className="input-group-text">Filter</span>
|
<span className="input-group-text">Filter</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
ListFilter.propTypes = {
|
ListFilter.propTypes = {
|
||||||
updateFilter: PropTypes.func.isRequired,
|
updateFilter: PropTypes.func.isRequired,
|
||||||
placeHolder: PropTypes.string.isRequired,
|
placeHolder: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
export default ListFilter;
|
export default ListFilter;
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import EmptyImg from "../../../img/noimage.png"
|
import EmptyImg from "../../../img/noimage.png";
|
||||||
|
|
||||||
const Poster = ({ url, selected, onClick, onDoubleClick }) => {
|
const Poster = ({ url, selected, onClick, onDoubleClick }) => {
|
||||||
const className = selected ? "border-primary thumbnail-selected" : "border-secondary";
|
const className = selected
|
||||||
const src = (url === "") ? EmptyImg : url;
|
? "border-primary thumbnail-selected"
|
||||||
|
: "border-secondary";
|
||||||
|
const src = url === "" ? EmptyImg : url;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
@ -15,12 +17,12 @@ const Poster = ({ url, selected, onClick, onDoubleClick }) => {
|
|||||||
className={`my-1 m-md-2 img-thumbnail object-fit-cover ${className}`}
|
className={`my-1 m-md-2 img-thumbnail object-fit-cover ${className}`}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Poster.propTypes = {
|
Poster.propTypes = {
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
selected: PropTypes.bool.isRequired,
|
selected: PropTypes.bool.isRequired,
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
onDoubleClick: PropTypes.func,
|
onDoubleClick: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Poster;
|
export default Poster;
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react"
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { OrderedMap, Map } from "immutable"
|
import { OrderedMap, Map } from "immutable";
|
||||||
import fuzzy from "fuzzy";
|
import fuzzy from "fuzzy";
|
||||||
import InfiniteScroll from "react-infinite-scroll-component";
|
import InfiniteScroll from "react-infinite-scroll-component";
|
||||||
|
|
||||||
import ListFilter from "./filter"
|
import ListFilter from "./filter";
|
||||||
import ExplorerOptions from "./explorerOptions"
|
import ExplorerOptions from "./explorerOptions";
|
||||||
import Poster from "./poster"
|
import Poster from "./poster";
|
||||||
|
|
||||||
import Loader from "../loader/loader"
|
import Loader from "../loader/loader";
|
||||||
|
|
||||||
const ListPosters = (props) => {
|
const ListPosters = props => {
|
||||||
if (props.loading) {
|
if (props.loading) {
|
||||||
return (<Loader />);
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let elmts = props.data;
|
let elmts = props.data;
|
||||||
@ -20,23 +20,24 @@ const ListPosters = (props) => {
|
|||||||
|
|
||||||
// Filter the list of elements
|
// Filter the list of elements
|
||||||
if (props.filter !== "") {
|
if (props.filter !== "") {
|
||||||
elmts = elmts.filter((v) => fuzzy.test(props.filter, v.get("title")));
|
elmts = elmts.filter(v => fuzzy.test(props.filter, v.get("title")));
|
||||||
} else {
|
} else {
|
||||||
elmts = elmts.slice(0, listSize.items);
|
elmts = elmts.slice(0, listSize.items);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chose when to display filter / explore options
|
// Chose when to display filter / explore options
|
||||||
let displayFilter = true;
|
let displayFilter = true;
|
||||||
if ((props.params
|
if (
|
||||||
&& props.params.category
|
(props.params &&
|
||||||
&& props.params.category !== ""
|
props.params.category &&
|
||||||
&& props.params.source
|
props.params.category !== "" &&
|
||||||
&& props.params.source !== "")
|
props.params.source &&
|
||||||
|| (listSize === 0)) {
|
props.params.source !== "") ||
|
||||||
|
listSize === 0
|
||||||
|
) {
|
||||||
displayFilter = false;
|
displayFilter = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let displayExplorerOptions = false;
|
let displayExplorerOptions = false;
|
||||||
if (listSize !== 0) {
|
if (listSize !== 0) {
|
||||||
displayExplorerOptions = !displayFilter;
|
displayExplorerOptions = !displayFilter;
|
||||||
@ -44,12 +45,12 @@ const ListPosters = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="col-4 col-md-8 px-1">
|
<div className="col-4 col-md-8 px-1">
|
||||||
{displayFilter &&
|
{displayFilter && (
|
||||||
<ListFilter
|
<ListFilter
|
||||||
updateFilter={props.updateFilter}
|
updateFilter={props.updateFilter}
|
||||||
placeHolder={props.placeHolder}
|
placeHolder={props.placeHolder}
|
||||||
/>
|
/>
|
||||||
}
|
)}
|
||||||
<ExplorerOptions
|
<ExplorerOptions
|
||||||
type={props.type}
|
type={props.type}
|
||||||
display={displayExplorerOptions}
|
display={displayExplorerOptions}
|
||||||
@ -66,7 +67,7 @@ const ListPosters = (props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
ListPosters.propTypes = {
|
ListPosters.propTypes = {
|
||||||
data: PropTypes.instanceOf(OrderedMap),
|
data: PropTypes.instanceOf(OrderedMap),
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
@ -79,34 +80,39 @@ ListPosters.propTypes = {
|
|||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
placeHolder: PropTypes.string.isRequired,
|
placeHolder: PropTypes.string.isRequired,
|
||||||
updateFilter: PropTypes.func.isRequired,
|
updateFilter: PropTypes.func.isRequired,
|
||||||
filter: PropTypes.string,
|
filter: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ListPosters;
|
export default ListPosters;
|
||||||
|
|
||||||
const Posters = (props) => {
|
const Posters = props => {
|
||||||
const addMoreCount = 20;
|
const addMoreCount = 20;
|
||||||
const [size, setSize] = useState(0);
|
const [size, setSize] = useState(0);
|
||||||
const [postersPerRow, setPostersPerRow] = useState(0);
|
const [postersPerRow, setPostersPerRow] = useState(0);
|
||||||
const [posterHeight, setPosterHeight] = useState(0);
|
const [posterHeight, setPosterHeight] = useState(0);
|
||||||
|
|
||||||
const loadMore = () => {
|
const loadMore = () => {
|
||||||
if ((size === props.elmts.size)) { return }
|
if (size === props.elmts.size) {
|
||||||
|
return;
|
||||||
const newSize = (((size + addMoreCount) >= props.elmts.size)
|
|
||||||
? props.elmts.size
|
|
||||||
: size + addMoreCount);
|
|
||||||
|
|
||||||
setSize(newSize);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newSize =
|
||||||
|
size + addMoreCount >= props.elmts.size
|
||||||
|
? props.elmts.size
|
||||||
|
: size + addMoreCount;
|
||||||
|
|
||||||
|
setSize(newSize);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadMore()
|
loadMore();
|
||||||
}, [props.elmts.size]);
|
}, [props.elmts.size]);
|
||||||
|
|
||||||
const move = (event) => {
|
const move = event => {
|
||||||
// Only run the function if nothing else if actively focused
|
// Only run the function if nothing else if actively focused
|
||||||
if (document.activeElement.tagName.toLowerCase() !== "body") { return }
|
if (document.activeElement.tagName.toLowerCase() !== "body") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let diff = 0;
|
let diff = 0;
|
||||||
let moveFocus = 0;
|
let moveFocus = 0;
|
||||||
@ -121,8 +127,8 @@ const Posters = (props) => {
|
|||||||
diff = -1;
|
diff = -1;
|
||||||
break;
|
break;
|
||||||
case "k":
|
case "k":
|
||||||
diff = -1*postersPerRow;
|
diff = -1 * postersPerRow;
|
||||||
moveFocus = -1*posterHeight;
|
moveFocus = -1 * posterHeight;
|
||||||
break;
|
break;
|
||||||
case "j":
|
case "j":
|
||||||
diff = postersPerRow;
|
diff = postersPerRow;
|
||||||
@ -138,8 +144,8 @@ const Posters = (props) => {
|
|||||||
var newIdx = idx + diff;
|
var newIdx = idx + diff;
|
||||||
|
|
||||||
// Handle edge cases
|
// Handle edge cases
|
||||||
if (newIdx > props.elmts.size -1) {
|
if (newIdx > props.elmts.size - 1) {
|
||||||
newIdx = props.elmts.size -1;
|
newIdx = props.elmts.size - 1;
|
||||||
} else if (newIdx < 0) {
|
} else if (newIdx < 0) {
|
||||||
newIdx = 0;
|
newIdx = 0;
|
||||||
}
|
}
|
||||||
@ -152,31 +158,36 @@ const Posters = (props) => {
|
|||||||
if (moveFocus !== 0) {
|
if (moveFocus !== 0) {
|
||||||
window.scrollBy(0, moveFocus);
|
window.scrollBy(0, moveFocus);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const posterCount = useCallback(node => {
|
const posterCount = useCallback(node => {
|
||||||
if (node === null) { return }
|
if (node === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const parentWidth = node.getBoundingClientRect().width;
|
const parentWidth = node.getBoundingClientRect().width;
|
||||||
const childContainer = node.getElementsByClassName("img-thumbnail");
|
const childContainer = node.getElementsByClassName("img-thumbnail");
|
||||||
let childWidth = 0;
|
let childWidth = 0;
|
||||||
let posterHeight = 0;
|
let posterHeight = 0;
|
||||||
if ((childContainer !== null) && (childContainer.item(0) !== null)) {
|
if (childContainer !== null && childContainer.item(0) !== null) {
|
||||||
const child = childContainer.item(0);
|
const child = childContainer.item(0);
|
||||||
childWidth = child.getBoundingClientRect().width + child.getBoundingClientRect().left;
|
childWidth =
|
||||||
|
child.getBoundingClientRect().width +
|
||||||
|
child.getBoundingClientRect().left;
|
||||||
posterHeight = child.getBoundingClientRect().height;
|
posterHeight = child.getBoundingClientRect().height;
|
||||||
}
|
}
|
||||||
|
|
||||||
let numberPerRow = (childWidth >= parentWidth) ? 1 : Math.floor(parentWidth/childWidth);
|
let numberPerRow =
|
||||||
|
childWidth >= parentWidth ? 1 : Math.floor(parentWidth / childWidth);
|
||||||
setPostersPerRow(numberPerRow);
|
setPostersPerRow(numberPerRow);
|
||||||
setPosterHeight(posterHeight);
|
setPosterHeight(posterHeight);
|
||||||
})
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.onkeypress = move;
|
document.onkeypress = move;
|
||||||
return () => {
|
return () => {
|
||||||
document.onkeypress = null;
|
document.onkeypress = null;
|
||||||
}
|
};
|
||||||
}, [move])
|
}, [move]);
|
||||||
|
|
||||||
if (props.elmts.size === 0) {
|
if (props.elmts.size === 0) {
|
||||||
return (
|
return (
|
||||||
@ -192,12 +203,15 @@ const Posters = (props) => {
|
|||||||
className="poster-list d-flex flex-column flex-sm-row flex-sm-wrap justify-content-around"
|
className="poster-list d-flex flex-column flex-sm-row flex-sm-wrap justify-content-around"
|
||||||
dataLength={size}
|
dataLength={size}
|
||||||
next={loadMore}
|
next={loadMore}
|
||||||
hasMore={(size !== props.elmts.size)}
|
hasMore={size !== props.elmts.size}
|
||||||
loader={<Loader />}
|
loader={<Loader />}
|
||||||
>
|
>
|
||||||
{props.elmts.slice(0, size).toIndexedSeq().map(function(el, index) {
|
{props.elmts
|
||||||
|
.slice(0, size)
|
||||||
|
.toIndexedSeq()
|
||||||
|
.map(function(el, index) {
|
||||||
const imdbId = el.get("imdb_id");
|
const imdbId = el.get("imdb_id");
|
||||||
const selected = (imdbId === props.selectedImdbId) ? true : false;
|
const selected = imdbId === props.selectedImdbId ? true : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Poster
|
<Poster
|
||||||
@ -207,17 +221,17 @@ const Posters = (props) => {
|
|||||||
onClick={() => props.selectPoster(imdbId)}
|
onClick={() => props.selectPoster(imdbId)}
|
||||||
onDoubleClick={() => props.onDoubleClick(imdbId)}
|
onDoubleClick={() => props.onDoubleClick(imdbId)}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
} ,this)}
|
}, this)}
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Posters.propTypes = {
|
Posters.propTypes = {
|
||||||
elmts: PropTypes.instanceOf(OrderedMap),
|
elmts: PropTypes.instanceOf(OrderedMap),
|
||||||
selectedImdbId: PropTypes.string,
|
selectedImdbId: PropTypes.string,
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
onDoubleClick: PropTypes.func,
|
onDoubleClick: PropTypes.func,
|
||||||
onKeyEnter: PropTypes.func,
|
onKeyEnter: PropTypes.func,
|
||||||
selectPoster: PropTypes.func,
|
selectPoster: PropTypes.func
|
||||||
};
|
};
|
||||||
|
@ -1,14 +1,9 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import Loading from "react-loading"
|
import Loading from "react-loading";
|
||||||
|
|
||||||
const Loader = () => (
|
const Loader = () => (
|
||||||
<div className="col-12 col-md-6 offset-md-3">
|
<div className="col-12 col-md-6 offset-md-3">
|
||||||
<Loading
|
<Loading type="bars" height={"100%"} width={"100%"} color="#EBEBEB" />
|
||||||
type="bars"
|
|
||||||
height={"100%"}
|
|
||||||
width={"100%"}
|
|
||||||
color="#EBEBEB"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
export default Loader;
|
export default Loader;
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import Loader from "../loader/loader"
|
import Loader from "../loader/loader";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Map, List } from "immutable"
|
import { Map, List } from "immutable";
|
||||||
|
|
||||||
// TODO: udpate this
|
// TODO: udpate this
|
||||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||||
|
|
||||||
const Modules = (props) => {
|
const Modules = props => {
|
||||||
if (props.isLoading) {
|
if (props.isLoading) {
|
||||||
return <Loader />
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{props.modules && props.modules.keySeq().map((value, key) => (
|
{props.modules &&
|
||||||
|
props.modules
|
||||||
|
.keySeq()
|
||||||
|
.map((value, key) => (
|
||||||
<ModulesByVideoType
|
<ModulesByVideoType
|
||||||
key={key}
|
key={key}
|
||||||
videoType={value}
|
videoType={value}
|
||||||
@ -22,17 +25,16 @@ const Modules = (props) => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Modules.propTypes = {
|
Modules.propTypes = {
|
||||||
isLoading: PropTypes.bool.isRequired,
|
isLoading: PropTypes.bool.isRequired,
|
||||||
modules: PropTypes.instanceOf(Map),
|
modules: PropTypes.instanceOf(Map)
|
||||||
};
|
};
|
||||||
export default Modules;
|
export default Modules;
|
||||||
|
|
||||||
const capitalize = (string) =>
|
const capitalize = string => string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
string.charAt(0).toUpperCase() + string.slice(1);
|
|
||||||
|
|
||||||
const ModulesByVideoType = (props) => (
|
const ModulesByVideoType = props => (
|
||||||
<div className="col-12 col-md-6">
|
<div className="col-12 col-md-6">
|
||||||
<div className="card mb-3">
|
<div className="card mb-3">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
@ -40,11 +42,7 @@ const ModulesByVideoType = (props) => (
|
|||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{props.data.keySeq().map((value, key) => (
|
{props.data.keySeq().map((value, key) => (
|
||||||
<ModuleByType
|
<ModuleByType key={key} type={value} data={props.data.get(value)} />
|
||||||
key={key}
|
|
||||||
type={value}
|
|
||||||
data={props.data.get(value)}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -52,22 +50,16 @@ const ModulesByVideoType = (props) => (
|
|||||||
);
|
);
|
||||||
ModulesByVideoType.propTypes = {
|
ModulesByVideoType.propTypes = {
|
||||||
videoType: PropTypes.string.isRequired,
|
videoType: PropTypes.string.isRequired,
|
||||||
data: PropTypes.instanceOf(Map),
|
data: PropTypes.instanceOf(Map)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ModuleByType = (props) => (
|
const ModuleByType = props => (
|
||||||
<div>
|
<div>
|
||||||
<h4>{capitalize(props.type)}</h4>
|
<h4>{capitalize(props.type)}</h4>
|
||||||
<table className="table">
|
<table className="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{props.data.map(function(value, key) {
|
{props.data.map(function(value, key) {
|
||||||
return (
|
return <Module key={key} type={key} data={value} />;
|
||||||
<Module
|
|
||||||
key={key}
|
|
||||||
type={key}
|
|
||||||
data={value}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -75,41 +67,43 @@ const ModuleByType = (props) => (
|
|||||||
);
|
);
|
||||||
ModuleByType.propTypes = {
|
ModuleByType.propTypes = {
|
||||||
type: PropTypes.string.isRequired,
|
type: PropTypes.string.isRequired,
|
||||||
data: PropTypes.instanceOf(List),
|
data: PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
|
|
||||||
const Module = (props) => {
|
const Module = props => {
|
||||||
let iconClass, prettyStatus, badgeClass;
|
let iconClass, prettyStatus, badgeClass;
|
||||||
const name = props.data.get("name");
|
const name = props.data.get("name");
|
||||||
|
|
||||||
switch(props.data.get("status")) {
|
switch (props.data.get("status")) {
|
||||||
case "ok":
|
case "ok":
|
||||||
iconClass = "fa fa-check-circle"
|
iconClass = "fa fa-check-circle";
|
||||||
badgeClass = "badge badge-pill badge-success"
|
badgeClass = "badge badge-pill badge-success";
|
||||||
prettyStatus = "OK"
|
prettyStatus = "OK";
|
||||||
break;
|
break;
|
||||||
case "fail":
|
case "fail":
|
||||||
iconClass = "fa fa-times-circle"
|
iconClass = "fa fa-times-circle";
|
||||||
badgeClass = "badge badge-pill badge-danger"
|
badgeClass = "badge badge-pill badge-danger";
|
||||||
prettyStatus = "Fail"
|
prettyStatus = "Fail";
|
||||||
break;
|
break;
|
||||||
case "not_implemented":
|
case "not_implemented":
|
||||||
iconClass = "fa fa-question-circle"
|
iconClass = "fa fa-question-circle";
|
||||||
badgeClass = "badge badge-pill badge-default"
|
badgeClass = "badge badge-pill badge-default";
|
||||||
prettyStatus = "Not implemented"
|
prettyStatus = "Not implemented";
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
iconClass = "fa fa-question-circle"
|
iconClass = "fa fa-question-circle";
|
||||||
badgeClass = "badge badge-pill badge-warning"
|
badgeClass = "badge badge-pill badge-warning";
|
||||||
prettyStatus = "Unknown"
|
prettyStatus = "Unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
const tooltip = (
|
const tooltip = (
|
||||||
<Tooltip id={`tooltip-status-${name}`}>
|
<Tooltip id={`tooltip-status-${name}`}>
|
||||||
<p><span className={badgeClass}>Status: {prettyStatus}</span></p>
|
<p>
|
||||||
{props.data.get("error") !== "" &&
|
<span className={badgeClass}>Status: {prettyStatus}</span>
|
||||||
|
</p>
|
||||||
|
{props.data.get("error") !== "" && (
|
||||||
<p>Error: {props.data.get("error")}</p>
|
<p>Error: {props.data.get("error")}</p>
|
||||||
}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -125,7 +119,7 @@ const Module = (props) => {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Module.propTypes = {
|
Module.propTypes = {
|
||||||
data: PropTypes.instanceOf(Map),
|
data: PropTypes.instanceOf(Map)
|
||||||
};
|
};
|
||||||
|
@ -1,36 +1,41 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { OrderedMap, Map } from "immutable"
|
import { OrderedMap, Map } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { selectMovie, updateFilter, movieWishlistToggle } from "../../actions/movies"
|
import {
|
||||||
|
selectMovie,
|
||||||
|
updateFilter,
|
||||||
|
movieWishlistToggle
|
||||||
|
} from "../../actions/movies";
|
||||||
|
|
||||||
import ListDetails from "../list/details"
|
import ListDetails from "../list/details";
|
||||||
import ListPosters from "../list/posters"
|
import ListPosters from "../list/posters";
|
||||||
|
|
||||||
import { inLibrary, isWishlisted } from "../../utils"
|
import { inLibrary, isWishlisted } from "../../utils";
|
||||||
|
|
||||||
import { ShowMore } from "../buttons/showMore"
|
import { ShowMore } from "../buttons/showMore";
|
||||||
|
|
||||||
import { MovieSubtitlesButton } from "./subtitlesButton"
|
import { MovieSubtitlesButton } from "./subtitlesButton";
|
||||||
import { MovieTorrentsButton } from "./torrentsButton"
|
import { MovieTorrentsButton } from "./torrentsButton";
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
loading : state.movieStore.get("loading"),
|
loading: state.movieStore.get("loading"),
|
||||||
movies : state.movieStore.get("movies"),
|
movies: state.movieStore.get("movies"),
|
||||||
filter : state.movieStore.get("filter"),
|
filter: state.movieStore.get("filter"),
|
||||||
selectedImdbId : state.movieStore.get("selectedImdbId"),
|
selectedImdbId: state.movieStore.get("selectedImdbId"),
|
||||||
exploreOptions : state.movieStore.get("exploreOptions"),
|
exploreOptions: state.movieStore.get("exploreOptions")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
selectMovie, updateFilter, movieWishlistToggle,
|
selectMovie,
|
||||||
|
updateFilter,
|
||||||
|
movieWishlistToggle
|
||||||
};
|
};
|
||||||
|
|
||||||
const MovieList = (props) => {
|
const MovieList = props => {
|
||||||
let selectedMovie = Map();
|
let selectedMovie = Map();
|
||||||
if (props.movies !== undefined &&
|
if (props.movies !== undefined && props.movies.has(props.selectedImdbId)) {
|
||||||
props.movies.has(props.selectedImdbId)) {
|
|
||||||
selectedMovie = props.movies.get(props.selectedImdbId);
|
selectedMovie = props.movies.get(props.selectedImdbId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,15 +50,24 @@ const MovieList = (props) => {
|
|||||||
updateFilter={props.updateFilter}
|
updateFilter={props.updateFilter}
|
||||||
filter={props.filter}
|
filter={props.filter}
|
||||||
onClick={props.selectMovie}
|
onClick={props.selectMovie}
|
||||||
onDoubleClick={function() { return; }}
|
onDoubleClick={function() {
|
||||||
onKeyEnter={function() { return; }}
|
return;
|
||||||
|
}}
|
||||||
|
onKeyEnter={function() {
|
||||||
|
return;
|
||||||
|
}}
|
||||||
params={props.match.params}
|
params={props.match.params}
|
||||||
loading={props.loading}
|
loading={props.loading}
|
||||||
/>
|
/>
|
||||||
<ListDetails
|
<ListDetails
|
||||||
data={selectedMovie}
|
data={selectedMovie}
|
||||||
loading={props.loading}
|
loading={props.loading}
|
||||||
wishlist={() => props.movieWishlistToggle(selectedMovie.get("imdb_id"), isWishlisted(selectedMovie))}
|
wishlist={() =>
|
||||||
|
props.movieWishlistToggle(
|
||||||
|
selectedMovie.get("imdb_id"),
|
||||||
|
isWishlisted(selectedMovie)
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<ShowMore
|
<ShowMore
|
||||||
id={selectedMovie.get("imdb_id")}
|
id={selectedMovie.get("imdb_id")}
|
||||||
@ -75,7 +89,7 @@ const MovieList = (props) => {
|
|||||||
</ListDetails>
|
</ListDetails>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
MovieList.propTypes = {
|
MovieList.propTypes = {
|
||||||
movies: PropTypes.instanceOf(OrderedMap),
|
movies: PropTypes.instanceOf(OrderedMap),
|
||||||
exploreOptions: PropTypes.instanceOf(Map),
|
exploreOptions: PropTypes.instanceOf(Map),
|
||||||
@ -85,7 +99,7 @@ MovieList.propTypes = {
|
|||||||
updateFilter: PropTypes.func,
|
updateFilter: PropTypes.func,
|
||||||
movieWishlistToggle: PropTypes.func,
|
movieWishlistToggle: PropTypes.func,
|
||||||
selectMovie: PropTypes.func,
|
selectMovie: PropTypes.func,
|
||||||
match: PropTypes.object,
|
match: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(MovieList);
|
export default connect(mapStateToProps, mapDispatchToProps)(MovieList);
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Route } from "react-router-dom"
|
import { Route } from "react-router-dom";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { fetchMovies, getMovieExploreOptions } from "../../actions/movies"
|
import { fetchMovies, getMovieExploreOptions } from "../../actions/movies";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
isExplorerFetched: (state.movieStore.get("exploreOptions").size !== 0)
|
isExplorerFetched: state.movieStore.get("exploreOptions").size !== 0
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = { fetchMovies, getMovieExploreOptions };
|
const mapDispatchToProps = { fetchMovies, getMovieExploreOptions };
|
||||||
|
|
||||||
@ -17,7 +17,9 @@ const MoviesRoute = ({
|
|||||||
...otherProps
|
...otherProps
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Route {...otherProps} render={(props) => {
|
<Route
|
||||||
|
{...otherProps}
|
||||||
|
render={props => {
|
||||||
let fetchUrl = "";
|
let fetchUrl = "";
|
||||||
switch (props.match.path) {
|
switch (props.match.path) {
|
||||||
case "/movies/polochon":
|
case "/movies/polochon":
|
||||||
@ -33,9 +35,11 @@ const MoviesRoute = ({
|
|||||||
if (!isExplorerFetched) {
|
if (!isExplorerFetched) {
|
||||||
getMovieExploreOptions();
|
getMovieExploreOptions();
|
||||||
}
|
}
|
||||||
fetchUrl = "/movies/explore?source=" +
|
fetchUrl =
|
||||||
|
"/movies/explore?source=" +
|
||||||
encodeURI(props.match.params.source) +
|
encodeURI(props.match.params.source) +
|
||||||
"&category=" + encodeURI(props.match.params.category);
|
"&category=" +
|
||||||
|
encodeURI(props.match.params.category);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -45,16 +49,17 @@ const MoviesRoute = ({
|
|||||||
fetchMovies(fetchUrl);
|
fetchMovies(fetchUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Component {...props} />
|
return <Component {...props} />;
|
||||||
}} />
|
}}
|
||||||
)
|
/>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
MoviesRoute.propTypes = {
|
MoviesRoute.propTypes = {
|
||||||
component: PropTypes.func,
|
component: PropTypes.func,
|
||||||
match: PropTypes.object,
|
match: PropTypes.object,
|
||||||
isExplorerFetched: PropTypes.bool.isRequired,
|
isExplorerFetched: PropTypes.bool.isRequired,
|
||||||
fetchMovies: PropTypes.func.isRequired,
|
fetchMovies: PropTypes.func.isRequired,
|
||||||
getMovieExploreOptions: PropTypes.func.isRequired,
|
getMovieExploreOptions: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(MoviesRoute);
|
export default connect(mapStateToProps, mapDispatchToProps)(MoviesRoute);
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { searchMovieSubtitles } from "../../actions/subtitles"
|
import { searchMovieSubtitles } from "../../actions/subtitles";
|
||||||
|
|
||||||
import { SubtitlesButton } from "../buttons/subtitles"
|
import { SubtitlesButton } from "../buttons/subtitles";
|
||||||
|
|
||||||
const movieSubtitlesButton = ({
|
const movieSubtitlesButton = ({
|
||||||
inLibrary,
|
inLibrary,
|
||||||
imdbId,
|
imdbId,
|
||||||
searching,
|
searching,
|
||||||
searchMovieSubtitles,
|
searchMovieSubtitles,
|
||||||
subtitles,
|
subtitles
|
||||||
}) => (
|
}) => (
|
||||||
<SubtitlesButton
|
<SubtitlesButton
|
||||||
inLibrary={inLibrary}
|
inLibrary={inLibrary}
|
||||||
@ -20,14 +20,16 @@ const movieSubtitlesButton = ({
|
|||||||
subtitles={subtitles}
|
subtitles={subtitles}
|
||||||
search={() => searchMovieSubtitles(imdbId)}
|
search={() => searchMovieSubtitles(imdbId)}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
|
|
||||||
movieSubtitlesButton.propTypes = {
|
movieSubtitlesButton.propTypes = {
|
||||||
searching: PropTypes.bool,
|
searching: PropTypes.bool,
|
||||||
inLibrary: PropTypes.bool,
|
inLibrary: PropTypes.bool,
|
||||||
imdbId: PropTypes.string,
|
imdbId: PropTypes.string,
|
||||||
searchMovieSubtitles: PropTypes.func,
|
searchMovieSubtitles: PropTypes.func,
|
||||||
subtitles: PropTypes.instanceOf(List),
|
subtitles: PropTypes.instanceOf(List)
|
||||||
}
|
};
|
||||||
|
|
||||||
export const MovieSubtitlesButton = connect(null, {searchMovieSubtitles})(movieSubtitlesButton);
|
export const MovieSubtitlesButton = connect(null, { searchMovieSubtitles })(
|
||||||
|
movieSubtitlesButton
|
||||||
|
);
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { getMovieDetails } from "../../actions/movies"
|
import { getMovieDetails } from "../../actions/movies";
|
||||||
import { TorrentsButton } from "../buttons/torrents"
|
import { TorrentsButton } from "../buttons/torrents";
|
||||||
|
|
||||||
const movieTorrentsButton = ({
|
const movieTorrentsButton = ({
|
||||||
torrents,
|
torrents,
|
||||||
imdbId,
|
imdbId,
|
||||||
title,
|
title,
|
||||||
searching,
|
searching,
|
||||||
getMovieDetails,
|
getMovieDetails
|
||||||
}) => (
|
}) => (
|
||||||
<TorrentsButton
|
<TorrentsButton
|
||||||
torrents={torrents}
|
torrents={torrents}
|
||||||
@ -19,13 +19,15 @@ const movieTorrentsButton = ({
|
|||||||
search={() => getMovieDetails(imdbId)}
|
search={() => getMovieDetails(imdbId)}
|
||||||
url={`#/torrents/search/movies/${encodeURI(title)}`}
|
url={`#/torrents/search/movies/${encodeURI(title)}`}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
movieTorrentsButton.propTypes = {
|
movieTorrentsButton.propTypes = {
|
||||||
torrents: PropTypes.instanceOf(List),
|
torrents: PropTypes.instanceOf(List),
|
||||||
imdbId: PropTypes.string,
|
imdbId: PropTypes.string,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
searching: PropTypes.bool,
|
searching: PropTypes.bool,
|
||||||
getMovieDetails: PropTypes.func,
|
getMovieDetails: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MovieTorrentsButton = connect(null, {getMovieDetails})(movieTorrentsButton);
|
export const MovieTorrentsButton = connect(null, { getMovieDetails })(
|
||||||
|
movieTorrentsButton
|
||||||
|
);
|
||||||
|
@ -1,31 +1,41 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import { Route } from "react-router-dom"
|
import { Route } from "react-router-dom";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { LinkContainer } from "react-router-bootstrap"
|
import { LinkContainer } from "react-router-bootstrap";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import Nav from "react-bootstrap/Nav"
|
import Nav from "react-bootstrap/Nav";
|
||||||
import Navbar from "react-bootstrap/Navbar"
|
import Navbar from "react-bootstrap/Navbar";
|
||||||
import NavDropdown from "react-bootstrap/NavDropdown"
|
import NavDropdown from "react-bootstrap/NavDropdown";
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = state => {
|
||||||
let torrentCount = 0;
|
let torrentCount = 0;
|
||||||
if (state.torrentStore.has("torrents") && state.torrentStore.get("torrents") !== undefined) {
|
if (
|
||||||
|
state.torrentStore.has("torrents") &&
|
||||||
|
state.torrentStore.get("torrents") !== undefined
|
||||||
|
) {
|
||||||
torrentCount = state.torrentStore.get("torrents").size;
|
torrentCount = state.torrentStore.get("torrents").size;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
username: state.userStore.get("username"),
|
username: state.userStore.get("username"),
|
||||||
isAdmin: state.userStore.get("isAdmin"),
|
isAdmin: state.userStore.get("isAdmin"),
|
||||||
torrentCount: torrentCount,
|
torrentCount: torrentCount
|
||||||
}
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const AppNavBar = (props) => {
|
const AppNavBar = props => {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Navbar fixed="top" collapseOnSelect bg="dark" variant="dark"
|
<Navbar
|
||||||
expanded={expanded} expand="sm" onToggle={() => setExpanded(!expanded)}>
|
fixed="top"
|
||||||
|
collapseOnSelect
|
||||||
|
bg="dark"
|
||||||
|
variant="dark"
|
||||||
|
expanded={expanded}
|
||||||
|
expand="sm"
|
||||||
|
onToggle={() => setExpanded(!expanded)}
|
||||||
|
>
|
||||||
<LinkContainer to="/">
|
<LinkContainer to="/">
|
||||||
<Navbar.Brand>Canapé</Navbar.Brand>
|
<Navbar.Brand>Canapé</Navbar.Brand>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
@ -38,36 +48,39 @@ const AppNavBar = (props) => {
|
|||||||
<TorrentsDropdown torrentsCount={props.torrentCount} />
|
<TorrentsDropdown torrentsCount={props.torrentCount} />
|
||||||
</Nav>
|
</Nav>
|
||||||
<Nav>
|
<Nav>
|
||||||
<Route path="/movies" render={(props) =>
|
<Route
|
||||||
|
path="/movies"
|
||||||
|
render={props => (
|
||||||
<Search
|
<Search
|
||||||
placeholder="Search movies"
|
placeholder="Search movies"
|
||||||
path='/movies/search'
|
path="/movies/search"
|
||||||
history={props.history}
|
history={props.history}
|
||||||
setExpanded={setExpanded}
|
setExpanded={setExpanded}
|
||||||
/>
|
/>
|
||||||
}/>
|
)}
|
||||||
<Route path="/shows" render={(props) =>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/shows"
|
||||||
|
render={props => (
|
||||||
<Search
|
<Search
|
||||||
placeholder="Search shows"
|
placeholder="Search shows"
|
||||||
path='/shows/search'
|
path="/shows/search"
|
||||||
history={props.history}
|
history={props.history}
|
||||||
setExpanded={setExpanded}
|
setExpanded={setExpanded}
|
||||||
/>
|
/>
|
||||||
}/>
|
)}
|
||||||
<UserDropdown
|
|
||||||
username={props.username}
|
|
||||||
isAdmin={props.isAdmin}
|
|
||||||
/>
|
/>
|
||||||
|
<UserDropdown username={props.username} isAdmin={props.isAdmin} />
|
||||||
</Nav>
|
</Nav>
|
||||||
</Navbar.Collapse>
|
</Navbar.Collapse>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
AppNavBar.propTypes = {
|
AppNavBar.propTypes = {
|
||||||
torrentCount: PropTypes.number.isRequired,
|
torrentCount: PropTypes.number.isRequired,
|
||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
isAdmin: PropTypes.bool.isRequired,
|
isAdmin: PropTypes.bool.isRequired,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps)(AppNavBar);
|
export default connect(mapStateToProps)(AppNavBar);
|
||||||
@ -75,30 +88,30 @@ export default connect(mapStateToProps)(AppNavBar);
|
|||||||
const Search = ({ path, placeholder, setExpanded, history }) => {
|
const Search = ({ path, placeholder, setExpanded, history }) => {
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
const handleSearch = (ev) => {
|
const handleSearch = ev => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
history.push(`${path}/${encodeURI(search)}`);
|
history.push(`${path}/${encodeURI(search)}`);
|
||||||
setExpanded(false);
|
setExpanded(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
return(
|
return (
|
||||||
<div className="navbar-form navbar-right">
|
<div className="navbar-form navbar-right">
|
||||||
<form className="input-group" onSubmit={(ev) => handleSearch(ev)}>
|
<form className="input-group" onSubmit={ev => handleSearch(ev)}>
|
||||||
<input
|
<input
|
||||||
className="form-control"
|
className="form-control"
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={e => setSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Search.propTypes = {
|
Search.propTypes = {
|
||||||
placeholder: PropTypes.string.isRequired,
|
placeholder: PropTypes.string.isRequired,
|
||||||
setExpanded: PropTypes.func.isRequired,
|
setExpanded: PropTypes.func.isRequired,
|
||||||
path: PropTypes.string.isRequired,
|
path: PropTypes.string.isRequired,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
const MoviesDropdown = () => (
|
const MoviesDropdown = () => (
|
||||||
@ -110,7 +123,7 @@ const MoviesDropdown = () => (
|
|||||||
<NavDropdown.Item>My movies</NavDropdown.Item>
|
<NavDropdown.Item>My movies</NavDropdown.Item>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
</NavDropdown>
|
</NavDropdown>
|
||||||
)
|
);
|
||||||
|
|
||||||
const ShowsDropdown = () => (
|
const ShowsDropdown = () => (
|
||||||
<NavDropdown title="Shows" id="navbar-shows-dropdown">
|
<NavDropdown title="Shows" id="navbar-shows-dropdown">
|
||||||
@ -123,14 +136,14 @@ const ShowsDropdown = () => (
|
|||||||
</NavDropdown>
|
</NavDropdown>
|
||||||
);
|
);
|
||||||
|
|
||||||
const UserDropdown = (props) => (
|
const UserDropdown = props => (
|
||||||
<Nav>
|
<Nav>
|
||||||
<NavDropdown title={props.username} alignRight>
|
<NavDropdown title={props.username} alignRight>
|
||||||
{props.isAdmin &&
|
{props.isAdmin && (
|
||||||
<LinkContainer to="/admin">
|
<LinkContainer to="/admin">
|
||||||
<NavDropdown.Item>Admin Panel</NavDropdown.Item>
|
<NavDropdown.Item>Admin Panel</NavDropdown.Item>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
}
|
)}
|
||||||
<LinkContainer to="/users/profile">
|
<LinkContainer to="/users/profile">
|
||||||
<NavDropdown.Item>Profile</NavDropdown.Item>
|
<NavDropdown.Item>Profile</NavDropdown.Item>
|
||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
@ -142,10 +155,10 @@ const UserDropdown = (props) => (
|
|||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
</NavDropdown>
|
</NavDropdown>
|
||||||
</Nav>
|
</Nav>
|
||||||
)
|
);
|
||||||
UserDropdown.propTypes = {
|
UserDropdown.propTypes = {
|
||||||
username: PropTypes.string.isRequired,
|
username: PropTypes.string.isRequired,
|
||||||
isAdmin: PropTypes.bool.isRequired,
|
isAdmin: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const WishlistDropdown = () => (
|
const WishlistDropdown = () => (
|
||||||
@ -159,9 +172,9 @@ const WishlistDropdown = () => (
|
|||||||
</NavDropdown>
|
</NavDropdown>
|
||||||
);
|
);
|
||||||
|
|
||||||
const TorrentsDropdown = (props) => {
|
const TorrentsDropdown = props => {
|
||||||
const title = (<TorrentsDropdownTitle torrentsCount={props.torrentsCount} />)
|
const title = <TorrentsDropdownTitle torrentsCount={props.torrentsCount} />;
|
||||||
return(
|
return (
|
||||||
<NavDropdown title={title} id="navbar-wishlit-dropdown">
|
<NavDropdown title={title} id="navbar-wishlit-dropdown">
|
||||||
<LinkContainer to="/torrents/list">
|
<LinkContainer to="/torrents/list">
|
||||||
<NavDropdown.Item>Downloads</NavDropdown.Item>
|
<NavDropdown.Item>Downloads</NavDropdown.Item>
|
||||||
@ -171,22 +184,27 @@ const TorrentsDropdown = (props) => {
|
|||||||
</LinkContainer>
|
</LinkContainer>
|
||||||
</NavDropdown>
|
</NavDropdown>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
TorrentsDropdown.propTypes = { torrentsCount: PropTypes.number.isRequired };
|
TorrentsDropdown.propTypes = { torrentsCount: PropTypes.number.isRequired };
|
||||||
|
|
||||||
const TorrentsDropdownTitle = (props) => {
|
const TorrentsDropdownTitle = props => {
|
||||||
if (props.torrentsCount === 0) {
|
if (props.torrentsCount === 0) {
|
||||||
return (
|
return <span>Torrents</span>;
|
||||||
<span>Torrents</span>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span> Torrents
|
|
||||||
<span>
|
<span>
|
||||||
<span className="badge badge-info badge-pill">{props.torrentsCount}</span>
|
{" "}
|
||||||
|
Torrents
|
||||||
|
<span>
|
||||||
|
{" "}
|
||||||
|
<span className="badge badge-info badge-pill">
|
||||||
|
{props.torrentsCount}
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
TorrentsDropdownTitle.propTypes = { torrentsCount: PropTypes.number.isRequired };
|
TorrentsDropdownTitle.propTypes = {
|
||||||
|
torrentsCount: PropTypes.number.isRequired
|
||||||
|
};
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { Toast } from "react-bootstrap"
|
import { Toast } from "react-bootstrap";
|
||||||
|
|
||||||
import { removeNotification } from "../../actions/notifications"
|
import { removeNotification } from "../../actions/notifications";
|
||||||
|
|
||||||
const NotificationConnected = ({
|
const NotificationConnected = ({
|
||||||
id,
|
id,
|
||||||
@ -14,39 +14,30 @@ const NotificationConnected = ({
|
|||||||
imageUrl,
|
imageUrl,
|
||||||
autohide,
|
autohide,
|
||||||
delay,
|
delay,
|
||||||
removeNotification,
|
removeNotification
|
||||||
}) => {
|
}) => {
|
||||||
const [show, setShow] = useState(true)
|
const [show, setShow] = useState(true);
|
||||||
|
|
||||||
const hide = () => {
|
const hide = () => {
|
||||||
setShow(false);
|
setShow(false);
|
||||||
setTimeout(() => removeNotification(id), 200);
|
setTimeout(() => removeNotification(id), 200);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Toast
|
<Toast show={show} onClose={hide} autohide={autohide} delay={delay}>
|
||||||
show={show}
|
|
||||||
onClose={hide}
|
|
||||||
autohide={autohide}
|
|
||||||
delay={delay}
|
|
||||||
>
|
|
||||||
<Toast.Header>
|
<Toast.Header>
|
||||||
{icon !== "" &&
|
{icon !== "" && <i className={`fa fa-${icon} mr-2`} />}
|
||||||
<i className={`fa fa-${icon} mr-2`} />
|
|
||||||
}
|
|
||||||
<strong className="mr-auto">{title}</strong>
|
<strong className="mr-auto">{title}</strong>
|
||||||
</Toast.Header>
|
</Toast.Header>
|
||||||
<Toast.Body>
|
<Toast.Body>
|
||||||
{message !== "" &&
|
{message !== "" && <span>{message}</span>}
|
||||||
<span>{message}</span>
|
{imageUrl !== "" && (
|
||||||
}
|
|
||||||
{imageUrl !== "" &&
|
|
||||||
<img src={imageUrl} className="img-fluid mt-2 mr-auto" />
|
<img src={imageUrl} className="img-fluid mt-2 mr-auto" />
|
||||||
}
|
)}
|
||||||
</Toast.Body>
|
</Toast.Body>
|
||||||
</Toast>
|
</Toast>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
NotificationConnected.propTypes = {
|
NotificationConnected.propTypes = {
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
@ -55,7 +46,7 @@ NotificationConnected.propTypes = {
|
|||||||
imageUrl: PropTypes.string,
|
imageUrl: PropTypes.string,
|
||||||
autohide: PropTypes.bool,
|
autohide: PropTypes.bool,
|
||||||
delay: PropTypes.number,
|
delay: PropTypes.number,
|
||||||
removeNotification: PropTypes.func,
|
removeNotification: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
NotificationConnected.defaultProps = {
|
NotificationConnected.defaultProps = {
|
||||||
@ -64,7 +55,9 @@ NotificationConnected.defaultProps = {
|
|||||||
icon: "",
|
icon: "",
|
||||||
imageUrl: "",
|
imageUrl: "",
|
||||||
title: "Info",
|
title: "Info",
|
||||||
message: "",
|
message: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Notification = connect(null, {removeNotification})(NotificationConnected);
|
export const Notification = connect(null, { removeNotification })(
|
||||||
|
NotificationConnected
|
||||||
|
);
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { Notification } from "./notification"
|
import { Notification } from "./notification";
|
||||||
|
|
||||||
const NotificationsConnected = ({ notifications }) => {
|
const NotificationsConnected = ({ notifications }) => {
|
||||||
return (
|
return (
|
||||||
<div className="notifications">
|
<div className="notifications">
|
||||||
{notifications.map((el) => (
|
{notifications.map(el => (
|
||||||
<Notification
|
<Notification
|
||||||
key={el.get("id")}
|
key={el.get("id")}
|
||||||
id={el.get("id")}
|
id={el.get("id")}
|
||||||
@ -21,14 +21,14 @@ const NotificationsConnected = ({ notifications }) => {
|
|||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
NotificationsConnected.propTypes = {
|
NotificationsConnected.propTypes = {
|
||||||
notifications: PropTypes.instanceOf(List),
|
notifications: PropTypes.instanceOf(List)
|
||||||
}
|
};
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
notifications: state.notifications,
|
notifications: state.notifications
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Notifications = connect(mapStateToProps)(NotificationsConnected);
|
export const Notifications = connect(mapStateToProps)(NotificationsConnected);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { addPolochon } from "../../actions/polochon"
|
import { addPolochon } from "../../actions/polochon";
|
||||||
|
|
||||||
import { PolochonEdit } from "./edit"
|
import { PolochonEdit } from "./edit";
|
||||||
|
|
||||||
export const PolochonAddConnected = ({ addPolochon }) => {
|
export const PolochonAddConnected = ({ addPolochon }) => {
|
||||||
const [modal, setModal] = useState(false);
|
const [modal, setModal] = useState(false);
|
||||||
@ -24,10 +24,10 @@ export const PolochonAddConnected = ({ addPolochon }) => {
|
|||||||
update={addPolochon}
|
update={addPolochon}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
PolochonAddConnected.propTypes = {
|
PolochonAddConnected.propTypes = {
|
||||||
addPolochon: PropTypes.func,
|
addPolochon: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PolochonAdd = connect(null, {addPolochon})(PolochonAddConnected);
|
export const PolochonAdd = connect(null, { addPolochon })(PolochonAddConnected);
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useState, useEffect } from "react"
|
import React, { useState, useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import { FormModal } from "../forms/modal"
|
import { FormModal } from "../forms/modal";
|
||||||
import { FormInput } from "../forms/input"
|
import { FormInput } from "../forms/input";
|
||||||
|
|
||||||
export const PolochonEdit = ({
|
export const PolochonEdit = ({
|
||||||
show,
|
show,
|
||||||
@ -13,7 +13,7 @@ export const PolochonEdit = ({
|
|||||||
initialName,
|
initialName,
|
||||||
initialUrl,
|
initialUrl,
|
||||||
initialToken,
|
initialToken,
|
||||||
update,
|
update
|
||||||
}) => {
|
}) => {
|
||||||
const [name, setName] = useState(initialName);
|
const [name, setName] = useState(initialName);
|
||||||
const [url, setUrl] = useState(initialUrl);
|
const [url, setUrl] = useState(initialUrl);
|
||||||
@ -23,7 +23,7 @@ export const PolochonEdit = ({
|
|||||||
setName(initialName);
|
setName(initialName);
|
||||||
setUrl(initialUrl);
|
setUrl(initialUrl);
|
||||||
setToken(initialToken);
|
setToken(initialToken);
|
||||||
}, [id, initialName, initialUrl, initialToken])
|
}, [id, initialName, initialUrl, initialToken]);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
update({ id, name, url, token });
|
update({ id, name, url, token });
|
||||||
@ -42,8 +42,8 @@ export const PolochonEdit = ({
|
|||||||
<FormInput label="URL" value={url} updateValue={setUrl} />
|
<FormInput label="URL" value={url} updateValue={setUrl} />
|
||||||
<FormInput label="Token" value={token} updateValue={setToken} />
|
<FormInput label="Token" value={token} updateValue={setToken} />
|
||||||
</FormModal>
|
</FormModal>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
PolochonEdit.propTypes = {
|
PolochonEdit.propTypes = {
|
||||||
show: PropTypes.bool,
|
show: PropTypes.bool,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
@ -53,7 +53,7 @@ PolochonEdit.propTypes = {
|
|||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
initialName: PropTypes.string,
|
initialName: PropTypes.string,
|
||||||
initialUrl: PropTypes.string,
|
initialUrl: PropTypes.string,
|
||||||
initialToken: PropTypes.string,
|
initialToken: PropTypes.string
|
||||||
};
|
};
|
||||||
PolochonEdit.defaultProps = {
|
PolochonEdit.defaultProps = {
|
||||||
id: "",
|
id: "",
|
||||||
@ -61,5 +61,5 @@ PolochonEdit.defaultProps = {
|
|||||||
icon: "edit",
|
icon: "edit",
|
||||||
initialName: "",
|
initialName: "",
|
||||||
initialUrl: "",
|
initialUrl: "",
|
||||||
initialToken: "",
|
initialToken: ""
|
||||||
};
|
};
|
||||||
|
@ -1,29 +1,25 @@
|
|||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
|
|
||||||
import { getManagedPolochons } from "../../actions/polochon"
|
import { getManagedPolochons } from "../../actions/polochon";
|
||||||
|
|
||||||
import { Polochon } from "./polochon"
|
import { Polochon } from "./polochon";
|
||||||
import { PolochonAdd } from "./add"
|
import { PolochonAdd } from "./add";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
managedList: state.polochon.get("managed"),
|
managedList: state.polochon.get("managed")
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
getManagedPolochons,
|
getManagedPolochons
|
||||||
}
|
};
|
||||||
|
|
||||||
const PolochonListConnected = ({
|
|
||||||
getManagedPolochons,
|
|
||||||
managedList,
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
|
const PolochonListConnected = ({ getManagedPolochons, managedList }) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getManagedPolochons();
|
getManagedPolochons();
|
||||||
}, [getManagedPolochons])
|
}, [getManagedPolochons]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
@ -46,11 +42,14 @@ const PolochonListConnected = ({
|
|||||||
<PolochonAdd />
|
<PolochonAdd />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
PolochonListConnected.propTypes = {
|
PolochonListConnected.propTypes = {
|
||||||
getManagedPolochons: PropTypes.func,
|
getManagedPolochons: PropTypes.func,
|
||||||
managedList: PropTypes.instanceOf(List),
|
managedList: PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PolochonList = connect(mapStateToProps, mapDispatchToProps)(PolochonListConnected);
|
export const PolochonList = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(PolochonListConnected);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { PolochonUsers } from "./users"
|
import { PolochonUsers } from "./users";
|
||||||
import { PolochonEdit } from "./edit"
|
import { PolochonEdit } from "./edit";
|
||||||
|
|
||||||
import { updatePolochon, deletePolochon } from "../../actions/polochon"
|
import { updatePolochon, deletePolochon } from "../../actions/polochon";
|
||||||
|
|
||||||
export const PolochonConnected = ({
|
export const PolochonConnected = ({
|
||||||
id,
|
id,
|
||||||
@ -16,7 +16,7 @@ export const PolochonConnected = ({
|
|||||||
authToken,
|
authToken,
|
||||||
users,
|
users,
|
||||||
updatePolochon,
|
updatePolochon,
|
||||||
deletePolochon,
|
deletePolochon
|
||||||
}) => {
|
}) => {
|
||||||
const [edit, setEdit] = useState(false);
|
const [edit, setEdit] = useState(false);
|
||||||
|
|
||||||
@ -25,9 +25,7 @@ export const PolochonConnected = ({
|
|||||||
<div className="card mb-2">
|
<div className="card mb-2">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
{name !== "" ? name : "-"}
|
{name !== "" ? name : "-"}
|
||||||
<small className="ml-1">
|
<small className="ml-1">({url !== "" ? url : "-"})</small>
|
||||||
({url !== "" ? url : "-"})
|
|
||||||
</small>
|
|
||||||
<span className="pull-right">
|
<span className="pull-right">
|
||||||
<i
|
<i
|
||||||
className="fa fa-edit mr-3 clickable"
|
className="fa fa-edit mr-3 clickable"
|
||||||
@ -58,8 +56,8 @@ export const PolochonConnected = ({
|
|||||||
initialToken={token}
|
initialToken={token}
|
||||||
/>
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
PolochonConnected.propTypes = {
|
PolochonConnected.propTypes = {
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
@ -68,7 +66,9 @@ PolochonConnected.propTypes = {
|
|||||||
authToken: PropTypes.string,
|
authToken: PropTypes.string,
|
||||||
users: PropTypes.instanceOf(List),
|
users: PropTypes.instanceOf(List),
|
||||||
updatePolochon: PropTypes.func,
|
updatePolochon: PropTypes.func,
|
||||||
deletePolochon: PropTypes.func,
|
deletePolochon: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Polochon = connect(null, { updatePolochon, deletePolochon })(PolochonConnected);
|
export const Polochon = connect(null, { updatePolochon, deletePolochon })(
|
||||||
|
PolochonConnected
|
||||||
|
);
|
||||||
|
@ -1,30 +1,26 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
|
|
||||||
export const PolochonSelect = ({
|
export const PolochonSelect = ({ value, changeValue, polochonList }) => {
|
||||||
value,
|
|
||||||
changeValue,
|
|
||||||
polochonList,
|
|
||||||
}) => {
|
|
||||||
return (
|
return (
|
||||||
<select
|
<select
|
||||||
className="form-control"
|
className="form-control"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => changeValue(e.target.options[e.target.selectedIndex].value)}
|
onChange={e =>
|
||||||
|
changeValue(e.target.options[e.target.selectedIndex].value)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{polochonList.map((el, index) => (
|
{polochonList.map((el, index) => (
|
||||||
<option
|
<option value={el.get("id")} key={index}>
|
||||||
value={el.get("id")}
|
|
||||||
key={index}>
|
|
||||||
{el.get("name")} ({el.get("url")})
|
{el.get("name")} ({el.get("url")})
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
PolochonSelect.propTypes = {
|
PolochonSelect.propTypes = {
|
||||||
value: PropTypes.string,
|
value: PropTypes.string,
|
||||||
changeValue: PropTypes.func,
|
changeValue: PropTypes.func,
|
||||||
polochonList: PropTypes.instanceOf(List),
|
polochonList: PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import React, { useState, useEffect } from "react"
|
import React, { useState, useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { editPolochonUser } from "../../actions/polochon"
|
import { editPolochonUser } from "../../actions/polochon";
|
||||||
|
|
||||||
import Toggle from "react-bootstrap-toggle";
|
import Toggle from "react-bootstrap-toggle";
|
||||||
|
|
||||||
import { FormModal } from "../forms/modal"
|
import { FormModal } from "../forms/modal";
|
||||||
import { FormInput } from "../forms/input"
|
import { FormInput } from "../forms/input";
|
||||||
|
|
||||||
export const PolochonUserConnected = ({
|
export const PolochonUserConnected = ({
|
||||||
polochonId,
|
polochonId,
|
||||||
@ -15,7 +15,7 @@ export const PolochonUserConnected = ({
|
|||||||
name,
|
name,
|
||||||
initialToken,
|
initialToken,
|
||||||
initialActivated,
|
initialActivated,
|
||||||
editPolochonUser,
|
editPolochonUser
|
||||||
}) => {
|
}) => {
|
||||||
const [edit, setEdit] = useState(false);
|
const [edit, setEdit] = useState(false);
|
||||||
const [token, setToken] = useState(initialToken);
|
const [token, setToken] = useState(initialToken);
|
||||||
@ -24,26 +24,22 @@ export const PolochonUserConnected = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActivated(initialActivated);
|
setActivated(initialActivated);
|
||||||
setToken(initialToken);
|
setToken(initialToken);
|
||||||
}, [initialActivated, initialToken])
|
}, [initialActivated, initialToken]);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
editPolochonUser({
|
editPolochonUser({
|
||||||
polochonId,
|
polochonId,
|
||||||
id,
|
id,
|
||||||
token,
|
token,
|
||||||
activated,
|
activated
|
||||||
});
|
});
|
||||||
setEdit(false);
|
setEdit(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>{name}</td>
|
||||||
{name}
|
<td>{activated ? "Activated" : "Not activated"}</td>
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{activated ? "Activated" : "Not activated"}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<i className="fa fa-edit ml-2" onClick={() => setEdit(true)} />
|
<i className="fa fa-edit ml-2" onClick={() => setEdit(true)} />
|
||||||
<FormModal
|
<FormModal
|
||||||
@ -63,20 +59,23 @@ export const PolochonUserConnected = ({
|
|||||||
active={activated}
|
active={activated}
|
||||||
offstyle="info"
|
offstyle="info"
|
||||||
handlestyle="secondary"
|
handlestyle="secondary"
|
||||||
onClick={() => setActivated(!activated)} />
|
onClick={() => setActivated(!activated)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</FormModal>
|
</FormModal>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
PolochonUserConnected.propTypes = {
|
PolochonUserConnected.propTypes = {
|
||||||
polochonId: PropTypes.string,
|
polochonId: PropTypes.string,
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
name: PropTypes.string,
|
name: PropTypes.string,
|
||||||
initialToken: PropTypes.string,
|
initialToken: PropTypes.string,
|
||||||
initialActivated: PropTypes.bool,
|
initialActivated: PropTypes.bool,
|
||||||
editPolochonUser: PropTypes.func,
|
editPolochonUser: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PolochonUser = connect(null, {editPolochonUser})(PolochonUserConnected);
|
export const PolochonUser = connect(null, { editPolochonUser })(
|
||||||
|
PolochonUserConnected
|
||||||
|
);
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
|
|
||||||
import { PolochonUser } from "./user"
|
import { PolochonUser } from "./user";
|
||||||
|
|
||||||
export const PolochonUsers = ({ id, users }) => {
|
export const PolochonUsers = ({ id, users }) => {
|
||||||
if (users === null || users.size === 0) { return null }
|
if (users === null || users.size === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<table className="table border border-light table-dark">
|
<table className="table border border-light table-dark">
|
||||||
@ -29,9 +31,9 @@ export const PolochonUsers = ({ id, users }) => {
|
|||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
PolochonUsers.propTypes = {
|
PolochonUsers.propTypes = {
|
||||||
id: PropTypes.string,
|
id: PropTypes.string,
|
||||||
users: PropTypes.instanceOf(List),
|
users: PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Map } from "immutable"
|
import { Map } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import Loader from "../loader/loader"
|
import Loader from "../loader/loader";
|
||||||
|
|
||||||
import { Fanart } from "./details/fanart"
|
import { Fanart } from "./details/fanart";
|
||||||
import { Header } from "./details/header"
|
import { Header } from "./details/header";
|
||||||
import { SeasonsList } from "./details/seasons"
|
import { SeasonsList } from "./details/seasons";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
loading: state.showStore.get("loading"),
|
loading: state.showStore.get("loading"),
|
||||||
show: state.showStore.get("show"),
|
show: state.showStore.get("show")
|
||||||
})
|
});
|
||||||
|
|
||||||
const showDetails = ({ show, loading }) => {
|
const showDetails = ({ show, loading }) => {
|
||||||
if (loading === true) {
|
if (loading === true) {
|
||||||
return (<Loader />);
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -28,9 +28,9 @@ const showDetails = ({ show, loading }) => {
|
|||||||
</div>
|
</div>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
showDetails.propTypes = {
|
showDetails.propTypes = {
|
||||||
loading: PropTypes.bool,
|
loading: PropTypes.bool,
|
||||||
show: PropTypes.instanceOf(Map),
|
show: PropTypes.instanceOf(Map)
|
||||||
};
|
};
|
||||||
export const ShowDetails = connect(mapStateToProps)(showDetails);
|
export const ShowDetails = connect(mapStateToProps)(showDetails);
|
||||||
|
@ -1,31 +1,35 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Map } from "immutable"
|
import { Map } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { showWishlistToggle } from "../../../actions/shows"
|
import { showWishlistToggle } from "../../../actions/shows";
|
||||||
|
|
||||||
import { inLibrary, isEpisodeWishlisted, prettyEpisodeName } from "../../../utils"
|
import {
|
||||||
|
inLibrary,
|
||||||
|
isEpisodeWishlisted,
|
||||||
|
prettyEpisodeName
|
||||||
|
} from "../../../utils";
|
||||||
|
|
||||||
import { Plot } from "../../details/plot"
|
import { Plot } from "../../details/plot";
|
||||||
import { PolochonMetadata } from "../../details/polochon"
|
import { PolochonMetadata } from "../../details/polochon";
|
||||||
import { ReleaseDate } from "../../details/releaseDate"
|
import { ReleaseDate } from "../../details/releaseDate";
|
||||||
import { Runtime } from "../../details/runtime"
|
import { Runtime } from "../../details/runtime";
|
||||||
import { Title } from "../../details/title"
|
import { Title } from "../../details/title";
|
||||||
|
|
||||||
import { DownloadAndStream } from "../../buttons/download"
|
import { DownloadAndStream } from "../../buttons/download";
|
||||||
import { ShowMore } from "../../buttons/showMore"
|
import { ShowMore } from "../../buttons/showMore";
|
||||||
|
|
||||||
import { EpisodeSubtitlesButton } from "./subtitlesButton"
|
import { EpisodeSubtitlesButton } from "./subtitlesButton";
|
||||||
import { EpisodeThumb } from "./episodeThumb"
|
import { EpisodeThumb } from "./episodeThumb";
|
||||||
import { EpisodeTorrentsButton } from "./torrentsButton"
|
import { EpisodeTorrentsButton } from "./torrentsButton";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
trackedSeason: state.showStore.getIn(["show", "tracked_season"], null),
|
trackedSeason: state.showStore.getIn(["show", "tracked_season"], null),
|
||||||
trackedEpisode: state.showStore.getIn(["show", "tracked_episode"], null),
|
trackedEpisode: state.showStore.getIn(["show", "tracked_episode"], null)
|
||||||
})
|
});
|
||||||
|
|
||||||
const episode = (props) => (
|
const episode = props => (
|
||||||
<div className="d-flex flex-column flex-lg-row mb-3 pb-3 border-bottom border-light">
|
<div className="d-flex flex-column flex-lg-row mb-3 pb-3 border-bottom border-light">
|
||||||
<EpisodeThumb url={props.data.get("thumb")} />
|
<EpisodeThumb url={props.data.get("thumb")} />
|
||||||
<div className="d-flex flex-column">
|
<div className="d-flex flex-column">
|
||||||
@ -34,20 +38,26 @@ const episode = (props) => (
|
|||||||
wishlisted={isEpisodeWishlisted(
|
wishlisted={isEpisodeWishlisted(
|
||||||
props.data,
|
props.data,
|
||||||
props.trackedSeason,
|
props.trackedSeason,
|
||||||
props.trackedEpisode,
|
props.trackedEpisode
|
||||||
)}
|
)}
|
||||||
wishlist={() => props.showWishlistToggle(
|
wishlist={() =>
|
||||||
|
props.showWishlistToggle(
|
||||||
isEpisodeWishlisted(props.data),
|
isEpisodeWishlisted(props.data),
|
||||||
props.data.get("show_imdb_id"),
|
props.data.get("show_imdb_id"),
|
||||||
props.data.get("season"),
|
props.data.get("season"),
|
||||||
props.data.get("episode"),
|
props.data.get("episode")
|
||||||
)}
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<ReleaseDate date={props.data.get("aired")} />
|
<ReleaseDate date={props.data.get("aired")} />
|
||||||
<Runtime runtime={props.data.get("runtime")} />
|
<Runtime runtime={props.data.get("runtime")} />
|
||||||
<Plot plot={props.data.get("plot")} />
|
<Plot plot={props.data.get("plot")} />
|
||||||
<DownloadAndStream
|
<DownloadAndStream
|
||||||
name={prettyEpisodeName(props.showName, props.data.get("season"), props.data.get("episode"))}
|
name={prettyEpisodeName(
|
||||||
|
props.showName,
|
||||||
|
props.data.get("season"),
|
||||||
|
props.data.get("episode")
|
||||||
|
)}
|
||||||
url={props.data.get("polochon_url")}
|
url={props.data.get("polochon_url")}
|
||||||
subtitles={props.data.get("subtitles")}
|
subtitles={props.data.get("subtitles")}
|
||||||
/>
|
/>
|
||||||
@ -60,7 +70,11 @@ const episode = (props) => (
|
|||||||
light
|
light
|
||||||
/>
|
/>
|
||||||
<ShowMore
|
<ShowMore
|
||||||
id={prettyEpisodeName(props.showName, props.data.get("season"), props.data.get("episode"))}
|
id={prettyEpisodeName(
|
||||||
|
props.showName,
|
||||||
|
props.data.get("season"),
|
||||||
|
props.data.get("episode")
|
||||||
|
)}
|
||||||
inLibrary={inLibrary(props.data)}
|
inLibrary={inLibrary(props.data)}
|
||||||
>
|
>
|
||||||
<EpisodeTorrentsButton
|
<EpisodeTorrentsButton
|
||||||
@ -82,13 +96,15 @@ const episode = (props) => (
|
|||||||
</ShowMore>
|
</ShowMore>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
episode.propTypes = {
|
episode.propTypes = {
|
||||||
data: PropTypes.instanceOf(Map).isRequired,
|
data: PropTypes.instanceOf(Map).isRequired,
|
||||||
trackedSeason: PropTypes.number,
|
trackedSeason: PropTypes.number,
|
||||||
trackedEpisode: PropTypes.number,
|
trackedEpisode: PropTypes.number,
|
||||||
showName: PropTypes.string.isRequired,
|
showName: PropTypes.string.isRequired,
|
||||||
showWishlistToggle: PropTypes.func,
|
showWishlistToggle: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Episode = connect(mapStateToProps, {showWishlistToggle})(episode);
|
export const Episode = connect(mapStateToProps, { showWishlistToggle })(
|
||||||
|
episode
|
||||||
|
);
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const EpisodeThumb = ({ url }) => {
|
export const EpisodeThumb = ({ url }) => {
|
||||||
if (url === "") { return null }
|
if (url === "") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className="mr-0 mr-lg-2 mb-2 mb-lg-0 episode-thumb">
|
<div className="mr-0 mr-lg-2 mb-2 mb-lg-0 episode-thumb">
|
||||||
<img src={url} />
|
<img src={url} />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
EpisodeThumb.propTypes = { url: PropTypes.string };
|
EpisodeThumb.propTypes = { url: PropTypes.string };
|
||||||
EpisodeThumb.defaultProps = { url: "" };
|
EpisodeThumb.defaultProps = { url: "" };
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
export const Fanart = ({ url }) => (
|
export const Fanart = ({ url }) => (
|
||||||
<div className="show-fanart mx-n3 mt-n1">
|
<div className="show-fanart mx-n3 mt-n1">
|
||||||
@ -8,7 +8,7 @@ export const Fanart = ({ url }) => (
|
|||||||
src={url}
|
src={url}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
Fanart.propTypes = {
|
Fanart.propTypes = {
|
||||||
url: PropTypes.string,
|
url: PropTypes.string
|
||||||
}
|
};
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Map } from "immutable"
|
import { Map } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { isWishlisted } from "../../../utils"
|
import { isWishlisted } from "../../../utils";
|
||||||
|
|
||||||
import { showWishlistToggle } from "../../../actions/shows"
|
import { showWishlistToggle } from "../../../actions/shows";
|
||||||
|
|
||||||
import { Plot } from "../../details/plot"
|
import { Plot } from "../../details/plot";
|
||||||
import { Rating } from "../../details/rating"
|
import { Rating } from "../../details/rating";
|
||||||
import { ReleaseDate } from "../../details/releaseDate"
|
import { ReleaseDate } from "../../details/releaseDate";
|
||||||
import { Title } from "../../details/title"
|
import { Title } from "../../details/title";
|
||||||
import { TrackingLabel } from "../../details/tracking"
|
import { TrackingLabel } from "../../details/tracking";
|
||||||
|
|
||||||
import { ImdbBadge } from "../../buttons/imdb"
|
import { ImdbBadge } from "../../buttons/imdb";
|
||||||
|
|
||||||
export const header = (props) => (
|
export const header = props => (
|
||||||
<div className="card col-12 col-md-10 offset-md-1 mt-n3 mb-3">
|
<div className="card col-12 col-md-10 offset-md-1 mt-n3 mb-3">
|
||||||
<div className="d-flex flex-column flex-md-row">
|
<div className="d-flex flex-column flex-md-row">
|
||||||
<div className="d-flex justify-content-center">
|
<div className="d-flex justify-content-center">
|
||||||
@ -30,9 +30,12 @@ export const header = (props) => (
|
|||||||
<Title
|
<Title
|
||||||
title={props.data.get("title")}
|
title={props.data.get("title")}
|
||||||
wishlisted={isWishlisted(props.data)}
|
wishlisted={isWishlisted(props.data)}
|
||||||
wishlist={() => props.showWishlistToggle(
|
wishlist={() =>
|
||||||
isWishlisted(props.data), props.data.get("imdb_id"),
|
props.showWishlistToggle(
|
||||||
)}
|
isWishlisted(props.data),
|
||||||
|
props.data.get("imdb_id")
|
||||||
|
)
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<p className="card-text">
|
<p className="card-text">
|
||||||
@ -61,7 +64,7 @@ export const header = (props) => (
|
|||||||
);
|
);
|
||||||
header.propTypes = {
|
header.propTypes = {
|
||||||
data: PropTypes.instanceOf(Map),
|
data: PropTypes.instanceOf(Map),
|
||||||
showWishlistToggle: PropTypes.func,
|
showWishlistToggle: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Header = connect(null, {showWishlistToggle})(header);
|
export const Header = connect(null, { showWishlistToggle })(header);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Map } from "immutable"
|
import { Map } from "immutable";
|
||||||
|
|
||||||
import { Episode } from "./episode"
|
import { Episode } from "./episode";
|
||||||
|
|
||||||
export const Season = (props) => {
|
export const Season = props => {
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -12,8 +12,13 @@ export const Season = (props) => {
|
|||||||
<div className="card-header clickable" onClick={() => setShow(!show)}>
|
<div className="card-header clickable" onClick={() => setShow(!show)}>
|
||||||
<h5 className="m-0">
|
<h5 className="m-0">
|
||||||
Season {props.season}
|
Season {props.season}
|
||||||
<small className="text-primary"> — ({props.data.toList().size} episodes)</small>
|
<small className="text-primary">
|
||||||
<i className={`float-right fa fa-chevron-${show ? "down" : "left"}`}></i>
|
{" "}
|
||||||
|
— ({props.data.toList().size} episodes)
|
||||||
|
</small>
|
||||||
|
<i
|
||||||
|
className={`float-right fa fa-chevron-${show ? "down" : "left"}`}
|
||||||
|
></i>
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div className={`card-body ${show ? "d-flex flex-column" : "d-none"}`}>
|
<div className={`card-body ${show ? "d-flex flex-column" : "d-none"}`}>
|
||||||
@ -29,12 +34,12 @@ export const Season = (props) => {
|
|||||||
getEpisodeDetails={props.getEpisodeDetails}
|
getEpisodeDetails={props.getEpisodeDetails}
|
||||||
refreshSubtitles={props.refreshSubtitles}
|
refreshSubtitles={props.refreshSubtitles}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
Season.propTypes = {
|
Season.propTypes = {
|
||||||
data: PropTypes.instanceOf(Map),
|
data: PropTypes.instanceOf(Map),
|
||||||
season: PropTypes.number,
|
season: PropTypes.number,
|
||||||
@ -42,5 +47,5 @@ Season.propTypes = {
|
|||||||
addToWishlist: PropTypes.func,
|
addToWishlist: PropTypes.func,
|
||||||
addTorrent: PropTypes.func,
|
addTorrent: PropTypes.func,
|
||||||
refreshSubtitles: PropTypes.func,
|
refreshSubtitles: PropTypes.func,
|
||||||
getEpisodeDetails: PropTypes.func,
|
getEpisodeDetails: PropTypes.func
|
||||||
};
|
};
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Map } from "immutable"
|
import { Map } from "immutable";
|
||||||
|
|
||||||
import { Season } from "./season"
|
import { Season } from "./season";
|
||||||
|
|
||||||
export const SeasonsList = (props) => (
|
export const SeasonsList = props => (
|
||||||
<div className="col col-12 col-md-10 offset-md-1">
|
<div className="col col-12 col-md-10 offset-md-1">
|
||||||
{props.data.get("seasons").entrySeq().map(function([season, data]) {
|
{props.data
|
||||||
if (season === 0) { return null }
|
.get("seasons")
|
||||||
|
.entrySeq()
|
||||||
|
.map(function([season, data]) {
|
||||||
|
if (season === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Season
|
<Season
|
||||||
key={`season-list-key-${season}`}
|
key={`season-list-key-${season}`}
|
||||||
@ -28,5 +33,5 @@ SeasonsList.propTypes = {
|
|||||||
addToWishlist: PropTypes.func,
|
addToWishlist: PropTypes.func,
|
||||||
addTorrent: PropTypes.func,
|
addTorrent: PropTypes.func,
|
||||||
refreshSubtitles: PropTypes.func,
|
refreshSubtitles: PropTypes.func,
|
||||||
getEpisodeDetails: PropTypes.func,
|
getEpisodeDetails: PropTypes.func
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { searchEpisodeSubtitles } from "../../../actions/subtitles"
|
import { searchEpisodeSubtitles } from "../../../actions/subtitles";
|
||||||
|
|
||||||
import { SubtitlesButton } from "../../buttons/subtitles"
|
import { SubtitlesButton } from "../../buttons/subtitles";
|
||||||
|
|
||||||
const episodeSubtitlesButton = ({
|
const episodeSubtitlesButton = ({
|
||||||
inLibrary,
|
inLibrary,
|
||||||
@ -14,7 +14,7 @@ const episodeSubtitlesButton = ({
|
|||||||
episode,
|
episode,
|
||||||
searching,
|
searching,
|
||||||
searchEpisodeSubtitles,
|
searchEpisodeSubtitles,
|
||||||
subtitles,
|
subtitles
|
||||||
}) => (
|
}) => (
|
||||||
<SubtitlesButton
|
<SubtitlesButton
|
||||||
subtitles={subtitles}
|
subtitles={subtitles}
|
||||||
@ -22,7 +22,7 @@ const episodeSubtitlesButton = ({
|
|||||||
searching={searching}
|
searching={searching}
|
||||||
search={() => searchEpisodeSubtitles(imdbId, season, episode)}
|
search={() => searchEpisodeSubtitles(imdbId, season, episode)}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
|
|
||||||
episodeSubtitlesButton.propTypes = {
|
episodeSubtitlesButton.propTypes = {
|
||||||
inLibrary: PropTypes.bool,
|
inLibrary: PropTypes.bool,
|
||||||
@ -31,7 +31,9 @@ episodeSubtitlesButton.propTypes = {
|
|||||||
season: PropTypes.number,
|
season: PropTypes.number,
|
||||||
episode: PropTypes.number,
|
episode: PropTypes.number,
|
||||||
searchEpisodeSubtitles: PropTypes.func,
|
searchEpisodeSubtitles: PropTypes.func,
|
||||||
subtitles: PropTypes.instanceOf(List),
|
subtitles: PropTypes.instanceOf(List)
|
||||||
}
|
};
|
||||||
|
|
||||||
export const EpisodeSubtitlesButton = connect(null, {searchEpisodeSubtitles})(episodeSubtitlesButton);
|
export const EpisodeSubtitlesButton = connect(null, { searchEpisodeSubtitles })(
|
||||||
|
episodeSubtitlesButton
|
||||||
|
);
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
|
|
||||||
import { getEpisodeDetails } from "../../../actions/shows"
|
import { getEpisodeDetails } from "../../../actions/shows";
|
||||||
import { TorrentsButton } from "../../buttons/torrents"
|
import { TorrentsButton } from "../../buttons/torrents";
|
||||||
import { prettyEpisodeName } from "../../../utils"
|
import { prettyEpisodeName } from "../../../utils";
|
||||||
|
|
||||||
const episodeTorrentsButton = ({
|
const episodeTorrentsButton = ({
|
||||||
torrents,
|
torrents,
|
||||||
@ -14,17 +14,17 @@ const episodeTorrentsButton = ({
|
|||||||
episode,
|
episode,
|
||||||
showName,
|
showName,
|
||||||
searching,
|
searching,
|
||||||
getEpisodeDetails,
|
getEpisodeDetails
|
||||||
}) => (
|
}) => (
|
||||||
<TorrentsButton
|
<TorrentsButton
|
||||||
torrents={torrents}
|
torrents={torrents}
|
||||||
searching={searching}
|
searching={searching}
|
||||||
search={() => getEpisodeDetails(imdbId, season, episode)}
|
search={() => getEpisodeDetails(imdbId, season, episode)}
|
||||||
url={`#/torrents/search/shows/${
|
url={`#/torrents/search/shows/${encodeURI(
|
||||||
encodeURI(prettyEpisodeName(showName, season, episode))
|
prettyEpisodeName(showName, season, episode)
|
||||||
}`}
|
)}`}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
episodeTorrentsButton.propTypes = {
|
episodeTorrentsButton.propTypes = {
|
||||||
torrents: PropTypes.instanceOf(List),
|
torrents: PropTypes.instanceOf(List),
|
||||||
showName: PropTypes.string.isRequired,
|
showName: PropTypes.string.isRequired,
|
||||||
@ -32,7 +32,9 @@ episodeTorrentsButton.propTypes = {
|
|||||||
episode: PropTypes.number.isRequired,
|
episode: PropTypes.number.isRequired,
|
||||||
season: PropTypes.number.isRequired,
|
season: PropTypes.number.isRequired,
|
||||||
searching: PropTypes.bool.isRequired,
|
searching: PropTypes.bool.isRequired,
|
||||||
getEpisodeDetails: PropTypes.func.isRequired,
|
getEpisodeDetails: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EpisodeTorrentsButton = connect(null, {getEpisodeDetails})(episodeTorrentsButton);
|
export const EpisodeTorrentsButton = connect(null, { getEpisodeDetails })(
|
||||||
|
episodeTorrentsButton
|
||||||
|
);
|
||||||
|
@ -1,33 +1,39 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Map } from "immutable"
|
import { Map } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { selectShow, showWishlistToggle,
|
import {
|
||||||
getShowDetails, updateFilter } from "../../actions/shows"
|
selectShow,
|
||||||
|
showWishlistToggle,
|
||||||
|
getShowDetails,
|
||||||
|
updateFilter
|
||||||
|
} from "../../actions/shows";
|
||||||
|
|
||||||
import { isWishlisted } from "../../utils"
|
import { isWishlisted } from "../../utils";
|
||||||
|
|
||||||
import ListDetails from "../list/details"
|
import ListDetails from "../list/details";
|
||||||
import ListPosters from "../list/posters"
|
import ListPosters from "../list/posters";
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
loading : state.showsStore.get("loading"),
|
loading: state.showsStore.get("loading"),
|
||||||
shows : state.showsStore.get("shows"),
|
shows: state.showsStore.get("shows"),
|
||||||
filter : state.showsStore.get("filter"),
|
filter: state.showsStore.get("filter"),
|
||||||
selectedImdbId : state.showsStore.get("selectedImdbId"),
|
selectedImdbId: state.showsStore.get("selectedImdbId"),
|
||||||
exploreOptions : state.showsStore.get("exploreOptions"),
|
exploreOptions: state.showsStore.get("exploreOptions")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
selectShow, showWishlistToggle,
|
selectShow,
|
||||||
getShowDetails, updateFilter,
|
showWishlistToggle,
|
||||||
|
getShowDetails,
|
||||||
|
updateFilter
|
||||||
};
|
};
|
||||||
|
|
||||||
const ShowList = (props) => {
|
const ShowList = props => {
|
||||||
const showDetails = (imdbId) => {
|
const showDetails = imdbId => {
|
||||||
props.history.push("/shows/details/" + imdbId);
|
props.history.push("/shows/details/" + imdbId);
|
||||||
}
|
};
|
||||||
|
|
||||||
let selectedShow = Map();
|
let selectedShow = Map();
|
||||||
if (props.selectedImdbId !== "") {
|
if (props.selectedImdbId !== "") {
|
||||||
@ -53,14 +59,18 @@ const ShowList = (props) => {
|
|||||||
<ListDetails
|
<ListDetails
|
||||||
data={selectedShow}
|
data={selectedShow}
|
||||||
loading={props.loading}
|
loading={props.loading}
|
||||||
wishlist={() => props.showWishlistToggle(
|
wishlist={() =>
|
||||||
isWishlisted(selectedShow), selectedShow.get("imdb_id"),
|
props.showWishlistToggle(
|
||||||
)}
|
isWishlisted(selectedShow),
|
||||||
|
selectedShow.get("imdb_id")
|
||||||
|
)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
<button onClick={
|
<button
|
||||||
() => showDetails(selectedShow.get("imdb_id"))}
|
onClick={() => showDetails(selectedShow.get("imdb_id"))}
|
||||||
className="btn btn-primary btn-sm w-md-100">
|
className="btn btn-primary btn-sm w-md-100"
|
||||||
|
>
|
||||||
<i className="fa fa-external-link mr-1" />
|
<i className="fa fa-external-link mr-1" />
|
||||||
Details
|
Details
|
||||||
</button>
|
</button>
|
||||||
@ -68,7 +78,7 @@ const ShowList = (props) => {
|
|||||||
</ListDetails>
|
</ListDetails>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
ShowList.propTypes = {
|
ShowList.propTypes = {
|
||||||
match: PropTypes.object,
|
match: PropTypes.object,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
@ -80,6 +90,6 @@ ShowList.propTypes = {
|
|||||||
showWishlistToggle: PropTypes.func,
|
showWishlistToggle: PropTypes.func,
|
||||||
selectShow: PropTypes.func,
|
selectShow: PropTypes.func,
|
||||||
getShowDetails: PropTypes.func,
|
getShowDetails: PropTypes.func,
|
||||||
updateFilter: PropTypes.func,
|
updateFilter: PropTypes.func
|
||||||
};
|
};
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ShowList);
|
export default connect(mapStateToProps, mapDispatchToProps)(ShowList);
|
||||||
|
@ -1,13 +1,21 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Route } from "react-router-dom"
|
import { Route } from "react-router-dom";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { fetchShows, fetchShowDetails, getShowExploreOptions } from "../../actions/shows"
|
import {
|
||||||
|
fetchShows,
|
||||||
|
fetchShowDetails,
|
||||||
|
getShowExploreOptions
|
||||||
|
} from "../../actions/shows";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
isExplorerFetched: (state.showsStore.get("exploreOptions").size !== 0)
|
isExplorerFetched: state.showsStore.get("exploreOptions").size !== 0
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = { fetchShows, fetchShowDetails, getShowExploreOptions };
|
const mapDispatchToProps = {
|
||||||
|
fetchShows,
|
||||||
|
fetchShowDetails,
|
||||||
|
getShowExploreOptions
|
||||||
|
};
|
||||||
|
|
||||||
const ShowsRoute = ({
|
const ShowsRoute = ({
|
||||||
component: Component,
|
component: Component,
|
||||||
@ -18,7 +26,9 @@ const ShowsRoute = ({
|
|||||||
...otherProps
|
...otherProps
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<Route {...otherProps} render={(props) => {
|
<Route
|
||||||
|
{...otherProps}
|
||||||
|
render={props => {
|
||||||
let fetchUrl = "";
|
let fetchUrl = "";
|
||||||
switch (props.match.path) {
|
switch (props.match.path) {
|
||||||
case "/shows/polochon":
|
case "/shows/polochon":
|
||||||
@ -34,30 +44,33 @@ const ShowsRoute = ({
|
|||||||
if (!isExplorerFetched) {
|
if (!isExplorerFetched) {
|
||||||
getShowExploreOptions();
|
getShowExploreOptions();
|
||||||
}
|
}
|
||||||
fetchUrl = "/shows/explore?source=" +
|
fetchUrl =
|
||||||
|
"/shows/explore?source=" +
|
||||||
encodeURI(props.match.params.source) +
|
encodeURI(props.match.params.source) +
|
||||||
"&category=" + encodeURI(props.match.params.category);
|
"&category=" +
|
||||||
|
encodeURI(props.match.params.category);
|
||||||
break;
|
break;
|
||||||
case "/shows/details/:imdbId":
|
case "/shows/details/:imdbId":
|
||||||
fetchShowDetails(props.match.params.imdbId);
|
fetchShowDetails(props.match.params.imdbId);
|
||||||
fetchUrl = ""
|
fetchUrl = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fetchUrl != "") {
|
if (fetchUrl != "") {
|
||||||
fetchShows(fetchUrl);
|
fetchShows(fetchUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Component {...props} />
|
return <Component {...props} />;
|
||||||
}} />
|
}}
|
||||||
)
|
/>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
ShowsRoute.propTypes = {
|
ShowsRoute.propTypes = {
|
||||||
component: PropTypes.func,
|
component: PropTypes.func,
|
||||||
match: PropTypes.object,
|
match: PropTypes.object,
|
||||||
isExplorerFetched: PropTypes.bool.isRequired,
|
isExplorerFetched: PropTypes.bool.isRequired,
|
||||||
fetchShows: PropTypes.func.isRequired,
|
fetchShows: PropTypes.func.isRequired,
|
||||||
fetchShowDetails: PropTypes.func.isRequired,
|
fetchShowDetails: PropTypes.func.isRequired,
|
||||||
getShowExploreOptions: PropTypes.func.isRequired,
|
getShowExploreOptions: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ShowsRoute);
|
export default connect(mapStateToProps, mapDispatchToProps)(ShowsRoute);
|
||||||
|
@ -1,26 +1,29 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { Map, List } from "immutable"
|
import { Map, List } from "immutable";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
import { prettySize } from "../../utils"
|
import { prettySize } from "../../utils";
|
||||||
import { fetchTorrents, addTorrent, removeTorrent } from "../../actions/torrents"
|
import {
|
||||||
|
fetchTorrents,
|
||||||
|
addTorrent,
|
||||||
|
removeTorrent
|
||||||
|
} from "../../actions/torrents";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
torrents: state.torrentStore.get("torrents")
|
torrents: state.torrentStore.get("torrents")
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
fetchTorrents, addTorrent, removeTorrent,
|
fetchTorrents,
|
||||||
|
addTorrent,
|
||||||
|
removeTorrent
|
||||||
};
|
};
|
||||||
|
|
||||||
const TorrentList = (props) => (
|
const TorrentList = props => (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<AddTorrent addTorrent={props.addTorrent} />
|
<AddTorrent addTorrent={props.addTorrent} />
|
||||||
<Torrents
|
<Torrents torrents={props.torrents} removeTorrent={props.removeTorrent} />
|
||||||
torrents={props.torrents}
|
|
||||||
removeTorrent={props.removeTorrent}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -28,38 +31,40 @@ TorrentList.propTypes = {
|
|||||||
fetchTorrents: PropTypes.func.isRequired,
|
fetchTorrents: PropTypes.func.isRequired,
|
||||||
addTorrent: PropTypes.func.isRequired,
|
addTorrent: PropTypes.func.isRequired,
|
||||||
removeTorrent: PropTypes.func.isRequired,
|
removeTorrent: PropTypes.func.isRequired,
|
||||||
torrents: PropTypes.instanceOf(List),
|
torrents: PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(TorrentList);
|
export default connect(mapStateToProps, mapDispatchToProps)(TorrentList);
|
||||||
|
|
||||||
const AddTorrent = (props) => {
|
const AddTorrent = props => {
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (url === "") { return; }
|
if (url === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
props.addTorrent(url);
|
props.addTorrent(url);
|
||||||
setUrl("");
|
setUrl("");
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={(e) => handleSubmit(e)}>
|
<form onSubmit={e => handleSubmit(e)}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control mb-3 w-100"
|
className="form-control mb-3 w-100"
|
||||||
placeholder="Add torrent URL"
|
placeholder="Add torrent URL"
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
value={url}
|
value={url}
|
||||||
onChange={(e) => setUrl(e.target.value)}
|
onChange={e => setUrl(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
AddTorrent.propTypes = {
|
AddTorrent.propTypes = {
|
||||||
addTorrent: PropTypes.func.isRequired,
|
addTorrent: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const Torrents = (props) => {
|
const Torrents = props => {
|
||||||
if (props.torrents.size === 0) {
|
if (props.torrents.size === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="jumbotron">
|
<div className="jumbotron">
|
||||||
@ -71,31 +76,29 @@ const Torrents = (props) => {
|
|||||||
return (
|
return (
|
||||||
<div className="d-flex flex-wrap">
|
<div className="d-flex flex-wrap">
|
||||||
{props.torrents.map((el, index) => (
|
{props.torrents.map((el, index) => (
|
||||||
<Torrent
|
<Torrent key={index} data={el} removeTorrent={props.removeTorrent} />
|
||||||
key={index}
|
|
||||||
data={el}
|
|
||||||
removeTorrent={props.removeTorrent}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Torrents.propTypes = {
|
Torrents.propTypes = {
|
||||||
removeTorrent: PropTypes.func.isRequired,
|
removeTorrent: PropTypes.func.isRequired,
|
||||||
torrents: PropTypes.instanceOf(List),
|
torrents: PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
|
|
||||||
const Torrent = (props) => {
|
const Torrent = props => {
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
props.removeTorrent(props.data.get("id"));
|
props.removeTorrent(props.data.get("id"));
|
||||||
}
|
};
|
||||||
|
|
||||||
const done = props.data.get("is_finished");
|
const done = props.data.get("is_finished");
|
||||||
var progressStyle = done ? "success" : "info progress-bar-striped progress-bar-animated";
|
var progressStyle = done
|
||||||
|
? "success"
|
||||||
|
: "info progress-bar-striped progress-bar-animated";
|
||||||
const progressBarClass = "progress-bar bg-" + progressStyle;
|
const progressBarClass = "progress-bar bg-" + progressStyle;
|
||||||
|
|
||||||
var percentDone = props.data.get("percent_done");
|
var percentDone = props.data.get("percent_done");
|
||||||
const started = (percentDone !== 0);
|
const started = percentDone !== 0;
|
||||||
if (started) {
|
if (started) {
|
||||||
percentDone = Number(percentDone).toFixed(1) + "%";
|
percentDone = Number(percentDone).toFixed(1) + "%";
|
||||||
}
|
}
|
||||||
@ -108,32 +111,35 @@ const Torrent = (props) => {
|
|||||||
<div className="card w-100 mb-3">
|
<div className="card w-100 mb-3">
|
||||||
<h5 className="card-header">
|
<h5 className="card-header">
|
||||||
<span className="text text-break">{props.data.get("name")}</span>
|
<span className="text text-break">{props.data.get("name")}</span>
|
||||||
<span className="fa fa-trash clickable pull-right" onClick={() => handleClick()}></span>
|
<span
|
||||||
|
className="fa fa-trash clickable pull-right"
|
||||||
|
onClick={() => handleClick()}
|
||||||
|
></span>
|
||||||
</h5>
|
</h5>
|
||||||
<div className="card-body pb-0">
|
<div className="card-body pb-0">
|
||||||
{started &&
|
{started && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className="progress bg-light">
|
<div className="progress bg-light">
|
||||||
<div
|
<div
|
||||||
className={progressBarClass}
|
className={progressBarClass}
|
||||||
style={{width: percentDone}}
|
style={{ width: percentDone }}
|
||||||
role="progressbar"
|
role="progressbar"
|
||||||
aria-valuenow={percentDone}
|
aria-valuenow={percentDone}
|
||||||
aria-valuemin="0"
|
aria-valuemin="0"
|
||||||
aria-valuemax="100"
|
aria-valuemax="100"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<p>{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}</p>
|
<p>
|
||||||
|
{downloadedSize} / {totalSize} - {percentDone} - {downloadRate}
|
||||||
|
</p>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
}
|
)}
|
||||||
{!started &&
|
{!started && <p>Download not yet started</p>}
|
||||||
<p>Download not yet started</p>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Torrent.propTypes = {
|
Torrent.propTypes = {
|
||||||
removeTorrent: PropTypes.func.isRequired,
|
removeTorrent: PropTypes.func.isRequired,
|
||||||
data: PropTypes.instanceOf(Map),
|
data: PropTypes.instanceOf(Map)
|
||||||
};
|
};
|
||||||
|
@ -1,34 +1,37 @@
|
|||||||
import React, { useState, useEffect } from "react"
|
import React, { useState, useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { addTorrent, searchTorrents } from "../../actions/torrents"
|
import { addTorrent, searchTorrents } from "../../actions/torrents";
|
||||||
import { Map, List } from "immutable"
|
import { Map, List } from "immutable";
|
||||||
import Loader from "../loader/loader"
|
import Loader from "../loader/loader";
|
||||||
|
|
||||||
import { OverlayTrigger, Tooltip } from "react-bootstrap"
|
import { OverlayTrigger, Tooltip } from "react-bootstrap";
|
||||||
|
|
||||||
import { prettySize } from "../../utils"
|
import { prettySize } from "../../utils";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
searching: state.torrentStore.get("searching"),
|
searching: state.torrentStore.get("searching"),
|
||||||
results: state.torrentStore.get("searchResults"),
|
results: state.torrentStore.get("searchResults")
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = { addTorrent, searchTorrents };
|
const mapDispatchToProps = { addTorrent, searchTorrents };
|
||||||
|
|
||||||
const TorrentSearch = (props) => {
|
const TorrentSearch = props => {
|
||||||
const [search, setSearch] = useState(props.match.params.search || "");
|
const [search, setSearch] = useState(props.match.params.search || "");
|
||||||
const [type, setType] = useState(props.match.params.type || "");
|
const [type, setType] = useState(props.match.params.type || "");
|
||||||
const [url, setUrl] = useState("");
|
const [url, setUrl] = useState("");
|
||||||
|
|
||||||
const getUrl = () =>
|
const getUrl = () => `/torrents/search/${type}/${encodeURI(search)}`;
|
||||||
`/torrents/search/${type}/${encodeURI(search)}`;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (search === "") { return }
|
if (search === "") {
|
||||||
if (type === "") { return }
|
return;
|
||||||
|
}
|
||||||
|
if (type === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const url = getUrl();
|
const url = getUrl();
|
||||||
props.searchTorrents(url)
|
props.searchTorrents(url);
|
||||||
props.history.push(url);
|
props.history.push(url);
|
||||||
}, [url]);
|
}, [url]);
|
||||||
|
|
||||||
@ -40,7 +43,7 @@ const TorrentSearch = (props) => {
|
|||||||
className="form-control mb-1 w-100 form-control-lg"
|
className="form-control mb-1 w-100 form-control-lg"
|
||||||
placeholder="Search torrents"
|
placeholder="Search torrents"
|
||||||
value={search}
|
value={search}
|
||||||
onChange={(e) => setSearch(e.target.value)}
|
onChange={e => setSearch(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className="mb-3 w-100 d-flex">
|
<div className="mb-3 w-100 d-flex">
|
||||||
<SearchButton
|
<SearchButton
|
||||||
@ -50,7 +53,8 @@ const TorrentSearch = (props) => {
|
|||||||
handleClick={() => {
|
handleClick={() => {
|
||||||
setType("movies");
|
setType("movies");
|
||||||
setUrl(getUrl());
|
setUrl(getUrl());
|
||||||
}}/>
|
}}
|
||||||
|
/>
|
||||||
<SearchButton
|
<SearchButton
|
||||||
text="Search shows"
|
text="Search shows"
|
||||||
type="shows"
|
type="shows"
|
||||||
@ -58,7 +62,8 @@ const TorrentSearch = (props) => {
|
|||||||
handleClick={() => {
|
handleClick={() => {
|
||||||
setType("shows");
|
setType("shows");
|
||||||
setUrl(getUrl());
|
setUrl(getUrl());
|
||||||
}}/>
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
@ -71,7 +76,7 @@ const TorrentSearch = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
TorrentSearch.propTypes = {
|
TorrentSearch.propTypes = {
|
||||||
searching: PropTypes.bool.isRequired,
|
searching: PropTypes.bool.isRequired,
|
||||||
results: PropTypes.instanceOf(List),
|
results: PropTypes.instanceOf(List),
|
||||||
@ -79,12 +84,11 @@ TorrentSearch.propTypes = {
|
|||||||
match: PropTypes.object,
|
match: PropTypes.object,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
addTorrent: PropTypes.func.isRequired,
|
addTorrent: PropTypes.func.isRequired,
|
||||||
searchTorrents: PropTypes.func.isRequired,
|
searchTorrents: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SearchButton = props => {
|
||||||
const SearchButton = (props) => {
|
const variant = props.type === props.typeFromURL ? "primary" : "secondary";
|
||||||
const variant = (props.type === props.typeFromURL) ? "primary" : "secondary";
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -94,17 +98,17 @@ const SearchButton = (props) => {
|
|||||||
<i className="fa fa-search" aria-hidden="true"></i> {props.text}
|
<i className="fa fa-search" aria-hidden="true"></i> {props.text}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
SearchButton.propTypes = {
|
SearchButton.propTypes = {
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
typeFromURL: PropTypes.string,
|
typeFromURL: PropTypes.string,
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
handleClick: PropTypes.func.isRequired,
|
handleClick: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const TorrentList = (props) => {
|
const TorrentList = props => {
|
||||||
if (props.searching) {
|
if (props.searching) {
|
||||||
return (<Loader />);
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.searchFromURL === "") {
|
if (props.searchFromURL === "") {
|
||||||
@ -122,59 +126,65 @@ const TorrentList = (props) => {
|
|||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{props.results.map(function(el, index) {
|
{props.results.map(function(el, index) {
|
||||||
return (
|
return <Torrent key={index} data={el} addTorrent={props.addTorrent} />;
|
||||||
<Torrent
|
|
||||||
key={index}
|
|
||||||
data={el}
|
|
||||||
addTorrent={props.addTorrent}
|
|
||||||
/>);
|
|
||||||
})}
|
})}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
TorrentList.propTypes = {
|
TorrentList.propTypes = {
|
||||||
searching: PropTypes.bool.isRequired,
|
searching: PropTypes.bool.isRequired,
|
||||||
results: PropTypes.instanceOf(List),
|
results: PropTypes.instanceOf(List),
|
||||||
searchFromURL: PropTypes.string,
|
searchFromURL: PropTypes.string,
|
||||||
addTorrent: PropTypes.func.isRequired,
|
addTorrent: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const Torrent = (props) => (
|
const Torrent = props => (
|
||||||
<div className="alert d-flex border-bottom border-secondary align-items-center">
|
<div className="alert d-flex border-bottom border-secondary align-items-center">
|
||||||
<TorrentHealth
|
<TorrentHealth
|
||||||
url={props.data.get("url")}
|
url={props.data.get("url")}
|
||||||
seeders={props.data.get("seeders")}
|
seeders={props.data.get("seeders")}
|
||||||
leechers={props.data.get("leechers")}
|
leechers={props.data.get("leechers")}
|
||||||
/>
|
/>
|
||||||
<span className="mx-3 text text-start text-break flex-fill">{props.data.get("name")}</span>
|
<span className="mx-3 text text-start text-break flex-fill">
|
||||||
|
{props.data.get("name")}
|
||||||
|
</span>
|
||||||
<div>
|
<div>
|
||||||
{props.data.get("size") !== 0 &&
|
{props.data.get("size") !== 0 && (
|
||||||
<span className="mx-1 badge badge-pill badge-warning">
|
<span className="mx-1 badge badge-pill badge-warning">
|
||||||
{prettySize(props.data.get("size"))}
|
{prettySize(props.data.get("size"))}
|
||||||
</span>
|
</span>
|
||||||
}
|
)}
|
||||||
|
|
||||||
<span className="mx-1 badge badge-pill badge-warning">{props.data.get("quality")}</span>
|
<span className="mx-1 badge badge-pill badge-warning">
|
||||||
<span className="mx-1 badge badge-pill badge-success">{props.data.get("source")}</span>
|
{props.data.get("quality")}
|
||||||
<span className="mx-1 badge badge-pill badge-info">{props.data.get("upload_user")}</span>
|
</span>
|
||||||
|
<span className="mx-1 badge badge-pill badge-success">
|
||||||
|
{props.data.get("source")}
|
||||||
|
</span>
|
||||||
|
<span className="mx-1 badge badge-pill badge-info">
|
||||||
|
{props.data.get("upload_user")}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="align-self-end ml-3">
|
<div className="align-self-end ml-3">
|
||||||
<i className="fa fa-cloud-download clickable" onClick={() => props.addTorrent(props.data.get("url"))}></i>
|
<i
|
||||||
|
className="fa fa-cloud-download clickable"
|
||||||
|
onClick={() => props.addTorrent(props.data.get("url"))}
|
||||||
|
></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
Torrent.propTypes = {
|
Torrent.propTypes = {
|
||||||
data: PropTypes.instanceOf(Map),
|
data: PropTypes.instanceOf(Map),
|
||||||
addTorrent: PropTypes.func.isRequired,
|
addTorrent: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const TorrentHealth = (props) => {
|
const TorrentHealth = props => {
|
||||||
const seeders = props.seeders || 0;
|
const seeders = props.seeders || 0;
|
||||||
const leechers = props.leechers || 1;
|
const leechers = props.leechers || 1;
|
||||||
|
|
||||||
let color;
|
let color;
|
||||||
let health;
|
let health;
|
||||||
let ratio = seeders/leechers;
|
let ratio = seeders / leechers;
|
||||||
|
|
||||||
if (seeders > 20) {
|
if (seeders > 20) {
|
||||||
health = "good";
|
health = "good";
|
||||||
@ -192,7 +202,9 @@ const TorrentHealth = (props) => {
|
|||||||
const className = `align-self-start text text-center text-${color}`;
|
const className = `align-self-start text text-center text-${color}`;
|
||||||
const tooltip = (
|
const tooltip = (
|
||||||
<Tooltip id={`tooltip-health-${props.url}`}>
|
<Tooltip id={`tooltip-health-${props.url}`}>
|
||||||
<p><span className={className}>Health: {health}</span></p>
|
<p>
|
||||||
|
<span className={className}>Health: {health}</span>
|
||||||
|
</p>
|
||||||
<p>Seeders: {seeders}</p>
|
<p>Seeders: {seeders}</p>
|
||||||
<p>Leechers: {props.leechers}</p>
|
<p>Leechers: {props.leechers}</p>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -205,11 +217,11 @@ const TorrentHealth = (props) => {
|
|||||||
</span>
|
</span>
|
||||||
</OverlayTrigger>
|
</OverlayTrigger>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
TorrentHealth.propTypes = {
|
TorrentHealth.propTypes = {
|
||||||
url: PropTypes.string,
|
url: PropTypes.string,
|
||||||
seeders: PropTypes.number,
|
seeders: PropTypes.number,
|
||||||
leechers: PropTypes.number,
|
leechers: PropTypes.number
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(TorrentSearch);
|
export default connect(mapStateToProps, mapDispatchToProps)(TorrentSearch);
|
||||||
|
@ -1,20 +1,20 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { Redirect, Link } from "react-router-dom"
|
import { Redirect, Link } from "react-router-dom";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
isActivated: state.userStore.get("isActivated"),
|
isActivated: state.userStore.get("isActivated"),
|
||||||
isLogged: state.userStore.get("isLogged"),
|
isLogged: state.userStore.get("isLogged")
|
||||||
});
|
});
|
||||||
|
|
||||||
const UserActivation = (props) => {
|
const UserActivation = props => {
|
||||||
if (!props.isLogged) {
|
if (!props.isLogged) {
|
||||||
return (<Redirect to="/users/login"/>);
|
return <Redirect to="/users/login" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.isActivated) {
|
if (props.isActivated) {
|
||||||
return (<Redirect to="/"/>);
|
return <Redirect to="/" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -22,15 +22,18 @@ const UserActivation = (props) => {
|
|||||||
<div className="col-12 col-md-8 offset-md-2">
|
<div className="col-12 col-md-8 offset-md-2">
|
||||||
<h2>Waiting for activation</h2>
|
<h2>Waiting for activation</h2>
|
||||||
<hr />
|
<hr />
|
||||||
<h3>Hang tight! Your user will soon be activated by the administrators of this site.</h3>
|
<h3>
|
||||||
|
Hang tight! Your user will soon be activated by the administrators of
|
||||||
|
this site.
|
||||||
|
</h3>
|
||||||
<Link to="/users/logout">Logout</Link>
|
<Link to="/users/logout">Logout</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
UserActivation.propTypes = {
|
UserActivation.propTypes = {
|
||||||
isActivated: PropTypes.bool.isRequired,
|
isActivated: PropTypes.bool.isRequired,
|
||||||
isLogged: PropTypes.bool.isRequired,
|
isLogged: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps)(UserActivation);
|
export default connect(mapStateToProps)(UserActivation);
|
||||||
|
@ -1,24 +1,26 @@
|
|||||||
import React, { useState, useEffect } from "react"
|
import React, { useState, useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import Loader from "../loader/loader"
|
import Loader from "../loader/loader";
|
||||||
import { List } from "immutable"
|
import { List } from "immutable";
|
||||||
|
|
||||||
import { getUserInfos, updateUser } from "../../actions/users"
|
import { getUserInfos, updateUser } from "../../actions/users";
|
||||||
import { getPolochons } from "../../actions/polochon"
|
import { getPolochons } from "../../actions/polochon";
|
||||||
|
|
||||||
import { PolochonSelect } from "../polochons/select"
|
import { PolochonSelect } from "../polochons/select";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
loading: state.userStore.get("loading"),
|
loading: state.userStore.get("loading"),
|
||||||
publicPolochons: state.polochon.get("public"),
|
publicPolochons: state.polochon.get("public"),
|
||||||
polochonId: state.userStore.get("polochonId"),
|
polochonId: state.userStore.get("polochonId"),
|
||||||
polochonActivated: state.userStore.get("polochonActivated"),
|
polochonActivated: state.userStore.get("polochonActivated")
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
updateUser, getPolochons, getUserInfos,
|
updateUser,
|
||||||
}
|
getPolochons,
|
||||||
|
getUserInfos
|
||||||
|
};
|
||||||
|
|
||||||
const UserEditConnect = ({
|
const UserEditConnect = ({
|
||||||
loading,
|
loading,
|
||||||
@ -27,7 +29,7 @@ const UserEditConnect = ({
|
|||||||
updateUser,
|
updateUser,
|
||||||
getPolochons,
|
getPolochons,
|
||||||
getUserInfos,
|
getUserInfos,
|
||||||
publicPolochons,
|
publicPolochons
|
||||||
}) => {
|
}) => {
|
||||||
const [id, setId] = useState(polochonId);
|
const [id, setId] = useState(polochonId);
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
@ -36,37 +38,45 @@ const UserEditConnect = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getPolochons();
|
getPolochons();
|
||||||
getUserInfos();
|
getUserInfos();
|
||||||
}, [getPolochons, getUserInfos])
|
}, [getPolochons, getUserInfos]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setId(polochonId);
|
setId(polochonId);
|
||||||
}, [polochonId])
|
}, [polochonId]);
|
||||||
|
|
||||||
const handleSubmit = (ev) => {
|
const handleSubmit = ev => {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
updateUser({
|
updateUser({
|
||||||
"password": password,
|
password: password,
|
||||||
"password_confirm": passwordConfirm,
|
password_confirm: passwordConfirm, // eslint-disable-line camelcase
|
||||||
"polochon_id": id,
|
polochon_id: id // eslint-disable-line camelcase
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
if (loading) { return (<Loader />) }
|
if (loading) {
|
||||||
|
return <Loader />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row mb-3">
|
<div className="row mb-3">
|
||||||
<div className="col-12 col-md-8 offset-md-2">
|
<div className="col-12 col-md-8 offset-md-2">
|
||||||
<h2>Edit user</h2>
|
<h2>Edit user</h2>
|
||||||
<hr />
|
<hr />
|
||||||
<form className="form-horizontal" onSubmit={(ev) => handleSubmit(ev)}>
|
<form className="form-horizontal" onSubmit={ev => handleSubmit(ev)}>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="control-label">
|
<label className="control-label">
|
||||||
Polochon
|
Polochon
|
||||||
{polochonActivated ||
|
{polochonActivated || (
|
||||||
<span className="ml-1 text text-primary">(Needs activation from admin)</span>
|
<span className="ml-1 text text-primary">
|
||||||
}
|
(Needs activation from admin)
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
<PolochonSelect value={id} changeValue={setId} polochonList={publicPolochons} />
|
<PolochonSelect
|
||||||
|
value={id}
|
||||||
|
changeValue={setId}
|
||||||
|
polochonList={publicPolochons}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
@ -78,7 +88,7 @@ const UserEditConnect = ({
|
|||||||
type="password"
|
type="password"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={e => setPassword(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -89,18 +99,22 @@ const UserEditConnect = ({
|
|||||||
type="password"
|
type="password"
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
value={passwordConfirm}
|
value={passwordConfirm}
|
||||||
onChange={(e) => setPasswordConfirm(e.target.value)}
|
onChange={e => setPasswordConfirm(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<input type="submit" className="btn btn-primary pull-right" value="Update"/>
|
<input
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-primary pull-right"
|
||||||
|
value="Update"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
UserEditConnect.propTypes = {
|
UserEditConnect.propTypes = {
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
polochonId: PropTypes.string,
|
polochonId: PropTypes.string,
|
||||||
@ -108,7 +122,10 @@ UserEditConnect.propTypes = {
|
|||||||
updateUser: PropTypes.func,
|
updateUser: PropTypes.func,
|
||||||
getPolochons: PropTypes.func,
|
getPolochons: PropTypes.func,
|
||||||
getUserInfos: PropTypes.func,
|
getUserInfos: PropTypes.func,
|
||||||
publicPolochons: PropTypes.instanceOf(List),
|
publicPolochons: PropTypes.instanceOf(List)
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UserEdit = connect(mapStateToProps, mapDispatchToProps)(UserEditConnect);
|
export const UserEdit = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(UserEditConnect);
|
||||||
|
@ -1,27 +1,27 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { Redirect, Link } from "react-router-dom"
|
import { Redirect, Link } from "react-router-dom";
|
||||||
|
|
||||||
import { loginUser } from "../../actions/users"
|
import { loginUser } from "../../actions/users";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
isLogged: state.userStore.get("isLogged"),
|
isLogged: state.userStore.get("isLogged"),
|
||||||
isLoading: state.userStore.get("loading"),
|
isLoading: state.userStore.get("loading"),
|
||||||
error: state.userStore.get("error"),
|
error: state.userStore.get("error")
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = { loginUser };
|
const mapDispatchToProps = { loginUser };
|
||||||
|
|
||||||
const UserLoginForm = (props) => {
|
const UserLoginForm = props => {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (!props.isLoading) {
|
if (!props.isLoading) {
|
||||||
props.loginUser(username, password);
|
props.loginUser(username, password);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
if (props.isLogged) {
|
if (props.isLogged) {
|
||||||
return <Redirect to="/" />;
|
return <Redirect to="/" />;
|
||||||
@ -31,25 +31,33 @@ const UserLoginForm = (props) => {
|
|||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-10 offset-1 col-md-6 offset-md-3">
|
<div className="col-10 offset-1 col-md-6 offset-md-3">
|
||||||
<h2>Log in</h2>
|
<h2>Log in</h2>
|
||||||
<hr/>
|
<hr />
|
||||||
{props.error && props.error !== "" &&
|
{props.error && props.error !== "" && (
|
||||||
<div className="alert alert-danger">
|
<div className="alert alert-danger">{props.error}</div>
|
||||||
{props.error}
|
)}
|
||||||
</div>
|
<form className="form-horizontal" onSubmit={e => handleSubmit(e)}>
|
||||||
}
|
|
||||||
<form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}>
|
|
||||||
<div>
|
<div>
|
||||||
<label>Username</label>
|
<label>Username</label>
|
||||||
<br/>
|
<br />
|
||||||
<input className="form-control" type="username" autoFocus
|
<input
|
||||||
value={username} onChange={(e) => setUsername(e.target.value)}/>
|
className="form-control"
|
||||||
|
type="username"
|
||||||
|
autoFocus
|
||||||
|
value={username}
|
||||||
|
onChange={e => setUsername(e.target.value)}
|
||||||
|
/>
|
||||||
<p></p>
|
<p></p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label>Password</label>
|
<label>Password</label>
|
||||||
<br/>
|
<br />
|
||||||
<input className="form-control" type="password" autoComplete="new-password"
|
<input
|
||||||
value={password} onChange={(e) => setPassword(e.target.value)}/>
|
className="form-control"
|
||||||
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
|
value={password}
|
||||||
|
onChange={e => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
<p></p>
|
<p></p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -58,28 +66,32 @@ const UserLoginForm = (props) => {
|
|||||||
No account yet ? <Link to="/users/signup">Create one</Link>
|
No account yet ? <Link to="/users/signup">Create one</Link>
|
||||||
</small>
|
</small>
|
||||||
</span>
|
</span>
|
||||||
{props.isLoading &&
|
{props.isLoading && (
|
||||||
<button className="btn btn-primary pull-right">
|
<button className="btn btn-primary pull-right">
|
||||||
<i className="fa fa-spinner fa-spin"></i>
|
<i className="fa fa-spinner fa-spin"></i>
|
||||||
</button>
|
</button>
|
||||||
}
|
)}
|
||||||
{props.isLoading ||
|
{props.isLoading || (
|
||||||
<span className="spaced-icons">
|
<span className="spaced-icons">
|
||||||
<input className="btn btn-primary pull-right" type="submit" value="Log in"/>
|
<input
|
||||||
|
className="btn btn-primary pull-right"
|
||||||
|
type="submit"
|
||||||
|
value="Log in"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
}
|
)}
|
||||||
<br/>
|
<br />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
UserLoginForm.propTypes = {
|
UserLoginForm.propTypes = {
|
||||||
loginUser: PropTypes.func.isRequired,
|
loginUser: PropTypes.func.isRequired,
|
||||||
isLoading: PropTypes.bool.isRequired,
|
isLoading: PropTypes.bool.isRequired,
|
||||||
isLogged: PropTypes.bool.isRequired,
|
isLogged: PropTypes.bool.isRequired,
|
||||||
error: PropTypes.string,
|
error: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(UserLoginForm);
|
export default connect(mapStateToProps, mapDispatchToProps)(UserLoginForm);
|
||||||
|
@ -1,28 +1,26 @@
|
|||||||
import React from "react"
|
import React from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { Redirect } from "react-router-dom"
|
import { Redirect } from "react-router-dom";
|
||||||
|
|
||||||
import { userLogout } from "../../actions/users"
|
import { userLogout } from "../../actions/users";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
isLogged: state.userStore.get("isLogged"),
|
isLogged: state.userStore.get("isLogged")
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = { userLogout };
|
const mapDispatchToProps = { userLogout };
|
||||||
|
|
||||||
const UserLogout = (props) => {
|
const UserLogout = props => {
|
||||||
if (props.isLogged) {
|
if (props.isLogged) {
|
||||||
props.userLogout();
|
props.userLogout();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <Redirect to="/users/login" />;
|
||||||
<Redirect to="/users/login" />
|
};
|
||||||
);
|
|
||||||
}
|
|
||||||
UserLogout.propTypes = {
|
UserLogout.propTypes = {
|
||||||
isLogged: PropTypes.bool.isRequired,
|
isLogged: PropTypes.bool.isRequired,
|
||||||
userLogout: PropTypes.func.isRequired,
|
userLogout: PropTypes.func.isRequired,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(UserLogout);
|
export default connect(mapStateToProps, mapDispatchToProps)(UserLogout);
|
||||||
|
@ -1,46 +1,38 @@
|
|||||||
import React, { useEffect } from "react"
|
import React, { useEffect } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { Map } from "immutable"
|
import { Map } from "immutable";
|
||||||
|
|
||||||
import { PolochonList } from "../polochons/list"
|
import { PolochonList } from "../polochons/list";
|
||||||
import { UserEdit } from "./edit"
|
import { UserEdit } from "./edit";
|
||||||
|
|
||||||
import { getUserModules } from "../../actions/users"
|
import { getUserModules } from "../../actions/users";
|
||||||
import Modules from "../modules/modules"
|
import Modules from "../modules/modules";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
modules : state.userStore.get("modules"),
|
modules: state.userStore.get("modules"),
|
||||||
modulesLoading : state.userStore.get("modulesLoading"),
|
modulesLoading: state.userStore.get("modulesLoading")
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = { getUserModules }
|
const mapDispatchToProps = { getUserModules };
|
||||||
|
|
||||||
const UserProfile = ({
|
const UserProfile = ({ modules, modulesLoading, getUserModules }) => {
|
||||||
modules,
|
|
||||||
modulesLoading,
|
|
||||||
getUserModules
|
|
||||||
|
|
||||||
}) => {
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getUserModules();
|
getUserModules();
|
||||||
}, [getUserModules])
|
}, [getUserModules]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<UserEdit />
|
<UserEdit />
|
||||||
<PolochonList />
|
<PolochonList />
|
||||||
<Modules
|
<Modules modules={modules} isLoading={modulesLoading} />
|
||||||
modules={modules}
|
|
||||||
isLoading={modulesLoading}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
UserProfile.propTypes = {
|
UserProfile.propTypes = {
|
||||||
getUserModules: PropTypes.func.isRequired,
|
getUserModules: PropTypes.func.isRequired,
|
||||||
modules: PropTypes.instanceOf(Map),
|
modules: PropTypes.instanceOf(Map),
|
||||||
modulesLoading: PropTypes.bool.isRequired,
|
modulesLoading: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
|
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
|
||||||
|
@ -1,86 +1,100 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { Redirect } from "react-router-dom"
|
import { Redirect } from "react-router-dom";
|
||||||
|
|
||||||
import { userSignUp } from "../../actions/users"
|
import { userSignUp } from "../../actions/users";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
isLogged: state.userStore.get("isLogged"),
|
isLogged: state.userStore.get("isLogged"),
|
||||||
isLoading: state.userStore.get("loading"),
|
isLoading: state.userStore.get("loading"),
|
||||||
error: state.userStore.get("error"),
|
error: state.userStore.get("error")
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = { userSignUp };
|
const mapDispatchToProps = { userSignUp };
|
||||||
|
|
||||||
const UserSignUp = (props) => {
|
const UserSignUp = props => {
|
||||||
if (props.isLogged) {
|
|
||||||
return (<Redirect to="/"/>);
|
|
||||||
}
|
|
||||||
|
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [passwordConfirm, setPasswordConfirm] = useState("");
|
const [passwordConfirm, setPasswordConfirm] = useState("");
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
if (props.isLogged) {
|
||||||
|
return <Redirect to="/" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSubmit = e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
props.userSignUp({
|
props.userSignUp({
|
||||||
"username": username,
|
username: username,
|
||||||
"password": password,
|
password: password,
|
||||||
"password_confirm": passwordConfirm,
|
password_confirm: passwordConfirm // eslint-disable-line camelcase
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Row">
|
<div className="Row">
|
||||||
<div className="col-10 offset-1 col-md-6 offset-md-3">
|
<div className="col-10 offset-1 col-md-6 offset-md-3">
|
||||||
<h2>Sign up</h2>
|
<h2>Sign up</h2>
|
||||||
<hr />
|
<hr />
|
||||||
{props.error && props.error !== "" &&
|
{props.error && props.error !== "" && (
|
||||||
<div className="alert alert-danger">
|
<div className="alert alert-danger">{props.error}</div>
|
||||||
{props.error}
|
)}
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<form className="form-horizontal" onSubmit={(e) => handleSubmit(e)}>
|
<form className="form-horizontal" onSubmit={e => handleSubmit(e)}>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="control-label">Username</label>
|
<label className="control-label">Username</label>
|
||||||
<input autoFocus="autofocus" className="form-control"
|
<input
|
||||||
value={username} onChange={(e) => setUsername(e.target.value)} />
|
autoFocus="autofocus"
|
||||||
|
className="form-control"
|
||||||
|
value={username}
|
||||||
|
onChange={e => setUsername(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="control-label">Password</label>
|
<label className="control-label">Password</label>
|
||||||
<input className="form-control" type="password"
|
<input
|
||||||
value={password} onChange={(e) => setPassword(e.target.value)} />
|
className="form-control"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={e => setPassword(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label className="control-label">Password confirm</label>
|
<label className="control-label">Password confirm</label>
|
||||||
<input className="form-control" type="password"
|
<input
|
||||||
value={passwordConfirm} onChange={(e) => setPasswordConfirm(e.target.value)} />
|
className="form-control"
|
||||||
|
type="password"
|
||||||
|
value={passwordConfirm}
|
||||||
|
onChange={e => setPasswordConfirm(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{props.isLoading &&
|
{props.isLoading && (
|
||||||
<button className="btn btn-primary pull-right">
|
<button className="btn btn-primary pull-right">
|
||||||
<i className="fa fa-spinner fa-spin"></i>
|
<i className="fa fa-spinner fa-spin"></i>
|
||||||
</button>
|
</button>
|
||||||
}
|
)}
|
||||||
{props.isLoading ||
|
{props.isLoading || (
|
||||||
<span>
|
<span>
|
||||||
<input className="btn btn-primary pull-right" type="submit" value="Sign up"/>
|
<input
|
||||||
|
className="btn btn-primary pull-right"
|
||||||
|
type="submit"
|
||||||
|
value="Sign up"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
UserSignUp.propTypes = {
|
UserSignUp.propTypes = {
|
||||||
isLogged: PropTypes.bool.isRequired,
|
isLogged: PropTypes.bool.isRequired,
|
||||||
isLoading: PropTypes.bool.isRequired,
|
isLoading: PropTypes.bool.isRequired,
|
||||||
userSignUp: PropTypes.func.isRequired,
|
userSignUp: PropTypes.func.isRequired,
|
||||||
error: PropTypes.string,
|
error: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(UserSignUp);
|
export default connect(mapStateToProps, mapDispatchToProps)(UserSignUp);
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react";
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { UAParser } from "ua-parser-js"
|
import { UAParser } from "ua-parser-js";
|
||||||
import moment from "moment"
|
import moment from "moment";
|
||||||
import { Map, List } from "immutable"
|
import { Map, List } from "immutable";
|
||||||
|
|
||||||
import { getUserTokens, deleteUserToken } from "../../actions/users"
|
import { getUserTokens, deleteUserToken } from "../../actions/users";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
tokens: state.userStore.get("tokens"),
|
tokens: state.userStore.get("tokens")
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = { getUserTokens, deleteUserToken };
|
const mapDispatchToProps = { getUserTokens, deleteUserToken };
|
||||||
|
|
||||||
const UserTokens = (props) => {
|
const UserTokens = props => {
|
||||||
const [fetched, setIsFetched] = useState(false);
|
const [fetched, setIsFetched] = useState(false);
|
||||||
if (!fetched) {
|
if (!fetched) {
|
||||||
props.getUserTokens();
|
props.getUserTokens();
|
||||||
@ -28,15 +28,15 @@ const UserTokens = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
UserTokens.propTypes = {
|
UserTokens.propTypes = {
|
||||||
tokens: PropTypes.instanceOf(List),
|
tokens: PropTypes.instanceOf(List),
|
||||||
isLoading: PropTypes.bool.isRequired,
|
isLoading: PropTypes.bool,
|
||||||
getUserTokens: PropTypes.func.isRequired,
|
getUserTokens: PropTypes.func.isRequired,
|
||||||
deleteUserToken: PropTypes.func.isRequired,
|
deleteUserToken: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const Token = (props) => {
|
const Token = props => {
|
||||||
const ua = UAParser(props.data.get("user_agent"));
|
const ua = UAParser(props.data.get("user_agent"));
|
||||||
return (
|
return (
|
||||||
<div className="card mt-3">
|
<div className="card mt-3">
|
||||||
@ -54,43 +54,49 @@ const Token = (props) => {
|
|||||||
<p>Created: {moment(props.data.get("created_at")).fromNow()}</p>
|
<p>Created: {moment(props.data.get("created_at")).fromNow()}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-12 col-md-6">
|
<div className="col-12 col-md-6">
|
||||||
<p>Device: <Device {...ua.device}/></p>
|
<p>
|
||||||
<p>OS: <OS {...ua.os}/></p>
|
Device: <Device {...ua.device} />
|
||||||
<p>Browser: <Browser {...ua.browser}/></p>
|
</p>
|
||||||
|
<p>
|
||||||
|
OS: <OS {...ua.os} />
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Browser: <Browser {...ua.browser} />
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Token.propTypes = {
|
Token.propTypes = {
|
||||||
data: PropTypes.instanceOf(Map).isRequired,
|
data: PropTypes.instanceOf(Map).isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const Actions = (props) => {
|
const Actions = props => {
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
props.deleteToken(props.data.get("token"));
|
props.deleteToken(props.data.get("token"));
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className="fa fa-trash fa-lg pull-right clickable"
|
className="fa fa-trash fa-lg pull-right clickable"
|
||||||
onClick={handleClick}>
|
onClick={handleClick}
|
||||||
</span>
|
></span>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
Actions.propTypes = {
|
Actions.propTypes = {
|
||||||
data: PropTypes.instanceOf(Map).isRequired,
|
data: PropTypes.instanceOf(Map).isRequired,
|
||||||
deleteToken: PropTypes.func.isRequired,
|
deleteToken: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const Logo = (props) => {
|
const Logo = ({ ua, device, browser }) => {
|
||||||
var className;
|
var className;
|
||||||
if (props.ua === "canape-cli") {
|
if (ua === "canape-cli") {
|
||||||
className = "terminal";
|
className = "terminal";
|
||||||
} else if (props.device.type == "mobile" ){
|
} else if (device.type == "mobile") {
|
||||||
className = "mobile";
|
className = "mobile";
|
||||||
} else {
|
} else {
|
||||||
switch (props.browser.name) {
|
switch (browser.name) {
|
||||||
case "Chrome":
|
case "Chrome":
|
||||||
case "chrome":
|
case "chrome":
|
||||||
className = "chrome";
|
className = "chrome";
|
||||||
@ -109,50 +115,60 @@ const Logo = (props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (<span className={`fa fa-${className}`}></span>);
|
return <span className={`fa fa-${className}`}></span>;
|
||||||
}
|
};
|
||||||
|
Logo.propTypes = {
|
||||||
|
ua: PropTypes.string,
|
||||||
|
device: PropTypes.object,
|
||||||
|
browser: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
const OS = (props) => {
|
const OS = ({ name, version }) => {
|
||||||
var osName = "-";
|
var osName = "-";
|
||||||
|
|
||||||
if (props.name !== undefined) {
|
if (name !== undefined) {
|
||||||
osName = props.name;
|
osName = name;
|
||||||
|
|
||||||
if (props.version !== undefined) {
|
if (version !== undefined) {
|
||||||
osName += " " + props.version;
|
osName += " " + version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <span> {osName}</span>;
|
||||||
<span> {osName}</span>
|
};
|
||||||
);
|
OS.propTypes = {
|
||||||
}
|
name: PropTypes.string,
|
||||||
|
version: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
const Device = (props) => {
|
const Device = ({ model }) => {
|
||||||
var deviceName = "-";
|
var deviceName = "-";
|
||||||
|
|
||||||
if (props.model !== undefined) {
|
if (model !== undefined) {
|
||||||
deviceName = props.model;
|
deviceName = model;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <span> {deviceName}</span>;
|
||||||
<span> {deviceName}</span>
|
};
|
||||||
);
|
Device.propTypes = {
|
||||||
}
|
model: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
const Browser = (props) => {
|
const Browser = ({ name, version }) => {
|
||||||
var browserName = "-";
|
var browserName = "-";
|
||||||
if (props.name !== undefined) {
|
if (name !== undefined) {
|
||||||
browserName = props.name;
|
browserName = name;
|
||||||
|
|
||||||
if (props.version !== undefined) {
|
if (version !== undefined) {
|
||||||
browserName += " - " + props.version;
|
browserName += " - " + version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return <span> {browserName}</span>;
|
||||||
<span> {browserName}</span>
|
};
|
||||||
);
|
Browser.propTypes = {
|
||||||
}
|
name: PropTypes.string,
|
||||||
|
version: PropTypes.string
|
||||||
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(UserTokens);
|
export default connect(mapStateToProps, mapDispatchToProps)(UserTokens);
|
||||||
|
@ -1,22 +1,26 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react";
|
||||||
import { connect } from "react-redux"
|
import { connect } from "react-redux";
|
||||||
import { setFetchedTorrents } from "../actions/torrents"
|
import { setFetchedTorrents } from "../actions/torrents";
|
||||||
import { newMovieEvent } from "../actions/movies"
|
import { newMovieEvent } from "../actions/movies";
|
||||||
import { newEpisodeEvent } from "../actions/shows"
|
import { newEpisodeEvent } from "../actions/shows";
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
isLogged: state.userStore.get("isLogged"),
|
isLogged: state.userStore.get("isLogged")
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = { setFetchedTorrents, newMovieEvent, newEpisodeEvent };
|
const mapDispatchToProps = {
|
||||||
|
setFetchedTorrents,
|
||||||
|
newMovieEvent,
|
||||||
|
newEpisodeEvent
|
||||||
|
};
|
||||||
|
|
||||||
const WsHandler = (props) => {
|
const WsHandler = props => {
|
||||||
const [ws, setWs] = useState(null);
|
const [ws, setWs] = useState(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const intervalID = setInterval(() => {
|
const intervalID = setInterval(() => {
|
||||||
if (!ws) {
|
if (!ws) {
|
||||||
connect();
|
connect();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ws.readyState === WebSocket.CLOSED) {
|
if (ws.readyState === WebSocket.CLOSED) {
|
||||||
@ -38,41 +42,51 @@ const WsHandler = (props) => {
|
|||||||
|
|
||||||
const stop = () => {
|
const stop = () => {
|
||||||
if (!ws) {
|
if (!ws) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
ws.close()
|
ws.close();
|
||||||
setWs(null);
|
setWs(null);
|
||||||
}
|
};
|
||||||
|
|
||||||
const connect = () => {
|
const connect = () => {
|
||||||
if (ws) { return; }
|
if (ws) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!props.isLogged) {
|
if (!props.isLogged) {
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
const type = (location.protocol === "https:") ? "wss" : "ws";
|
const type = location.protocol === "https:" ? "wss" : "ws";
|
||||||
const socket = new WebSocket(type + ":" + location.host + "/events")
|
const socket = new WebSocket(type + ":" + location.host + "/events");
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
socket.send(JSON.stringify({
|
socket.send(
|
||||||
"type": "subscribe",
|
JSON.stringify({
|
||||||
"message": "torrents",
|
type: "subscribe",
|
||||||
}))
|
message: "torrents"
|
||||||
socket.send(JSON.stringify({
|
})
|
||||||
"type": "subscribe",
|
);
|
||||||
"message": "newVideo",
|
socket.send(
|
||||||
}))
|
JSON.stringify({
|
||||||
|
type: "subscribe",
|
||||||
|
message: "newVideo"
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onmessage = event => {
|
||||||
|
if (!event.data) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
|
||||||
if (!event.data) { return }
|
|
||||||
|
|
||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
if (!data.type){ return }
|
if (!data.type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (data.status !== "ok") {
|
if (data.status !== "ok") {
|
||||||
// TODO: handle this somehow
|
// TODO: handle this somehow
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data.type) {
|
switch (data.type) {
|
||||||
@ -81,20 +95,22 @@ const WsHandler = (props) => {
|
|||||||
break;
|
break;
|
||||||
case "newVideo":
|
case "newVideo":
|
||||||
if (data.message.type === "movie") {
|
if (data.message.type === "movie") {
|
||||||
props.newMovieEvent(data.message.data)
|
props.newMovieEvent(data.message.data);
|
||||||
} else if (data.message.type === "episode") {
|
} else if (data.message.type === "episode") {
|
||||||
props.newEpisodeEvent(data.message.data)
|
props.newEpisodeEvent(data.message.data);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
socket.onerror = () => { stop(); }
|
socket.onerror = () => {
|
||||||
|
stop();
|
||||||
|
};
|
||||||
|
|
||||||
setWs(socket)
|
setWs(socket);
|
||||||
}
|
};
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(WsHandler);
|
export default connect(mapStateToProps, mapDispatchToProps)(WsHandler);
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
import { Map, List, fromJS } from "immutable"
|
import { Map, List, fromJS } from "immutable";
|
||||||
|
|
||||||
export const defaultState = Map({
|
export const defaultState = Map({
|
||||||
"fetchingModules": false,
|
fetchingModules: false,
|
||||||
"users": List(),
|
users: List(),
|
||||||
"stats": Map({}),
|
stats: Map({}),
|
||||||
"modules": Map({}),
|
modules: Map({})
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"ADMIN_LIST_USERS_FULFILLED": (state, action) => state.set("users", fromJS(action.payload.response.data)),
|
ADMIN_LIST_USERS_FULFILLED: (state, action) =>
|
||||||
"ADMIN_GET_STATS_FULFILLED": (state, action) => state.set("stats", fromJS(action.payload.response.data)),
|
state.set("users", fromJS(action.payload.response.data)),
|
||||||
"ADMIN_GET_MODULES_PENDING": state => state.set("fetchingModules", true),
|
ADMIN_GET_STATS_FULFILLED: (state, action) =>
|
||||||
"ADMIN_GET_MODULES_FULFILLED": (state, action) => state.merge(fromJS({
|
state.set("stats", fromJS(action.payload.response.data)),
|
||||||
"modules": action.payload.response.data,
|
ADMIN_GET_MODULES_PENDING: state => state.set("fetchingModules", true),
|
||||||
"fetchingModules": false,
|
ADMIN_GET_MODULES_FULFILLED: (state, action) =>
|
||||||
})),
|
state.merge(
|
||||||
}
|
fromJS({
|
||||||
|
modules: action.payload.response.data,
|
||||||
|
fetchingModules: false
|
||||||
|
})
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||||
|
@ -1,29 +1,37 @@
|
|||||||
import { Map } from "immutable"
|
import { Map } from "immutable";
|
||||||
|
|
||||||
const defaultState = Map({
|
const defaultState = Map({
|
||||||
show: false,
|
show: false,
|
||||||
message: "",
|
message: "",
|
||||||
type: "",
|
type: ""
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"ADD_ALERT_ERROR": (state, action) => state.merge(Map({
|
ADD_ALERT_ERROR: (state, action) =>
|
||||||
|
state.merge(
|
||||||
|
Map({
|
||||||
message: action.payload.message,
|
message: action.payload.message,
|
||||||
show: true,
|
show: true,
|
||||||
type: "error",
|
type: "error"
|
||||||
})),
|
})
|
||||||
"ADD_ALERT_OK": (state, action) => state.merge(Map({
|
),
|
||||||
|
ADD_ALERT_OK: (state, action) =>
|
||||||
|
state.merge(
|
||||||
|
Map({
|
||||||
message: action.payload.message,
|
message: action.payload.message,
|
||||||
show: true,
|
show: true,
|
||||||
type: "success",
|
type: "success"
|
||||||
})),
|
})
|
||||||
"DISMISS_ALERT": state => state.merge(Map({
|
),
|
||||||
|
DISMISS_ALERT: state =>
|
||||||
|
state.merge(
|
||||||
|
Map({
|
||||||
message: "",
|
message: "",
|
||||||
show: false,
|
show: false,
|
||||||
type: "",
|
type: ""
|
||||||
})),
|
})
|
||||||
}
|
)
|
||||||
|
};
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { combineReducers } from "redux";
|
import { combineReducers } from "redux";
|
||||||
|
|
||||||
import movieStore from "./movies"
|
import movieStore from "./movies";
|
||||||
import showsStore from "./shows"
|
import showsStore from "./shows";
|
||||||
import showStore from "./show"
|
import showStore from "./show";
|
||||||
import userStore from "./users"
|
import userStore from "./users";
|
||||||
import alerts from "./alerts"
|
import alerts from "./alerts";
|
||||||
import torrentStore from "./torrents"
|
import torrentStore from "./torrents";
|
||||||
import adminStore from "./admins"
|
import adminStore from "./admins";
|
||||||
import polochon from "./polochon"
|
import polochon from "./polochon";
|
||||||
import notifications from "./notifications"
|
import notifications from "./notifications";
|
||||||
|
|
||||||
export default combineReducers({
|
export default combineReducers({
|
||||||
movieStore,
|
movieStore,
|
||||||
@ -19,5 +19,5 @@ export default combineReducers({
|
|||||||
torrentStore,
|
torrentStore,
|
||||||
adminStore,
|
adminStore,
|
||||||
polochon,
|
polochon,
|
||||||
notifications,
|
notifications
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { OrderedMap, Map, fromJS } from "immutable"
|
import { OrderedMap, Map, fromJS } from "immutable";
|
||||||
|
|
||||||
const defaultState = Map({
|
const defaultState = Map({
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -6,61 +6,95 @@ const defaultState = Map({
|
|||||||
filter: "",
|
filter: "",
|
||||||
selectedImdbId: "",
|
selectedImdbId: "",
|
||||||
lastFetchUrl: "",
|
lastFetchUrl: "",
|
||||||
exploreOptions: Map(),
|
exploreOptions: Map()
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"MOVIE_LIST_FETCH_PENDING": state => state.set("loading", true),
|
MOVIE_LIST_FETCH_PENDING: state => state.set("loading", true),
|
||||||
"MOVIE_LIST_FETCH_ERROR": state => state.set("loading", false),
|
MOVIE_LIST_FETCH_ERROR: state => state.set("loading", false),
|
||||||
"MOVIE_LIST_FETCH_FULFILLED": (state, action) => {
|
MOVIE_LIST_FETCH_FULFILLED: (state, action) => {
|
||||||
let allMoviesInPolochon = true
|
let allMoviesInPolochon = true;
|
||||||
let movies = Map()
|
let movies = Map();
|
||||||
action.payload.response.data.map(function (movie) {
|
action.payload.response.data.map(function(movie) {
|
||||||
movie.fetchingDetails = false;
|
movie.fetchingDetails = false;
|
||||||
movie.fetchingSubtitles = false;
|
movie.fetchingSubtitles = false;
|
||||||
if (movie.polochon_url === "") {
|
if (movie.polochon_url === "") {
|
||||||
allMoviesInPolochon = false
|
allMoviesInPolochon = false;
|
||||||
}
|
}
|
||||||
movies = movies.set(movie.imdb_id, fromJS(movie));
|
movies = movies.set(movie.imdb_id, fromJS(movie));
|
||||||
})
|
});
|
||||||
|
|
||||||
// Select the first movie if the list is not empty
|
// Select the first movie if the list is not empty
|
||||||
let selectedImdbId = "";
|
let selectedImdbId = "";
|
||||||
if (movies.size > 0) {
|
if (movies.size > 0) {
|
||||||
// Sort by year
|
// Sort by year
|
||||||
movies = movies.sort((a,b) => {
|
movies = movies.sort((a, b) => {
|
||||||
if (!allMoviesInPolochon) {
|
if (!allMoviesInPolochon) {
|
||||||
return b.get("year") - a.get("year")
|
return b.get("year") - a.get("year");
|
||||||
}
|
}
|
||||||
|
|
||||||
const dateA = new Date(a.get("date_added"))
|
const dateA = new Date(a.get("date_added"));
|
||||||
const dateB = new Date(b.get("date_added"))
|
const dateB = new Date(b.get("date_added"));
|
||||||
return dateA > dateB ? -1 : dateA < dateB ? 1 : 0;
|
return dateA > dateB ? -1 : dateA < dateB ? 1 : 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
selectedImdbId = movies.first().get("imdb_id");
|
selectedImdbId = movies.first().get("imdb_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.delete("movies").merge(Map({
|
return state.delete("movies").merge(
|
||||||
|
Map({
|
||||||
movies: movies,
|
movies: movies,
|
||||||
filter: "",
|
filter: "",
|
||||||
loading: false,
|
loading: false,
|
||||||
selectedImdbId: selectedImdbId,
|
selectedImdbId: selectedImdbId
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
"MOVIE_GET_DETAILS_PENDING" : (state, action) => state.setIn(["movies", action.payload.main.imdbId, "fetchingDetails"], true),
|
MOVIE_GET_DETAILS_PENDING: (state, action) =>
|
||||||
"MOVIE_GET_DETAILS_FULFILLED" : (state, action) => state.setIn(["movies", action.payload.response.data.imdb_id], fromJS(action.payload.response.data))
|
state.setIn(
|
||||||
.setIn(["movies", action.payload.response.data.imdb_id, "fetchingDetails"], false)
|
["movies", action.payload.main.imdbId, "fetchingDetails"],
|
||||||
.setIn(["movies", action.payload.response.data.imdb_id, "fetchingSubtitles"], false),
|
true
|
||||||
"MOVIE_UPDATE_STORE_WISHLIST" : (state, action) => state.setIn(["movies", action.payload.imdbId, "wishlisted"], action.payload.wishlisted),
|
),
|
||||||
"MOVIE_GET_EXPLORE_OPTIONS_FULFILLED" : (state, action) => state.set("exploreOptions", fromJS(action.payload.response.data)),
|
MOVIE_GET_DETAILS_FULFILLED: (state, action) =>
|
||||||
"UPDATE_LAST_MOVIE_FETCH_URL" : (state, action) => state.set("lastFetchUrl", action.payload.url),
|
state
|
||||||
"MOVIE_SUBTITLES_UPDATE_PENDING" : (state, action) => state.setIn(["movies", action.payload.main.imdbId, "fetchingSubtitles"], true),
|
.setIn(
|
||||||
"MOVIE_SUBTITLES_UPDATE_FULFILLED" : (state, action) => state.setIn(["movies", action.payload.main.imdbId, "fetchingSubtitles"], false)
|
["movies", action.payload.response.data.imdb_id],
|
||||||
.setIn(["movies", action.payload.main.imdbId, "subtitles"], fromJS(action.payload.response.data)),
|
fromJS(action.payload.response.data)
|
||||||
"SELECT_MOVIE" : (state, action) => state.set("selectedImdbId", action.payload.imdbId),
|
)
|
||||||
"MOVIE_UPDATE_FILTER" : (state, action) => state.set("filter", action.payload.filter),
|
.setIn(
|
||||||
}
|
["movies", action.payload.response.data.imdb_id, "fetchingDetails"],
|
||||||
|
false
|
||||||
|
)
|
||||||
|
.setIn(
|
||||||
|
["movies", action.payload.response.data.imdb_id, "fetchingSubtitles"],
|
||||||
|
false
|
||||||
|
),
|
||||||
|
MOVIE_UPDATE_STORE_WISHLIST: (state, action) =>
|
||||||
|
state.setIn(
|
||||||
|
["movies", action.payload.imdbId, "wishlisted"],
|
||||||
|
action.payload.wishlisted
|
||||||
|
),
|
||||||
|
MOVIE_GET_EXPLORE_OPTIONS_FULFILLED: (state, action) =>
|
||||||
|
state.set("exploreOptions", fromJS(action.payload.response.data)),
|
||||||
|
UPDATE_LAST_MOVIE_FETCH_URL: (state, action) =>
|
||||||
|
state.set("lastFetchUrl", action.payload.url),
|
||||||
|
MOVIE_SUBTITLES_UPDATE_PENDING: (state, action) =>
|
||||||
|
state.setIn(
|
||||||
|
["movies", action.payload.main.imdbId, "fetchingSubtitles"],
|
||||||
|
true
|
||||||
|
),
|
||||||
|
MOVIE_SUBTITLES_UPDATE_FULFILLED: (state, action) =>
|
||||||
|
state
|
||||||
|
.setIn(["movies", action.payload.main.imdbId, "fetchingSubtitles"], false)
|
||||||
|
.setIn(
|
||||||
|
["movies", action.payload.main.imdbId, "subtitles"],
|
||||||
|
fromJS(action.payload.response.data)
|
||||||
|
),
|
||||||
|
SELECT_MOVIE: (state, action) =>
|
||||||
|
state.set("selectedImdbId", action.payload.imdbId),
|
||||||
|
MOVIE_UPDATE_FILTER: (state, action) =>
|
||||||
|
state.set("filter", action.payload.filter)
|
||||||
|
};
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||||
|
@ -1,15 +1,20 @@
|
|||||||
import { List, fromJS } from "immutable"
|
import { List, fromJS } from "immutable";
|
||||||
|
|
||||||
const defaultState = List();
|
const defaultState = List();
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"ADD_NOTIFICATION": (state, action) => state.push(fromJS({
|
ADD_NOTIFICATION: (state, action) =>
|
||||||
id: Math.random().toString(36).substring(7),
|
state.push(
|
||||||
|
fromJS({
|
||||||
|
id: Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substring(7),
|
||||||
...action.payload
|
...action.payload
|
||||||
})),
|
})
|
||||||
"REMOVE_NOTIFICATION": (state, action) =>
|
),
|
||||||
state.filter((e) => (e.get("id") !== action.payload.id)),
|
REMOVE_NOTIFICATION: (state, action) =>
|
||||||
}
|
state.filter(e => e.get("id") !== action.payload.id)
|
||||||
|
};
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||||
|
@ -1,28 +1,29 @@
|
|||||||
import { List, Map, fromJS } from "immutable"
|
import { List, Map, fromJS } from "immutable";
|
||||||
|
|
||||||
const defaultState = Map({
|
const defaultState = Map({
|
||||||
loadingPublic: false,
|
loadingPublic: false,
|
||||||
loadingManaged: false,
|
loadingManaged: false,
|
||||||
public: List(),
|
public: List(),
|
||||||
managed: List(),
|
managed: List()
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"PUBLIC_POLOCHON_LIST_FETCH_PENDING": state => state.set("loadingPublic", true),
|
PUBLIC_POLOCHON_LIST_FETCH_PENDING: state => state.set("loadingPublic", true),
|
||||||
"PUBLIC_POLOCHON_LIST_FETCH_FULFILLED": (state, action) => {
|
PUBLIC_POLOCHON_LIST_FETCH_FULFILLED: (state, action) => {
|
||||||
return state.merge({
|
return state.merge({
|
||||||
loadingPublic: false,
|
loadingPublic: false,
|
||||||
public: List(fromJS(action.payload.response.data)),
|
public: List(fromJS(action.payload.response.data))
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
"MANAGED_POLOCHON_LIST_FETCH_PENDING": state => state.set("loadingManaged", true),
|
MANAGED_POLOCHON_LIST_FETCH_PENDING: state =>
|
||||||
"MANAGED_POLOCHON_LIST_FETCH_FULFILLED": (state, action) => {
|
state.set("loadingManaged", true),
|
||||||
|
MANAGED_POLOCHON_LIST_FETCH_FULFILLED: (state, action) => {
|
||||||
return state.merge({
|
return state.merge({
|
||||||
loadingManaged: false,
|
loadingManaged: false,
|
||||||
managed: List(fromJS(action.payload.response.data)),
|
managed: List(fromJS(action.payload.response.data))
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||||
|
@ -1,35 +1,82 @@
|
|||||||
import { OrderedMap, Map, fromJS } from "immutable"
|
import { OrderedMap, Map, fromJS } from "immutable";
|
||||||
|
|
||||||
const defaultState = Map({
|
const defaultState = Map({
|
||||||
loading: false,
|
loading: false,
|
||||||
show: Map({
|
show: Map({
|
||||||
seasons: OrderedMap(),
|
seasons: OrderedMap()
|
||||||
}),
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"SHOW_FETCH_DETAILS_PENDING": state => state.set("loading", true),
|
SHOW_FETCH_DETAILS_PENDING: state => state.set("loading", true),
|
||||||
"SHOW_FETCH_DETAILS_FULFILLED": (state, action) => sortEpisodes(state, action.payload.response.data),
|
SHOW_FETCH_DETAILS_FULFILLED: (state, action) =>
|
||||||
"SHOW_UPDATE_STORE_WISHLIST": (state, action) => {
|
sortEpisodes(state, action.payload.response.data),
|
||||||
return state.mergeDeep(fromJS({
|
SHOW_UPDATE_STORE_WISHLIST: (state, action) => {
|
||||||
"show": {
|
return state.mergeDeep(
|
||||||
"tracked_season": action.payload.season,
|
fromJS({
|
||||||
"tracked_episode": action.payload.episode,
|
show: {
|
||||||
|
tracked_season: action.payload.season, // eslint-disable-line camelcase
|
||||||
|
tracked_episode: action.payload.episode // eslint-disable-line camelcase
|
||||||
}
|
}
|
||||||
}))},
|
})
|
||||||
"EPISODE_GET_DETAILS_PENDING": (state, action) => state.setIn(["show", "seasons", action.payload.main.season, action.payload.main.episode, "fetching"], true),
|
);
|
||||||
"EPISODE_GET_DETAILS_FULFILLED": (state, action) => {
|
|
||||||
let data = action.payload.response.data;
|
|
||||||
if (!data) { return state }
|
|
||||||
data.fetching = false;
|
|
||||||
return state.setIn(["show", "seasons", data.season, data.episode], fromJS(data));
|
|
||||||
},
|
},
|
||||||
"EPISODE_SUBTITLES_UPDATE_PENDING" : (state, action) =>
|
EPISODE_GET_DETAILS_PENDING: (state, action) =>
|
||||||
state.setIn(["show", "seasons", action.payload.main.season, action.payload.main.episode, "fetchingSubtitles"], true),
|
state.setIn(
|
||||||
"EPISODE_SUBTITLES_UPDATE_FULFILLED": (state, action) =>
|
[
|
||||||
state.setIn(["show", "seasons", action.payload.main.season, action.payload.main.episode, "subtitles"], fromJS(action.payload.response.data))
|
"show",
|
||||||
.setIn(["show", "seasons", action.payload.main.season, action.payload.main.episode, "fetchingSubtitles"], false),
|
"seasons",
|
||||||
}
|
action.payload.main.season,
|
||||||
|
action.payload.main.episode,
|
||||||
|
"fetching"
|
||||||
|
],
|
||||||
|
true
|
||||||
|
),
|
||||||
|
EPISODE_GET_DETAILS_FULFILLED: (state, action) => {
|
||||||
|
let data = action.payload.response.data;
|
||||||
|
if (!data) {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
data.fetching = false;
|
||||||
|
return state.setIn(
|
||||||
|
["show", "seasons", data.season, data.episode],
|
||||||
|
fromJS(data)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
EPISODE_SUBTITLES_UPDATE_PENDING: (state, action) =>
|
||||||
|
state.setIn(
|
||||||
|
[
|
||||||
|
"show",
|
||||||
|
"seasons",
|
||||||
|
action.payload.main.season,
|
||||||
|
action.payload.main.episode,
|
||||||
|
"fetchingSubtitles"
|
||||||
|
],
|
||||||
|
true
|
||||||
|
),
|
||||||
|
EPISODE_SUBTITLES_UPDATE_FULFILLED: (state, action) =>
|
||||||
|
state
|
||||||
|
.setIn(
|
||||||
|
[
|
||||||
|
"show",
|
||||||
|
"seasons",
|
||||||
|
action.payload.main.season,
|
||||||
|
action.payload.main.episode,
|
||||||
|
"subtitles"
|
||||||
|
],
|
||||||
|
fromJS(action.payload.response.data)
|
||||||
|
)
|
||||||
|
.setIn(
|
||||||
|
[
|
||||||
|
"show",
|
||||||
|
"seasons",
|
||||||
|
action.payload.main.season,
|
||||||
|
action.payload.main.episode,
|
||||||
|
"fetchingSubtitles"
|
||||||
|
],
|
||||||
|
false
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
const sortEpisodes = (state, show) => {
|
const sortEpisodes = (state, show) => {
|
||||||
let episodes = show.episodes;
|
let episodes = show.episodes;
|
||||||
@ -52,19 +99,19 @@ const sortEpisodes = (state, show) => {
|
|||||||
// Sort the episodes
|
// Sort the episodes
|
||||||
ret = ret.updateIn(["show", "seasons"], function(seasons) {
|
ret = ret.updateIn(["show", "seasons"], function(seasons) {
|
||||||
return seasons.map(function(episodes) {
|
return seasons.map(function(episodes) {
|
||||||
return episodes.sort((a,b) => a.get("episode") - b.get("episode"));
|
return episodes.sort((a, b) => a.get("episode") - b.get("episode"));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sort the seasons
|
// Sort the seasons
|
||||||
ret = ret.updateIn(["show", "seasons"], function(seasons) {
|
ret = ret.updateIn(["show", "seasons"], function(seasons) {
|
||||||
return seasons.sort(function(a,b) {
|
return seasons.sort(function(a, b) {
|
||||||
return a.first().get("season") - b.first().get("season");
|
return a.first().get("season") - b.first().get("season");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return ret
|
return ret;
|
||||||
}
|
};
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { OrderedMap, Map, fromJS } from "immutable"
|
import { OrderedMap, Map, fromJS } from "immutable";
|
||||||
|
|
||||||
const defaultState = Map({
|
const defaultState = Map({
|
||||||
loading: false,
|
loading: false,
|
||||||
@ -6,14 +6,14 @@ const defaultState = Map({
|
|||||||
filter: "",
|
filter: "",
|
||||||
selectedImdbId: "",
|
selectedImdbId: "",
|
||||||
lastFetchUrl: "",
|
lastFetchUrl: "",
|
||||||
exploreOptions: Map(),
|
exploreOptions: Map()
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"SHOW_LIST_FETCH_PENDING": state => state.set("loading", true),
|
SHOW_LIST_FETCH_PENDING: state => state.set("loading", true),
|
||||||
"SHOW_LIST_FETCH_FULFILLED": (state, action) => {
|
SHOW_LIST_FETCH_FULFILLED: (state, action) => {
|
||||||
let shows = Map();
|
let shows = Map();
|
||||||
action.payload.response.data.map(function (show) {
|
action.payload.response.data.map(function(show) {
|
||||||
show.fetchingDetails = false;
|
show.fetchingDetails = false;
|
||||||
show.fetchingSubtitles = false;
|
show.fetchingSubtitles = false;
|
||||||
shows = shows.set(show.imdb_id, fromJS(show));
|
shows = shows.set(show.imdb_id, fromJS(show));
|
||||||
@ -22,26 +22,30 @@ const handlers = {
|
|||||||
let selectedImdbId = "";
|
let selectedImdbId = "";
|
||||||
if (shows.size > 0) {
|
if (shows.size > 0) {
|
||||||
// Sort by year
|
// Sort by year
|
||||||
shows = shows.sort((a,b) => b.get("year") - a.get("year"));
|
shows = shows.sort((a, b) => b.get("year") - a.get("year"));
|
||||||
selectedImdbId = shows.first().get("imdb_id");
|
selectedImdbId = shows.first().get("imdb_id");
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.delete("shows").merge(Map({
|
return state.delete("shows").merge(
|
||||||
|
Map({
|
||||||
shows: shows,
|
shows: shows,
|
||||||
filter: "",
|
filter: "",
|
||||||
loading: false,
|
loading: false,
|
||||||
selectedImdbId: selectedImdbId,
|
selectedImdbId: selectedImdbId
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
"SHOW_GET_DETAILS_PENDING": (state, action) => state.setIn(["shows", action.payload.main.imdbId, "fetchingDetails"], true),
|
SHOW_GET_DETAILS_PENDING: (state, action) =>
|
||||||
"SHOW_GET_DETAILS_FULFILLED": (state, action) => {
|
state.setIn(["shows", action.payload.main.imdbId, "fetchingDetails"], true),
|
||||||
|
SHOW_GET_DETAILS_FULFILLED: (state, action) => {
|
||||||
let show = action.payload.response.data;
|
let show = action.payload.response.data;
|
||||||
show.fetchingDetails = false;
|
show.fetchingDetails = false;
|
||||||
show.fetchingSubtitles = false;
|
show.fetchingSubtitles = false;
|
||||||
return state.setIn(["shows", show.imdb_id], fromJS(show));
|
return state.setIn(["shows", show.imdb_id], fromJS(show));
|
||||||
},
|
},
|
||||||
"SHOW_GET_EXPLORE_OPTIONS_FULFILLED": (state, action) => state.set("exploreOptions", fromJS(action.payload.response.data)),
|
SHOW_GET_EXPLORE_OPTIONS_FULFILLED: (state, action) =>
|
||||||
"SHOW_UPDATE_STORE_WISHLIST": (state, action) => {
|
state.set("exploreOptions", fromJS(action.payload.response.data)),
|
||||||
|
SHOW_UPDATE_STORE_WISHLIST: (state, action) => {
|
||||||
let season = action.payload.season;
|
let season = action.payload.season;
|
||||||
let episode = action.payload.episode;
|
let episode = action.payload.episode;
|
||||||
if (action.payload.wishlisted) {
|
if (action.payload.wishlisted) {
|
||||||
@ -53,15 +57,21 @@ const handlers = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return state.mergeIn(["shows", action.payload.imdbId], Map({
|
return state.mergeIn(
|
||||||
"tracked_season": season,
|
["shows", action.payload.imdbId],
|
||||||
"tracked_episode": episode,
|
Map({
|
||||||
}));
|
tracked_season: season,
|
||||||
|
tracked_episode: episode
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
"UPDATE_LAST_SHOWS_FETCH_URL": (state, action) => state.set("lastFetchUrl", action.payload.url),
|
UPDATE_LAST_SHOWS_FETCH_URL: (state, action) =>
|
||||||
"SELECT_SHOW": (state, action) => state.set("selectedImdbId", action.payload.imdbId),
|
state.set("lastFetchUrl", action.payload.url),
|
||||||
"SHOWS_UPDATE_FILTER": (state, action) => state.set("filter", action.payload.filter),
|
SELECT_SHOW: (state, action) =>
|
||||||
}
|
state.set("selectedImdbId", action.payload.imdbId),
|
||||||
|
SHOWS_UPDATE_FILTER: (state, action) =>
|
||||||
|
state.set("filter", action.payload.filter)
|
||||||
|
};
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||||
|
@ -1,24 +1,30 @@
|
|||||||
import { Map, List, fromJS } from "immutable"
|
import { Map, List, fromJS } from "immutable";
|
||||||
|
|
||||||
const defaultState = Map({
|
const defaultState = Map({
|
||||||
"fetching": false,
|
fetching: false,
|
||||||
"searching": false,
|
searching: false,
|
||||||
"torrents": List(),
|
torrents: List(),
|
||||||
"searchResults": List(),
|
searchResults: List()
|
||||||
});
|
});
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"TORRENTS_FETCH_PENDING": state => state.set("fetching", true),
|
TORRENTS_FETCH_PENDING: state => state.set("fetching", true),
|
||||||
"TORRENTS_FETCH_FULFILLED": (state, action) => state.merge(fromJS({
|
TORRENTS_FETCH_FULFILLED: (state, action) =>
|
||||||
|
state.merge(
|
||||||
|
fromJS({
|
||||||
fetching: false,
|
fetching: false,
|
||||||
torrents: action.payload.response.data,
|
torrents: action.payload.response.data
|
||||||
})),
|
})
|
||||||
"TORRENTS_SEARCH_PENDING": state => state.set("searching", true),
|
),
|
||||||
"TORRENTS_SEARCH_FULFILLED": (state, action) => state.merge(fromJS({
|
TORRENTS_SEARCH_PENDING: state => state.set("searching", true),
|
||||||
|
TORRENTS_SEARCH_FULFILLED: (state, action) =>
|
||||||
|
state.merge(
|
||||||
|
fromJS({
|
||||||
searching: false,
|
searching: false,
|
||||||
searchResults: action.payload.response.data,
|
searchResults: action.payload.response.data
|
||||||
})),
|
})
|
||||||
}
|
)
|
||||||
|
};
|
||||||
|
|
||||||
export default (state = defaultState, action) =>
|
export default (state = defaultState, action) =>
|
||||||
handlers[action.type] ? handlers[action.type](state, action) : state;
|
handlers[action.type] ? handlers[action.type](state, action) : state;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Map, List, fromJS } from "immutable"
|
import { Map, List, fromJS } from "immutable";
|
||||||
|
|
||||||
import jwtDecode from "jwt-decode"
|
import jwtDecode from "jwt-decode";
|
||||||
import Cookies from "universal-cookie"
|
import Cookies from "universal-cookie";
|
||||||
|
|
||||||
const defaultState = Map({
|
const defaultState = Map({
|
||||||
error: "",
|
error: "",
|
||||||
@ -18,52 +18,66 @@ const defaultState = Map({
|
|||||||
polochonActivated: false,
|
polochonActivated: false,
|
||||||
tokens: List(),
|
tokens: List(),
|
||||||
modules: Map(),
|
modules: Map(),
|
||||||
modulesLoading: false,
|
modulesLoading: false
|
||||||
});
|
});
|
||||||
|
|
||||||
let initialState = defaultState
|
let initialState = defaultState;
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
if (token && token !== "") {
|
if (token && token !== "") {
|
||||||
initialState = updateFromToken(initialState, token)
|
initialState = updateFromToken(initialState, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlers = {
|
const handlers = {
|
||||||
"USER_LOGIN_PENDING": state => state.set("loading", true),
|
USER_LOGIN_PENDING: state => state.set("loading", true),
|
||||||
"USER_LOGIN_ERROR": (state, action) => {
|
USER_LOGIN_ERROR: (state, action) => {
|
||||||
state.set("loading", false);
|
state.set("loading", false);
|
||||||
return logoutUser(action.payload.response.message);
|
return logoutUser(action.payload.response.message);
|
||||||
},
|
},
|
||||||
"USER_LOGIN_FULFILLED": (state, action) => {
|
USER_LOGIN_FULFILLED: (state, action) => {
|
||||||
return updateFromToken(state, action.payload.response.data.token)
|
return updateFromToken(state, action.payload.response.data.token);
|
||||||
},
|
},
|
||||||
"USER_SIGNUP_PENDING": state => state.set("loading", true),
|
USER_SIGNUP_PENDING: state => state.set("loading", true),
|
||||||
"USER_SIGNUP_ERROR": (state, action) => state.merge(Map({
|
USER_SIGNUP_ERROR: (state, action) =>
|
||||||
|
state.merge(
|
||||||
|
Map({
|
||||||
error: action.payload.response.message,
|
error: action.payload.response.message,
|
||||||
loading: false,
|
loading: false
|
||||||
})),
|
})
|
||||||
"USER_SIGNUP_FULFILLED": state => state.merge(Map({error: "", loading: false})),
|
),
|
||||||
"USER_SET_TOKEN": (state, action) => updateFromToken(state, action.payload.token),
|
USER_SIGNUP_FULFILLED: state =>
|
||||||
"USER_LOGOUT": () => logoutUser(),
|
state.merge(Map({ error: "", loading: false })),
|
||||||
"GET_USER_PENDING": state => state.set("loading", true),
|
USER_SET_TOKEN: (state, action) =>
|
||||||
"GET_USER_FULFILLED": (state, action) => state.merge(Map({
|
updateFromToken(state, action.payload.token),
|
||||||
|
USER_LOGOUT: () => logoutUser(),
|
||||||
|
GET_USER_PENDING: state => state.set("loading", true),
|
||||||
|
GET_USER_FULFILLED: (state, action) =>
|
||||||
|
state.merge(
|
||||||
|
Map({
|
||||||
polochonToken: action.payload.response.data.token,
|
polochonToken: action.payload.response.data.token,
|
||||||
polochonUrl: action.payload.response.data.url,
|
polochonUrl: action.payload.response.data.url,
|
||||||
polochonName: action.payload.response.data.name,
|
polochonName: action.payload.response.data.name,
|
||||||
polochonId: action.payload.response.data.id,
|
polochonId: action.payload.response.data.id,
|
||||||
polochonActivated: action.payload.response.data.activated,
|
polochonActivated: action.payload.response.data.activated,
|
||||||
loading: false,
|
loading: false
|
||||||
})),
|
})
|
||||||
"GET_USER_TOKENS_PENDING": state => state.set("loading", true),
|
),
|
||||||
"GET_USER_TOKENS_FULFILLED": (state, action) => state.merge(Map({
|
GET_USER_TOKENS_PENDING: state => state.set("loading", true),
|
||||||
"tokens": fromJS(action.payload.response.data),
|
GET_USER_TOKENS_FULFILLED: (state, action) =>
|
||||||
"loading": false,
|
state.merge(
|
||||||
})),
|
Map({
|
||||||
"GET_USER_MODULES_PENDING": state => state.set("modulesLoading", true),
|
tokens: fromJS(action.payload.response.data),
|
||||||
"GET_USER_MODULES_FULFILLED": (state, action) => state.merge(Map({
|
loading: false
|
||||||
"modules": fromJS(action.payload.response.data),
|
})
|
||||||
"modulesLoading": false,
|
),
|
||||||
})),
|
GET_USER_MODULES_PENDING: state => state.set("modulesLoading", true),
|
||||||
}
|
GET_USER_MODULES_FULFILLED: (state, action) =>
|
||||||
|
state.merge(
|
||||||
|
Map({
|
||||||
|
modules: fromJS(action.payload.response.data),
|
||||||
|
modulesLoading: false
|
||||||
|
})
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
function logoutUser(error) {
|
function logoutUser(error) {
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
@ -72,7 +86,7 @@ function logoutUser(error) {
|
|||||||
if (error !== "") {
|
if (error !== "") {
|
||||||
return defaultState.set("error", error);
|
return defaultState.set("error", error);
|
||||||
} else {
|
} else {
|
||||||
return defaultState
|
return defaultState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,14 +97,16 @@ function updateFromToken(state, token) {
|
|||||||
const cookies = new Cookies();
|
const cookies = new Cookies();
|
||||||
cookies.set("token", token);
|
cookies.set("token", token);
|
||||||
|
|
||||||
return state.merge(Map({
|
return state.merge(
|
||||||
|
Map({
|
||||||
error: "",
|
error: "",
|
||||||
isLogged: true,
|
isLogged: true,
|
||||||
isTokenSet: true,
|
isTokenSet: true,
|
||||||
isAdmin: decodedToken.isAdmin,
|
isAdmin: decodedToken.isAdmin,
|
||||||
isActivated: decodedToken.isActivated,
|
isActivated: decodedToken.isActivated,
|
||||||
username: decodedToken.username,
|
username: decodedToken.username
|
||||||
}))
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default (state = initialState, action) =>
|
export default (state = initialState, action) =>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import axios from "axios"
|
import axios from "axios";
|
||||||
|
|
||||||
// This functions returns an axios instance, the token is added to the
|
// This functions returns an axios instance, the token is added to the
|
||||||
// configuration if found in the localStorage
|
// configuration if found in the localStorage
|
||||||
@ -6,17 +6,22 @@ export function configureAxios(headers = {}) {
|
|||||||
// Get the token from the localStorate
|
// Get the token from the localStorate
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
if (token) {
|
if (token) {
|
||||||
headers = { "Authorization": `Bearer ${token}` };
|
headers = { Authorization: `Bearer ${token}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
return axios.create({
|
return axios.create({
|
||||||
headers
|
headers
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function takes en event prefix to dispatch evens during the life of the
|
// This function takes en event prefix to dispatch evens during the life of the
|
||||||
// request, it also take a promise (axios request)
|
// request, it also take a promise (axios request)
|
||||||
export function request(eventPrefix, promise, callbackEvents = null, mainPayload = null) {
|
export function request(
|
||||||
|
eventPrefix,
|
||||||
|
promise,
|
||||||
|
callbackEvents = null,
|
||||||
|
mainPayload = null
|
||||||
|
) {
|
||||||
// Events
|
// Events
|
||||||
const pending = `${eventPrefix}_PENDING`;
|
const pending = `${eventPrefix}_PENDING`;
|
||||||
const fulfilled = `${eventPrefix}_FULFILLED`;
|
const fulfilled = `${eventPrefix}_FULFILLED`;
|
||||||
@ -26,37 +31,36 @@ export function request(eventPrefix, promise, callbackEvents = null, mainPayload
|
|||||||
dispatch({
|
dispatch({
|
||||||
type: pending,
|
type: pending,
|
||||||
payload: {
|
payload: {
|
||||||
main: mainPayload,
|
main: mainPayload
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return promise
|
return promise
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (response.data.status === "error")
|
if (response.data.status === "error") {
|
||||||
{
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "ADD_ALERT_ERROR",
|
type: "ADD_ALERT_ERROR",
|
||||||
payload: {
|
payload: {
|
||||||
message: response.data.message,
|
message: response.data.message,
|
||||||
main: mainPayload,
|
main: mainPayload
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
dispatch({
|
dispatch({
|
||||||
type: errored,
|
type: errored,
|
||||||
payload: {
|
payload: {
|
||||||
response: response.data,
|
response: response.data,
|
||||||
main: mainPayload,
|
main: mainPayload
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
type: fulfilled,
|
type: fulfilled,
|
||||||
payload: {
|
payload: {
|
||||||
response: response.data,
|
response: response.data,
|
||||||
main: mainPayload,
|
main: mainPayload
|
||||||
},
|
}
|
||||||
})
|
});
|
||||||
if (callbackEvents) {
|
if (callbackEvents) {
|
||||||
for (let event of callbackEvents) {
|
for (let event of callbackEvents) {
|
||||||
if (typeof event === "function") {
|
if (typeof event === "function") {
|
||||||
@ -70,16 +74,16 @@ export function request(eventPrefix, promise, callbackEvents = null, mainPayload
|
|||||||
// Unauthorized
|
// Unauthorized
|
||||||
if (error.response && error.response.status == 401) {
|
if (error.response && error.response.status == 401) {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "USER_LOGOUT",
|
type: "USER_LOGOUT"
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "ADD_ALERT_ERROR",
|
type: "ADD_ALERT_ERROR",
|
||||||
payload: {
|
payload: {
|
||||||
message: error.response.data,
|
message: error.response.data,
|
||||||
main: mainPayload,
|
main: mainPayload
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,7 @@ if (process.env.NODE_ENV === "development") {
|
|||||||
// Export the store
|
// Export the store
|
||||||
const store = createStore(
|
const store = createStore(
|
||||||
rootReducer,
|
rootReducer,
|
||||||
compose(
|
compose(applyMiddleware(...middlewares))
|
||||||
applyMiddleware(
|
|
||||||
...middlewares,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default store;
|
export default store;
|
||||||
|
|
||||||
|
@ -1,59 +1,64 @@
|
|||||||
export const prettyDurationFromMinutes = (runtime) => {
|
export const prettyDurationFromMinutes = runtime => {
|
||||||
const hours = Math.floor(runtime / 60);
|
const hours = Math.floor(runtime / 60);
|
||||||
const minutes = (runtime % 60);
|
const minutes = runtime % 60;
|
||||||
|
|
||||||
let duration = "";
|
let duration = "";
|
||||||
if (hours > 0) { duration += hours + "h" }
|
if (hours > 0) {
|
||||||
if (minutes > 0) { duration += ("0" + minutes).slice(-2) }
|
duration += hours + "h";
|
||||||
if (hours === 0) { duration += " min" }
|
}
|
||||||
|
if (minutes > 0) {
|
||||||
|
duration += ("0" + minutes).slice(-2);
|
||||||
|
}
|
||||||
|
if (hours === 0) {
|
||||||
|
duration += " min";
|
||||||
|
}
|
||||||
|
|
||||||
return duration;
|
return duration;
|
||||||
}
|
};
|
||||||
|
|
||||||
const pad = (d) => (d < 10) ? "0" + d.toString() : d.toString();
|
const pad = d => (d < 10 ? "0" + d.toString() : d.toString());
|
||||||
|
|
||||||
export const prettyEpisodeName = (showName, season, episode) =>
|
export const prettyEpisodeName = (showName, season, episode) =>
|
||||||
`${showName} S${pad(season)}E${pad(episode)}`;
|
`${showName} S${pad(season)}E${pad(episode)}`;
|
||||||
|
|
||||||
export const inLibrary = (element) =>
|
export const inLibrary = element => element.get("polochon_url", "") !== "";
|
||||||
element.get("polochon_url", "") !== "";
|
|
||||||
|
|
||||||
export const isWishlisted = (element) => {
|
export const isWishlisted = element => {
|
||||||
const wishlisted = element.get("wishlisted", undefined)
|
const wishlisted = element.get("wishlisted", undefined);
|
||||||
if (wishlisted !== undefined) {
|
if (wishlisted !== undefined) {
|
||||||
return wishlisted;
|
return wishlisted;
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackedSeason = element.get("tracked_season", null);
|
const trackedSeason = element.get("tracked_season", null);
|
||||||
const trackedEpisode = element.get("tracked_episode", null);
|
const trackedEpisode = element.get("tracked_episode", null);
|
||||||
if ((trackedSeason !== null) && (trackedEpisode !== null)) {
|
if (trackedSeason !== null && trackedEpisode !== null) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const isEpisodeWishlisted = (element, trackedSeason, trackedEpisode) => {
|
export const isEpisodeWishlisted = (element, trackedSeason, trackedEpisode) => {
|
||||||
if ((trackedSeason === null) && (trackedEpisode === null)) {
|
if (trackedSeason === null && trackedEpisode === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((trackedSeason === 0) && (trackedEpisode === 0)) {
|
if (trackedSeason === 0 && trackedEpisode === 0) {
|
||||||
return true
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const season = element.get("season", 0);
|
const season = element.get("season", 0);
|
||||||
const episode = element.get("episode", 0);
|
const episode = element.get("episode", 0);
|
||||||
if (season < trackedSeason) {
|
if (season < trackedSeason) {
|
||||||
return false
|
return false;
|
||||||
} else if (season > trackedSeason) {
|
} else if (season > trackedSeason) {
|
||||||
return true
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return (episode >= trackedEpisode)
|
return episode >= trackedEpisode;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const prettySize = (fileSizeInBytes) => {
|
export const prettySize = fileSizeInBytes => {
|
||||||
var i = -1;
|
var i = -1;
|
||||||
var byteUnits = [" kB", " MB", " GB", " TB", "PB", "EB", "ZB", "YB"];
|
var byteUnits = [" kB", " MB", " GB", " TB", "PB", "EB", "ZB", "YB"];
|
||||||
do {
|
do {
|
||||||
@ -62,4 +67,4 @@ export const prettySize = (fileSizeInBytes) => {
|
|||||||
} while (fileSizeInBytes > 1024);
|
} while (fileSizeInBytes > 1024);
|
||||||
|
|
||||||
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
|
return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
|
||||||
}
|
};
|
||||||
|
1972
frontend/package-lock.json
generated
1972
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -17,22 +17,20 @@
|
|||||||
"moment": "^2.20.1",
|
"moment": "^2.20.1",
|
||||||
"popper.js": "^1.15.0",
|
"popper.js": "^1.15.0",
|
||||||
"prop-types": "^15.6.0",
|
"prop-types": "^15.6.0",
|
||||||
"react": "^16.8.6",
|
"react": "^16.13.0",
|
||||||
"react-bootstrap": "^1.0.0-beta.9",
|
"react-bootstrap": "^1.0.0-beta.9",
|
||||||
"react-bootstrap-sweetalert": "^4.2.3",
|
"react-bootstrap-sweetalert": "^5.1.9",
|
||||||
"react-bootstrap-toggle": "^2.2.6",
|
"react-bootstrap-toggle": "^2.2.6",
|
||||||
"react-dom": "^16.2.0",
|
"react-dom": "^16.13.0",
|
||||||
"react-infinite-scroll-component": "^4.5.2",
|
"react-infinite-scroll-component": "^5.0.4",
|
||||||
"react-loading": "2.0.3",
|
"react-loading": "2.0.3",
|
||||||
"react-redux": "6.0.1",
|
"react-redux": "^7.2.0",
|
||||||
"react-router": "5.0.1",
|
|
||||||
"react-router-bootstrap": "^0.25.0",
|
"react-router-bootstrap": "^0.25.0",
|
||||||
"react-router-dom": "^5.0.1",
|
"react-router-dom": "^5.0.1",
|
||||||
"redux": "^4.0.1",
|
"redux": "^4.0.1",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"ua-parser-js": "^0.7.17",
|
"ua-parser-js": "^0.7.17"
|
||||||
"universal-cookie": "^2.1.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.5.0",
|
"@babel/core": "^7.5.0",
|
||||||
@ -40,26 +38,27 @@
|
|||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"autoprefixer": "^9.5.1",
|
"autoprefixer": "^9.5.1",
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"babel-eslint": "^10.0.1",
|
"babel-eslint": "^10.1.0",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
"clean-webpack-plugin": "^3.0.0",
|
"clean-webpack-plugin": "^3.0.0",
|
||||||
"css-loader": "^3.4.2",
|
"css-loader": "^3.4.2",
|
||||||
"del": "^3.0.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint": "^5.16.0",
|
"eslint-config-prettier": "^6.10.0",
|
||||||
|
"eslint-plugin-import": "^2.20.1",
|
||||||
|
"eslint-plugin-prettier": "^3.1.2",
|
||||||
"eslint-plugin-react": "^7.6.1",
|
"eslint-plugin-react": "^7.6.1",
|
||||||
"eslint-plugin-react-hooks": "^1.6.0",
|
"eslint-plugin-react-hooks": "^2.5.0",
|
||||||
"file-loader": "^3.0.1",
|
"file-loader": "^5.1.0",
|
||||||
"html-webpack-plugin": "^3.2.0",
|
"html-webpack-plugin": "^3.2.0",
|
||||||
"less": "^2.3.1",
|
|
||||||
"less-loader": "^4.0.5",
|
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^4.12.0",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
"sass-loader": "^7.1.0",
|
"prettier": "^1.19.1",
|
||||||
"style-loader": "^0.23.1",
|
"sass-loader": "^8.0.2",
|
||||||
"url-loader": "^1.1.2",
|
"style-loader": "^1.1.3",
|
||||||
|
"universal-cookie": "^4.0.3",
|
||||||
|
"url-loader": "^3.0.0",
|
||||||
"webpack": "^4.31.0",
|
"webpack": "^4.31.0",
|
||||||
"webpack-cli": "^3.3.2",
|
"webpack-cli": "^3.3.11",
|
||||||
"webpack-pwa-manifest": "^4.0.0",
|
"webpack-pwa-manifest": "^4.0.0"
|
||||||
"webpack-stream": "^5.2.1"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [require("autoprefixer")]
|
||||||
require("autoprefixer")
|
};
|
||||||
]
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
var webpack = require("webpack");
|
var webpack = require("webpack");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var WebpackPwaManifest = require("webpack-pwa-manifest")
|
var WebpackPwaManifest = require("webpack-pwa-manifest");
|
||||||
var HtmlWebpackPlugin = require("html-webpack-plugin");
|
var HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
var { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
var { CleanWebpackPlugin } = require("clean-webpack-plugin");
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ const config = {
|
|||||||
entry: path.join(SRC_DIR, "js/app.js"),
|
entry: path.join(SRC_DIR, "js/app.js"),
|
||||||
output: {
|
output: {
|
||||||
path: BUILD_DIR,
|
path: BUILD_DIR,
|
||||||
filename: "[contenthash]-app.js",
|
filename: "[contenthash]-app.js"
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
runtimeChunk: "single",
|
runtimeChunk: "single",
|
||||||
@ -28,10 +28,10 @@ const config = {
|
|||||||
vendor: {
|
vendor: {
|
||||||
test: /[\\/]node_modules[\\/]/,
|
test: /[\\/]node_modules[\\/]/,
|
||||||
name: "vendors",
|
name: "vendors",
|
||||||
chunks: "all",
|
chunks: "all"
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
@ -47,73 +47,68 @@ const config = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.scss$/,
|
test: /\.scss$/,
|
||||||
use: [
|
use: ["style-loader", "css-loader", "sass-loader", "postcss-loader"]
|
||||||
"style-loader",
|
|
||||||
"css-loader",
|
|
||||||
"sass-loader",
|
|
||||||
"postcss-loader",
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|jpg|svg|ico)$/,
|
test: /\.(png|jpg|svg|ico)$/,
|
||||||
use: ["file-loader?name=[hash]-[name].[ext]"],
|
use: ["file-loader?name=[hash]-[name].[ext]"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
use: ["url-loader?limit=10000&mimetype=application/font-woff"],
|
use: ["url-loader?limit=10000&mimetype=application/font-woff"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
use: ["file-loader"],
|
use: ["file-loader"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
|
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV)
|
||||||
}),
|
}),
|
||||||
new webpack.HashedModuleIdsPlugin(),
|
new webpack.HashedModuleIdsPlugin(),
|
||||||
new CleanWebpackPlugin({
|
new CleanWebpackPlugin({
|
||||||
cleanOnceBeforeBuildPatterns: ["**/*", "!img/**/*", "!img"],
|
cleanOnceBeforeBuildPatterns: ["**/*", "!img/**/*", "!img"]
|
||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: path.join(SRC_DIR, "index.html"),
|
template: path.join(SRC_DIR, "index.html")
|
||||||
}),
|
}),
|
||||||
new WebpackPwaManifest({
|
new WebpackPwaManifest({
|
||||||
fingerprints: true,
|
fingerprints: true,
|
||||||
inject: true,
|
inject: true,
|
||||||
ios: {
|
ios: {
|
||||||
"apple-mobile-web-app-status-bar-style": "default",
|
"apple-mobile-web-app-status-bar-style": "default",
|
||||||
"apple-mobile-web-app-title": "Canapé",
|
"apple-mobile-web-app-title": "Canapé"
|
||||||
},
|
},
|
||||||
name: "Canapé",
|
name: "Canapé",
|
||||||
short_name: "Canapé",
|
short_name: "Canapé", // eslint-disable-line camelcase
|
||||||
background_color: "#4e5d6c",
|
background_color: "#4e5d6c", // eslint-disable-line camelcase
|
||||||
theme_color: "#4e5d6c",
|
theme_color: "#4e5d6c", // eslint-disable-line camelcase
|
||||||
display: "standalone",
|
display: "standalone",
|
||||||
orientation: "omit",
|
orientation: "omit",
|
||||||
scope: "/",
|
scope: "/",
|
||||||
start_url: "/",
|
start_url: "/", // eslint-disable-line camelcase
|
||||||
icons: [
|
icons: [
|
||||||
{
|
{
|
||||||
src: path.resolve(__dirname, "img/android-chrome-512x512.png"),
|
src: path.resolve(__dirname, "img/android-chrome-512x512.png"),
|
||||||
sizes: [96, 128, 192, 256, 384, 512],
|
sizes: [96, 128, 192, 256, 384, 512]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
src: path.resolve(__dirname, "img/apple-touch-icon.png"),
|
src: path.resolve(__dirname, "img/apple-touch-icon.png"),
|
||||||
sizes: [80, 120, 152, 167, 180],
|
sizes: [80, 120, 152, 167, 180],
|
||||||
ios: true
|
ios: true
|
||||||
},
|
}
|
||||||
],
|
]
|
||||||
}),
|
})
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".js"],
|
extensions: [".js"]
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (process.env.NODE_ENV !== "production") {
|
if (process.env.NODE_ENV !== "production") {
|
||||||
config.devtool = "#cheap-module-source-map"
|
config.devtool = "#cheap-module-source-map";
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = config;
|
module.exports = config;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user