🦟 Fixed 404s upon page refresh

This commit is contained in:
2022-03-01 23:01:57 -08:00
parent 769e2e3edc
commit 9ec5040bd7
10 changed files with 238 additions and 189 deletions

View File

@@ -5,6 +5,7 @@ import {
IMAGETRANSFORMATION_SERVICE_BASE_URI,
LIBRARY_SERVICE_BASE_URI,
LIBRARY_SERVICE_HOST,
SEARCH_SERVICE_BASE_URI,
} from "../constants/endpoints";
import {
IMS_COMIC_BOOK_GROUPS_FETCHED,
@@ -257,7 +258,14 @@ export const extractComicArchive =
});
};
export const searchIssue = (options) => async (dispatch) => {};
export const searchIssue = (query) => async (dispatch) => {
const foo = await axios({
url: `${SEARCH_SERVICE_BASE_URI}/searchIssue`,
method: "POST",
data: query,
});
console.log(foo);
};
export const analyzeImage =
(imageFilePath: string | Buffer) => async (dispatch) => {
dispatch({

View File

@@ -12,7 +12,7 @@ import DownloadsPanel from "./ComicDetail/DownloadsPanel";
import { EditMetadataPanel } from "./ComicDetail/EditMetadataPanel";
import { Menu } from "./ComicDetail/ActionMenu/Menu";
import { isEmpty, isUndefined, isNil, findIndex } from "lodash";
import { isEmpty, isUndefined, isNil } from "lodash";
import { RootState } from "threetwo-ui-typings";
import { getComicBookDetailById } from "../actions/comicinfo.actions";
@@ -117,6 +117,7 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
},
};
// check for the availability of CV metadata
const isComicBookMetadataAvailable =
comicBookDetailData.sourcedMetadata &&
!isUndefined(comicBookDetailData.sourcedMetadata.comicvine) &&
@@ -125,6 +126,29 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
) &&
!isEmpty(comicBookDetailData.sourcedMetadata);
// check for the availability of rawFileDetails
const areRawFileDetailsAvailable =
!isUndefined(comicBookDetailData.rawFileDetails) &&
!isEmpty(comicBookDetailData.rawFileDetails.cover);
// query for airdc++
const airDCPPQuery = {};
if (isComicBookMetadataAvailable) {
Object.assign(airDCPPQuery, {
issue: {
name: comicBookDetailData.sourcedMetadata.comicvine.volumeInformation
.name,
},
});
} else if (areRawFileDetailsAvailable) {
Object.assign(airDCPPQuery, {
issue: {
name: comicBookDetailData.inferredMetadata.issue.name,
number: comicBookDetailData.inferredMetadata.issue.number,
},
});
}
// Tab content and header details
const tabGroup = [
{
@@ -134,7 +158,7 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
content: isComicBookMetadataAvailable ? (
<VolumeInformation data={comicBookDetailData} key={1} />
) : null,
include: isComicBookMetadataAvailable,
shouldShow: isComicBookMetadataAvailable,
},
{
id: 2,
@@ -156,8 +180,8 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
</div>
</div>
),
include:
!isNil(comicBookDetailData.sourcedMetadata) &&
shouldShow:
!isUndefined(comicBookDetailData.sourcedMetadata) &&
!isEmpty(comicBookDetailData.sourcedMetadata.comicInfo),
},
{
@@ -165,18 +189,14 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
icon: <i className="fa-regular fa-file-archive"></i>,
name: "Archive Operations",
content: <ArchiveOperations data={comicBookDetailData} key={3} />,
include:
!isUndefined(comicBookDetailData.rawFileDetails) &&
!isEmpty(comicBookDetailData.rawFileDetails.cover),
shouldShow: areRawFileDetailsAvailable,
},
{
id: 4,
icon: <i className="fa-solid fa-floppy-disk"></i>,
name: "Acquisition",
content: (
<AcquisitionPanel comicBookMetadata={comicBookDetailData} key={4} />
),
include: !isNil(comicBookDetailData.rawFileDetails),
content: <AcquisitionPanel query={airDCPPQuery} key={4} />,
shouldShow: true,
},
{
id: 5,
@@ -194,12 +214,11 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
key={5}
/>
),
include: !isNil(comicBookDetailData.rawFileDetails),
shouldShow: true,
},
];
// filtered Tabs
const filteredTabs = tabGroup.filter((tab) => tab.include);
const filteredTabs = tabGroup.filter((tab) => tab.shouldShow);
// Tabs
const MetadataTabGroup = () => {
@@ -245,10 +264,7 @@ export const ComicDetail = ({}: ComicDetailProps): ReactElement => {
// 2. from the CV-scraped version
let imagePath = "";
let comicBookTitle = "";
if (
!isUndefined(comicBookDetailData.rawFileDetails) &&
!isEmpty(comicBookDetailData.rawFileDetails.cover)
) {
if (areRawFileDetailsAvailable) {
const encodedFilePath = encodeURI(
`${LIBRARY_SERVICE_HOST}/${comicBookDetailData.rawFileDetails.cover.filePath}`,
);

View File

@@ -16,16 +16,15 @@ import ellipsize from "ellipsize";
import { isEmpty, isNil, map } from "lodash";
import { AirDCPPSocketContext } from "../../context/AirDCPPSocket";
interface IAcquisitionPanelProps {
comicBookMetadata: any;
query: any;
}
export const AcquisitionPanel = (
props: IAcquisitionPanelProps,
): ReactElement => {
const volumeName =
props.comicBookMetadata.sourcedMetadata.comicvine.volumeInformation.name;
const sanitizedVolumeName = volumeName.replace(/[^a-zA-Z0-9 ]/g, " ");
const issueName = props.comicBookMetadata.sourcedMetadata.comicvine.name;
console.log(props);
const issueName = props.query.issue.name;
const sanitizedIssueName = issueName.replace(/[^a-zA-Z0-9 ]/g, " ");
// Selectors for picking state
const airDCPPSearchResults = useSelector((state: RootState) => {
@@ -51,7 +50,7 @@ export const AcquisitionPanel = (
// AirDC++ search query
const dcppSearchQuery = {
query: {
pattern: `${sanitizedVolumeName.replace(/#/g, "")}`,
pattern: `${sanitizedIssueName.replace(/#/g, "")}`,
extensions: ["cbz", "cbr"],
},
hub_urls: map(

View File

@@ -191,151 +191,154 @@ export const Library = ({}: IComicBookLibraryProps): ReactElement => {
<h1 className="title">Library</h1>
{/* Search bar */}
<SearchBar />
<div>
<div className="library">
<table {...getTableProps()} className="table is-hoverable">
<thead>
{headerGroups.map((headerGroup, idx) => (
<tr key={idx} {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column, idx) => (
<th key={idx} {...column.getHeaderProps()}>
{column.render("Header")}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{page.map((row, idx) => {
prepareRow(row);
return (
<tr
key={idx}
{...row.getRowProps()}
onClick={() => navigateToComicDetail(row.original._id)}
>
{row.cells.map((cell, idx) => {
return (
<td
key={idx}
{...cell.getCellProps()}
className="is-vcentered"
>
{cell.render("Cell")}
</td>
);
})}
{!isUndefined(data) ? (
<div>
<div className="library">
<table {...getTableProps()} className="table is-hoverable">
<thead>
{headerGroups.map((headerGroup, idx) => (
<tr key={idx} {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column, idx) => (
<th key={idx} {...column.getHeaderProps()}>
{column.render("Header")}
</th>
))}
</tr>
);
})}
</tbody>
</table>
))}
</thead>
{/* pagination controls */}
<nav
className="pagination"
role="navigation"
aria-label="pagination"
>
{/* x of total indicator */}
<div>
Page {pageIndex} of {Math.ceil(pageTotal / pageSize)}
(Total resources: {pageTotal})
</div>
<tbody {...getTableBodyProps()}>
{page.map((row, idx) => {
prepareRow(row);
return (
<tr
key={idx}
{...row.getRowProps()}
onClick={() => navigateToComicDetail(row.original._id)}
>
{row.cells.map((cell, idx) => {
return (
<td
key={idx}
{...cell.getCellProps()}
className="is-vcentered"
>
{cell.render("Cell")}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
{/* previous page and next page controls */}
<div className="field has-addons">
<p className="control">
<button
className="button"
onClick={() => previousPage()}
disabled={!canPreviousPage}
>
Previous Page
</button>
</p>
<p className="control">
<button
className="button"
onClick={() => nextPage()}
disabled={!canNextPage}
>
<span>Next Page</span>
</button>
</p>
</div>
{/* first and last page controls */}
<div className="field has-addons">
<p className="control">
<button
className="button"
onClick={() => gotoPage(1)}
disabled={!canPreviousPage}
>
<i className="fas fa-angle-double-left"></i>
</button>
</p>
<p className="control">
<button
className="button"
onClick={() => gotoPage(Math.ceil(pageTotal / pageSize))}
disabled={!canNextPage}
>
<i className="fas fa-angle-double-right"></i>
</button>
</p>
</div>
{/* page selector */}
<span>
Go to page:
<input
type="number"
className="input"
defaultValue={pageIndex}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) : 0;
gotoPage(page);
}}
style={{ width: "100px" }}
/>
</span>
{/* page size selector */}
<div
className={
"dropdown " + (isPageSizeDropdownCollapsed ? "is-active" : "")
}
onBlur={() => togglePageSizeDropdown()}
{/* pagination controls */}
<nav
className="pagination"
role="navigation"
aria-label="pagination"
>
<div className="dropdown-trigger">
<button
className="button"
aria-haspopup="true"
aria-controls="dropdown-menu"
onClick={() => togglePageSizeDropdown()}
>
<span>Select Page Size</span>
<span className="icon is-small">
<i className="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
{/* x of total indicator */}
<div>
Page {pageIndex} of {Math.ceil(pageTotal / pageSize)}
(Total resources: {pageTotal})
</div>
<div className="dropdown-menu" id="dropdown-menu" role="menu">
<div className="dropdown-content">
{[10, 20, 30, 40, 50].map((pageSize) => (
<a href="#" className="dropdown-item" key={pageSize}>
Show {pageSize}
</a>
))}
{/* previous page and next page controls */}
<div className="field has-addons">
<p className="control">
<button
className="button"
onClick={() => previousPage()}
disabled={!canPreviousPage}
>
Previous Page
</button>
</p>
<p className="control">
<button
className="button"
onClick={() => nextPage()}
disabled={!canNextPage}
>
<span>Next Page</span>
</button>
</p>
</div>
{/* first and last page controls */}
<div className="field has-addons">
<p className="control">
<button
className="button"
onClick={() => gotoPage(1)}
disabled={!canPreviousPage}
>
<i className="fas fa-angle-double-left"></i>
</button>
</p>
<p className="control">
<button
className="button"
onClick={() => gotoPage(Math.ceil(pageTotal / pageSize))}
disabled={!canNextPage}
>
<i className="fas fa-angle-double-right"></i>
</button>
</p>
</div>
{/* page selector */}
<span>
Go to page:
<input
type="number"
className="input"
defaultValue={pageIndex}
onChange={(e) => {
const page = e.target.value ? Number(e.target.value) : 0;
gotoPage(page);
}}
style={{ width: "100px" }}
/>
</span>
{/* page size selector */}
<div
className={
"dropdown " +
(isPageSizeDropdownCollapsed ? "is-active" : "")
}
onBlur={() => togglePageSizeDropdown()}
>
<div className="dropdown-trigger">
<button
className="button"
aria-haspopup="true"
aria-controls="dropdown-menu"
onClick={() => togglePageSizeDropdown()}
>
<span>Select Page Size</span>
<span className="icon is-small">
<i className="fas fa-angle-down" aria-hidden="true"></i>
</span>
</button>
</div>
<div className="dropdown-menu" id="dropdown-menu" role="menu">
<div className="dropdown-content">
{[10, 20, 30, 40, 50].map((pageSize) => (
<a href="#" className="dropdown-item" key={pageSize}>
Show {pageSize}
</a>
))}
</div>
</div>
</div>
</div>
</nav>
</nav>
</div>
</div>
</div>
) : null}
</div>
</section>
);

View File

@@ -1,30 +1,47 @@
import React, { ReactElement } from "react";
import React, { ReactElement, useCallback } from "react";
import PropTypes from "prop-types";
import { Form, Field } from "react-final-form";
import { Link } from "react-router-dom";
import { useDispatch } from "react-redux";
import { searchIssue } from "../../actions/fileops.actions";
export const SearchBar = (): ReactElement => {
const foo = () => {};
const dispatch = useDispatch();
const handleSubmit = useCallback((e) => {
console.log(e);
dispatch(
searchIssue({
queryObject: {
volumeName: e.search,
},
}),
);
}, []);
return (
<div className="box sticky">
<Form
onSubmit={foo}
onSubmit={handleSubmit}
initialValues={{}}
render={({ handleSubmit, form, submitting, pristine, values }) => (
<div className="column is-three-quarters search">
<label>Search</label>
<Field name="search">
{({ input, meta }) => {
return (
<input
{...input}
className="input main-search-bar is-medium"
placeholder="Type an issue/volume name"
/>
);
}}
</Field>
</div>
<form onSubmit={handleSubmit}>
<div className="column is-three-quarters search">
<label>Search</label>
<Field name="search">
{({ input, meta }) => {
return (
<input
{...input}
className="input main-search-bar is-medium"
placeholder="Type an issue/volume name"
/>
);
}}
</Field>
<button className="button" type="submit">
Search
</button>
</div>
</form>
)}
/>
<div className="column one-fifth">

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect, useMemo, ReactElement } from "react";
import PropTypes from "prop-types";
import { useHistory } from "react-router";
import { useNavigate } from "react-router";
import {
removeLeadingPeriod,
escapePoundSymbol,
@@ -10,7 +10,7 @@ import prettyBytes from "pretty-bytes";
import ellipsize from "ellipsize";
import { useDispatch, useSelector } from "react-redux";
import { getComicBooks } from "../actions/fileops.actions";
import { isNil, isEmpty } from "lodash";
import { isNil, isEmpty, isUndefined } from "lodash";
import Masonry from "react-masonry-css";
import Card from "./Carda";
import { detectIssueTypes } from "../shared/utils/tradepaperback.utils";
@@ -45,7 +45,7 @@ export const LibraryGrid = (libraryGridProps: ILibraryGridProps) => {
{data.map(({ _id, rawFileDetails, sourcedMetadata }) => {
let imagePath = "";
let comicName = "";
if (!isNil(rawFileDetails)) {
if (!isEmpty(rawFileDetails.cover)) {
const encodedFilePath = encodeURI(
`${LIBRARY_SERVICE_HOST}/${removeLeadingPeriod(
rawFileDetails.cover.filePath,
@@ -71,7 +71,7 @@ export const LibraryGrid = (libraryGridProps: ILibraryGridProps) => {
title={comicName ? titleElement : null}
>
<div className="content is-flex is-flex-direction-row">
{!isNil(sourcedMetadata.comicvine) && (
{!isEmpty(sourcedMetadata.comicvine) && (
<span className="icon cv-icon is-small">
<img src="/dist/img/cvlogo.svg" />
</span>
@@ -81,13 +81,13 @@ export const LibraryGrid = (libraryGridProps: ILibraryGridProps) => {
<i className="fas fa-adjust" />
</span>
)}
{!isNil(sourcedMetadata.comicvine) &&
{!isUndefined(sourcedMetadata.comicvine.volumeInformation) &&
!isEmpty(
detectIssueTypes(
sourcedMetadata.comicvine.volumeInformation.description,
),
) ? (
<span className="tag is-warning">
<span className="tag is-warning ml-1">
{
detectIssueTypes(
sourcedMetadata.comicvine.volumeInformation

View File

@@ -161,7 +161,7 @@ const VolumeDetails = (props): ReactElement => {
if (
!isUndefined(comicBookDetails.sourcedMetadata) &&
!isUndefined(comicBookDetails.sourcedMetadata.comicvine)
!isUndefined(comicBookDetails.sourcedMetadata.comicvine.volumeInformation)
) {
return (
<div className="container volume-details">

View File

@@ -49,6 +49,12 @@ export const LIBRARY_SERVICE_BASE_URI = hostURIBuilder({
port: "3000",
apiPath: "/api/library",
});
export const SEARCH_SERVICE_BASE_URI = hostURIBuilder({
protocol: "http",
host: process.env.UNDERLYING_HOSTNAME || "localhost",
port: "3000",
apiPath: "/api/search",
});
export const SETTINGS_SERVICE_BASE_URI = hostURIBuilder({
protocol: "http",

View File

@@ -1,5 +1,5 @@
import { createStore, combineReducers, applyMiddleware } from "redux";
import { createBrowserHistory } from "history";
import { createHashHistory } from "history";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
import { createReduxHistoryContext } from "redux-first-history";
@@ -12,7 +12,7 @@ const socketConnection = io(SOCKET_BASE_URI, { transports: ["websocket"] });
const { createReduxHistory, routerMiddleware, routerReducer } =
createReduxHistoryContext({
history: createBrowserHistory(),
history: createHashHistory(),
});
export const store = createStore(