🔬 Wiring up the image analysis UI with the endpoint
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import axios from "axios";
|
||||
import rateLimiter from "axios-rate-limit";
|
||||
import { map } from "lodash";
|
||||
|
||||
import qs from "qs";
|
||||
import { IExtractionOptions } from "threetwo-ui-typings";
|
||||
import {
|
||||
@@ -9,8 +9,6 @@ import {
|
||||
CV_API_GENERIC_FAILURE,
|
||||
IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS,
|
||||
IMS_COMIC_BOOK_DB_OBJECT_FETCHED,
|
||||
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_SUCCESS,
|
||||
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
||||
CV_ISSUES_METADATA_CALL_IN_PROGRESS,
|
||||
CV_CLEANUP,
|
||||
IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED,
|
||||
@@ -175,39 +173,3 @@ export const applyComicVineMatch =
|
||||
IMS_inProgress: false,
|
||||
});
|
||||
};
|
||||
|
||||
export const extractComicArchive =
|
||||
(path: string, options: IExtractionOptions) => async (dispatch) => {
|
||||
const comicBookPages: string[] = [];
|
||||
dispatch({
|
||||
type: IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
||||
});
|
||||
const extractedComicBookArchive = await axios({
|
||||
method: "POST",
|
||||
url: `${LIBRARY_SERVICE_BASE_URI}/unrarArchive`,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
data: {
|
||||
options,
|
||||
filePath: path,
|
||||
},
|
||||
});
|
||||
map(extractedComicBookArchive.data, (page) => {
|
||||
const foo = page.path.split("/");
|
||||
const folderName = foo[foo.length - 1];
|
||||
const imagePath = encodeURI(
|
||||
`${LIBRARY_SERVICE_BASE_URI}/userdata/expanded/` +
|
||||
folderName +
|
||||
`/` +
|
||||
page.name +
|
||||
page.extension,
|
||||
);
|
||||
comicBookPages.push(imagePath);
|
||||
});
|
||||
console.log(comicBookPages);
|
||||
dispatch({
|
||||
type: IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_SUCCESS,
|
||||
extractedComicBookArchive: comicBookPages,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2,7 +2,9 @@ import axios from "axios";
|
||||
import { IFolderData } from "threetwo-ui-typings";
|
||||
import {
|
||||
COMICVINE_SERVICE_URI,
|
||||
IMAGETRANSFORMATION_SERVICE_BASE_URI,
|
||||
LIBRARY_SERVICE_BASE_URI,
|
||||
LIBRARY_SERVICE_HOST,
|
||||
} from "../constants/endpoints";
|
||||
import {
|
||||
IMS_COMIC_BOOK_GROUPS_FETCHED,
|
||||
@@ -16,9 +18,13 @@ import {
|
||||
IMS_CV_METADATA_IMPORT_SUCCESSFUL,
|
||||
IMS_CV_METADATA_IMPORT_FAILED,
|
||||
LS_IMPORT,
|
||||
IMG_ANALYSIS_CALL_IN_PROGRESS,
|
||||
IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
||||
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_SUCCESS,
|
||||
IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
||||
} from "../constants/action-types";
|
||||
import { success } from "react-notification-system-redux";
|
||||
import { isNil } from "lodash";
|
||||
import { isNil, map } from "lodash";
|
||||
|
||||
export async function walkFolder(path: string): Promise<Array<IFolderData>> {
|
||||
return axios
|
||||
@@ -212,3 +218,61 @@ export const fetchComicVineMatches =
|
||||
type: CV_CLEANUP,
|
||||
});
|
||||
};
|
||||
|
||||
export const extractComicArchive =
|
||||
(path: string, options: IExtractionOptions) => async (dispatch) => {
|
||||
const comicBookPages: string[] = [];
|
||||
dispatch({
|
||||
type: IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS,
|
||||
});
|
||||
const extractedComicBookArchive = await axios({
|
||||
method: "POST",
|
||||
url: `${LIBRARY_SERVICE_BASE_URI}/unrarArchive`,
|
||||
headers: {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
},
|
||||
data: {
|
||||
options,
|
||||
filePath: path,
|
||||
},
|
||||
});
|
||||
map(extractedComicBookArchive.data, (page) => {
|
||||
const pathItems = page.filePath.split("/");
|
||||
const folderName = pathItems[pathItems.length - 2];
|
||||
|
||||
const imagePath = encodeURI(
|
||||
`${LIBRARY_SERVICE_HOST}/userdata/expanded/` +
|
||||
folderName +
|
||||
`/` +
|
||||
page.name +
|
||||
page.extension,
|
||||
);
|
||||
comicBookPages.push(imagePath);
|
||||
});
|
||||
console.log(comicBookPages);
|
||||
dispatch({
|
||||
type: IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_SUCCESS,
|
||||
extractedComicBookArchive: comicBookPages,
|
||||
});
|
||||
};
|
||||
|
||||
export const analyzeImage =
|
||||
(imageFilePath: string | Buffer) => async (dispatch) => {
|
||||
console.log(imageFilePath);
|
||||
dispatch({
|
||||
type: IMG_ANALYSIS_CALL_IN_PROGRESS,
|
||||
});
|
||||
|
||||
const foo = await axios({
|
||||
url: `${IMAGETRANSFORMATION_SERVICE_BASE_URI}/analyze`,
|
||||
method: "POST",
|
||||
data: {
|
||||
imageFilePath,
|
||||
},
|
||||
});
|
||||
console.log(foo);
|
||||
dispatch({
|
||||
type: IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
||||
result: foo.data,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -14,6 +14,13 @@ $size-8: 0.9rem;
|
||||
font-size: $size-8;
|
||||
}
|
||||
|
||||
|
||||
// global style overrides
|
||||
|
||||
pre {
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin-top: 2em;
|
||||
}
|
||||
@@ -307,6 +314,15 @@ $size-8: 0.9rem;
|
||||
}
|
||||
|
||||
// Comic Detail
|
||||
.control-palette {
|
||||
display: inline-block;
|
||||
i {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
// padding: 1.5rem 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
// airdcpp downloads tab
|
||||
.tabs {
|
||||
|
||||
@@ -14,16 +14,13 @@ export const Menu = (props): ReactElement => {
|
||||
let issueSearchQuery: IComicVineSearchQuery = {} as IComicVineSearchQuery;
|
||||
|
||||
if (!isUndefined(data.rawFileDetails)) {
|
||||
console.log(data.rawFileDetails);
|
||||
issueSearchQuery = refineQuery(data.rawFileDetails.name);
|
||||
if (data.rawFileDetails.containedIn !== "comics") {
|
||||
seriesSearchQuery = refineQuery(
|
||||
data.rawFileDetails.containedIn.split("/").pop(),
|
||||
);
|
||||
}
|
||||
} else if (!isEmpty(data.sourcedMetadata)) {
|
||||
issueSearchQuery = refineQuery(data.sourcedMetadata.comicvine.name);
|
||||
}
|
||||
|
||||
console.log(seriesSearchQuery);
|
||||
dispatch(fetchComicVineMatches(data, issueSearchQuery, seriesSearchQuery));
|
||||
setSlidingPanelContentId("CVMatches");
|
||||
setVisible(true);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React, { ReactElement, useCallback } from "react";
|
||||
import React, { ReactElement, useCallback, useState } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { DnD } from "../../DnD";
|
||||
import { isEmpty } from "lodash";
|
||||
import Sticky from "react-stickynode";
|
||||
import { extractComicArchive } from "../../../actions/comicinfo.actions";
|
||||
import SlidingPane from "react-sliding-pane";
|
||||
import { extractComicArchive } from "../../../actions/fileops.actions";
|
||||
import { analyzeImage } from "../../../actions/fileops.actions";
|
||||
|
||||
export const ArchiveOperations = (props): ReactElement => {
|
||||
const { data } = props;
|
||||
@@ -14,23 +16,52 @@ export const ArchiveOperations = (props): ReactElement => {
|
||||
(state: RootState) => state.fileOps.extractedComicBookArchive,
|
||||
);
|
||||
|
||||
const imageAnalysisResult = useSelector(
|
||||
(state: RootState) => state.fileOps.imageAnalysisResults,
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const unpackComicArchive = useCallback(() => {
|
||||
dispatch(
|
||||
extractComicArchive(
|
||||
data.rawFileDetails.containedIn +
|
||||
"/" +
|
||||
data.rawFileDetails.name +
|
||||
data.rawFileDetails.extension,
|
||||
{
|
||||
extractTarget: "book",
|
||||
targetExtractionFolder:
|
||||
"./userdata/expanded/" + data.rawFileDetails.name,
|
||||
extractionMode: "all",
|
||||
},
|
||||
),
|
||||
extractComicArchive(data.rawFileDetails.filePath, {
|
||||
extractTarget: "book",
|
||||
targetExtractionFolder:
|
||||
"./userdata/expanded/" + data.rawFileDetails.name,
|
||||
extractionMode: "all",
|
||||
}),
|
||||
);
|
||||
}, [dispatch, data]);
|
||||
|
||||
// sliding panel config
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [slidingPanelContentId, setSlidingPanelContentId] = useState("");
|
||||
// current image
|
||||
const [currentImage, setCurrentImage] = useState([]);
|
||||
|
||||
// sliding panel init
|
||||
const contentForSlidingPanel = {
|
||||
imageAnalysis: {
|
||||
content: () => {
|
||||
return (
|
||||
<div>
|
||||
<pre>{currentImage}</pre>
|
||||
<pre className="is-size-7">
|
||||
{JSON.stringify(imageAnalysisResult, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// sliding panel handlers
|
||||
const openImageAnalysisPanel = useCallback((imageFilePath) => {
|
||||
setSlidingPanelContentId("imageAnalysis");
|
||||
dispatch(analyzeImage(imageFilePath));
|
||||
setCurrentImage(imageFilePath);
|
||||
setVisible(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div key={2}>
|
||||
<button
|
||||
@@ -49,7 +80,10 @@ export const ArchiveOperations = (props): ReactElement => {
|
||||
<div className="columns">
|
||||
<div className="mt-5">
|
||||
{!isEmpty(extractedComicBookArchive) ? (
|
||||
<DnD data={extractedComicBookArchive} />
|
||||
<DnD
|
||||
data={extractedComicBookArchive}
|
||||
onClickHandler={openImageAnalysisPanel}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
{!isEmpty(extractedComicBookArchive) ? (
|
||||
@@ -70,6 +104,15 @@ export const ArchiveOperations = (props): ReactElement => {
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<SlidingPane
|
||||
isOpen={visible}
|
||||
onRequestClose={() => setVisible(false)}
|
||||
title={"Image Analysis"}
|
||||
width={"600px"}
|
||||
>
|
||||
{slidingPanelContentId !== "" &&
|
||||
contentForSlidingPanel[slidingPanelContentId].content()}
|
||||
</SlidingPane>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@ export const Cover = forwardRef(
|
||||
...style,
|
||||
};
|
||||
|
||||
return <div ref={ref} style={inlineStyles} {...props} />;
|
||||
return <div ref={ref} style={inlineStyles} {...props}></div>;
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
import { Grid } from "./Grid";
|
||||
import { SortableCover } from "./SortableCover";
|
||||
import { Cover } from "./Cover";
|
||||
import { map } from "lodash";
|
||||
import { map } from "lodash";
|
||||
|
||||
export const DnD = (data) => {
|
||||
const [items, setItems] = useState(data.data);
|
||||
@@ -57,7 +57,25 @@ export const DnD = (data) => {
|
||||
<SortableContext items={items} strategy={rectSortingStrategy}>
|
||||
<Grid columns={4}>
|
||||
{map(items, (url, index) => {
|
||||
return <SortableCover key={url} url={url} index={index} />;
|
||||
return (
|
||||
<div>
|
||||
<SortableCover key={url} url={url} index={index} />
|
||||
<div
|
||||
className="mt-2 mb-2"
|
||||
onClick={(e) => data.onClickHandler(url)}
|
||||
>
|
||||
<div className="box p-2 pl-3 control-palette">
|
||||
<span className="tag is-warning mr-2">{index}</span>
|
||||
<span className="icon is-small mr-2">
|
||||
<i className="fa-solid fa-vial"></i>
|
||||
</span>
|
||||
<span className="icon is-small mr-2">
|
||||
<i className="fa-regular fa-trash-can"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</SortableContext>
|
||||
|
||||
@@ -9,6 +9,7 @@ import { escapePoundSymbol } from "../../shared/utils/formatting.utils";
|
||||
import prettyBytes from "pretty-bytes";
|
||||
|
||||
const PotentialLibraryMatches = (props): ReactElement => {
|
||||
console.log(props);
|
||||
const dispatch = useDispatch();
|
||||
const comicBooks = useSelector(
|
||||
(state: RootState) => state.comicInfo.comicBooksDetails,
|
||||
@@ -64,7 +65,7 @@ const PotentialLibraryMatches = (props): ReactElement => {
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div>asdasd</div>
|
||||
<div>No matches found in library.</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -19,7 +19,7 @@ const VolumeDetails = (props): ReactElement => {
|
||||
default: 6,
|
||||
1100: 4,
|
||||
700: 3,
|
||||
600: 2,
|
||||
500: 2,
|
||||
};
|
||||
// sliding panel config
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
@@ -53,7 +53,8 @@ export const CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS =
|
||||
"CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS";
|
||||
export const CV_ISSUES_FOR_VOLUME_IN_LIBRARY_UPDATED =
|
||||
"CV_ISSUES_FOR_VOLUME_IN_LIBRARY_UPDATED";
|
||||
export const CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED = "CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED";
|
||||
export const CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED =
|
||||
"CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED";
|
||||
|
||||
// extracted comic archive
|
||||
export const IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_SUCCESS =
|
||||
@@ -63,6 +64,12 @@ export const IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_IN_PROGRESS =
|
||||
export const IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_FAILED =
|
||||
"IMS_COMIC_BOOK_ARCHIVE_EXTRACTION_CALL_FAILED";
|
||||
|
||||
// Image file stats
|
||||
export const IMG_ANALYSIS_CALL_IN_PROGRESS = "IMG_ANALYSIS_CALL_IN_PROGRESS";
|
||||
export const IMG_ANALYSIS_DATA_FETCH_SUCCESS =
|
||||
"IMG_ANALYSIS_DATA_FETCH_SUCCESS";
|
||||
export const IMG_ANALYSIS_DATA_FETCH_ERROR = "IMG_ANALYSIS_DATA_FETCH_ERROR";
|
||||
|
||||
// AirDC++
|
||||
export const AIRDCPP_SEARCH_IN_PROGRESS = "AIRDCPP_SEARCH_IN_PROGRESS";
|
||||
export const AIRDCPP_SEARCH_RESULTS_ADDED = "AIRDCPP_SEARCH_RESULTS_ADDED";
|
||||
|
||||
@@ -57,6 +57,13 @@ export const SETTINGS_SERVICE_BASE_URI = hostURIBuilder({
|
||||
apiPath: "/api/settings",
|
||||
});
|
||||
|
||||
export const IMAGETRANSFORMATION_SERVICE_BASE_URI = hostURIBuilder({
|
||||
protocol: "http",
|
||||
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||
port: "3000",
|
||||
apiPath: "/api/imagetransformation",
|
||||
});
|
||||
|
||||
export const SOCKET_BASE_URI = hostURIBuilder({
|
||||
protocol: "ws",
|
||||
host: process.env.UNDERLYING_HOSTNAME || "localhost",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { isEmpty, extend, each, matches } from "lodash";
|
||||
import { isEmpty } from "lodash";
|
||||
import {
|
||||
CV_API_CALL_IN_PROGRESS,
|
||||
CV_SEARCH_SUCCESS,
|
||||
@@ -7,12 +7,9 @@ import {
|
||||
IMS_COMIC_BOOKS_DB_OBJECTS_FETCHED,
|
||||
IMS_COMIC_BOOK_DB_OBJECT_CALL_IN_PROGRESS,
|
||||
CV_ISSUES_METADATA_CALL_IN_PROGRESS,
|
||||
CV_ISSUES_METADATA_FETCH_SUCCESS,
|
||||
CV_ISSUES_FOR_VOLUME_IN_LIBRARY_UPDATED,
|
||||
CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED,
|
||||
CV_ISSUES_FOR_VOLUME_IN_LIBRARY_SUCCESS,
|
||||
} from "../constants/action-types";
|
||||
import { refineQuery } from "filename-parser";
|
||||
|
||||
const initialState = {
|
||||
searchResults: [],
|
||||
@@ -79,23 +76,24 @@ function comicinfoReducer(state = initialState, action) {
|
||||
|
||||
case CV_ISSUES_MATCHES_IN_LIBRARY_FETCHED:
|
||||
const updatedState = [...state.issuesForVolume];
|
||||
const matches = action.matches.filter(
|
||||
(match) => !isEmpty(match.hits.hits),
|
||||
);
|
||||
matches.forEach((match) => {
|
||||
state.issuesForVolume.forEach((issue, idx) => {
|
||||
issue.matches = [];
|
||||
match.hits.hits.forEach((hit) => {
|
||||
if (
|
||||
parseInt(issue.issue_number, 10) ===
|
||||
hit._source.inferredMetadata.issue.number
|
||||
) {
|
||||
issue.matches.push(hit);
|
||||
}
|
||||
});
|
||||
action.matches.map((match) => {
|
||||
updatedState.map((issue, idx) => {
|
||||
let matches = [];
|
||||
if (!isEmpty(match.hits.hits)) {
|
||||
return match.hits.hits.map((hit) => {
|
||||
if (
|
||||
parseInt(issue.issue_number, 10) ===
|
||||
hit._source.inferredMetadata.issue.number
|
||||
) {
|
||||
matches.push(hit);
|
||||
const updatedIssueResult = { ...issue, matches };
|
||||
updatedState[idx] = updatedIssueResult;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
console.log(updatedState);
|
||||
return {
|
||||
...state,
|
||||
issuesForVolume: updatedState,
|
||||
|
||||
@@ -17,9 +17,13 @@ import {
|
||||
LS_IMPORT,
|
||||
LS_COVER_EXTRACTED,
|
||||
LS_COMIC_ADDED,
|
||||
IMG_ANALYSIS_CALL_IN_PROGRESS,
|
||||
IMG_ANALYSIS_DATA_FETCH_SUCCESS,
|
||||
} from "../constants/action-types";
|
||||
const initialState = {
|
||||
IMSCallInProgress: false,
|
||||
IMGCallInProgress: false,
|
||||
imageAnalysisResults: {},
|
||||
comicBookExtractionInProgress: false,
|
||||
comicBookMetadata: [],
|
||||
comicVolumeGroups: [],
|
||||
@@ -135,6 +139,18 @@ function fileOpsReducer(state = initialState, action) {
|
||||
...state,
|
||||
};
|
||||
}
|
||||
case IMG_ANALYSIS_CALL_IN_PROGRESS: {
|
||||
return {
|
||||
...state,
|
||||
IMGCallInProgress: true,
|
||||
};
|
||||
}
|
||||
case IMG_ANALYSIS_DATA_FETCH_SUCCESS: {
|
||||
return {
|
||||
...state,
|
||||
imageAnalysisResults: action.result,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user