🔬 Wiring up the image analysis UI with the endpoint

This commit is contained in:
2022-02-08 14:48:24 -08:00
parent 8fdd8d0226
commit 9ff048d541
13 changed files with 214 additions and 85 deletions

View File

@@ -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,
});
};

View File

@@ -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,
});
};

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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>
);
};

View File

@@ -18,7 +18,7 @@ export const Cover = forwardRef(
...style,
};
return <div ref={ref} style={inlineStyles} {...props} />;
return <div ref={ref} style={inlineStyles} {...props}></div>;
},
);

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -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);

View File

@@ -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";

View File

@@ -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",

View File

@@ -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,

View File

@@ -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;
}